From 06ebf9d3c262d64d3d1aa967c7893ae32a4fb191 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 29 Aug 2016 14:42:53 -0400 Subject: update recording file name --- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 2 -- 1 file changed, 2 deletions(-) (limited to 'MediaBrowser.Model/Configuration/ServerConfiguration.cs') diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index a891a422a..abe572cc8 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -176,7 +176,6 @@ namespace MediaBrowser.Model.Configuration public string UICulture { get; set; } public PeopleMetadataOptions PeopleMetadataOptions { get; set; } - public bool FindInternetTrailers { get; set; } public bool SaveMetadataHidden { get; set; } @@ -243,7 +242,6 @@ namespace MediaBrowser.Model.Configuration LibraryMonitorDelay = 60; EnableInternetProviders = true; - FindInternetTrailers = true; PathSubstitutions = new PathSubstitution[] { }; ContentTypes = new NameValuePair[] { }; -- cgit v1.2.3 From 3862207a7352feddfae6416ba2dff4e78620089a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 31 Aug 2016 16:46:09 -0400 Subject: move channel view setting to global --- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 1 + MediaBrowser.Model/Configuration/UserConfiguration.cs | 1 - MediaBrowser.Server.Implementations/Library/UserViewManager.cs | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Model/Configuration/ServerConfiguration.cs') diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index abe572cc8..9022f64a1 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -204,6 +204,7 @@ namespace MediaBrowser.Model.Configuration public bool DisplayCollectionsView { get; set; } public string[] LocalNetworkAddresses { get; set; } public string[] CodecsUsed { get; set; } + public bool EnableChannelView { get; set; } /// /// Initializes a new instance of the class. diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 313c5243c..a6785a06a 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -41,7 +41,6 @@ namespace MediaBrowser.Model.Configuration public string[] PlainFolderViews { get; set; } public bool HidePlayedInLatest { get; set; } - public bool EnableChannelView { get; set; } public bool RememberAudioSelections { get; set; } public bool RememberSubtitleSelections { get; set; } diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs index 5fffa3d1f..2cbee7c97 100644 --- a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs @@ -120,8 +120,8 @@ namespace MediaBrowser.Server.Implementations.Library }, cancellationToken).ConfigureAwait(false); var channels = channelResult.Items; - - if (user.Configuration.EnableChannelView && channels.Length > 0) + + if (_config.Configuration.EnableChannelView && channels.Length > 0) { list.Add(await _channelManager.GetInternalChannelFolder(cancellationToken).ConfigureAwait(false)); } -- cgit v1.2.3 From 699bdacebc94635f129d3e68cfaaa49ebc0ff6f6 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 31 Aug 2016 17:07:02 -0400 Subject: add setting to hide external content from suggestions --- MediaBrowser.Api/Library/LibraryService.cs | 7 +- MediaBrowser.Api/Movies/MoviesService.cs | 75 +++++++++++++--------- .../Configuration/ServerConfiguration.cs | 2 + 3 files changed, 51 insertions(+), 33 deletions(-) (limited to 'MediaBrowser.Model/Configuration/ServerConfiguration.cs') diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 14a771db0..c6b637f01 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -25,6 +25,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.Configuration; namespace MediaBrowser.Api.Library { @@ -288,12 +289,13 @@ namespace MediaBrowser.Api.Library private readonly ITVSeriesManager _tvManager; private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _config; /// /// Initializes a new instance of the class. /// public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, - IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILiveTvManager liveTv, ITVSeriesManager tvManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem) + IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILiveTvManager liveTv, ITVSeriesManager tvManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, IServerConfigurationManager config) { _itemRepo = itemRepo; _libraryManager = libraryManager; @@ -307,6 +309,7 @@ namespace MediaBrowser.Api.Library _tvManager = tvManager; _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; + _config = config; } public object Get(GetSimilarItems request) @@ -377,7 +380,7 @@ namespace MediaBrowser.Api.Library if (item is Movie || (program != null && program.IsMovie) || item is Trailer) { - return new MoviesService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService) + return new MoviesService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _config) { AuthorizationContext = AuthorizationContext, Logger = Logger, diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 66f2fac81..f153a0475 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.LiveTv; namespace MediaBrowser.Api.Movies @@ -88,6 +89,7 @@ namespace MediaBrowser.Api.Movies private readonly IItemRepository _itemRepo; private readonly IDtoService _dtoService; + private readonly IServerConfigurationManager _config; /// /// Initializes a new instance of the class. @@ -97,13 +99,14 @@ namespace MediaBrowser.Api.Movies /// The library manager. /// The item repo. /// The dto service. - public MoviesService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService) + public MoviesService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService, IServerConfigurationManager config) { _userManager = userManager; _userDataRepository = userDataRepository; _libraryManager = libraryManager; _itemRepo = itemRepo; _dtoService = dtoService; + _config = config; } /// @@ -146,15 +149,17 @@ namespace MediaBrowser.Api.Movies (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); + var itemTypes = new List { typeof(Movie).Name }; + if (_config.Configuration.EnableExternalContentInSuggestions) + { + itemTypes.Add(typeof(Trailer).Name); + itemTypes.Add(typeof(LiveTvProgram).Name); + } + var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) { Limit = request.Limit, - IncludeItemTypes = new[] - { - typeof(Movie).Name, - typeof(Trailer).Name, - typeof(LiveTvProgram).Name - }, + IncludeItemTypes = itemTypes.ToArray(), IsMovie = true, SimilarTo = item, EnableGroupByMetadataKey = true @@ -198,14 +203,16 @@ namespace MediaBrowser.Api.Movies var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList(); + var itemTypes = new List { typeof(Movie).Name }; + if (_config.Configuration.EnableExternalContentInSuggestions) + { + itemTypes.Add(typeof(Trailer).Name); + itemTypes.Add(typeof(LiveTvProgram).Name); + } + var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user) { - IncludeItemTypes = new[] - { - typeof(Movie).Name, - typeof(Trailer).Name, - typeof(LiveTvProgram).Name - }, + IncludeItemTypes = itemTypes.ToArray(), IsMovie = true, SortBy = new[] { ItemSortBy.Random }, SortOrder = SortOrder.Descending, @@ -278,6 +285,13 @@ namespace MediaBrowser.Api.Movies private IEnumerable GetWithDirector(User user, IEnumerable names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) { + var itemTypes = new List { typeof(Movie).Name }; + if (_config.Configuration.EnableExternalContentInSuggestions) + { + itemTypes.Add(typeof(Trailer).Name); + itemTypes.Add(typeof(LiveTvProgram).Name); + } + foreach (var name in names) { var items = _libraryManager.GetItemList(new InternalItemsQuery(user) @@ -286,12 +300,7 @@ namespace MediaBrowser.Api.Movies // Account for duplicates by imdb id, since the database doesn't support this yet Limit = itemLimit + 2, PersonTypes = new[] { PersonType.Director }, - IncludeItemTypes = new[] - { - typeof(Movie).Name, - typeof(Trailer).Name, - typeof(LiveTvProgram).Name - }, + IncludeItemTypes = itemTypes.ToArray(), IsMovie = true, EnableGroupByMetadataKey = true @@ -314,6 +323,13 @@ namespace MediaBrowser.Api.Movies private IEnumerable GetWithActor(User user, IEnumerable names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) { + var itemTypes = new List { typeof(Movie).Name }; + if (_config.Configuration.EnableExternalContentInSuggestions) + { + itemTypes.Add(typeof(Trailer).Name); + itemTypes.Add(typeof(LiveTvProgram).Name); + } + foreach (var name in names) { var items = _libraryManager.GetItemList(new InternalItemsQuery(user) @@ -321,12 +337,7 @@ namespace MediaBrowser.Api.Movies Person = name, // Account for duplicates by imdb id, since the database doesn't support this yet Limit = itemLimit + 2, - IncludeItemTypes = new[] - { - typeof(Movie).Name, - typeof(Trailer).Name, - typeof(LiveTvProgram).Name - }, + IncludeItemTypes = itemTypes.ToArray(), IsMovie = true, EnableGroupByMetadataKey = true @@ -349,17 +360,19 @@ namespace MediaBrowser.Api.Movies private IEnumerable GetSimilarTo(User user, List baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type) { + var itemTypes = new List { typeof(Movie).Name }; + if (_config.Configuration.EnableExternalContentInSuggestions) + { + itemTypes.Add(typeof(Trailer).Name); + itemTypes.Add(typeof(LiveTvProgram).Name); + } + foreach (var item in baselineItems) { var similar = _libraryManager.GetItemList(new InternalItemsQuery(user) { Limit = itemLimit, - IncludeItemTypes = new[] - { - typeof(Movie).Name, - typeof(Trailer).Name, - typeof(LiveTvProgram).Name - }, + IncludeItemTypes = itemTypes.ToArray(), IsMovie = true, SimilarTo = item, EnableGroupByMetadataKey = true diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 9022f64a1..b0595b558 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -205,6 +205,7 @@ namespace MediaBrowser.Model.Configuration public string[] LocalNetworkAddresses { get; set; } public string[] CodecsUsed { get; set; } public bool EnableChannelView { get; set; } + public bool EnableExternalContentInSuggestions { get; set; } /// /// Initializes a new instance of the class. @@ -218,6 +219,7 @@ namespace MediaBrowser.Model.Configuration EnableLocalizedGuids = true; DisplaySpecialsWithinSeasons = true; + EnableExternalContentInSuggestions = true; ImageSavingConvention = ImageSavingConvention.Compatible; PublicPort = 8096; -- cgit v1.2.3 From 9c7eef891b808c60181704046a48dd9cdbe96bae Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 4 Sep 2016 11:01:31 -0400 Subject: add configurable encoding params --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 20 ++++++++++++++++++-- MediaBrowser.Controller/Entities/UserView.cs | 3 ++- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 7 ++++++- MediaBrowser.Model/Configuration/EncodingOptions.cs | 4 ++++ .../Configuration/ServerConfiguration.cs | 2 ++ .../UserViews/DynamicImageProvider.cs | 16 ++++++++++++++-- 6 files changed, 46 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.Model/Configuration/ServerConfiguration.cs') diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 4af564a5a..89ad02170 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -346,11 +346,27 @@ namespace MediaBrowser.Api.Playback var isVc1 = state.VideoStream != null && string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase); + var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); + if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) { - param = "-preset superfast"; + if (!string.IsNullOrWhiteSpace(encodingOptions.H264Preset)) + { + param = "-preset " + encodingOptions.H264Preset; + } + else + { + param = "-preset superfast"; + } - param += " -crf 23"; + if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51) + { + param = " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture); + } + else + { + param += " -crf 23"; + } } else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 35375e7e6..194ba0ee4 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -113,7 +113,8 @@ namespace MediaBrowser.Controller.Entities { var standaloneTypes = new List { - CollectionType.Playlists + CollectionType.Playlists, + CollectionType.BoxSets }; var collectionFolder = folder as ICollectionFolder; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index ad84ffee8..75d85cd32 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -928,7 +928,12 @@ namespace MediaBrowser.MediaEncoding.Encoder { StartProcess(processWrapper); - ranToCompletion = process.WaitForExit(10000); + var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; + if (timeoutMs <= 0) + { + timeoutMs = 10000; + } + ranToCompletion = process.WaitForExit(timeoutMs); if (!ranToCompletion) { diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 3c03dc12a..d49816803 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Configuration public string HardwareAccelerationType { get; set; } public string EncoderAppPath { get; set; } public string VaapiDevice { get; set; } + public int H264Crf { get; set; } + public string H264Preset { get; set; } public EncodingOptions() { @@ -19,6 +21,8 @@ namespace MediaBrowser.Model.Configuration ThrottleDelaySeconds = 180; EncodingThreadCount = -1; VaapiDevice = "/dev/dri/card0"; + H264Crf = 23; + H264Preset = "superfast"; } } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index b0595b558..44a2ae954 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -207,6 +207,7 @@ namespace MediaBrowser.Model.Configuration public bool EnableChannelView { get; set; } public bool EnableExternalContentInSuggestions { get; set; } + public int ImageExtractionTimeoutMs { get; set; } /// /// Initializes a new instance of the class. /// @@ -216,6 +217,7 @@ namespace MediaBrowser.Model.Configuration Migrations = new string[] { }; CodecsUsed = new string[] { }; SqliteCacheSize = 0; + ImageExtractionTimeoutMs = 10000; EnableLocalizedGuids = true; DisplaySpecialsWithinSeasons = true; diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index 3c75c8a48..f40072897 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -14,17 +14,20 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.LiveTv; namespace MediaBrowser.Server.Implementations.UserViews { public class DynamicImageProvider : BaseDynamicImageProvider { private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; - public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager) + public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) { _userManager = userManager; + _libraryManager = libraryManager; } public override IEnumerable GetSupportedImages(IHasImages item) @@ -50,7 +53,15 @@ namespace MediaBrowser.Server.Implementations.UserViews if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) { - return new List(); + var programs = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, + ImageTypes = new[] { ImageType.Primary }, + Limit = 30, + IsMovie = true + }).ToList(); + + return GetFinalItems(programs).ToList(); } if (string.Equals(view.ViewType, SpecialFolder.MovieGenre, StringComparison.OrdinalIgnoreCase) || @@ -147,6 +158,7 @@ namespace MediaBrowser.Server.Implementations.UserViews CollectionType.MusicVideos, CollectionType.HomeVideos, CollectionType.BoxSets, + CollectionType.LiveTv, CollectionType.Playlists, CollectionType.Photos, string.Empty -- cgit v1.2.3 From 67505e24bdccae33387e59358c01471dd9536d42 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 6 Sep 2016 01:02:05 -0400 Subject: fix artist editor --- MediaBrowser.Api/LiveTv/LiveTvService.cs | 8 +--- MediaBrowser.Controller/Library/NameExtensions.cs | 4 +- .../LiveTv/IHasRegistrationInfo.cs | 15 ------ MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 4 +- .../MediaBrowser.Controller.csproj | 1 - .../Configuration/ServerConfiguration.cs | 2 +- .../Music/AudioMetadataService.cs | 3 +- .../Dto/DtoService.cs | 20 ++++++++ .../LiveTv/EmbyTV/EmbyTV.cs | 26 ++-------- .../LiveTv/LiveTvManager.cs | 40 +++++++--------- MediaBrowser.Server.Mono/Native/BaseMonoApp.cs | 5 ++ .../ApplicationHost.cs | 16 ++++++- MediaBrowser.Server.Startup.Common/INativeApp.cs | 2 + .../Native/RegisterServer.bat | 2 + .../Native/WindowsApp.cs | 55 ++++++++++++++++++++++ 15 files changed, 126 insertions(+), 77 deletions(-) delete mode 100644 MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs (limited to 'MediaBrowser.Model/Configuration/ServerConfiguration.cs') diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index f6817e0e7..8868daaca 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -538,12 +538,6 @@ namespace MediaBrowser.Api.LiveTv [Authenticated] public class GetLiveTvRegistrationInfo : IReturn { - [ApiMember(Name = "ChannelId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ChannelId { get; set; } - - [ApiMember(Name = "ProgramId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ProgramId { get; set; } - [ApiMember(Name = "Feature", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string Feature { get; set; } } @@ -595,7 +589,7 @@ namespace MediaBrowser.Api.LiveTv public async Task Get(GetLiveTvRegistrationInfo request) { - var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false); + var result = await _liveTvManager.GetRegistrationInfo(request.Feature).ConfigureAwait(false); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs index 6973dce64..72f036b0a 100644 --- a/MediaBrowser.Controller/Library/NameExtensions.cs +++ b/MediaBrowser.Controller/Library/NameExtensions.cs @@ -54,7 +54,7 @@ namespace MediaBrowser.Controller.Library } } - class TextComparer : IComparer, IEqualityComparer + public class DistinctNameComparer : IComparer, IEqualityComparer { public int Compare(string x, string y) { @@ -63,7 +63,7 @@ namespace MediaBrowser.Controller.Library return 0; } - return string.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace); + return string.Compare(x.RemoveDiacritics(), y.RemoveDiacritics(), StringComparison.OrdinalIgnoreCase); } public bool Equals(string x, string y) diff --git a/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs b/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs deleted file mode 100644 index 3626c18e5..000000000 --- a/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using MediaBrowser.Model.Entities; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.LiveTv -{ - public interface IHasRegistrationInfo - { - /// - /// Gets the registration information. - /// - /// The feature. - /// Task<MBRegistrationRecord>. - Task GetRegistrationInfo(string feature); - } -} diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index ed64127c3..d30231eb9 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -364,11 +364,9 @@ namespace MediaBrowser.Controller.LiveTv /// /// Gets the registration information. /// - /// The channel identifier. - /// The program identifier. /// The feature. /// Task<MBRegistrationRecord>. - Task GetRegistrationInfo(string channelId, string programId, string feature); + Task GetRegistrationInfo(string feature); /// /// Adds the channel information. diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5e74a3999..7cfd56c1e 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -199,7 +199,6 @@ - diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 44a2ae954..26bf3107d 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -217,7 +217,7 @@ namespace MediaBrowser.Model.Configuration Migrations = new string[] { }; CodecsUsed = new string[] { }; SqliteCacheSize = 0; - ImageExtractionTimeoutMs = 10000; + ImageExtractionTimeoutMs = 14000; EnableLocalizedGuids = true; DisplaySpecialsWithinSeasons = true; diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs index 532128186..67ddd8981 100644 --- a/MediaBrowser.Providers/Music/AudioMetadataService.cs +++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; using System.Collections.Generic; +using System.Linq; using CommonIO; namespace MediaBrowser.Providers.Music @@ -21,7 +22,7 @@ namespace MediaBrowser.Providers.Music if (replaceData || targetItem.Artists.Count == 0) { - targetItem.Artists = sourceItem.Artists; + targetItem.Artists = sourceItem.Artists.ToList(); } if (replaceData || string.IsNullOrEmpty(targetItem.Album)) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index be68162ca..9284f4fc7 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1168,6 +1168,26 @@ namespace MediaBrowser.Server.Implementations.Dto }; }) .ToList(); + + // Include artists that are not in the database yet, e.g., just added via metadata editor + var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); + dto.ArtistItems.AddRange(hasArtist.Artists + .Except(foundArtists, new DistinctNameComparer()) + .Select(i => + { + var artist = _libraryManager.GetArtist(i); + if (artist != null) + { + return new NameIdPair + { + Name = artist.Name, + Id = artist.Id.ToString("N") + }; + } + + return null; + + }).Where(i => i != null)); } var hasAlbumArtist = item as IHasAlbumArtist; diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 649024d16..8f5b42df0 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -33,7 +33,7 @@ using Microsoft.Win32; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { - public class EmbyTV : ILiveTvService, ISupportsNewTimerIds, IHasRegistrationInfo, IDisposable + public class EmbyTV : ILiveTvService, ISupportsNewTimerIds, IDisposable { private readonly IApplicationHost _appHpst; private readonly ILogger _logger; @@ -46,7 +46,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private readonly LiveTvManager _liveTvManager; private readonly IFileSystem _fileSystem; - private readonly ISecurityManager _security; private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; @@ -62,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private readonly ConcurrentDictionary _activeRecordings = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement) + public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement) { Current = this; @@ -71,7 +70,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _httpClient = httpClient; _config = config; _fileSystem = fileSystem; - _security = security; _libraryManager = libraryManager; _libraryMonitor = libraryMonitor; _providerManager = providerManager; @@ -1114,7 +1112,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV if (config.EnableRecordingEncoding) { - var regInfo = await _security.GetRegistrationStatus("embytvrecordingconversion").ConfigureAwait(false); + var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false); if (regInfo.IsValid) { @@ -1171,8 +1169,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private async Task UpdateTimersForSeriesTimer(List epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers) { var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList(); - - var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); + + var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); if (registration.IsValid) { @@ -1349,20 +1347,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - public Task GetRegistrationInfo(string feature) - { - if (string.Equals(feature, "seriesrecordings", StringComparison.OrdinalIgnoreCase)) - { - return _security.GetRegistrationStatus("embytvseriesrecordings"); - } - - return Task.FromResult(new MBRegistrationRecord - { - IsValid = true, - IsRegistered = true - }); - } - public List GetRecordingFolders() { var list = new List(); diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 3d9a0f80e..72d1dc120 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -31,6 +31,7 @@ using CommonIO; using IniParser; using IniParser.Model; using MediaBrowser.Common.Events; +using MediaBrowser.Common.Security; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Events; @@ -51,6 +52,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly ITaskManager _taskManager; private readonly IJsonSerializer _jsonSerializer; private readonly IProviderManager _providerManager; + private readonly ISecurityManager _security; private readonly IDtoService _dtoService; private readonly ILocalizationManager _localization; @@ -73,7 +75,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv public event EventHandler> TimerCreated; public event EventHandler> SeriesTimerCreated; - public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem) + public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem, ISecurityManager security) { _config = config; _logger = logger; @@ -85,6 +87,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv _jsonSerializer = jsonSerializer; _providerManager = providerManager; _fileSystem = fileSystem; + _security = security; _dtoService = dtoService; _userDataManager = userDataManager; @@ -2133,6 +2136,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken) { + var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); + + if (!registration.IsValid) + { + _logger.Info("Creating series recordings requires an active Emby Premiere subscription."); + return; + } + var service = GetService(timer.ServiceName); var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false); @@ -2695,33 +2706,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv } } - public Task GetRegistrationInfo(string channelId, string programId, string feature) + public Task GetRegistrationInfo(string feature) { - ILiveTvService service; - - if (string.IsNullOrWhiteSpace(programId)) + if (string.Equals(feature, "seriesrecordings", StringComparison.OrdinalIgnoreCase)) { - var channel = GetInternalChannel(channelId); - service = GetService(channel); + feature = "embytvseriesrecordings"; } - else - { - var program = GetInternalProgram(programId); - service = GetService(program); - } - - var hasRegistration = service as IHasRegistrationInfo; - if (hasRegistration != null) - { - return hasRegistration.GetRegistrationInfo(feature); - } - - return Task.FromResult(new MBRegistrationRecord - { - IsValid = true, - IsRegistered = true - }); + return _security.GetRegistrationStatus(feature); } public List GetSatIniMappings() diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs index 2185f4804..48f6a2a48 100644 --- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs +++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs @@ -283,6 +283,11 @@ namespace MediaBrowser.Server.Mono.Native { } + + public bool PortsRequireAuthorization(string applicationPath) + { + return false; + } } public class NullPowerManagement : IPowerManagement diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 86c5c06bc..2417c5b11 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -520,7 +520,7 @@ namespace MediaBrowser.Server.Startup.Common PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("PlaylistManager"), UserManager, ProviderManager); RegisterSingleInstance(PlaylistManager); - LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager); + LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager, SecurityManager); RegisterSingleInstance(LiveTvManager); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager); @@ -773,7 +773,19 @@ namespace MediaBrowser.Server.Startup.Common /// protected override void FindParts() { - if (!ServerConfigurationManager.Configuration.IsPortAuthorized) + var isAuthorized = ServerConfigurationManager.Configuration.IsPortAuthorized; + if (isAuthorized) + { + try + { + isAuthorized = !NativeApp.PortsRequireAuthorization(ConfigurationManager.CommonApplicationPaths.ApplicationPath); + } + catch + { + + } + } + if (!isAuthorized) { RegisterServerWithAdministratorAccess(); ServerConfigurationManager.Configuration.IsPortAuthorized = true; diff --git a/MediaBrowser.Server.Startup.Common/INativeApp.cs b/MediaBrowser.Server.Startup.Common/INativeApp.cs index d2e278a3b..9297a6d37 100644 --- a/MediaBrowser.Server.Startup.Common/INativeApp.cs +++ b/MediaBrowser.Server.Startup.Common/INativeApp.cs @@ -25,6 +25,8 @@ namespace MediaBrowser.Server.Startup.Common /// The temporary directory. void AuthorizeServer(int udpPort, int httpServerPort, int httpsServerPort, string applicationPath, string tempDirectory); + bool PortsRequireAuthorization(string applicationPath); + /// /// Gets the environment. /// diff --git a/MediaBrowser.ServerApplication/Native/RegisterServer.bat b/MediaBrowser.ServerApplication/Native/RegisterServer.bat index 27f863d58..85baa0d03 100644 --- a/MediaBrowser.ServerApplication/Native/RegisterServer.bat +++ b/MediaBrowser.ServerApplication/Native/RegisterServer.bat @@ -20,7 +20,9 @@ netsh advfirewall firewall add rule name="Port %3" dir=in action=allow protocol= if [%4]==[] GOTO DONE +netsh advfirewall firewall delete rule name="mediabrowser.serverapplication.exe" netsh advfirewall firewall delete rule name="Emby Server" + netsh advfirewall firewall add rule name="Emby Server" dir=in action=allow protocol=TCP program=%4 enable=yes netsh advfirewall firewall add rule name="Emby Server" dir=in action=allow protocol=UDP program=%4 enable=yes diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs index 139471f11..f7b19162d 100644 --- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs +++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs @@ -208,5 +208,60 @@ namespace MediaBrowser.ServerApplication.Native { LoopUtil.Run(appName); } + + public bool PortsRequireAuthorization(string applicationPath) + { + var appNameSrch = Path.GetFileName(applicationPath); + + var startInfo = new ProcessStartInfo + { + FileName = "netsh", + + Arguments = "advfirewall firewall show rule \"" + appNameSrch + "\"", + + CreateNoWindow = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false, + RedirectStandardOutput = true + }; + + using (var process = Process.Start(startInfo)) + { + process.Start(); + + try + { + var data = process.StandardOutput.ReadToEnd() ?? string.Empty; + + //_logger.Debug("Found windows firewall rule: " + data); + if (data.IndexOf("Block", StringComparison.OrdinalIgnoreCase) != -1) + { + return true; + } + + //var parts = data.Split('\n'); + + //return parts.Length > 4; + return false; + } + catch (Exception ex) + { + _logger.ErrorException("Error querying windows firewall", ex); + + // Hate having to do this + try + { + process.Kill(); + } + catch (Exception ex1) + { + _logger.ErrorException("Error killing process", ex1); + } + + throw; + } + } + } } } \ No newline at end of file -- cgit v1.2.3 From 232b5758f08e8e748427dcef7d7bdc7c0d7b94ae Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 8 Sep 2016 16:32:30 -0400 Subject: update dialogs --- MediaBrowser.Controller/LiveTv/ProgramInfo.cs | 7 +++++++ MediaBrowser.Model/Configuration/ServerConfiguration.cs | 1 + MediaBrowser.Model/LiveTv/ProgramAudio.cs | 3 ++- .../LiveTv/Listings/SchedulesDirect.cs | 7 ++++++- .../LiveTv/LiveTvManager.cs | 17 ++++++++++------- 5 files changed, 26 insertions(+), 9 deletions(-) (limited to 'MediaBrowser.Model/Configuration/ServerConfiguration.cs') diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index a6a3e6108..ea5e6dbc6 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -1,6 +1,7 @@ using MediaBrowser.Model.LiveTv; using System; using System.Collections.Generic; +using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.LiveTv { @@ -66,6 +67,8 @@ namespace MediaBrowser.Controller.LiveTv /// true if this instance is hd; otherwise, false. public bool? IsHD { get; set; } + public bool? Is3D { get; set; } + /// /// Gets or sets the audio. /// @@ -84,6 +87,8 @@ namespace MediaBrowser.Controller.LiveTv /// true if this instance is repeat; otherwise, false. public bool IsRepeat { get; set; } + public bool IsSubjectToBlackout { get; set; } + /// /// Gets or sets the episode title. /// @@ -144,6 +149,8 @@ namespace MediaBrowser.Controller.LiveTv /// true if this instance is kids; otherwise, false. public bool IsKids { get; set; } + public bool IsEducational { get; set; } + /// /// Gets or sets a value indicating whether this instance is premiere. /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 26bf3107d..a444afb07 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -234,6 +234,7 @@ namespace MediaBrowser.Model.Configuration EnableAnonymousUsageReporting = true; EnableAutomaticRestart = true; + EnableFolderView = true; EnableUPnP = true; SharingExpirationDays = 30; diff --git a/MediaBrowser.Model/LiveTv/ProgramAudio.cs b/MediaBrowser.Model/LiveTv/ProgramAudio.cs index 902079b9a..9a272492c 100644 --- a/MediaBrowser.Model/LiveTv/ProgramAudio.cs +++ b/MediaBrowser.Model/LiveTv/ProgramAudio.cs @@ -6,6 +6,7 @@ Stereo, Dolby, DolbyDigital, - Thx + Thx, + Atmos } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 206e90e40..7cac87590 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -348,7 +348,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings if (programInfo.audioProperties != null) { - if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase))) + if (programInfo.audioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase))) + { + audioType = ProgramAudio.Atmos; + } + else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase))) { audioType = ProgramAudio.DolbyDigital; } @@ -405,6 +409,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings if (programInfo.videoProperties != null) { info.IsHD = programInfo.videoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase); + info.Is3D = programInfo.videoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase); } if (details.contentRating != null && details.contentRating.Count > 0) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index a135a188e..b3ced55a5 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -2803,15 +2803,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv feature = "embytvseriesrecordings"; } - var config = GetConfiguration(); - if (config.TunerHosts.Count(i => i.IsEnabled) > 0 && - config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0) + if (string.Equals(feature, "dvr", StringComparison.OrdinalIgnoreCase)) { - return Task.FromResult(new MBRegistrationRecord + var config = GetConfiguration(); + if (config.TunerHosts.Count(i => i.IsEnabled) > 0 && + config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0) { - IsRegistered = true, - IsValid = true - }); + return Task.FromResult(new MBRegistrationRecord + { + IsRegistered = true, + IsValid = true + }); + } } return _security.GetRegistrationStatus(feature); -- cgit v1.2.3 From 064ea2799464c7db5e6dbebd6228721840568690 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 9 Sep 2016 14:26:05 -0400 Subject: update default image extraction timeouts --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 3 ++- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Model/Configuration/ServerConfiguration.cs') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 75d85cd32..a2707aace 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -931,8 +931,9 @@ namespace MediaBrowser.MediaEncoding.Encoder var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; if (timeoutMs <= 0) { - timeoutMs = 10000; + timeoutMs = Environment.Is64BitOperatingSystem ? (Environment.ProcessorCount > 2 ? 14000 : 20000) : 40000; } + ranToCompletion = process.WaitForExit(timeoutMs); if (!ranToCompletion) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index a444afb07..40ac4be8a 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -217,7 +217,7 @@ namespace MediaBrowser.Model.Configuration Migrations = new string[] { }; CodecsUsed = new string[] { }; SqliteCacheSize = 0; - ImageExtractionTimeoutMs = 14000; + ImageExtractionTimeoutMs = 0; EnableLocalizedGuids = true; DisplaySpecialsWithinSeasons = true; -- cgit v1.2.3 From 62d9eb1ec7da1b7017818e5620c2334ad336ac2f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 11 Sep 2016 03:33:53 -0400 Subject: rework upnp discovery --- MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs | 14 +- MediaBrowser.Controller/Dlna/ISsdpHandler.cs | 1 - MediaBrowser.Controller/LiveTv/ProgramInfo.cs | 2 + MediaBrowser.Dlna/Main/DlnaEntryPoint.cs | 86 ++- MediaBrowser.Dlna/MediaBrowser.Dlna.csproj | 12 +- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 13 +- MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 26 +- MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs | 213 ++----- MediaBrowser.Dlna/Ssdp/SsdpHandler.cs | 383 ------------ .../MediaBrowser.Model.Portable.csproj | 3 - .../MediaBrowser.Model.net35.csproj | 5 +- MediaBrowser.Model/Configuration/AutoOnOff.cs | 10 - .../Configuration/ServerConfiguration.cs | 3 - MediaBrowser.Model/MediaBrowser.Model.csproj | 1 - .../EntryPoints/ExternalPortForwarding.cs | 66 ++- .../IO/LibraryMonitor.cs | 20 +- .../LiveTv/Listings/SchedulesDirect.cs | 86 ++- .../TunerHosts/HdHomerun/HdHomerunDiscovery.cs | 9 +- .../LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs | 11 +- .../MediaBrowser.Server.Implementations.csproj | 7 +- .../packages.config | 1 - .../MediaBrowser.Server.Mono.csproj | 5 +- MediaBrowser.Server.Mono/app.config | 14 +- .../ApplicationHost.cs | 4 +- .../MediaBrowser.Server.Startup.Common.csproj | 3 +- MediaBrowser.sln | 32 + Mono.Nat/AbstractNatDevice.cs | 97 +++ Mono.Nat/AsyncResults/AsyncResult.cs | 71 +++ Mono.Nat/Enums/MapState.cs | 36 ++ Mono.Nat/Enums/ProtocolType.cs | 36 ++ Mono.Nat/EventArgs/DeviceEventArgs.cs | 45 ++ Mono.Nat/Exceptions/MappingException.cs | 87 +++ Mono.Nat/IMapper.cs | 50 ++ Mono.Nat/INatDevice.cs | 62 ++ Mono.Nat/ISearcher.cs | 51 ++ Mono.Nat/Mapping.cs | 123 ++++ Mono.Nat/Mono.Nat.csproj | 104 ++++ Mono.Nat/NatProtocol.cs | 9 + Mono.Nat/NatUtility.cs | 264 +++++++++ Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs | 52 ++ Mono.Nat/Pmp/Mappers/PmpMapper.cs | 83 +++ Mono.Nat/Pmp/Pmp.cs | 118 ++++ Mono.Nat/Pmp/PmpConstants.cs | 56 ++ Mono.Nat/Pmp/PmpNatDevice.cs | 347 +++++++++++ Mono.Nat/Pmp/Searchers/PmpSearcher.cs | 149 +++++ Mono.Nat/Properties/AssemblyInfo.cs | 31 + .../Upnp/AsyncResults/GetAllMappingsAsyncResult.cs | 56 ++ Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs | 75 +++ Mono.Nat/Upnp/Mappers/UpnpMapper.cs | 110 ++++ Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs | 60 ++ Mono.Nat/Upnp/Messages/ErrorMessage.cs | 63 ++ Mono.Nat/Upnp/Messages/GetServicesMessage.cs | 62 ++ .../Messages/Requests/CreatePortMappingMessage.cs | 75 +++ .../Messages/Requests/DeletePortMappingMessage.cs | 57 ++ .../Requests/GetExternalIPAddressMessage.cs | 51 ++ .../Requests/GetGenericPortMappingEntry.cs | 55 ++ .../Requests/GetSpecificPortMappingEntryMessage.cs | 60 ++ .../Responses/CreatePortMappingResponseMessage.cs | 46 ++ .../Responses/DeletePortMappingResponseMessage.cs | 44 ++ .../GetExternalIPAddressResponseMessage.cs | 53 ++ .../GetGenericPortMappingEntryResponseMessage.cs | 108 ++++ Mono.Nat/Upnp/Messages/UpnpMessage.cs | 132 +++++ Mono.Nat/Upnp/Searchers/UpnpSearcher.cs | 287 +++++++++ Mono.Nat/Upnp/Upnp.cs | 83 +++ Mono.Nat/Upnp/UpnpNatDevice.cs | 651 +++++++++++++++++++++ 65 files changed, 4329 insertions(+), 700 deletions(-) delete mode 100644 MediaBrowser.Model/Configuration/AutoOnOff.cs create mode 100644 Mono.Nat/AbstractNatDevice.cs create mode 100644 Mono.Nat/AsyncResults/AsyncResult.cs create mode 100644 Mono.Nat/Enums/MapState.cs create mode 100644 Mono.Nat/Enums/ProtocolType.cs create mode 100644 Mono.Nat/EventArgs/DeviceEventArgs.cs create mode 100644 Mono.Nat/Exceptions/MappingException.cs create mode 100644 Mono.Nat/IMapper.cs create mode 100644 Mono.Nat/INatDevice.cs create mode 100644 Mono.Nat/ISearcher.cs create mode 100644 Mono.Nat/Mapping.cs create mode 100644 Mono.Nat/Mono.Nat.csproj create mode 100644 Mono.Nat/NatProtocol.cs create mode 100644 Mono.Nat/NatUtility.cs create mode 100644 Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs create mode 100644 Mono.Nat/Pmp/Mappers/PmpMapper.cs create mode 100644 Mono.Nat/Pmp/Pmp.cs create mode 100644 Mono.Nat/Pmp/PmpConstants.cs create mode 100644 Mono.Nat/Pmp/PmpNatDevice.cs create mode 100644 Mono.Nat/Pmp/Searchers/PmpSearcher.cs create mode 100644 Mono.Nat/Properties/AssemblyInfo.cs create mode 100644 Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs create mode 100644 Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs create mode 100644 Mono.Nat/Upnp/Mappers/UpnpMapper.cs create mode 100644 Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/ErrorMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/GetServicesMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs create mode 100644 Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/UpnpMessage.cs create mode 100644 Mono.Nat/Upnp/Searchers/UpnpSearcher.cs create mode 100644 Mono.Nat/Upnp/Upnp.cs create mode 100644 Mono.Nat/Upnp/UpnpNatDevice.cs (limited to 'MediaBrowser.Model/Configuration/ServerConfiguration.cs') diff --git a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs index e8083b363..d2c5b9e4e 100644 --- a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs +++ b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs @@ -1,10 +1,20 @@ using System; +using System.Collections.Generic; +using System.Net; +using MediaBrowser.Model.Events; namespace MediaBrowser.Controller.Dlna { public interface IDeviceDiscovery { - event EventHandler DeviceDiscovered; - event EventHandler DeviceLeft; + event EventHandler> DeviceDiscovered; + event EventHandler> DeviceLeft; + } + + public class UpnpDeviceInfo + { + public Uri Location { get; set; } + public Dictionary Headers { get; set; } + public IPEndPoint LocalEndPoint { get; set; } } } diff --git a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs index e4126ddcf..ec3a00aad 100644 --- a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs +++ b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs @@ -4,6 +4,5 @@ namespace MediaBrowser.Controller.Dlna { public interface ISsdpHandler { - event EventHandler MessageReceived; } } diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index ea5e6dbc6..d0377fbfd 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -107,6 +107,8 @@ namespace MediaBrowser.Controller.LiveTv /// The image URL. public string ImageUrl { get; set; } + public string LogoImageUrl { get; set; } + /// /// Gets or sets a value indicating whether this instance has image. /// diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs index 9f2726b31..af03f325f 100644 --- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -14,8 +14,10 @@ using MediaBrowser.Dlna.Ssdp; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using MediaBrowser.Controller.MediaEncoding; +using Rssdp; namespace MediaBrowser.Dlna.Main { @@ -38,12 +40,11 @@ namespace MediaBrowser.Dlna.Main private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaEncoder _mediaEncoder; - private readonly SsdpHandler _ssdpHandler; private readonly IDeviceDiscovery _deviceDiscovery; - private readonly List _registeredServerIds = new List(); private bool _ssdpHandlerStarted; private bool _dlnaServerStarted; + private SsdpDevicePublisher _Publisher; public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, @@ -58,7 +59,7 @@ namespace MediaBrowser.Dlna.Main IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, - ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder) + IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder) { _config = config; _appHost = appHost; @@ -74,7 +75,6 @@ namespace MediaBrowser.Dlna.Main _mediaSourceManager = mediaSourceManager; _deviceDiscovery = deviceDiscovery; _mediaEncoder = mediaEncoder; - _ssdpHandler = (SsdpHandler)ssdpHandler; _logger = logManager.GetLogger("Dlna"); } @@ -154,7 +154,7 @@ namespace MediaBrowser.Dlna.Main { try { - _ssdpHandler.Start(); + StartPublishing(); _ssdpHandlerStarted = true; StartDeviceDiscovery(); @@ -165,13 +165,16 @@ namespace MediaBrowser.Dlna.Main } } + private void StartPublishing() + { + _Publisher = new SsdpDevicePublisher(); + } + private void StartDeviceDiscovery() { try { - ((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler); - - //DlnaChannel.Current.Start(() => _registeredServerIds.ToList()); + ((DeviceDiscovery)_deviceDiscovery).Start(); } catch (Exception ex) { @@ -199,8 +202,6 @@ namespace MediaBrowser.Dlna.Main { ((DeviceDiscovery)_deviceDiscovery).Dispose(); - _ssdpHandler.Dispose(); - _ssdpHandlerStarted = false; } catch (Exception ex) @@ -225,6 +226,14 @@ namespace MediaBrowser.Dlna.Main private async Task RegisterServerEndpoints() { + if (!_config.GetDlnaConfiguration().BlastAliveMessages) + { + return; + } + + var cacheLength = _config.GetDlnaConfiguration().BlastAliveMessageIntervalSeconds*2; + _Publisher.SupportPnpRootDevice = true; + foreach (var address in await _appHost.GetLocalIpAddresses().ConfigureAwait(false)) { //if (IPAddress.IsLoopback(address)) @@ -234,25 +243,41 @@ namespace MediaBrowser.Dlna.Main //} var addressString = address.ToString(); - var udn = addressString.GetMD5().ToString("N"); - - var descriptorURI = "/dlna/" + udn + "/description.xml"; - - var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI); var services = new List { - "upnp:rootdevice", "urn:schemas-upnp-org:device:MediaServer:1", "urn:schemas-upnp-org:service:ContentDirectory:1", "urn:schemas-upnp-org:service:ConnectionManager:1", - "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1", - "uuid:" + udn + "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1" }; - _ssdpHandler.RegisterNotification(udn, uri, address, services); + var udn = (addressString).GetMD5().ToString("N"); + + foreach (var fullService in services) + { + var descriptorURI = "/dlna/" + udn + "/description.xml"; + var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI); + + var service = fullService.Replace("urn:", string.Empty).Replace(":1", string.Empty); - _registeredServerIds.Add(udn); + var serviceParts = service.Split(':'); + + var deviceTypeNamespace = serviceParts[0].Replace('.', '-'); + + _Publisher.AddDevice(new SsdpRootDevice + { + CacheLifetime = TimeSpan.FromSeconds(cacheLength), //How long SSDP clients can cache this info. + Location = uri, // Must point to the URL that serves your devices UPnP description document. + DeviceTypeNamespace = deviceTypeNamespace, + DeviceClass = serviceParts[1], + DeviceType = serviceParts[2], + FriendlyName = "Emby Server", + Manufacturer = "Emby", + ModelName = "Emby Server", + Uuid = udn // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. + }); + } } } @@ -315,20 +340,23 @@ namespace MediaBrowser.Dlna.Main public void DisposeDlnaServer() { - foreach (var id in _registeredServerIds) + if (_Publisher != null) { - try - { - _ssdpHandler.UnregisterNotification(id); - } - catch (Exception ex) + var devices = _Publisher.Devices.ToList(); + foreach (var device in devices) { - _logger.ErrorException("Error unregistering server", ex); + try + { + _Publisher.RemoveDevice(device); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending bye bye", ex); + } } + _Publisher.Dispose(); } - _registeredServerIds.Clear(); - _dlnaServerStarted = false; } } diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index d10a5f7b5..b25376d1b 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -1,5 +1,5 @@  - + Debug @@ -14,6 +14,7 @@ 2.0 v4.5 ..\ + true @@ -23,7 +24,7 @@ DEBUG;TRACE prompt 4 - v4.5 + v4.5.1 none @@ -50,8 +51,15 @@ ..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll + + ..\ThirdParty\rssdp\Rssdp.NetFx40.dll + + + ..\ThirdParty\rssdp\Rssdp.Portable.dll + + diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index 5622885fc..d958d0e37 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -19,6 +19,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Events; namespace MediaBrowser.Dlna.PlayTo { @@ -122,16 +123,18 @@ namespace MediaBrowser.Dlna.PlayTo } } - void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e) + void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs e) { + var info = e.Argument; + string nts; - e.Headers.TryGetValue("NTS", out nts); + info.Headers.TryGetValue("NTS", out nts); string usn; - if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty; + if (!info.Headers.TryGetValue("USN", out usn)) usn = String.Empty; string nt; - if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty; + if (!info.Headers.TryGetValue("NT", out nt)) nt = String.Empty; if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 && !_disposed) @@ -653,7 +656,7 @@ namespace MediaBrowser.Dlna.PlayTo _device.PlaybackProgress -= _device_PlaybackProgress; _device.PlaybackStopped -= _device_PlaybackStopped; _device.MediaChanged -= _device_MediaChanged; - _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft; + //_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft; _device.OnDeviceUnavailable = null; _device.Dispose(); diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index cd9a7b1f0..6d6986f01 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -12,7 +12,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Threading.Tasks; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Events; namespace MediaBrowser.Dlna.PlayTo { @@ -61,16 +63,17 @@ namespace MediaBrowser.Dlna.PlayTo _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; } - async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) + async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) { + var info = e.Argument; + string usn; - if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty; string nt; - if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty; - string location; - if (!e.Headers.TryGetValue("Location", out location)) location = string.Empty; + string location = info.Location.ToString(); // It has to report that it's a media renderer if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 && @@ -100,7 +103,7 @@ namespace MediaBrowser.Dlna.PlayTo } } - var uri = new Uri(location); + var uri = info.Location; _logger.Debug("Attempting to create PlayToController from location {0}", location); var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false); @@ -121,7 +124,7 @@ namespace MediaBrowser.Dlna.PlayTo if (controller == null) { - var serverAddress = GetServerAddress(e.LocalEndPoint.Address); + var serverAddress = await GetServerAddress(info.LocalEndPoint == null ? null : info.LocalEndPoint.Address).ConfigureAwait(false); string accessToken = null; sessionInfo.SessionController = controller = new PlayToController(sessionInfo, @@ -173,9 +176,14 @@ namespace MediaBrowser.Dlna.PlayTo } } - private string GetServerAddress(IPAddress localIp) + private Task GetServerAddress(IPAddress localIp) { - return _appHost.GetLocalApiUrl(localIp); + if (localIp == null) + { + return _appHost.GetLocalApiUrl(); + } + + return Task.FromResult(_appHost.GetLocalApiUrl(localIp)); } public void Dispose() diff --git a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs index 68768745e..91dbeb96e 100644 --- a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs +++ b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs @@ -11,6 +11,8 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; +using MediaBrowser.Model.Events; +using Rssdp; namespace MediaBrowser.Dlna.Ssdp { @@ -20,132 +22,43 @@ namespace MediaBrowser.Dlna.Ssdp private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - private SsdpHandler _ssdpHandler; private readonly CancellationTokenSource _tokenSource; - private readonly IServerApplicationHost _appHost; - public event EventHandler DeviceDiscovered; - public event EventHandler DeviceLeft; - private readonly INetworkManager _networkManager; + public event EventHandler> DeviceDiscovered; + public event EventHandler> DeviceLeft; - public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, IServerApplicationHost appHost, INetworkManager networkManager) + private SsdpDeviceLocator _DeviceLocator; + + public DeviceDiscovery(ILogger logger, IServerConfigurationManager config) { _tokenSource = new CancellationTokenSource(); _logger = logger; _config = config; - _appHost = appHost; - _networkManager = networkManager; - } - - private List GetLocalIpAddresses() - { - return _networkManager.GetLocalIpAddresses().ToList(); - } - - public void Start(SsdpHandler ssdpHandler) - { - _ssdpHandler = ssdpHandler; - _ssdpHandler.MessageReceived += _ssdpHandler_MessageReceived; - - foreach (var localIp in GetLocalIpAddresses()) - { - try - { - CreateListener(localIp); - } - catch (Exception e) - { - _logger.ErrorException("Failed to Initilize Socket", e); - } - } } - async void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e) + // Call this method from somewhere in your code to start the search. + public void BeginSearch() { - string nts; - e.Headers.TryGetValue("NTS", out nts); - - if (String.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) && - String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) && - !_disposed) - { - EventHelper.FireEventIfNotNull(DeviceLeft, this, e, _logger); - return; - } - - try - { - if (e.LocalEndPoint == null) - { - var ip = (await _appHost.GetLocalIpAddresses().ConfigureAwait(false)).FirstOrDefault(i => !IPAddress.IsLoopback(i)); - if (ip != null) - { - e.LocalEndPoint = new IPEndPoint(ip, 0); - } - } - - if (e.LocalEndPoint != null) - { - TryCreateDevice(e); - } - } - catch (OperationCanceledException) - { - } - catch (Exception ex) - { - _logger.ErrorException("Error creating play to controller", ex); - } - } - - private void CreateListener(IPAddress localIp) - { - Task.Factory.StartNew(async (o) => - { - try - { - _logger.Info("Creating SSDP listener on {0}", localIp); - - var endPoint = new IPEndPoint(localIp, 1900); - - using (var socket = GetMulticastSocket(localIp, endPoint)) - { - var receiveBuffer = new byte[64000]; - - CreateNotifier(localIp); - - while (!_tokenSource.IsCancellationRequested) - { - var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000); - - if (receivedBytes > 0) - { - var args = SsdpHelper.ParseSsdpResponse(receiveBuffer); - args.EndPoint = endPoint; - args.LocalEndPoint = new IPEndPoint(localIp, 0); - - _ssdpHandler.LogMessageReceived(args, true); - - TryCreateDevice(args); - } - } - } - - _logger.Info("SSDP listener - Task completed"); - } - catch (OperationCanceledException) - { - } - catch (Exception e) - { - _logger.ErrorException("Error in listener", e); - } - - }, _tokenSource.Token, TaskCreationOptions.LongRunning); + _DeviceLocator = new SsdpDeviceLocator(); + + // (Optional) Set the filter so we only see notifications for devices we care about + // (can be any search target value i.e device type, uuid value etc - any value that appears in the + // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method). + //_DeviceLocator.NotificationFilter = "upnp:rootdevice"; + + // Connect our event handler so we process devices as they are found + _DeviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable; + _DeviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable; + // Enable listening for notifications (optional) + _DeviceLocator.StartListeningForNotifications(); + + // Perform a search so we don't have to wait for devices to broadcast notifications + // again to get any results right away (notifications are broadcast periodically). + StartAsyncSearch(); } - private void CreateNotifier(IPAddress localIp) + private void StartAsyncSearch() { Task.Factory.StartNew(async (o) => { @@ -153,7 +66,7 @@ namespace MediaBrowser.Dlna.Ssdp { while (true) { - _ssdpHandler.SendSearchMessage(new IPEndPoint(localIp, 1900)); + await _DeviceLocator.SearchAsync().ConfigureAwait(false); var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000; @@ -165,66 +78,60 @@ namespace MediaBrowser.Dlna.Ssdp } catch (Exception ex) { - _logger.ErrorException("Error in notifier", ex); + _logger.ErrorException("Error searching for devices", ex); } - }, _tokenSource.Token, TaskCreationOptions.LongRunning); + }, CancellationToken.None, TaskCreationOptions.LongRunning); } - private Socket GetMulticastSocket(IPAddress localIpAddress, EndPoint localEndpoint) + // Process each found device in the event handler + void deviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs e) { - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIpAddress)); - socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); + var originalHeaders = e.DiscoveredDevice.ResponseHeaders; - socket.Bind(localEndpoint); + var headerDict = originalHeaders == null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); - return socket; - } - - private void TryCreateDevice(SsdpMessageEventArgs args) - { - string nts; - args.Headers.TryGetValue("NTS", out nts); + var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - if (String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase)) + var args = new GenericEventArgs { - if (String.Equals(args.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase)) + Argument = new UpnpDeviceInfo { - if (!_disposed) - { - EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger); - } + Location = e.DiscoveredDevice.DescriptionLocation, + Headers = headers } + }; - return; - } + EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger); + } - string usn; - if (!args.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e) + { + var originalHeaders = e.DiscoveredDevice.ResponseHeaders; + + var headerDict = originalHeaders == null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); - string nt; - if (!args.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - // Need to be able to download device description - string location; - if (!args.Headers.TryGetValue("Location", out location) || - string.IsNullOrEmpty(location)) + var args = new GenericEventArgs { - return; - } + Argument = new UpnpDeviceInfo + { + Location = e.DiscoveredDevice.DescriptionLocation, + Headers = headers + } + }; - EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger); + EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger); } - public void Dispose() + public void Start() { - if (_ssdpHandler != null) - { - _ssdpHandler.MessageReceived -= _ssdpHandler_MessageReceived; - } + BeginSearch(); + } + public void Dispose() + { if (!_disposed) { _disposed = true; diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs index 720ea71a0..0d0ca98a2 100644 --- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs +++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs @@ -83,90 +83,6 @@ namespace MediaBrowser.Dlna.Ssdp } } - public event EventHandler MessageReceived; - - private async void OnMessageReceived(SsdpMessageEventArgs args, bool isMulticast) - { - if (IgnoreMessage(args, isMulticast)) - { - return; - } - - LogMessageReceived(args, isMulticast); - - var headers = args.Headers; - string st; - - if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase) && headers.TryGetValue("st", out st)) - { - TimeSpan delay = GetSearchDelay(headers); - - if (_config.GetDlnaConfiguration().EnableDebugLog) - { - _logger.Debug("Delaying search response by {0} seconds", delay.TotalSeconds); - } - - await Task.Delay(delay).ConfigureAwait(false); - - RespondToSearch(args.EndPoint, st); - } - - EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger); - } - - internal void LogMessageReceived(SsdpMessageEventArgs args, bool isMulticast) - { - var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog; - - if (enableDebugLogging) - { - var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)); - var headerText = string.Join(",", headerTexts.ToArray()); - - var protocol = isMulticast ? "Multicast" : "Unicast"; - var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString(); - _logger.Debug("{0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol); - } - } - - internal bool IgnoreMessage(SsdpMessageEventArgs args, bool isMulticast) - { - string usn; - if (args.Headers.TryGetValue("USN", out usn)) - { - // USN=uuid:b67df29b5c379445fde78c3774ab518d::urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1 - if (RegisteredDevices.Any(i => string.Equals(i.USN, usn, StringComparison.OrdinalIgnoreCase))) - { - //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)); - //var headerText = string.Join(",", headerTexts.ToArray()); - - //var protocol = isMulticast ? "Multicast" : "Unicast"; - //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString(); - //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol); - - return true; - } - } - - string serverId; - if (args.Headers.TryGetValue("X-EMBY-SERVERID", out serverId)) - { - if (string.Equals(serverId, _appHost.SystemId, StringComparison.OrdinalIgnoreCase)) - { - //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)); - //var headerText = string.Join(",", headerTexts.ToArray()); - - //var protocol = isMulticast ? "Multicast" : "Unicast"; - //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString(); - //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol); - - return true; - } - } - - return false; - } - public IEnumerable RegisteredDevices { get @@ -188,8 +104,6 @@ namespace MediaBrowser.Dlna.Ssdp RestartSocketListener(); ReloadAliveNotifier(); - CreateUnicastClient(); - SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged; SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; } @@ -202,32 +116,6 @@ namespace MediaBrowser.Dlna.Ssdp } } - public void SendSearchMessage(EndPoint localIp) - { - var values = new Dictionary(StringComparer.OrdinalIgnoreCase); - - values["HOST"] = "239.255.255.250:1900"; - values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2"; - values["X-EMBY-SERVERID"] = _appHost.SystemId; - - values["MAN"] = "\"ssdp:discover\""; - - // Search target - values["ST"] = "ssdp:all"; - - // Seconds to delay response - values["MX"] = "3"; - - var header = "M-SEARCH * HTTP/1.1"; - - var msg = new SsdpMessageBuilder().BuildMessage(header, values); - - // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2) - SendDatagram(msg, _ssdpEndp, localIp, true); - - SendUnicastRequest(msg); - } - public async void SendDatagram(string msg, EndPoint endpoint, EndPoint localAddress, @@ -248,75 +136,6 @@ namespace MediaBrowser.Dlna.Ssdp } } - /// - /// According to the spec: http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0-20080424.pdf - /// Device responses should be delayed a random duration between 0 and this many seconds to balance - /// load for the control point when it processes responses. In my testing kodi times out after mx - /// so we will generate from mx - 1 - /// - /// The mx headers - /// A timepsan for the amount to delay before returning search result. - private TimeSpan GetSearchDelay(Dictionary headers) - { - string mx; - headers.TryGetValue("mx", out mx); - int delaySeconds = 0; - if (!string.IsNullOrWhiteSpace(mx) - && int.TryParse(mx, NumberStyles.Any, CultureInfo.InvariantCulture, out delaySeconds) - && delaySeconds > 1) - { - delaySeconds = new Random().Next(delaySeconds - 1); - } - - return TimeSpan.FromSeconds(delaySeconds); - } - - private void RespondToSearch(EndPoint endpoint, string deviceType) - { - var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog; - - var isLogged = false; - - const string header = "HTTP/1.1 200 OK"; - - foreach (var d in RegisteredDevices) - { - if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) || - string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase)) - { - if (!isLogged) - { - if (enableDebugLogging) - { - _logger.Debug("Responding to search from {0} for {1}", endpoint, deviceType); - } - isLogged = true; - } - - var values = new Dictionary(StringComparer.OrdinalIgnoreCase); - - values["CACHE-CONTROL"] = "max-age = 600"; - values["DATE"] = DateTime.Now.ToString("R"); - values["EXT"] = ""; - values["LOCATION"] = d.Descriptor.ToString(); - values["SERVER"] = _serverSignature; - values["ST"] = d.Type; - values["USN"] = d.USN; - - var msg = new SsdpMessageBuilder().BuildMessage(header, values); - - SendDatagram(msg, endpoint, null, false, 2); - SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 2); - //SendDatagram(header, values, endpoint, null, true); - - if (enableDebugLogging) - { - _logger.Debug("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString()); - } - } - } - } - private void RestartSocketListener() { if (_isDisposed) @@ -329,8 +148,6 @@ namespace MediaBrowser.Dlna.Ssdp _multicastSocket = CreateMulticastSocket(); _logger.Info("MultiCast socket created"); - - Receive(); } catch (Exception ex) { @@ -339,74 +156,6 @@ namespace MediaBrowser.Dlna.Ssdp } } - private void Receive() - { - try - { - var buffer = new byte[1024]; - - EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort); - - _multicastSocket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpoint, ReceiveCallback, buffer); - } - catch (ObjectDisposedException) - { - if (!_isDisposed) - { - //StartSocketRetryTimer(); - } - } - catch (Exception ex) - { - _logger.Debug("Error in BeginReceiveFrom", ex); - } - } - - private void ReceiveCallback(IAsyncResult result) - { - if (_isDisposed) - { - return; - } - - try - { - EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort); - - var length = _multicastSocket.EndReceiveFrom(result, ref endpoint); - - var received = (byte[])result.AsyncState; - - var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog; - - if (enableDebugLogging) - { - _logger.Debug(Encoding.ASCII.GetString(received)); - } - - var args = SsdpHelper.ParseSsdpResponse(received); - args.EndPoint = endpoint; - - OnMessageReceived(args, true); - } - catch (ObjectDisposedException) - { - if (!_isDisposed) - { - //StartSocketRetryTimer(); - } - } - catch (Exception ex) - { - _logger.ErrorException("Failed to read SSDP message", ex); - } - - if (_multicastSocket != null) - { - Receive(); - } - } - public void Dispose() { _config.NamedConfigurationUpdated -= _config_ConfigurationUpdated; @@ -414,7 +163,6 @@ namespace MediaBrowser.Dlna.Ssdp _isDisposed = true; - DisposeUnicastClient(); DisposeSocket(); StopAliveNotifier(); } @@ -523,137 +271,6 @@ namespace MediaBrowser.Dlna.Ssdp } } - private void CreateUnicastClient() - { - if (_unicastClient == null) - { - try - { - _unicastClient = new UdpClient(_unicastPort); - } - catch (Exception ex) - { - _logger.ErrorException("Error creating unicast client", ex); - } - - UnicastSetBeginReceive(); - } - } - - private void DisposeUnicastClient() - { - if (_unicastClient != null) - { - try - { - _unicastClient.Close(); - } - catch (Exception ex) - { - _logger.ErrorException("Error closing unicast client", ex); - } - - _unicastClient = null; - } - } - - /// - /// Listen for Unicast SSDP Responses - /// - private void UnicastSetBeginReceive() - { - try - { - var ipRxEnd = new IPEndPoint(IPAddress.Any, _unicastPort); - var udpListener = new UdpState { EndPoint = ipRxEnd }; - - udpListener.UdpClient = _unicastClient; - _unicastClient.BeginReceive(UnicastReceiveCallback, udpListener); - } - catch (Exception ex) - { - _logger.ErrorException("Error in UnicastSetBeginReceive", ex); - } - } - - /// - /// The UnicastReceiveCallback receives Http Responses - /// and Fired the SatIpDeviceFound Event for adding the SatIpDevice - /// - /// - private void UnicastReceiveCallback(IAsyncResult ar) - { - var udpClient = ((UdpState)(ar.AsyncState)).UdpClient; - var endpoint = ((UdpState)(ar.AsyncState)).EndPoint; - if (udpClient.Client != null) - { - try - { - var responseBytes = udpClient.EndReceive(ar, ref endpoint); - var args = SsdpHelper.ParseSsdpResponse(responseBytes); - - args.EndPoint = endpoint; - - OnMessageReceived(args, false); - - UnicastSetBeginReceive(); - } - catch (ObjectDisposedException) - { - - } - catch (SocketException) - { - - } - catch (Exception) - { - // If called while shutting down, seeing a NullReferenceException inside EndReceive - } - } - } - - private void SendUnicastRequest(string request, int sendCount = 3) - { - if (_unicastClient == null) - { - return; - } - - var ipSsdp = IPAddress.Parse(SSDPAddr); - var ipTxEnd = new IPEndPoint(ipSsdp, SSDPPort); - - SendUnicastRequest(request, ipTxEnd, sendCount); - } - - private async void SendUnicastRequest(string request, IPEndPoint toEndPoint, int sendCount = 3) - { - if (_unicastClient == null) - { - return; - } - - //_logger.Debug("Sending unicast request"); - - byte[] req = Encoding.ASCII.GetBytes(request); - - try - { - for (var i = 0; i < sendCount; i++) - { - if (i > 0) - { - await Task.Delay(50).ConfigureAwait(false); - } - _unicastClient.Send(req, req.Length, toEndPoint); - } - } - catch (Exception ex) - { - _logger.ErrorException("Error in SendUnicastRequest", ex); - } - } - private readonly object _notificationTimerSyncLock = new object(); private int _aliveNotifierIntervalMs; private void ReloadAliveNotifier() diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 351740e6e..ad7dea0a5 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -175,9 +175,6 @@ Configuration\AccessSchedule.cs - - Configuration\AutoOnOff.cs - Configuration\BaseApplicationConfiguration.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index ad3811646..61f2f3f13 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -147,9 +147,6 @@ Configuration\AccessSchedule.cs - - Configuration\AutoOnOff.cs - Configuration\BaseApplicationConfiguration.cs @@ -1193,4 +1190,4 @@ --> - + \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/AutoOnOff.cs b/MediaBrowser.Model/Configuration/AutoOnOff.cs deleted file mode 100644 index e911a0ff1..000000000 --- a/MediaBrowser.Model/Configuration/AutoOnOff.cs +++ /dev/null @@ -1,10 +0,0 @@ - -namespace MediaBrowser.Model.Configuration -{ - public enum AutoOnOff - { - Auto, - Enabled, - Disabled - } -} diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 40ac4be8a..5cf266674 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -183,8 +183,6 @@ namespace MediaBrowser.Model.Configuration public int RemoteClientBitrateLimit { get; set; } - public AutoOnOff EnableLibraryMonitor { get; set; } - public int SharingExpirationDays { get; set; } public string[] Migrations { get; set; } @@ -244,7 +242,6 @@ namespace MediaBrowser.Model.Configuration // 5 minutes MinResumeDurationSeconds = 300; - EnableLibraryMonitor = AutoOnOff.Auto; LibraryMonitorDelay = 60; EnableInternetProviders = true; diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index db70b8606..c1a01680d 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -89,7 +89,6 @@ - diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 280bec65b..1021d8823 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Globalization; using System.Net; using MediaBrowser.Common.Threading; +using MediaBrowser.Model.Events; namespace MediaBrowser.Server.Implementations.EntryPoints { @@ -17,17 +18,17 @@ namespace MediaBrowser.Server.Implementations.EntryPoints private readonly IServerApplicationHost _appHost; private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - private readonly ISsdpHandler _ssdp; + private readonly IDeviceDiscovery _deviceDiscovery; private PeriodicTimer _timer; private bool _isStarted; - public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp) + public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery) { _logger = logmanager.GetLogger("PortMapper"); _appHost = appHost; _config = config; - _ssdp = ssdp; + _deviceDiscovery = deviceDiscovery; } private string _lastConfigIdentifier; @@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints public void Run() { - //NatUtility.Logger = new LogWriter(_logger); + NatUtility.Logger = _logger; if (_config.Configuration.EnableUPnP) { @@ -93,33 +94,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); - _ssdp.MessageReceived += _ssdp_MessageReceived; + _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; _lastConfigIdentifier = GetConfigIdentifier(); _isStarted = true; } - private void ClearCreatedRules(object state) - { - _createdRules = new List(); - _usnsHandled = new List(); - } - - void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e) + private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) { - var endpoint = e.EndPoint as IPEndPoint; - - if (endpoint == null || e.LocalEndPoint == null) - { - return; - } + var info = e.Argument; string usn; - if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty; string nt; - if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty; // Filter device type if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && @@ -132,15 +122,45 @@ namespace MediaBrowser.Server.Implementations.EntryPoints var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn; - if (!_usnsHandled.Contains(identifier)) + if (info.Location != null && !_usnsHandled.Contains(identifier)) { _usnsHandled.Add(identifier); _logger.Debug("Calling Nat.Handle on " + identifier); - NatUtility.Handle(e.LocalEndPoint.Address, e.Message, endpoint, NatProtocol.Upnp); + + IPAddress address; + if (IPAddress.TryParse(info.Location.Host, out address)) + { + // The Handle method doesn't need the port + var endpoint = new IPEndPoint(address, info.Location.Port); + + IPAddress localAddress = null; + + try + { + var localAddressString = await _appHost.GetLocalApiUrl().ConfigureAwait(false); + + if (!IPAddress.TryParse(localAddressString, out localAddress)) + { + return; + } + } + catch + { + return; + } + + NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp); + } } } + private void ClearCreatedRules(object state) + { + _createdRules = new List(); + _usnsHandled = new List(); + } + void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e) { var ex = e.ExceptionObject as Exception; @@ -228,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _timer = null; } - _ssdp.MessageReceived -= _ssdp_MessageReceived; + _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; try { diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index c87d10ef4..80364bb55 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -164,32 +164,16 @@ namespace MediaBrowser.Server.Implementations.IO Start(); } - private bool EnableLibraryMonitor - { - get - { - switch (ConfigurationManager.Configuration.EnableLibraryMonitor) - { - case AutoOnOff.Auto: - return Environment.OSVersion.Platform == PlatformID.Win32NT; - case AutoOnOff.Enabled: - return true; - default: - return false; - } - } - } - private bool IsLibraryMonitorEnabaled(BaseItem item) { var options = LibraryManager.GetLibraryOptions(item); - if (options != null && options.SchemaVersion >= 1) + if (options != null) { return options.EnableRealtimeMonitor; } - return EnableLibraryMonitor; + return false; } public void Start() diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 1a5ebedc2..6d2f79fa0 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -166,7 +166,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10)); if (imageIndex > -1) { - programDict[schedule.programID].images = GetProgramLogo(ApiUrl, images[imageIndex]); + var programEntry = programDict[schedule.programID]; + + var data = images[imageIndex].data ?? new List(); + data = data.OrderByDescending(GetSizeOrder).ToList(); + + programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true); + //programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false); + //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-LO", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-LOT", false); + + if (!string.IsNullOrWhiteSpace(programEntry.thumbImage)) + { + var b = true; + } + + if (!string.IsNullOrWhiteSpace(programEntry.bannerImage)) + { + var b = true; + } } } @@ -179,6 +199,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings return programsInfo; } + private int GetSizeOrder(ScheduleDirect.ImageData image) + { + if (!string.IsNullOrWhiteSpace(image.size)) + { + int value; + if (int.TryParse(image.size, out value)) + { + return value; + } + } + + return 0; + } + private readonly object _channelCacheLock = new object(); private ScheduleDirect.Station GetStation(string listingsId, string channelNumber, string channelName) { @@ -384,13 +418,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings episodeTitle = details.episodeTitle150; } - string imageUrl = null; - - if (details.hasImageArtwork) - { - imageUrl = details.images; - } - var showType = details.showType ?? string.Empty; var info = new ProgramInfo @@ -406,7 +433,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings Audio = audioType, IsRepeat = repeat, IsSeries = showType.IndexOf("series", StringComparison.OrdinalIgnoreCase) != -1, - ImageUrl = imageUrl, + ImageUrl = details.primaryImage, IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase), IsSports = showType.IndexOf("sports", StringComparison.OrdinalIgnoreCase) != -1, IsMovie = showType.IndexOf("movie", StringComparison.OrdinalIgnoreCase) != -1 || showType.IndexOf("film", StringComparison.OrdinalIgnoreCase) != -1, @@ -485,36 +512,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings return date; } - private string GetProgramLogo(string apiUrl, ScheduleDirect.ShowImages images) + private string GetProgramImage(string apiUrl, List images, string category, bool returnDefaultImage) { string url = null; - if (images.data != null) + + var logoIndex = images.FindIndex(i => string.Equals(i.category, category, StringComparison.OrdinalIgnoreCase)); + if (logoIndex == -1) { - var smallImages = images.data.Where(i => i.size == "Sm").ToList(); - if (smallImages.Any()) + if (!returnDefaultImage) { - images.data = smallImages; + return null; } - var logoIndex = images.data.FindIndex(i => i.category == "Logo"); - if (logoIndex == -1) + logoIndex = 0; + } + var uri = images[logoIndex].uri; + + if (!string.IsNullOrWhiteSpace(uri)) + { + if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1) { - logoIndex = 0; + url = uri; } - var uri = images.data[logoIndex].uri; - - if (!string.IsNullOrWhiteSpace(uri)) + else { - if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1) - { - url = uri; - } - else - { - url = apiUrl + "/image/" + uri; - } + url = apiUrl + "/image/" + uri; } - //_logger.Debug("URL for image is : " + url); } + //_logger.Debug("URL for image is : " + url); return url; } @@ -1204,7 +1228,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings public List crew { get; set; } public string showType { get; set; } public bool hasImageArtwork { get; set; } - public string images { get; set; } + public string primaryImage { get; set; } + public string thumbImage { get; set; } + public string bannerImage { get; set; } public string imageID { get; set; } public string md5 { get; set; } public List contentAdvisory { get; set; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs index 9ba1c60cc..ef37e3b35 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs @@ -10,6 +10,7 @@ using System; using System.Linq; using System.Threading; using MediaBrowser.Common.Net; +using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun @@ -39,13 +40,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; } - void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) + void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) { string server = null; - if (e.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1) + var info = e.Argument; + + if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1) { string location; - if (e.Headers.TryGetValue("Location", out location)) + if (info.Headers.TryGetValue("Location", out location)) { //_logger.Debug("HdHomerun found at {0}", location); diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs index cb0e573da..a0b8ef5f7 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs @@ -14,6 +14,7 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Extensions; using System.Xml.Linq; +using MediaBrowser.Model.Events; namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp { @@ -50,18 +51,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; } - void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) + void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) { + var info = e.Argument; + string st = null; string nt = null; - e.Headers.TryGetValue("ST", out st); - e.Headers.TryGetValue("NT", out nt); + info.Headers.TryGetValue("ST", out st); + info.Headers.TryGetValue("NT", out nt); if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) || string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase)) { string location; - if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location)) + if (info.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location)) { _logger.Debug("SAT IP found at {0}", location); diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 8850f3d35..e182ad6a5 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -105,9 +105,6 @@ ..\ThirdParty\UniversalDetector\UniversalDetector.dll - - ..\packages\Mono.Nat.1.2.24.0\lib\net40\Mono.Nat.dll - @@ -390,6 +387,10 @@ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model + + {d7453b88-2266-4805-b39b-2b5a2a33e1ba} + Mono.Nat + diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 746dc7f62..94522cd50 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -5,7 +5,6 @@ - diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index bcbb10174..e7acb3f50 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -1,5 +1,5 @@  - + Debug x86 @@ -10,8 +10,9 @@ MediaBrowser.Server.Mono MediaBrowser.Server.Mono MediaBrowser.Server.Mono.MainClass - v4.5 + v4.5.1 ..\ + true diff --git a/MediaBrowser.Server.Mono/app.config b/MediaBrowser.Server.Mono/app.config index b0e8558fd..e14b908ad 100644 --- a/MediaBrowser.Server.Mono/app.config +++ b/MediaBrowser.Server.Mono/app.config @@ -1,21 +1,21 @@ - + -
+
- - + + - - + + - + diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 433855ea0..e6d9b482e 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -549,7 +549,7 @@ namespace MediaBrowser.Server.Startup.Common SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager); RegisterSingleInstance(SubtitleManager); - RegisterSingleInstance(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, this, NetworkManager)); + RegisterSingleInstance(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager)); ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository); RegisterSingleInstance(ChapterManager); @@ -566,8 +566,6 @@ namespace MediaBrowser.Server.Startup.Common await sharingRepo.Initialize().ConfigureAwait(false); RegisterSingleInstance(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this)); - RegisterSingleInstance(new SsdpHandler(LogManager.GetLogger("SsdpHandler"), ServerConfigurationManager, this)); - var activityLogRepo = await GetActivityLogRepository().ConfigureAwait(false); RegisterSingleInstance(activityLogRepo); RegisterSingleInstance(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager)); diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj index 778002e50..7eba89650 100644 --- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj +++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj @@ -9,9 +9,10 @@ Properties MediaBrowser.Server.Startup.Common MediaBrowser.Server.Startup.Common - v4.5 + v4.5.1 512 ..\ + true diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 0b9dd90cd..7e0d834fa 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -64,6 +64,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Startup EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{D7453B88-2266-4805-B39B-2B5A2A33E1BA}" +EndProject Global GlobalSection(Performance) = preSolution HasPerformanceSessions = true @@ -522,6 +524,36 @@ Global {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Win32.ActiveCfg = Release|Any CPU {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x64.ActiveCfg = Release|Any CPU {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x86.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.ActiveCfg = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.Build.0 = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.ActiveCfg = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.Build.0 = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.Build.0 = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Mono.Nat/AbstractNatDevice.cs b/Mono.Nat/AbstractNatDevice.cs new file mode 100644 index 000000000..046cfc10f --- /dev/null +++ b/Mono.Nat/AbstractNatDevice.cs @@ -0,0 +1,97 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; + +namespace Mono.Nat +{ + public abstract class AbstractNatDevice : INatDevice + { + private DateTime lastSeen; + + protected AbstractNatDevice () + { + + } + + public abstract IPAddress LocalAddress { get; } + + public DateTime LastSeen + { + get { return lastSeen; } + set { lastSeen = value; } + } + + public virtual void CreatePortMap (Mapping mapping) + { + IAsyncResult result = BeginCreatePortMap (mapping, null, null); + EndCreatePortMap(result); + } + + public virtual void DeletePortMap (Mapping mapping) + { + IAsyncResult result = BeginDeletePortMap (mapping, null, mapping); + EndDeletePortMap(result); + } + + public virtual Mapping[] GetAllMappings () + { + IAsyncResult result = BeginGetAllMappings (null, null); + return EndGetAllMappings (result); + } + + public virtual IPAddress GetExternalIP () + { + IAsyncResult result = BeginGetExternalIP(null, null); + return EndGetExternalIP(result); + } + + public virtual Mapping GetSpecificMapping (Protocol protocol, int port) + { + IAsyncResult result = this.BeginGetSpecificMapping (protocol, port, null, null); + return this.EndGetSpecificMapping(result); + } + + public abstract IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState); + public abstract IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState); + + public abstract IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState); + public abstract IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState); + public abstract IAsyncResult BeginGetSpecificMapping(Protocol protocol, int externalPort, AsyncCallback callback, object asyncState); + + public abstract void EndCreatePortMap (IAsyncResult result); + public abstract void EndDeletePortMap (IAsyncResult result); + + public abstract Mapping[] EndGetAllMappings (IAsyncResult result); + public abstract IPAddress EndGetExternalIP (IAsyncResult result); + public abstract Mapping EndGetSpecificMapping (IAsyncResult result); + } +} diff --git a/Mono.Nat/AsyncResults/AsyncResult.cs b/Mono.Nat/AsyncResults/AsyncResult.cs new file mode 100644 index 000000000..e98e7d7ca --- /dev/null +++ b/Mono.Nat/AsyncResults/AsyncResult.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Mono.Nat +{ + internal class AsyncResult : IAsyncResult + { + private object asyncState; + private AsyncCallback callback; + private bool completedSynchronously; + private bool isCompleted; + private Exception storedException; + private ManualResetEvent waitHandle; + + public AsyncResult(AsyncCallback callback, object asyncState) + { + this.callback = callback; + this.asyncState = asyncState; + waitHandle = new ManualResetEvent(false); + } + + public object AsyncState + { + get { return asyncState; } + } + + public ManualResetEvent AsyncWaitHandle + { + get { return waitHandle; } + } + + WaitHandle IAsyncResult.AsyncWaitHandle + { + get { return waitHandle; } + } + + public bool CompletedSynchronously + { + get { return completedSynchronously; } + protected internal set { completedSynchronously = value; } + } + + public bool IsCompleted + { + get { return isCompleted; } + protected internal set { isCompleted = value; } + } + + public Exception StoredException + { + get { return storedException; } + } + + public void Complete() + { + Complete(storedException); + } + + public void Complete(Exception ex) + { + storedException = ex; + isCompleted = true; + waitHandle.Set(); + + if (callback != null) + callback(this); + } + } +} diff --git a/Mono.Nat/Enums/MapState.cs b/Mono.Nat/Enums/MapState.cs new file mode 100644 index 000000000..5ed2abd8f --- /dev/null +++ b/Mono.Nat/Enums/MapState.cs @@ -0,0 +1,36 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat +{ + public enum MapState + { + AlreadyMapped, + Available + } +} \ No newline at end of file diff --git a/Mono.Nat/Enums/ProtocolType.cs b/Mono.Nat/Enums/ProtocolType.cs new file mode 100644 index 000000000..a1f5cbb0e --- /dev/null +++ b/Mono.Nat/Enums/ProtocolType.cs @@ -0,0 +1,36 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat +{ + public enum Protocol + { + Tcp, + Udp + } +} \ No newline at end of file diff --git a/Mono.Nat/EventArgs/DeviceEventArgs.cs b/Mono.Nat/EventArgs/DeviceEventArgs.cs new file mode 100644 index 000000000..fbbbf63e3 --- /dev/null +++ b/Mono.Nat/EventArgs/DeviceEventArgs.cs @@ -0,0 +1,45 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat +{ + public class DeviceEventArgs : EventArgs + { + private INatDevice device; + + public DeviceEventArgs(INatDevice device) + { + this.device = device; + } + + public INatDevice Device + { + get { return this.device; } + } + } +} \ No newline at end of file diff --git a/Mono.Nat/Exceptions/MappingException.cs b/Mono.Nat/Exceptions/MappingException.cs new file mode 100644 index 000000000..bb2e6a69d --- /dev/null +++ b/Mono.Nat/Exceptions/MappingException.cs @@ -0,0 +1,87 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Security.Permissions; + +namespace Mono.Nat +{ + [Serializable] + public class MappingException : Exception + { + private int errorCode; + private string errorText; + + public int ErrorCode + { + get { return this.errorCode; } + } + + public string ErrorText + { + get { return this.errorText; } + } + + #region Constructors + public MappingException() + : base() + { + } + + public MappingException(string message) + : base(message) + { + } + + public MappingException(int errorCode, string errorText) + : base (string.Format ("Error {0}: {1}", errorCode, errorText)) + { + this.errorCode = errorCode; + this.errorText = errorText; + } + + public MappingException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected MappingException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } + #endregion + + [SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)] + public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + { + if(info==null) throw new ArgumentNullException("info"); + + this.errorCode = info.GetInt32("errorCode"); + this.errorText = info.GetString("errorText"); + base.GetObjectData(info, context); + } + } +} diff --git a/Mono.Nat/IMapper.cs b/Mono.Nat/IMapper.cs new file mode 100644 index 000000000..b18e6cff2 --- /dev/null +++ b/Mono.Nat/IMapper.cs @@ -0,0 +1,50 @@ +// +// Authors: +// Nicholas Terry +// +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Mono.Nat +{ + public enum MapperType + { + Pmp, + Upnp + } + + internal interface IMapper + { + event EventHandler DeviceFound; + + void Map(IPAddress gatewayAddress); + + void Handle(IPAddress localAddres, byte[] response); + } +} diff --git a/Mono.Nat/INatDevice.cs b/Mono.Nat/INatDevice.cs new file mode 100644 index 000000000..c9f27055b --- /dev/null +++ b/Mono.Nat/INatDevice.cs @@ -0,0 +1,62 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; + +namespace Mono.Nat +{ + public interface INatDevice + { + void CreatePortMap (Mapping mapping); + void DeletePortMap (Mapping mapping); + + IPAddress LocalAddress { get; } + Mapping[] GetAllMappings (); + IPAddress GetExternalIP (); + Mapping GetSpecificMapping (Protocol protocol, int port); + + IAsyncResult BeginCreatePortMap (Mapping mapping, AsyncCallback callback, object asyncState); + IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState); + + IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState); + IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState); + IAsyncResult BeginGetSpecificMapping (Protocol protocol, int externalPort, AsyncCallback callback, object asyncState); + + void EndCreatePortMap (IAsyncResult result); + void EndDeletePortMap (IAsyncResult result); + + Mapping[] EndGetAllMappings (IAsyncResult result); + IPAddress EndGetExternalIP (IAsyncResult result); + Mapping EndGetSpecificMapping (IAsyncResult result); + + DateTime LastSeen { get; set; } + } +} diff --git a/Mono.Nat/ISearcher.cs b/Mono.Nat/ISearcher.cs new file mode 100644 index 000000000..56e438105 --- /dev/null +++ b/Mono.Nat/ISearcher.cs @@ -0,0 +1,51 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net.Sockets; +using System.Net; + +namespace Mono.Nat +{ + public delegate void NatDeviceCallback(INatDevice device); + + internal interface ISearcher + { + event EventHandler DeviceFound; + event EventHandler DeviceLost; + + void Search(); + void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint); + DateTime NextSearch { get; } + NatProtocol Protocol { get; } + } +} diff --git a/Mono.Nat/Mapping.cs b/Mono.Nat/Mapping.cs new file mode 100644 index 000000000..dd49404c6 --- /dev/null +++ b/Mono.Nat/Mapping.cs @@ -0,0 +1,123 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat +{ + public class Mapping + { + private string description; + private DateTime expiration; + private int lifetime; + private int privatePort; + private Protocol protocol; + private int publicPort; + + + + public Mapping (Protocol protocol, int privatePort, int publicPort) + : this (protocol, privatePort, publicPort, 0) + { + } + + public Mapping (Protocol protocol, int privatePort, int publicPort, int lifetime) + { + this.protocol = protocol; + this.privatePort = privatePort; + this.publicPort = publicPort; + this.lifetime = lifetime; + + if (lifetime == int.MaxValue) + this.expiration = DateTime.MaxValue; + else if (lifetime == 0) + this.expiration = DateTime.Now; + else + this.expiration = DateTime.Now.AddSeconds (lifetime); + } + + public string Description + { + get { return description; } + set { description = value; } + } + + public Protocol Protocol + { + get { return protocol; } + internal set { protocol = value; } + } + + public int PrivatePort + { + get { return privatePort; } + internal set { privatePort = value; } + } + + public int PublicPort + { + get { return publicPort; } + internal set { publicPort = value; } + } + + public int Lifetime + { + get { return lifetime; } + internal set { lifetime = value; } + } + + public DateTime Expiration + { + get { return expiration; } + internal set { expiration = value; } + } + + public bool IsExpired () + { + return expiration < DateTime.Now; + } + + public override bool Equals (object obj) + { + Mapping other = obj as Mapping; + return other == null ? false : this.protocol == other.protocol && + this.privatePort == other.privatePort && this.publicPort == other.publicPort; + } + + public override int GetHashCode() + { + return this.protocol.GetHashCode() ^ this.privatePort.GetHashCode() ^ this.publicPort.GetHashCode(); + } + + public override string ToString( ) + { + return String.Format( "Protocol: {0}, Public Port: {1}, Private Port: {2}, Description: {3}, Expiration: {4}, Lifetime: {5}", + this.protocol, this.publicPort, this.privatePort, this.description, this.expiration, this.lifetime ); + } + } +} diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj new file mode 100644 index 000000000..9c2781433 --- /dev/null +++ b/Mono.Nat/Mono.Nat.csproj @@ -0,0 +1,104 @@ + + + + + Debug + AnyCPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA} + Library + Properties + Mono.Nat + Mono.Nat + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + Properties\SharedVersion.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} + MediaBrowser.Controller + + + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + MediaBrowser.Model + + + + + \ No newline at end of file diff --git a/Mono.Nat/NatProtocol.cs b/Mono.Nat/NatProtocol.cs new file mode 100644 index 000000000..ade8d921c --- /dev/null +++ b/Mono.Nat/NatProtocol.cs @@ -0,0 +1,9 @@ + +namespace Mono.Nat +{ + public enum NatProtocol + { + Upnp = 0, + Pmp = 1 + } +} diff --git a/Mono.Nat/NatUtility.cs b/Mono.Nat/NatUtility.cs new file mode 100644 index 000000000..6d91d2513 --- /dev/null +++ b/Mono.Nat/NatUtility.cs @@ -0,0 +1,264 @@ +// +// Authors: +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Linq; +using System.Collections.Generic; +using System.IO; +using System.Net.NetworkInformation; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Model.Logging; +using Mono.Nat.Pmp.Mappers; +using Mono.Nat.Upnp.Mappers; + +namespace Mono.Nat +{ + public static class NatUtility + { + private static ManualResetEvent searching; + public static event EventHandler DeviceFound; + public static event EventHandler DeviceLost; + + public static event EventHandler UnhandledException; + + private static List controllers; + private static bool verbose; + + public static List EnabledProtocols { get; set; } + + public static ILogger Logger { get; set; } + + public static bool Verbose + { + get { return verbose; } + set { verbose = value; } + } + + static NatUtility() + { + EnabledProtocols = new List + { + NatProtocol.Upnp, + NatProtocol.Pmp + }; + + searching = new ManualResetEvent(false); + + controllers = new List(); + controllers.Add(UpnpSearcher.Instance); + controllers.Add(PmpSearcher.Instance); + + controllers.ForEach(searcher => + { + searcher.DeviceFound += (sender, args) => + { + if (DeviceFound != null) + DeviceFound(sender, args); + }; + searcher.DeviceLost += (sender, args) => + { + if (DeviceLost != null) + DeviceLost(sender, args); + }; + }); + Thread t = new Thread(SearchAndListen); + t.IsBackground = true; + t.Start(); + } + + internal static void Log(string format, params object[] args) + { + var logger = Logger; + if (logger != null) + logger.Debug(format, args); + } + + private static void SearchAndListen() + { + while (true) + { + searching.WaitOne(); + + try + { + var enabledProtocols = EnabledProtocols.ToList(); + + if (enabledProtocols.Contains(UpnpSearcher.Instance.Protocol)) + { + Receive(UpnpSearcher.Instance, UpnpSearcher.sockets); + } + if (enabledProtocols.Contains(PmpSearcher.Instance.Protocol)) + { + Receive(PmpSearcher.Instance, PmpSearcher.sockets); + } + + foreach (ISearcher s in controllers) + if (s.NextSearch < DateTime.Now && enabledProtocols.Contains(s.Protocol)) + { + Log("Searching for: {0}", s.GetType().Name); + s.Search(); + } + } + catch (Exception e) + { + if (UnhandledException != null) + UnhandledException(typeof(NatUtility), new UnhandledExceptionEventArgs(e, false)); + } + Thread.Sleep(10); + } + } + + static void Receive (ISearcher searcher, List clients) + { + IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351); + foreach (UdpClient client in clients) + { + if (client.Available > 0) + { + IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address; + byte[] data = client.Receive(ref received); + searcher.Handle(localAddress, data, received); + } + } + } + + static void Receive(IMapper mapper, List clients) + { + IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351); + foreach (UdpClient client in clients) + { + if (client.Available > 0) + { + IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address; + byte[] data = client.Receive(ref received); + mapper.Handle(localAddress, data); + } + } + } + + public static void StartDiscovery () + { + searching.Set(); + } + + public static void StopDiscovery () + { + searching.Reset(); + } + + //This is for when you know the Gateway IP and want to skip the costly search... + public static void DirectMap(IPAddress gatewayAddress, MapperType type) + { + IMapper mapper; + switch (type) + { + case MapperType.Pmp: + mapper = new PmpMapper(); + break; + case MapperType.Upnp: + mapper = new UpnpMapper(); + mapper.DeviceFound += (sender, args) => + { + if (DeviceFound != null) + DeviceFound(sender, args); + }; + mapper.Map(gatewayAddress); + break; + default: + throw new InvalidOperationException("Unsuported type given"); + + } + searching.Reset(); + + } + + //So then why is it here? -Nick + [Obsolete ("This method serves no purpose and shouldn't be used")] + public static IPAddress[] GetLocalAddresses (bool includeIPv6) + { + List addresses = new List (); + + IPHostEntry hostInfo = Dns.GetHostEntry (Dns.GetHostName ()); + foreach (IPAddress address in hostInfo.AddressList) { + if (address.AddressFamily == AddressFamily.InterNetwork || + (includeIPv6 && address.AddressFamily == AddressFamily.InterNetworkV6)) { + addresses.Add (address); + } + } + + return addresses.ToArray (); + } + + //checks if an IP address is a private address space as defined by RFC 1918 + public static bool IsPrivateAddressSpace (IPAddress address) + { + byte[] ba = address.GetAddressBytes (); + + switch ((int)ba[0]) { + case 10: + return true; //10.x.x.x + case 172: + return ((int)ba[1] & 16) != 0; //172.16-31.x.x + case 192: + return (int)ba[1] == 168; //192.168.x.x + default: + return false; + } + } + + public static void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint, NatProtocol protocol) + { + switch (protocol) + { + case NatProtocol.Upnp: + UpnpSearcher.Instance.Handle(localAddress, response, endpoint); + break; + case NatProtocol.Pmp: + PmpSearcher.Instance.Handle(localAddress, response, endpoint); + break; + default: + throw new ArgumentException("Unexpected protocol: " + protocol); + } + } + + public static void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint, NatProtocol protocol) + { + switch (protocol) + { + case NatProtocol.Upnp: + UpnpSearcher.Instance.Handle(localAddress, deviceInfo, endpoint); + break; + default: + throw new ArgumentException("Unexpected protocol: " + protocol); + } + } + } +} diff --git a/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs new file mode 100644 index 000000000..c8ccf5435 --- /dev/null +++ b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs @@ -0,0 +1,52 @@ +// +// Authors: +// Ben Motmans +// +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat.Pmp +{ + internal class PortMapAsyncResult : AsyncResult + { + private Mapping mapping; + + internal PortMapAsyncResult (Mapping mapping, AsyncCallback callback, object asyncState) + : base (callback, asyncState) + { + this.mapping = mapping; + } + + internal PortMapAsyncResult (Protocol protocol, int port, int lifetime, AsyncCallback callback, object asyncState) + : base (callback, asyncState) + { + this.mapping = new Mapping (protocol, port, port, lifetime); + } + + internal Mapping Mapping + { + get { return mapping; } + } + } +} diff --git a/Mono.Nat/Pmp/Mappers/PmpMapper.cs b/Mono.Nat/Pmp/Mappers/PmpMapper.cs new file mode 100644 index 000000000..f33ca44c3 --- /dev/null +++ b/Mono.Nat/Pmp/Mappers/PmpMapper.cs @@ -0,0 +1,83 @@ +// +// Authors: +// Nicholas Terry +// +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using Mono.Nat.Pmp; + +namespace Mono.Nat.Pmp.Mappers +{ + internal class PmpMapper : Pmp, IMapper + { + public event EventHandler DeviceFound; + + static PmpMapper() + { + CreateSocketsAndAddGateways(); + } + + public void Map(IPAddress gatewayAddress) + { + sockets.ForEach(x => Map(x, gatewayAddress)); + } + + void Map(UdpClient client, IPAddress gatewayAddress) + { + // The nat-pmp search message. Must be sent to GatewayIP:53531 + byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode }; + + client.Send(buffer, buffer.Length, new IPEndPoint(gatewayAddress, PmpConstants.ServerPort)); + } + + public void Handle(IPAddress localAddres, byte[] response) + { + //if (!IsSearchAddress(endpoint.Address)) + // return; + if (response.Length != 12) + return; + if (response[0] != PmpConstants.Version) + return; + if (response[1] != PmpConstants.ServerNoop) + return; + int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2)); + if (errorcode != 0) + NatUtility.Log("Non zero error: {0}", errorcode); + + IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] }); + OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(localAddres, publicIp))); + } + + private void OnDeviceFound(DeviceEventArgs args) + { + if (DeviceFound != null) + DeviceFound(this, args); + } + } +} diff --git a/Mono.Nat/Pmp/Pmp.cs b/Mono.Nat/Pmp/Pmp.cs new file mode 100644 index 000000000..6795561b1 --- /dev/null +++ b/Mono.Nat/Pmp/Pmp.cs @@ -0,0 +1,118 @@ +// +// Authors: +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Text; + +namespace Mono.Nat.Pmp +{ + internal abstract class Pmp + { + public static List sockets; + protected static Dictionary> gatewayLists; + + internal static void CreateSocketsAndAddGateways() + { + sockets = new List(); + gatewayLists = new Dictionary>(); + + try + { + foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces()) + { + if (n.OperationalStatus != OperationalStatus.Up && n.OperationalStatus != OperationalStatus.Unknown) + continue; + IPInterfaceProperties properties = n.GetIPProperties(); + List gatewayList = new List(); + + foreach (GatewayIPAddressInformation gateway in properties.GatewayAddresses) + { + if (gateway.Address.AddressFamily == AddressFamily.InterNetwork) + { + gatewayList.Add(new IPEndPoint(gateway.Address, PmpConstants.ServerPort)); + } + } + if (gatewayList.Count == 0) + { + /* Mono on OSX doesn't give any gateway addresses, so check DNS entries */ + foreach (var gw2 in properties.DnsAddresses) + { + if (gw2.AddressFamily == AddressFamily.InterNetwork) + { + gatewayList.Add(new IPEndPoint(gw2, PmpConstants.ServerPort)); + } + } + foreach (var unicast in properties.UnicastAddresses) + { + if (/*unicast.DuplicateAddressDetectionState == DuplicateAddressDetectionState.Preferred + && unicast.AddressPreferredLifetime != UInt32.MaxValue + && */unicast.Address.AddressFamily == AddressFamily.InterNetwork) + { + var bytes = unicast.Address.GetAddressBytes(); + bytes[3] = 1; + gatewayList.Add(new IPEndPoint(new IPAddress(bytes), PmpConstants.ServerPort)); + } + } + } + + if (gatewayList.Count > 0) + { + foreach (UnicastIPAddressInformation address in properties.UnicastAddresses) + { + if (address.Address.AddressFamily == AddressFamily.InterNetwork) + { + UdpClient client; + + try + { + client = new UdpClient(new IPEndPoint(address.Address, 0)); + } + catch (SocketException) + { + continue; // Move on to the next address. + } + + gatewayLists.Add(client, gatewayList); + sockets.Add(client); + } + } + } + } + } + catch (Exception) + { + // NAT-PMP does not use multicast, so there isn't really a good fallback. + } + } + } +} diff --git a/Mono.Nat/Pmp/PmpConstants.cs b/Mono.Nat/Pmp/PmpConstants.cs new file mode 100644 index 000000000..ff3eb6230 --- /dev/null +++ b/Mono.Nat/Pmp/PmpConstants.cs @@ -0,0 +1,56 @@ +// +// Authors: +// Ben Motmans +// +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat.Pmp +{ + internal static class PmpConstants + { + public const byte Version = (byte)0; + + public const byte OperationCode = (byte)0; + public const byte OperationCodeUdp = (byte)1; + public const byte OperationCodeTcp = (byte)2; + public const byte ServerNoop = (byte)128; + + public const int ClientPort = 5350; + public const int ServerPort = 5351; + + public const int RetryDelay = 250; + public const int RetryAttempts = 9; + + public const int RecommendedLeaseTime = 60 * 60; + public const int DefaultLeaseTime = RecommendedLeaseTime; + + public const short ResultCodeSuccess = 0; + public const short ResultCodeUnsupportedVersion = 1; + public const short ResultCodeNotAuthorized = 2; + public const short ResultCodeNetworkFailure = 3; + public const short ResultCodeOutOfResources = 4; + public const short ResultCodeUnsupportedOperationCode = 5; + } +} \ No newline at end of file diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs new file mode 100644 index 000000000..9a2962c4d --- /dev/null +++ b/Mono.Nat/Pmp/PmpNatDevice.cs @@ -0,0 +1,347 @@ +// +// Authors: +// Ben Motmans +// +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Collections.Generic; + +namespace Mono.Nat.Pmp +{ + internal sealed class PmpNatDevice : AbstractNatDevice, IEquatable + { + private AsyncResult externalIpResult; + private bool pendingOp; + private IPAddress localAddress; + private IPAddress publicAddress; + + internal PmpNatDevice (IPAddress localAddress, IPAddress publicAddress) + { + this.localAddress = localAddress; + this.publicAddress = publicAddress; + } + + public override IPAddress LocalAddress + { + get { return localAddress; } + } + + public override IPAddress GetExternalIP () + { + return publicAddress; + } + + public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState) + { + PortMapAsyncResult pmar = new PortMapAsyncResult (mapping.Protocol, mapping.PublicPort, PmpConstants.DefaultLeaseTime, callback, asyncState); + ThreadPool.QueueUserWorkItem (delegate + { + try + { + CreatePortMap(pmar.Mapping, true); + pmar.Complete(); + } + catch (Exception e) + { + pmar.Complete(e); + } + }); + return pmar; + } + + public override IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState) + { + PortMapAsyncResult pmar = new PortMapAsyncResult (mapping, callback, asyncState); + ThreadPool.QueueUserWorkItem (delegate { + try { + CreatePortMap(pmar.Mapping, false); + pmar.Complete(); + } catch (Exception e) { + pmar.Complete(e); + } + }); + return pmar; + } + + public override void EndCreatePortMap (IAsyncResult result) + { + PortMapAsyncResult pmar = result as PortMapAsyncResult; + pmar.AsyncWaitHandle.WaitOne (); + } + + public override void EndDeletePortMap (IAsyncResult result) + { + PortMapAsyncResult pmar = result as PortMapAsyncResult; + pmar.AsyncWaitHandle.WaitOne (); + } + + public override IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState) + { + //NAT-PMP does not specify a way to get all port mappings + throw new NotSupportedException (); + } + + public override IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState) + { + StartOp(ref externalIpResult, callback, asyncState); + AsyncResult result = externalIpResult; + result.Complete(); + return result; + } + + public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState) + { + //NAT-PMP does not specify a way to get a specific port map + throw new NotSupportedException (); + } + + public override Mapping[] EndGetAllMappings (IAsyncResult result) + { + //NAT-PMP does not specify a way to get all port mappings + throw new NotSupportedException (); + } + + public override IPAddress EndGetExternalIP (IAsyncResult result) + { + EndOp(result, ref externalIpResult); + return publicAddress; + } + + private void StartOp(ref AsyncResult result, AsyncCallback callback, object asyncState) + { + if (pendingOp == true) + throw new InvalidOperationException("Can only have one simultaenous async operation"); + + pendingOp = true; + result = new AsyncResult(callback, asyncState); + } + + private void EndOp(IAsyncResult supplied, ref AsyncResult actual) + { + if (supplied == null) + throw new ArgumentNullException("result"); + + if (supplied != actual) + throw new ArgumentException("Supplied IAsyncResult does not match the stored result"); + + if (!supplied.IsCompleted) + supplied.AsyncWaitHandle.WaitOne(); + + if (actual.StoredException != null) + throw actual.StoredException; + + pendingOp = false; + actual = null; + } + + public override Mapping EndGetSpecificMapping (IAsyncResult result) + { + //NAT-PMP does not specify a way to get a specific port map + throw new NotSupportedException (); + } + + public override bool Equals(object obj) + { + PmpNatDevice device = obj as PmpNatDevice; + return (device == null) ? false : this.Equals(device); + } + + public override int GetHashCode () + { + return this.publicAddress.GetHashCode(); + } + + public bool Equals (PmpNatDevice other) + { + return (other == null) ? false : this.publicAddress.Equals(other.publicAddress); + } + + private Mapping CreatePortMap (Mapping mapping, bool create) + { + List package = new List (); + + package.Add (PmpConstants.Version); + package.Add (mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp); + package.Add ((byte)0); //reserved + package.Add ((byte)0); //reserved + package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder((short)mapping.PrivatePort))); + package.AddRange (BitConverter.GetBytes (create ? IPAddress.HostToNetworkOrder((short)mapping.PublicPort) : (short)0)); + package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder(mapping.Lifetime))); + + CreatePortMapAsyncState state = new CreatePortMapAsyncState (); + state.Buffer = package.ToArray (); + state.Mapping = mapping; + + ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapAsync), state); + WaitHandle.WaitAll (new WaitHandle[] {state.ResetEvent}); + + if (!state.Success) { + string type = create ? "create" : "delete"; + throw new MappingException (String.Format ("Failed to {0} portmap (protocol={1}, private port={2}", type, mapping.Protocol, mapping.PrivatePort)); + } + + return state.Mapping; + } + + private void CreatePortMapAsync (object obj) + { + CreatePortMapAsyncState state = obj as CreatePortMapAsyncState; + + UdpClient udpClient = new UdpClient (); + CreatePortMapListenState listenState = new CreatePortMapListenState (state, udpClient); + + int attempt = 0; + int delay = PmpConstants.RetryDelay; + + ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapListen), listenState); + + while (attempt < PmpConstants.RetryAttempts && !listenState.Success) { + udpClient.Send (state.Buffer, state.Buffer.Length, new IPEndPoint (localAddress, PmpConstants.ServerPort)); + listenState.UdpClientReady.Set(); + + attempt++; + delay *= 2; + Thread.Sleep (delay); + } + + state.Success = listenState.Success; + + udpClient.Close (); + state.ResetEvent.Set (); + } + + private void CreatePortMapListen (object obj) + { + CreatePortMapListenState state = obj as CreatePortMapListenState; + + UdpClient udpClient = state.UdpClient; + state.UdpClientReady.WaitOne(); // Evidently UdpClient has some lazy-init Send/Receive race? + IPEndPoint endPoint = new IPEndPoint (localAddress, PmpConstants.ServerPort); + + while (!state.Success) + { + byte[] data; + try + { + data = udpClient.Receive(ref endPoint); + } + catch (SocketException) + { + state.Success = false; + return; + } + + catch (ObjectDisposedException) + { + state.Success = false; + return; + } + + if (data.Length < 16) + continue; + + if (data[0] != PmpConstants.Version) + continue; + + byte opCode = (byte)(data[1] & (byte)127); + + Protocol protocol = Protocol.Tcp; + if (opCode == PmpConstants.OperationCodeUdp) + protocol = Protocol.Udp; + + short resultCode = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 2)); + uint epoch = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 4)); + + int privatePort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 8)); + int publicPort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 10)); + + uint lifetime = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 12)); + + if (publicPort < 0 || privatePort < 0 || resultCode != PmpConstants.ResultCodeSuccess) + { + state.Success = false; + return; + } + + if (lifetime == 0) + { + //mapping was deleted + state.Success = true; + state.Mapping = null; + return; + } + else + { + //mapping was created + //TODO: verify that the private port+protocol are a match + Mapping mapping = state.Mapping; + mapping.PublicPort = publicPort; + mapping.Protocol = protocol; + mapping.Expiration = DateTime.Now.AddSeconds (lifetime); + + state.Success = true; + } + } + } + + + /// + /// Overridden. + /// + /// + public override string ToString( ) + { + return String.Format( "PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}", + this.localAddress, this.publicAddress, this.LastSeen ); + } + + + private class CreatePortMapAsyncState + { + internal byte[] Buffer; + internal ManualResetEvent ResetEvent = new ManualResetEvent (false); + internal Mapping Mapping; + + internal bool Success; + } + + private class CreatePortMapListenState + { + internal volatile bool Success; + internal Mapping Mapping; + internal UdpClient UdpClient; + internal ManualResetEvent UdpClientReady; + + internal CreatePortMapListenState (CreatePortMapAsyncState state, UdpClient client) + { + Mapping = state.Mapping; + UdpClient = client; UdpClientReady = new ManualResetEvent(false); + } + } + } +} \ No newline at end of file diff --git a/Mono.Nat/Pmp/Searchers/PmpSearcher.cs b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs new file mode 100644 index 000000000..df0273ccb --- /dev/null +++ b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs @@ -0,0 +1,149 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using Mono.Nat.Pmp; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Linq; + +namespace Mono.Nat +{ + internal class PmpSearcher : Pmp.Pmp, ISearcher + { + static PmpSearcher instance = new PmpSearcher(); + + + public static PmpSearcher Instance + { + get { return instance; } + } + + private int timeout; + private DateTime nextSearch; + public event EventHandler DeviceFound; + public event EventHandler DeviceLost; + + static PmpSearcher() + { + CreateSocketsAndAddGateways(); + } + + PmpSearcher() + { + timeout = 250; + } + + public void Search() + { + foreach (UdpClient s in sockets) + { + try + { + Search(s); + } + catch + { + // Ignore any search errors + } + } + } + + void Search (UdpClient client) + { + // Sort out the time for the next search first. The spec says the + // timeout should double after each attempt. Once it reaches 64 seconds + // (and that attempt fails), assume no devices available + nextSearch = DateTime.Now.AddMilliseconds(timeout); + timeout *= 2; + + // We've tried 9 times as per spec, try searching again in 5 minutes + if (timeout == 128 * 1000) + { + timeout = 250; + nextSearch = DateTime.Now.AddMinutes(10); + return; + } + + // The nat-pmp search message. Must be sent to GatewayIP:53531 + byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode }; + foreach (IPEndPoint gatewayEndpoint in gatewayLists[client]) + client.Send(buffer, buffer.Length, gatewayEndpoint); + } + + bool IsSearchAddress(IPAddress address) + { + foreach (List gatewayList in gatewayLists.Values) + foreach (IPEndPoint gatewayEndpoint in gatewayList) + if (gatewayEndpoint.Address.Equals(address)) + return true; + return false; + } + + public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) + { + if (!IsSearchAddress(endpoint.Address)) + return; + if (response.Length != 12) + return; + if (response[0] != PmpConstants.Version) + return; + if (response[1] != PmpConstants.ServerNoop) + return; + int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2)); + if (errorcode != 0) + NatUtility.Log("Non zero error: {0}", errorcode); + + IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] }); + nextSearch = DateTime.Now.AddMinutes(5); + timeout = 250; + OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(endpoint.Address, publicIp))); + } + + public DateTime NextSearch + { + get { return nextSearch; } + } + private void OnDeviceFound(DeviceEventArgs args) + { + if (DeviceFound != null) + DeviceFound(this, args); + } + + public NatProtocol Protocol + { + get { return NatProtocol.Pmp; } + } + } +} diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c3c3101de --- /dev/null +++ b/Mono.Nat/Properties/AssemblyInfo.cs @@ -0,0 +1,31 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Mono.Nat")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Mono.Nat")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d7453b88-2266-4805-b39b-2b5a2a33e1ba")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// \ No newline at end of file diff --git a/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs new file mode 100644 index 000000000..51ecfbaf0 --- /dev/null +++ b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs @@ -0,0 +1,56 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; + +namespace Mono.Nat.Upnp +{ + internal class GetAllMappingsAsyncResult : PortMapAsyncResult + { + private List mappings; + private Mapping specificMapping; + + public GetAllMappingsAsyncResult(WebRequest request, AsyncCallback callback, object asyncState) + : base(request, callback, asyncState) + { + mappings = new List(); + } + + public List Mappings + { + get { return this.mappings; } + } + + public Mapping SpecificMapping + { + get { return this.specificMapping; } + set { this.specificMapping = value; } + } + } +} diff --git a/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs new file mode 100644 index 000000000..d8ac3fe61 --- /dev/null +++ b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs @@ -0,0 +1,75 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + + +using System; +using System.Net; +using System.Threading; + +namespace Mono.Nat.Upnp +{ + internal class PortMapAsyncResult : AsyncResult + { + private WebRequest request; + private MessageBase savedMessage; + + protected PortMapAsyncResult(WebRequest request, AsyncCallback callback, object asyncState) + : base (callback, asyncState) + { + this.request = request; + } + + internal WebRequest Request + { + get { return this.request; } + set { this.request = value; } + } + + internal MessageBase SavedMessage + { + get { return this.savedMessage; } + set { this.savedMessage = value; } + } + + internal static PortMapAsyncResult Create (MessageBase message, WebRequest request, AsyncCallback storedCallback, object asyncState) + { + if (message is GetGenericPortMappingEntry) + return new GetAllMappingsAsyncResult(request, storedCallback, asyncState); + + if (message is GetSpecificPortMappingEntryMessage) + { + GetSpecificPortMappingEntryMessage mapMessage = (GetSpecificPortMappingEntryMessage)message; + GetAllMappingsAsyncResult result = new GetAllMappingsAsyncResult(request, storedCallback, asyncState); + + result.SpecificMapping = new Mapping(mapMessage.protocol, 0, mapMessage.externalPort, 0); + return result; + } + + return new PortMapAsyncResult(request, storedCallback, asyncState); + } + } +} diff --git a/Mono.Nat/Upnp/Mappers/UpnpMapper.cs b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs new file mode 100644 index 000000000..6f2716805 --- /dev/null +++ b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs @@ -0,0 +1,110 @@ +// +// Authors: +// Nicholas Terry +// +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace Mono.Nat.Upnp.Mappers +{ + internal class UpnpMapper : Upnp, IMapper + { + + public event EventHandler DeviceFound; + + public UdpClient Client { get; set; } + + public UpnpMapper() + { + //Bind to local port 1900 for ssdp responses + Client = new UdpClient(1900); + } + + public void Map(IPAddress gatewayAddress) + { + //Get the httpu request payload + byte[] data = DiscoverDeviceMessage.EncodeUnicast(gatewayAddress); + + Client.Send(data, data.Length, new IPEndPoint(gatewayAddress, 1900)); + + new Thread(Receive).Start(); + } + + public void Receive() + { + while (true) + { + IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351); + if (Client.Available > 0) + { + IPAddress localAddress = ((IPEndPoint)Client.Client.LocalEndPoint).Address; + byte[] data = Client.Receive(ref received); + Handle(localAddress, data, received); + } + } + } + + public void Handle(IPAddress localAddres, byte[] response) + { + Handle(localAddres, response, null); + } + + public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) + { + // No matter what, this method should never throw an exception. If something goes wrong + // we should still be in a position to handle the next reply correctly. + try + { + UpnpNatDevice d = base.Handle(localAddress, response, endpoint); + d.GetServicesList(DeviceSetupComplete); + } + catch (Exception ex) + { + Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: "); + Trace.WriteLine("ErrorMessage:"); + Trace.WriteLine(ex.Message); + Trace.WriteLine("Data string:"); + Trace.WriteLine(Encoding.UTF8.GetString(response)); + } + } + + private void DeviceSetupComplete(INatDevice device) + { + OnDeviceFound(new DeviceEventArgs(device)); + } + + private void OnDeviceFound(DeviceEventArgs args) + { + if (DeviceFound != null) + DeviceFound(this, args); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs new file mode 100644 index 000000000..87f5835a6 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs @@ -0,0 +1,60 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Net; +using System.Text; + +namespace Mono.Nat.Upnp +{ + internal static class DiscoverDeviceMessage + { + /// + /// The message sent to discover all uPnP devices on the network + /// + /// + public static byte[] EncodeSSDP() + { + string s = "M-SEARCH * HTTP/1.1\r\n" + + "HOST: 239.255.255.250:1900\r\n" + + "MAN: \"ssdp:discover\"\r\n" + + "MX: 3\r\n" + + "ST: ssdp:all\r\n\r\n"; + return UTF8Encoding.ASCII.GetBytes(s); + } + + public static byte[] EncodeUnicast(IPAddress gatewayAddress) + { + //Format obtained from http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf pg 31 + //This method only works with upnp 1.1 routers... unfortunately + string s = "M-SEARCH * HTTP/1.1\r\n" + + "HOST: " + gatewayAddress + ":1900\r\n" + + "MAN: \"ssdp:discover\"\r\n" + + "ST: ssdp:all\r\n\r\n"; + //+ "USER-AGENT: unix/5.1 UPnP/1.1 MyProduct/1.0\r\n\r\n"; + return UTF8Encoding.ASCII.GetBytes(s); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/ErrorMessage.cs b/Mono.Nat/Upnp/Messages/ErrorMessage.cs new file mode 100644 index 000000000..ce5270e9b --- /dev/null +++ b/Mono.Nat/Upnp/Messages/ErrorMessage.cs @@ -0,0 +1,63 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat.Upnp +{ + internal class ErrorMessage : MessageBase + { + #region Member Variables + public string Description + { + get { return this.description; } + } + private string description; + + public int ErrorCode + { + get { return this.errorCode; } + } + private int errorCode; + #endregion + + + #region Constructors + public ErrorMessage(int errorCode, string description) + :base(null) + { + this.description = description; + this.errorCode = errorCode; + } + #endregion + + + public override System.Net.WebRequest Encode(out byte[] body) + { + throw new NotImplementedException(); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs new file mode 100644 index 000000000..c5d7bce70 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs @@ -0,0 +1,62 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Diagnostics; +using System.Net; + +namespace Mono.Nat.Upnp +{ + internal class GetServicesMessage : MessageBase + { + private string servicesDescriptionUrl; + private EndPoint hostAddress; + + public GetServicesMessage(string description, EndPoint hostAddress) + :base(null) + { + if (string.IsNullOrEmpty(description)) + Trace.WriteLine("Description is null"); + + if (hostAddress == null) + Trace.WriteLine("hostaddress is null"); + + this.servicesDescriptionUrl = description; + this.hostAddress = hostAddress; + } + + + public override WebRequest Encode(out byte[] body) + { + HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://" + this.hostAddress.ToString() + this.servicesDescriptionUrl); + req.Headers.Add("ACCEPT-LANGUAGE", "en"); + req.Method = "GET"; + + body = new byte[0]; + return req; + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs new file mode 100644 index 000000000..da650fb41 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs @@ -0,0 +1,75 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Net; +using System.IO; +using System.Globalization; +using System.Text; +using System.Xml; + +namespace Mono.Nat.Upnp +{ + internal class CreatePortMappingMessage : MessageBase + { + #region Private Fields + + private IPAddress localIpAddress; + private Mapping mapping; + + #endregion + + + #region Constructors + public CreatePortMappingMessage(Mapping mapping, IPAddress localIpAddress, UpnpNatDevice device) + : base(device) + { + this.mapping = mapping; + this.localIpAddress = localIpAddress; + } + #endregion + + + public override WebRequest Encode(out byte[] body) + { + CultureInfo culture = CultureInfo.InvariantCulture; + + StringBuilder builder = new StringBuilder(256); + XmlWriter writer = CreateWriter(builder); + + WriteFullElement(writer, "NewRemoteHost", string.Empty); + WriteFullElement(writer, "NewExternalPort", this.mapping.PublicPort.ToString(culture)); + WriteFullElement(writer, "NewProtocol", this.mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP"); + WriteFullElement(writer, "NewInternalPort", this.mapping.PrivatePort.ToString(culture)); + WriteFullElement(writer, "NewInternalClient", this.localIpAddress.ToString()); + WriteFullElement(writer, "NewEnabled", "1"); + WriteFullElement(writer, "NewPortMappingDescription", string.IsNullOrEmpty(mapping.Description) ? "Mono.Nat" : mapping.Description); + WriteFullElement(writer, "NewLeaseDuration", mapping.Lifetime.ToString()); + + writer.Flush(); + return CreateRequest("AddPortMapping", builder.ToString(), out body); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs new file mode 100644 index 000000000..d9be89a69 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs @@ -0,0 +1,57 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Net; +using System.IO; +using System.Text; +using System.Xml; + +namespace Mono.Nat.Upnp +{ + internal class DeletePortMappingMessage : MessageBase + { + private Mapping mapping; + + public DeletePortMappingMessage(Mapping mapping, UpnpNatDevice device) + : base(device) + { + this.mapping = mapping; + } + + public override WebRequest Encode(out byte[] body) + { + StringBuilder builder = new StringBuilder(256); + XmlWriter writer = CreateWriter(builder); + + WriteFullElement(writer, "NewRemoteHost", string.Empty); + WriteFullElement(writer, "NewExternalPort", mapping.PublicPort.ToString(MessageBase.Culture)); + WriteFullElement(writer, "NewProtocol", mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP"); + + writer.Flush(); + return CreateRequest("DeletePortMapping", builder.ToString(), out body); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs new file mode 100644 index 000000000..8f97002ea --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs @@ -0,0 +1,51 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.IO; + +namespace Mono.Nat.Upnp +{ + internal class GetExternalIPAddressMessage : MessageBase + { + + #region Constructors + public GetExternalIPAddressMessage(UpnpNatDevice device) + :base(device) + { + } + #endregion + + + public override WebRequest Encode(out byte[] body) + { + return CreateRequest("GetExternalIPAddress", string.Empty, out body); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs new file mode 100644 index 000000000..c0c555881 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs @@ -0,0 +1,55 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; + +namespace Mono.Nat.Upnp +{ + internal class GetGenericPortMappingEntry : MessageBase + { + private int index; + + public GetGenericPortMappingEntry(int index, UpnpNatDevice device) + :base(device) + { + this.index = index; + } + + public override System.Net.WebRequest Encode(out byte[] body) + { + StringBuilder sb = new StringBuilder(128); + XmlWriter writer = CreateWriter(sb); + + WriteFullElement(writer, "NewPortMappingIndex", index.ToString()); + + writer.Flush(); + return CreateRequest("GetGenericPortMappingEntry", sb.ToString(), out body); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs new file mode 100644 index 000000000..314468ece --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs @@ -0,0 +1,60 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; +using System.Net; + +namespace Mono.Nat.Upnp +{ + internal class GetSpecificPortMappingEntryMessage : MessageBase + { + internal Protocol protocol; + internal int externalPort; + + public GetSpecificPortMappingEntryMessage(Protocol protocol, int externalPort, UpnpNatDevice device) + : base(device) + { + this.protocol = protocol; + this.externalPort = externalPort; + } + + public override WebRequest Encode(out byte[] body) + { + StringBuilder sb = new StringBuilder(64); + XmlWriter writer = CreateWriter(sb); + + WriteFullElement(writer, "NewRemoteHost", string.Empty); + WriteFullElement(writer, "NewExternalPort", externalPort.ToString()); + WriteFullElement(writer, "NewProtocol", protocol == Protocol.Tcp ? "TCP" : "UDP"); + writer.Flush(); + + return CreateRequest("GetSpecificPortMappingEntry", sb.ToString(), out body); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs new file mode 100644 index 000000000..e75926b09 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs @@ -0,0 +1,46 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + + +using System; +namespace Mono.Nat.Upnp +{ + internal class CreatePortMappingResponseMessage : MessageBase + { + #region Constructors + public CreatePortMappingResponseMessage() + :base(null) + { + } + #endregion + + public override System.Net.WebRequest Encode(out byte[] body) + { + throw new NotImplementedException(); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs new file mode 100644 index 000000000..1fce4eb04 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs @@ -0,0 +1,44 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + + +using System; +namespace Mono.Nat.Upnp +{ + internal class DeletePortMapResponseMessage : MessageBase + { + public DeletePortMapResponseMessage() + :base(null) + { + } + + public override System.Net.WebRequest Encode(out byte[] body) + { + throw new NotSupportedException(); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs new file mode 100644 index 000000000..ee4b18cd1 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs @@ -0,0 +1,53 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; + +namespace Mono.Nat.Upnp +{ + internal class GetExternalIPAddressResponseMessage : MessageBase + { + public IPAddress ExternalIPAddress + { + get { return this.externalIPAddress; } + } + private IPAddress externalIPAddress; + + public GetExternalIPAddressResponseMessage(string ip) + :base(null) + { + this.externalIPAddress = IPAddress.Parse(ip); + } + + public override WebRequest Encode(out byte[] body) + { + throw new NotImplementedException(); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs new file mode 100644 index 000000000..b11bfa027 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs @@ -0,0 +1,108 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; + +namespace Mono.Nat.Upnp +{ + internal class GetGenericPortMappingEntryResponseMessage : MessageBase + { + private string remoteHost; + private int externalPort; + private Protocol protocol; + private int internalPort; + private string internalClient; + private bool enabled; + private string portMappingDescription; + private int leaseDuration; + + public string RemoteHost + { + get { return this.remoteHost; } + } + + public int ExternalPort + { + get { return this.externalPort; } + } + + public Protocol Protocol + { + get { return this.protocol; } + } + + public int InternalPort + { + get { return this.internalPort; } + } + + public string InternalClient + { + get { return this.internalClient; } + } + + public bool Enabled + { + get { return this.enabled; } + } + + public string PortMappingDescription + { + get { return this.portMappingDescription; } + } + + public int LeaseDuration + { + get { return this.leaseDuration; } + } + + + public GetGenericPortMappingEntryResponseMessage(XmlNode data, bool genericMapping) + : base(null) + { + remoteHost = (genericMapping) ? data["NewRemoteHost"].InnerText : string.Empty; + externalPort = (genericMapping) ? Convert.ToInt32(data["NewExternalPort"].InnerText) : -1; + if (genericMapping) + protocol = data["NewProtocol"].InnerText.Equals("TCP", StringComparison.InvariantCultureIgnoreCase) ? Protocol.Tcp : Protocol.Udp; + else + protocol = Protocol.Udp; + + internalPort = Convert.ToInt32(data["NewInternalPort"].InnerText); + internalClient = data["NewInternalClient"].InnerText; + enabled = data["NewEnabled"].InnerText == "1" ? true : false; + portMappingDescription = data["NewPortMappingDescription"].InnerText; + leaseDuration = Convert.ToInt32(data["NewLeaseDuration"].InnerText); + } + + public override System.Net.WebRequest Encode(out byte[] body) + { + throw new NotImplementedException(); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs new file mode 100644 index 000000000..44c16eec6 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/UpnpMessage.cs @@ -0,0 +1,132 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Diagnostics; +using System.Xml; +using System.Net; +using System.IO; +using System.Text; +using System.Globalization; + +namespace Mono.Nat.Upnp +{ + internal abstract class MessageBase + { + internal static readonly CultureInfo Culture = CultureInfo.InvariantCulture; + protected UpnpNatDevice device; + + protected MessageBase(UpnpNatDevice device) + { + this.device = device; + } + + protected WebRequest CreateRequest(string upnpMethod, string methodParameters, out byte[] body) + { + string ss = "http://" + this.device.HostEndPoint.ToString() + this.device.ControlUrl; + NatUtility.Log("Initiating request to: {0}", ss); + Uri location = new Uri(ss); + + HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(location); + req.KeepAlive = false; + req.Method = "POST"; + req.ContentType = "text/xml; charset=\"utf-8\""; + req.Headers.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\""); + + string bodyString = "" + + "" + + "" + + methodParameters + + "" + + "" + + "\r\n\r\n"; + + body = System.Text.Encoding.UTF8.GetBytes(bodyString); + return req; + } + + public static MessageBase Decode(UpnpNatDevice device, string message) + { + XmlNode node; + XmlDocument doc = new XmlDocument(); + doc.LoadXml(message); + + XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable); + + // Error messages should be found under this namespace + nsm.AddNamespace("errorNs", "urn:schemas-upnp-org:control-1-0"); + nsm.AddNamespace("responseNs", device.ServiceType); + + // Check to see if we have a fault code message. + if ((node = doc.SelectSingleNode("//errorNs:UPnPError", nsm)) != null) { + string errorCode = node["errorCode"] != null ? node["errorCode"].InnerText : ""; + string errorDescription = node["errorDescription"] != null ? node["errorDescription"].InnerText : ""; + + return new ErrorMessage(Convert.ToInt32(errorCode, CultureInfo.InvariantCulture), errorDescription); + } + + if ((doc.SelectSingleNode("//responseNs:AddPortMappingResponse", nsm)) != null) + return new CreatePortMappingResponseMessage(); + + if ((doc.SelectSingleNode("//responseNs:DeletePortMappingResponse", nsm)) != null) + return new DeletePortMapResponseMessage(); + + if ((node = doc.SelectSingleNode("//responseNs:GetExternalIPAddressResponse", nsm)) != null) { + string newExternalIPAddress = node["NewExternalIPAddress"] != null ? node["NewExternalIPAddress"].InnerText : ""; + return new GetExternalIPAddressResponseMessage(newExternalIPAddress); + } + + if ((node = doc.SelectSingleNode("//responseNs:GetGenericPortMappingEntryResponse", nsm)) != null) + return new GetGenericPortMappingEntryResponseMessage(node, true); + + if ((node = doc.SelectSingleNode("//responseNs:GetSpecificPortMappingEntryResponse", nsm)) != null) + return new GetGenericPortMappingEntryResponseMessage(node, false); + + NatUtility.Log("Unknown message returned. Please send me back the following XML:"); + NatUtility.Log(message); + return null; + } + + public abstract WebRequest Encode(out byte[] body); + + internal static void WriteFullElement(XmlWriter writer, string element, string value) + { + writer.WriteStartElement(element); + writer.WriteString(value); + writer.WriteEndElement(); + } + + internal static XmlWriter CreateWriter(StringBuilder sb) + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.ConformanceLevel = ConformanceLevel.Fragment; + return XmlWriter.Create(sb, settings); + } + } +} diff --git a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs new file mode 100644 index 000000000..edc5a5d76 --- /dev/null +++ b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs @@ -0,0 +1,287 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using Mono.Nat.Upnp; +using System.Diagnostics; +using System.Net.Sockets; +using System.Net.NetworkInformation; +using MediaBrowser.Controller.Dlna; + +namespace Mono.Nat +{ + internal class UpnpSearcher : ISearcher + { + private const int SearchPeriod = 5 * 60; // The time in seconds between each search + static UpnpSearcher instance = new UpnpSearcher(); + public static List sockets = CreateSockets(); + + public static UpnpSearcher Instance + { + get { return instance; } + } + + public event EventHandler DeviceFound; + public event EventHandler DeviceLost; + + private List devices; + private Dictionary lastFetched; + private DateTime nextSearch; + private IPEndPoint searchEndpoint; + + UpnpSearcher() + { + devices = new List(); + lastFetched = new Dictionary(); + //searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900); + searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900); + } + + static List CreateSockets() + { + List clients = new List(); + try + { + foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces()) + { + foreach (UnicastIPAddressInformation address in n.GetIPProperties().UnicastAddresses) + { + if (address.Address.AddressFamily == AddressFamily.InterNetwork) + { + try + { + clients.Add(new UdpClient(new IPEndPoint(address.Address, 0))); + } + catch + { + continue; // Move on to the next address. + } + } + } + } + } + catch (Exception) + { + clients.Add(new UdpClient(0)); + } + return clients; + } + + public void Search() + { + foreach (UdpClient s in sockets) + { + try + { + Search(s); + } + catch + { + // Ignore any search errors + } + } + } + + void Search(UdpClient client) + { + nextSearch = DateTime.Now.AddSeconds(SearchPeriod); + byte[] data = DiscoverDeviceMessage.EncodeSSDP(); + + // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2) + for (int i = 0; i < 3; i++) + client.Send(data, data.Length, searchEndpoint); + } + + public IPEndPoint SearchEndpoint + { + get { return searchEndpoint; } + } + + public void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint) + { + // No matter what, this method should never throw an exception. If something goes wrong + // we should still be in a position to handle the next reply correctly. + try + { + /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. + Any other device type is no good to us for this purpose. See the IGP overview paper + page 5 for an overview of device types and their hierarchy. + http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */ + + /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which + version it is and apply the correct URN. */ + + /* Some routers don't correctly implement the version ID on the URN, so we only search for the type + prefix. */ + + // We have an internet gateway device now + UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty); + + if (devices.Contains(d)) + { + // We already have found this device, so we just refresh it to let people know it's + // Still alive. If a device doesn't respond to a search, we dump it. + devices[devices.IndexOf(d)].LastSeen = DateTime.Now; + } + else + { + + // If we send 3 requests at a time, ensure we only fetch the services list once + // even if three responses are received + if (lastFetched.ContainsKey(endpoint.Address)) + { + DateTime last = lastFetched[endpoint.Address]; + if ((DateTime.Now - last) < TimeSpan.FromSeconds(20)) + return; + } + lastFetched[endpoint.Address] = DateTime.Now; + + // Once we've parsed the information we need, we tell the device to retrieve it's service list + // Once we successfully receive the service list, the callback provided will be invoked. + NatUtility.Log("Fetching service list: {0}", d.HostEndPoint); + d.GetServicesList(DeviceSetupComplete); + } + } + catch (Exception ex) + { + NatUtility.Log("Unhandled exception when trying to decode a device's response Send me the following data: "); + NatUtility.Log("ErrorMessage:"); + NatUtility.Log(ex.Message); + } + } + + public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) + { + // Convert it to a string for easy parsing + string dataString = null; + + // No matter what, this method should never throw an exception. If something goes wrong + // we should still be in a position to handle the next reply correctly. + try { + string urn; + dataString = Encoding.UTF8.GetString(response); + + if (NatUtility.Verbose) + NatUtility.Log("UPnP Response: {0}", dataString); + + /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. + Any other device type is no good to us for this purpose. See the IGP overview paper + page 5 for an overview of device types and their hierarchy. + http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */ + + /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which + version it is and apply the correct URN. */ + + /* Some routers don't correctly implement the version ID on the URN, so we only search for the type + prefix. */ + + string log = "UPnP Response: Router advertised a '{0}' service"; + StringComparison c = StringComparison.OrdinalIgnoreCase; + if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1) { + urn = "urn:schemas-upnp-org:service:WANIPConnection:1"; + NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1"); + } else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1) { + urn = "urn:schemas-upnp-org:service:WANPPPConnection:1"; + NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:"); + } else + return; + + // We have an internet gateway device now + UpnpNatDevice d = new UpnpNatDevice(localAddress, dataString, urn); + + if (devices.Contains(d)) + { + // We already have found this device, so we just refresh it to let people know it's + // Still alive. If a device doesn't respond to a search, we dump it. + devices[devices.IndexOf(d)].LastSeen = DateTime.Now; + } + else + { + + // If we send 3 requests at a time, ensure we only fetch the services list once + // even if three responses are received + if (lastFetched.ContainsKey(endpoint.Address)) + { + DateTime last = lastFetched[endpoint.Address]; + if ((DateTime.Now - last) < TimeSpan.FromSeconds(20)) + return; + } + lastFetched[endpoint.Address] = DateTime.Now; + + // Once we've parsed the information we need, we tell the device to retrieve it's service list + // Once we successfully receive the service list, the callback provided will be invoked. + NatUtility.Log("Fetching service list: {0}", d.HostEndPoint); + d.GetServicesList(DeviceSetupComplete); + } + } + catch (Exception ex) + { + Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: "); + Trace.WriteLine("ErrorMessage:"); + Trace.WriteLine(ex.Message); + Trace.WriteLine("Data string:"); + Trace.WriteLine(dataString); + } + } + + public DateTime NextSearch + { + get { return nextSearch; } + } + + private void DeviceSetupComplete(INatDevice device) + { + lock (this.devices) + { + // We don't want the same device in there twice + if (devices.Contains(device)) + return; + + devices.Add(device); + } + + OnDeviceFound(new DeviceEventArgs(device)); + } + + private void OnDeviceFound(DeviceEventArgs args) + { + if (DeviceFound != null) + DeviceFound(this, args); + } + + public NatProtocol Protocol + { + get { return NatProtocol.Upnp; } + } + } +} diff --git a/Mono.Nat/Upnp/Upnp.cs b/Mono.Nat/Upnp/Upnp.cs new file mode 100644 index 000000000..e44a51c24 --- /dev/null +++ b/Mono.Nat/Upnp/Upnp.cs @@ -0,0 +1,83 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; + +namespace Mono.Nat.Upnp +{ + internal class Upnp + { + public UpnpNatDevice Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) + { + // Convert it to a string for easy parsing + string dataString = null; + + + string urn; + dataString = Encoding.UTF8.GetString(response); + + if (NatUtility.Verbose) + NatUtility.Log("UPnP Response: {0}", dataString); + + /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. + Any other device type is no good to us for this purpose. See the IGP overview paper + page 5 for an overview of device types and their hierarchy. + http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */ + + /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which + version it is and apply the correct URN. */ + + /* Some routers don't correctly implement the version ID on the URN, so we only search for the type + prefix. */ + + string log = "UPnP Response: Router advertised a '{0}' service"; + StringComparison c = StringComparison.OrdinalIgnoreCase; + if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1) + { + urn = "urn:schemas-upnp-org:service:WANIPConnection:1"; + NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1"); + } + else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1) + { + urn = "urn:schemas-upnp-org:service:WANPPPConnection:1"; + NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:"); + } + else + throw new NotSupportedException("Received non-supported device type"); + + // We have an internet gateway device now + return new UpnpNatDevice(localAddress, dataString, urn); + } + } +} diff --git a/Mono.Nat/Upnp/UpnpNatDevice.cs b/Mono.Nat/Upnp/UpnpNatDevice.cs new file mode 100644 index 000000000..1160d3ac2 --- /dev/null +++ b/Mono.Nat/Upnp/UpnpNatDevice.cs @@ -0,0 +1,651 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.IO; +using System.Net; +using System.Xml; +using System.Text; +using System.Diagnostics; +using MediaBrowser.Controller.Dlna; + +namespace Mono.Nat.Upnp +{ + public sealed class UpnpNatDevice : AbstractNatDevice, IEquatable + { + private EndPoint hostEndPoint; + private IPAddress localAddress; + private string serviceDescriptionUrl; + private string controlUrl; + private string serviceType; + + public override IPAddress LocalAddress + { + get { return localAddress; } + } + + /// + /// The callback to invoke when we are finished setting up the device + /// + private NatDeviceCallback callback; + + internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType) + { + this.LastSeen = DateTime.Now; + this.localAddress = localAddress; + + // Split the string at the "location" section so i can extract the ipaddress and service description url + string locationDetails = deviceInfo.Location.ToString(); + this.serviceType = serviceType; + + // Make sure we have no excess whitespace + locationDetails = locationDetails.Trim(); + + // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address + // Are we going to get addresses with the "http://" attached? + if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)) + { + NatUtility.Log("Found device at: {0}", locationDetails); + // This bit strings out the "http://" from the string + locationDetails = locationDetails.Substring(7); + + this.hostEndPoint = hostEndPoint; + + NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString()); + + // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip + // and port information + this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/')); + } + else + { + NatUtility.Log("Couldn't decode address. Please send following string to the developer: "); + } + } + + internal UpnpNatDevice (IPAddress localAddress, string deviceDetails, string serviceType) + { + this.LastSeen = DateTime.Now; + this.localAddress = localAddress; + + // Split the string at the "location" section so i can extract the ipaddress and service description url + string locationDetails = deviceDetails.Substring(deviceDetails.IndexOf("Location", StringComparison.InvariantCultureIgnoreCase) + 9).Split('\r')[0]; + this.serviceType = serviceType; + + // Make sure we have no excess whitespace + locationDetails = locationDetails.Trim(); + + // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address + // Are we going to get addresses with the "http://" attached? + if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)) + { + NatUtility.Log("Found device at: {0}", locationDetails); + // This bit strings out the "http://" from the string + locationDetails = locationDetails.Substring(7); + + // We then split off the end of the string to get something like: 192.168.0.3:241 in our string + string hostAddressAndPort = locationDetails.Remove(locationDetails.IndexOf('/')); + + // From this we parse out the IP address and Port + if (hostAddressAndPort.IndexOf(':') > 0) + { + this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort.Remove(hostAddressAndPort.IndexOf(':'))), + Convert.ToUInt16(hostAddressAndPort.Substring(hostAddressAndPort.IndexOf(':') + 1), System.Globalization.CultureInfo.InvariantCulture)); + } + else + { + // there is no port specified, use default port (80) + this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort), 80); + } + + NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString()); + + // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip + // and port information + this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/')); + } + else + { + Trace.WriteLine("Couldn't decode address. Please send following string to the developer: "); + Trace.WriteLine(deviceDetails); + } + } + + /// + /// The EndPoint that the device is at + /// + internal EndPoint HostEndPoint + { + get { return this.hostEndPoint; } + } + + /// + /// The relative url of the xml file that describes the list of services is at + /// + internal string ServiceDescriptionUrl + { + get { return this.serviceDescriptionUrl; } + } + + /// + /// The relative url that we can use to control the port forwarding + /// + internal string ControlUrl + { + get { return this.controlUrl; } + } + + /// + /// The service type we're using on the device + /// + public string ServiceType + { + get { return serviceType; } + } + + /// + /// Begins an async call to get the external ip address of the router + /// + public override IAsyncResult BeginGetExternalIP(AsyncCallback callback, object asyncState) + { + // Create the port map message + GetExternalIPAddressMessage message = new GetExternalIPAddressMessage(this); + return BeginMessageInternal(message, callback, asyncState, EndGetExternalIPInternal); + } + + /// + /// Maps the specified port to this computer + /// + public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState) + { + CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this); + return BeginMessageInternal(message, callback, asyncState, EndCreatePortMapInternal); + } + + /// + /// Removes a port mapping from this computer + /// + public override IAsyncResult BeginDeletePortMap(Mapping mapping, AsyncCallback callback, object asyncState) + { + DeletePortMappingMessage message = new DeletePortMappingMessage(mapping, this); + return BeginMessageInternal(message, callback, asyncState, EndDeletePortMapInternal); + } + + + public override IAsyncResult BeginGetAllMappings(AsyncCallback callback, object asyncState) + { + GetGenericPortMappingEntry message = new GetGenericPortMappingEntry(0, this); + return BeginMessageInternal(message, callback, asyncState, EndGetAllMappingsInternal); + } + + + public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState) + { + GetSpecificPortMappingEntryMessage message = new GetSpecificPortMappingEntryMessage(protocol, port, this); + return this.BeginMessageInternal(message, callback, asyncState, new AsyncCallback(this.EndGetSpecificMappingInternal)); + } + + /// + /// + /// + /// + public override void EndCreatePortMap(IAsyncResult result) + { + if (result == null) throw new ArgumentNullException("result"); + + PortMapAsyncResult mappingResult = result as PortMapAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); + + // Check if we need to wait for the operation to finish + if (!result.IsCompleted) + result.AsyncWaitHandle.WaitOne(); + + // If we have a saved exception, it means something went wrong during the mapping + // so we just rethrow the exception and let the user figure out what they should do. + if (mappingResult.SavedMessage is ErrorMessage) + { + ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; + throw new MappingException(msg.ErrorCode, msg.Description); + } + + //return result.AsyncState as Mapping; + } + + + /// + /// + /// + /// + public override void EndDeletePortMap(IAsyncResult result) + { + if (result == null) + throw new ArgumentNullException("result"); + + PortMapAsyncResult mappingResult = result as PortMapAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); + + // Check if we need to wait for the operation to finish + if (!mappingResult.IsCompleted) + mappingResult.AsyncWaitHandle.WaitOne(); + + // If we have a saved exception, it means something went wrong during the mapping + // so we just rethrow the exception and let the user figure out what they should do. + if (mappingResult.SavedMessage is ErrorMessage) + { + ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; + throw new MappingException(msg.ErrorCode, msg.Description); + } + + // If all goes well, we just return + //return true; + } + + + public override Mapping[] EndGetAllMappings(IAsyncResult result) + { + if (result == null) + throw new ArgumentNullException("result"); + + GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); + + if (!mappingResult.IsCompleted) + mappingResult.AsyncWaitHandle.WaitOne(); + + if (mappingResult.SavedMessage is ErrorMessage) + { + ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; + if (msg.ErrorCode != 713) + throw new MappingException(msg.ErrorCode, msg.Description); + } + + return mappingResult.Mappings.ToArray(); + } + + + /// + /// Ends an async request to get the external ip address of the router + /// + public override IPAddress EndGetExternalIP(IAsyncResult result) + { + if (result == null) throw new ArgumentNullException("result"); + + PortMapAsyncResult mappingResult = result as PortMapAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); + + if (!result.IsCompleted) + result.AsyncWaitHandle.WaitOne(); + + if (mappingResult.SavedMessage is ErrorMessage) + { + ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; + throw new MappingException(msg.ErrorCode, msg.Description); + } + + if (mappingResult.SavedMessage == null) + return null; + else + return ((GetExternalIPAddressResponseMessage)mappingResult.SavedMessage).ExternalIPAddress; + } + + + public override Mapping EndGetSpecificMapping(IAsyncResult result) + { + if (result == null) + throw new ArgumentNullException("result"); + + GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); + + if (!mappingResult.IsCompleted) + mappingResult.AsyncWaitHandle.WaitOne(); + + if (mappingResult.SavedMessage is ErrorMessage) + { + ErrorMessage message = mappingResult.SavedMessage as ErrorMessage; + if (message.ErrorCode != 0x2ca) + { + throw new MappingException(message.ErrorCode, message.Description); + } + } + if (mappingResult.Mappings.Count == 0) + return new Mapping (Protocol.Tcp, -1, -1); + + return mappingResult.Mappings[0]; + } + + + public override bool Equals(object obj) + { + UpnpNatDevice device = obj as UpnpNatDevice; + return (device == null) ? false : this.Equals((device)); + } + + + public bool Equals(UpnpNatDevice other) + { + return (other == null) ? false : (this.hostEndPoint.Equals(other.hostEndPoint) + //&& this.controlUrl == other.controlUrl + && this.serviceDescriptionUrl == other.serviceDescriptionUrl); + } + + public override int GetHashCode() + { + return (this.hostEndPoint.GetHashCode() ^ this.controlUrl.GetHashCode() ^ this.serviceDescriptionUrl.GetHashCode()); + } + + private IAsyncResult BeginMessageInternal(MessageBase message, AsyncCallback storedCallback, object asyncState, AsyncCallback callback) + { + byte[] body; + WebRequest request = message.Encode(out body); + PortMapAsyncResult mappingResult = PortMapAsyncResult.Create(message, request, storedCallback, asyncState); + + if (body.Length > 0) + { + request.ContentLength = body.Length; + request.BeginGetRequestStream(delegate(IAsyncResult result) { + try + { + Stream s = request.EndGetRequestStream(result); + s.Write(body, 0, body.Length); + request.BeginGetResponse(callback, mappingResult); + } + catch (Exception ex) + { + mappingResult.Complete(ex); + } + }, null); + } + else + { + request.BeginGetResponse(callback, mappingResult); + } + return mappingResult; + } + + private void CompleteMessage(IAsyncResult result) + { + PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult; + mappingResult.CompletedSynchronously = result.CompletedSynchronously; + mappingResult.Complete(); + } + + private MessageBase DecodeMessageFromResponse(Stream s, long length) + { + StringBuilder data = new StringBuilder(); + int bytesRead = 0; + int totalBytesRead = 0; + byte[] buffer = new byte[10240]; + + // Read out the content of the message, hopefully picking everything up in the case where we have no contentlength + if (length != -1) + { + while (totalBytesRead < length) + { + bytesRead = s.Read(buffer, 0, buffer.Length); + data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); + totalBytesRead += bytesRead; + } + } + else + { + while ((bytesRead = s.Read(buffer, 0, buffer.Length)) != 0) + data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); + } + + // Once we have our content, we need to see what kind of message it is. It'll either a an error + // or a response based on the action we performed. + return MessageBase.Decode(this, data.ToString()); + } + + private void EndCreatePortMapInternal(IAsyncResult result) + { + EndMessageInternal(result); + CompleteMessage(result); + } + + private void EndMessageInternal(IAsyncResult result) + { + HttpWebResponse response = null; + PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult; + + try + { + try + { + response = (HttpWebResponse)mappingResult.Request.EndGetResponse(result); + } + catch (WebException ex) + { + // Even if the request "failed" i want to continue on to read out the response from the router + response = ex.Response as HttpWebResponse; + if (response == null) + mappingResult.SavedMessage = new ErrorMessage((int)ex.Status, ex.Message); + } + if (response != null) + mappingResult.SavedMessage = DecodeMessageFromResponse(response.GetResponseStream(), response.ContentLength); + } + + finally + { + if (response != null) + response.Close(); + } + } + + private void EndDeletePortMapInternal(IAsyncResult result) + { + EndMessageInternal(result); + CompleteMessage(result); + } + + private void EndGetAllMappingsInternal(IAsyncResult result) + { + EndMessageInternal(result); + + GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult; + GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage; + if (message != null) + { + Mapping mapping = new Mapping (message.Protocol, message.InternalPort, message.ExternalPort, message.LeaseDuration); + mapping.Description = message.PortMappingDescription; + mappingResult.Mappings.Add(mapping); + GetGenericPortMappingEntry next = new GetGenericPortMappingEntry(mappingResult.Mappings.Count, this); + + // It's ok to do this synchronously because we should already be on anther thread + // and this won't block the user. + byte[] body; + WebRequest request = next.Encode(out body); + if (body.Length > 0) + { + request.ContentLength = body.Length; + request.GetRequestStream().Write(body, 0, body.Length); + } + mappingResult.Request = request; + request.BeginGetResponse(EndGetAllMappingsInternal, mappingResult); + return; + } + + CompleteMessage(result); + } + + private void EndGetExternalIPInternal(IAsyncResult result) + { + EndMessageInternal(result); + CompleteMessage(result); + } + + private void EndGetSpecificMappingInternal(IAsyncResult result) + { + EndMessageInternal(result); + + GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult; + GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage; + if (message != null) { + Mapping mapping = new Mapping(mappingResult.SpecificMapping.Protocol, message.InternalPort, mappingResult.SpecificMapping.PublicPort, message.LeaseDuration); + mapping.Description = mappingResult.SpecificMapping.Description; + mappingResult.Mappings.Add(mapping); + } + + CompleteMessage(result); + } + + internal void GetServicesList(NatDeviceCallback callback) + { + // Save the callback so i can use it again later when i've finished parsing the services available + this.callback = callback; + + // Create a HTTPWebRequest to download the list of services the device offers + byte[] body; + WebRequest request = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint).Encode(out body); + if (body.Length > 0) + NatUtility.Log("Error: Services Message contained a body"); + request.BeginGetResponse(this.ServicesReceived, request); + } + + private void ServicesReceived(IAsyncResult result) + { + HttpWebResponse response = null; + try + { + int abortCount = 0; + int bytesRead = 0; + byte[] buffer = new byte[10240]; + StringBuilder servicesXml = new StringBuilder(); + XmlDocument xmldoc = new XmlDocument(); + HttpWebRequest request = result.AsyncState as HttpWebRequest; + response = request.EndGetResponse(result) as HttpWebResponse; + Stream s = response.GetResponseStream(); + + if (response.StatusCode != HttpStatusCode.OK) { + NatUtility.Log("{0}: Couldn't get services list: {1}", HostEndPoint, response.StatusCode); + return; // FIXME: This the best thing to do?? + } + + while (true) + { + bytesRead = s.Read(buffer, 0, buffer.Length); + servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); + try + { + xmldoc.LoadXml(servicesXml.ToString()); + response.Close(); + break; + } + catch (XmlException) + { + // If we can't receive the entire XML within 500ms, then drop the connection + // Unfortunately not all routers supply a valid ContentLength (mine doesn't) + // so this hack is needed to keep testing our recieved data until it gets successfully + // parsed by the xmldoc. Without this, the code will never pick up my router. + if (abortCount++ > 50) + { + response.Close(); + return; + } + NatUtility.Log("{0}: Couldn't parse services list", HostEndPoint); + System.Threading.Thread.Sleep(10); + } + } + + NatUtility.Log("{0}: Parsed services list", HostEndPoint); + XmlNamespaceManager ns = new XmlNamespaceManager(xmldoc.NameTable); + ns.AddNamespace("ns", "urn:schemas-upnp-org:device-1-0"); + XmlNodeList nodes = xmldoc.SelectNodes("//*/ns:serviceList", ns); + + foreach (XmlNode node in nodes) + { + //Go through each service there + foreach (XmlNode service in node.ChildNodes) + { + //If the service is a WANIPConnection, then we have what we want + string type = service["serviceType"].InnerText; + NatUtility.Log("{0}: Found service: {1}", HostEndPoint, type); + StringComparison c = StringComparison.OrdinalIgnoreCase; + // TODO: Add support for version 2 of UPnP. + if (type.Equals("urn:schemas-upnp-org:service:WANPPPConnection:1", c) || + type.Equals("urn:schemas-upnp-org:service:WANIPConnection:1", c)) + { + this.controlUrl = service["controlURL"].InnerText; + NatUtility.Log("{0}: Found upnp service at: {1}", HostEndPoint, controlUrl); + try + { + Uri u = new Uri(controlUrl); + if (u.IsAbsoluteUri) + { + EndPoint old = hostEndPoint; + this.hostEndPoint = new IPEndPoint(IPAddress.Parse(u.Host), u.Port); + NatUtility.Log("{0}: Absolute URI detected. Host address is now: {1}", old, HostEndPoint); + this.controlUrl = controlUrl.Substring(u.GetLeftPart(UriPartial.Authority).Length); + NatUtility.Log("{0}: New control url: {1}", HostEndPoint, controlUrl); + } + } + catch + { + NatUtility.Log("{0}: Assuming control Uri is relative: {1}", HostEndPoint, controlUrl); + } + NatUtility.Log("{0}: Handshake Complete", HostEndPoint); + this.callback(this); + return; + } + } + } + + //If we get here, it means that we didn't get WANIPConnection service, which means no uPnP forwarding + //So we don't invoke the callback, so this device is never added to our lists + } + catch (WebException ex) + { + // Just drop the connection, FIXME: Should i retry? + NatUtility.Log("{0}: Device denied the connection attempt: {1}", HostEndPoint, ex); + } + finally + { + if (response != null) + response.Close(); + } + } + + /// + /// Overridden. + /// + /// + public override string ToString( ) + { + //GetExternalIP is blocking and can throw exceptions, can't use it here. + return String.Format( + "UpnpNatDevice - EndPoint: {0}, External IP: {1}, Control Url: {2}, Service Description Url: {3}, Service Type: {4}, Last Seen: {5}", + this.hostEndPoint, "Manually Check" /*this.GetExternalIP()*/, this.controlUrl, this.serviceDescriptionUrl, this.serviceType, this.LastSeen); + } + } +} \ No newline at end of file -- cgit v1.2.3 From 0f760af82c8f9186271ea68bd7b82a25475630f7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 11 Sep 2016 13:09:10 -0400 Subject: move download images in advance option from global to per library --- MediaBrowser.Model/Configuration/LibraryOptions.cs | 1 + .../Configuration/ServerConfiguration.cs | 2 -- .../Manager/ItemImageProvider.cs | 24 ++++++++-------- MediaBrowser.Providers/Manager/MetadataService.cs | 33 ++++++++++++++++------ .../ApplicationHost.cs | 3 +- 5 files changed, 39 insertions(+), 24 deletions(-) (limited to 'MediaBrowser.Model/Configuration/ServerConfiguration.cs') diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 551363223..770ad433d 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -8,6 +8,7 @@ public int SchemaVersion { get; set; } public bool EnableChapterImageExtraction { get; set; } public bool ExtractChapterImagesDuringLibraryScan { get; set; } + public bool DownloadImagesInAdvance { get; set; } public LibraryOptions() { diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 5cf266674..e45aa58c5 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -191,8 +191,6 @@ namespace MediaBrowser.Model.Configuration public int SchemaVersion { get; set; } public int SqliteCacheSize { get; set; } - public bool DownloadImagesInAdvance { get; set; } - public bool EnableAnonymousUsageReporting { get; set; } public bool EnableStandaloneMusicKeys { get; set; } public bool EnableLocalizedGuids { get; set; } diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 97dd1ed4c..1d8ba85f8 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -56,7 +56,7 @@ namespace MediaBrowser.Providers.Manager return hasChanges; } - public async Task RefreshImages(IHasImages item, IEnumerable imageProviders, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken) + public async Task RefreshImages(IHasImages item, LibraryOptions libraryOptions, IEnumerable imageProviders, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken) { if (refreshOptions.IsReplacingImage(ImageType.Backdrop)) { @@ -84,7 +84,7 @@ namespace MediaBrowser.Providers.Manager if (remoteProvider != null) { - await RefreshFromProvider(item, remoteProvider, refreshOptions, savedOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false); + await RefreshFromProvider(item, libraryOptions, remoteProvider, refreshOptions, savedOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false); providerIds.Add(provider.GetType().FullName.GetMD5()); continue; } @@ -249,7 +249,7 @@ namespace MediaBrowser.Providers.Manager /// The result. /// The cancellation token. /// Task. - private async Task RefreshFromProvider(IHasImages item, + private async Task RefreshFromProvider(IHasImages item, LibraryOptions libraryOptions, IRemoteImageProvider provider, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, @@ -293,7 +293,7 @@ namespace MediaBrowser.Providers.Manager if (!HasImage(item, imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Contains(imageType))) { minWidth = savedOptions.GetMinWidth(imageType); - var downloaded = await DownloadImage(item, provider, result, list, minWidth, imageType, cancellationToken).ConfigureAwait(false); + var downloaded = await DownloadImage(item, libraryOptions, provider, result, list, minWidth, imageType, cancellationToken).ConfigureAwait(false); if (downloaded) { @@ -305,7 +305,7 @@ namespace MediaBrowser.Providers.Manager if (!item.LockedFields.Contains(MetadataFields.Backdrops)) { minWidth = savedOptions.GetMinWidth(ImageType.Backdrop); - await DownloadBackdrops(item, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); + await DownloadBackdrops(item, libraryOptions, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); } if (!item.LockedFields.Contains(MetadataFields.Screenshots)) @@ -314,7 +314,7 @@ namespace MediaBrowser.Providers.Manager if (hasScreenshots != null) { minWidth = savedOptions.GetMinWidth(ImageType.Screenshot); - await DownloadBackdrops(item, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); + await DownloadBackdrops(item, libraryOptions, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); } } } @@ -472,7 +472,7 @@ namespace MediaBrowser.Providers.Manager return changed; } - private async Task DownloadImage(IHasImages item, + private async Task DownloadImage(IHasImages item, LibraryOptions libraryOptions, IRemoteImageProvider provider, RefreshResult result, IEnumerable images, @@ -484,7 +484,7 @@ namespace MediaBrowser.Providers.Manager .Where(i => i.Type == type && !(i.Width.HasValue && i.Width.Value < minWidth)) .ToList(); - if (EnableImageStub(item, type) && eligibleImages.Count > 0) + if (EnableImageStub(item, type, libraryOptions) && eligibleImages.Count > 0) { SaveImageStub(item, type, eligibleImages.Select(i => i.Url)); result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate; @@ -518,14 +518,14 @@ namespace MediaBrowser.Providers.Manager return false; } - private bool EnableImageStub(IHasImages item, ImageType type) + private bool EnableImageStub(IHasImages item, ImageType type, LibraryOptions libraryOptions) { if (item is LiveTvProgram) { return true; } - if (_config.Configuration.DownloadImagesInAdvance) + if (libraryOptions.DownloadImagesInAdvance) { return false; } @@ -585,7 +585,7 @@ namespace MediaBrowser.Providers.Manager }, newIndex); } - private async Task DownloadBackdrops(IHasImages item, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable images, int minWidth, CancellationToken cancellationToken) + private async Task DownloadBackdrops(IHasImages item, LibraryOptions libraryOptions, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable images, int minWidth, CancellationToken cancellationToken) { foreach (var image in images.Where(i => i.Type == imageType)) { @@ -601,7 +601,7 @@ namespace MediaBrowser.Providers.Manager var url = image.Url; - if (EnableImageStub(item, imageType)) + if (EnableImageStub(item, imageType, libraryOptions)) { SaveImageStub(item, imageType, new[] { url }); result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate; diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index a610df427..55f2c812c 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Manager @@ -120,6 +121,8 @@ namespace MediaBrowser.Providers.Manager } } + LibraryOptions libraryOptions = null; + // Next run remote image providers, but only if local image providers didn't throw an exception if (!localImagesFailed && refreshOptions.ImageRefreshMode != ImageRefreshMode.ValidationOnly) { @@ -127,7 +130,12 @@ namespace MediaBrowser.Providers.Manager if (providers.Count > 0) { - var result = await itemImageProvider.RefreshImages(itemOfType, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false); + if (libraryOptions == null) + { + libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item) ?? new LibraryOptions(); + } + + var result = await itemImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false); updateType = updateType | result.UpdateType; if (result.Failures == 0) @@ -180,8 +188,13 @@ namespace MediaBrowser.Providers.Manager item.DateLastRefreshed = default(DateTime); } + if (libraryOptions == null) + { + libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item) ?? new LibraryOptions(); + } + // Save to database - await SaveItem(metadataResult, updateType, cancellationToken).ConfigureAwait(false); + await SaveItem(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false); } await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false); @@ -196,17 +209,19 @@ namespace MediaBrowser.Providers.Manager lookupInfo.Year = result.ProductionYear; } - protected async Task SaveItem(MetadataResult result, ItemUpdateType reason, CancellationToken cancellationToken) + protected async Task SaveItem(MetadataResult result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken) { if (result.Item.SupportsPeople && result.People != null) { - await LibraryManager.UpdatePeople(result.Item as BaseItem, result.People.ToList()); - await SavePeopleMetadata(result.People, cancellationToken).ConfigureAwait(false); + var baseItem = result.Item as BaseItem; + + await LibraryManager.UpdatePeople(baseItem, result.People.ToList()); + await SavePeopleMetadata(result.People, libraryOptions, cancellationToken).ConfigureAwait(false); } await result.Item.UpdateToRepository(reason, cancellationToken).ConfigureAwait(false); } - private async Task SavePeopleMetadata(List people, CancellationToken cancellationToken) + private async Task SavePeopleMetadata(List people, LibraryOptions libraryOptions, CancellationToken cancellationToken) { foreach (var person in people) { @@ -229,7 +244,7 @@ namespace MediaBrowser.Providers.Manager if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary)) { - await AddPersonImage(personEntity, person.ImageUrl, cancellationToken).ConfigureAwait(false); + await AddPersonImage(personEntity, libraryOptions, person.ImageUrl, cancellationToken).ConfigureAwait(false); saveEntity = true; updateType = updateType | ItemUpdateType.ImageUpdate; @@ -243,9 +258,9 @@ namespace MediaBrowser.Providers.Manager } } - private async Task AddPersonImage(Person personEntity, string imageUrl, CancellationToken cancellationToken) + private async Task AddPersonImage(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken) { - if (ServerConfigurationManager.Configuration.DownloadImagesInAdvance) + if (libraryOptions.DownloadImagesInAdvance) { try { diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index e6d9b482e..3c8c5bf55 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -345,6 +345,7 @@ namespace MediaBrowser.Server.Startup.Common { var name = entryPoint.GetType().FullName; Logger.Info("Starting entry point {0}", name); + var now = DateTime.UtcNow; try { entryPoint.Run(); @@ -353,7 +354,7 @@ namespace MediaBrowser.Server.Startup.Common { Logger.ErrorException("Error in {0}", ex, name); } - Logger.Info("Entry point completed: {0}", name); + Logger.Info("Entry point completed: {0}. Duration: {1} seconds", name, (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture)); } Logger.Info("All entry points have started"); -- cgit v1.2.3 From 68268cfb73d83c21df939a7992f3e4dcee9234f5 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 27 Sep 2016 13:51:01 -0400 Subject: update closing of live streams --- .../Configuration/ServerConfiguration.cs | 1 + .../Library/LibraryManager.cs | 18 +++++++++++++++++- .../LiveTv/EmbyTV/EmbyTV.cs | 1 + .../LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs | 5 +++-- 4 files changed, 22 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Model/Configuration/ServerConfiguration.cs') diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index e45aa58c5..1d2928f67 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -73,6 +73,7 @@ namespace MediaBrowser.Model.Configuration /// /// The metadata path. public string MetadataPath { get; set; } + public string MetadataNetworkPath { get; set; } public string LastVersion { get; set; } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index b076996df..a3930f36a 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -2550,9 +2550,25 @@ namespace MediaBrowser.Server.Implementations.Library } } + var metadataPath = ConfigurationManager.Configuration.MetadataPath; + var metadataNetworkPath = ConfigurationManager.Configuration.MetadataNetworkPath; + + if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath)) + { + var metadataSubstitutionResult = SubstitutePathInternal(path, metadataPath, metadataNetworkPath); + if (metadataSubstitutionResult.Item2) + { + return metadataSubstitutionResult.Item1; + } + } + foreach (var map in ConfigurationManager.Configuration.PathSubstitutions) { - path = SubstitutePath(path, map.From, map.To); + var substitutionResult = SubstitutePathInternal(path, map.From, map.To); + if (substitutionResult.Item2) + { + return substitutionResult.Item1; + } } return path; diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 6ea3fbc5e..f53ec3ee8 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -908,6 +908,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV try { await stream.Close().ConfigureAwait(false); + _logger.Info("Live stream {0} closed successfully", id); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs index 6078c4a70..d3540d180 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs @@ -22,6 +22,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly IServerApplicationHost _appHost; private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); + private readonly TaskCompletionSource _liveStreamTaskCompletionSource = new TaskCompletionSource(); public HdHomerunLiveStream(MediaSourceInfo mediaSource, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost) : base(mediaSource) @@ -62,7 +63,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun { _liveStreamCancellationTokenSource.Cancel(); - return base.Close(); + return _liveStreamTaskCompletionSource.Task; } private async Task StartStreamingToTempFile(Stream outputStream, string tempFilePath, string url, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) @@ -120,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - await Task.Delay(5000).ConfigureAwait(false); + _liveStreamTaskCompletionSource.TrySetResult(true); DeleteTempFile(tempFilePath); -- cgit v1.2.3 From 50e66869872579d2cbd8337c4b114cf68dff814a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 7 Oct 2016 11:08:13 -0400 Subject: update live stream management --- Emby.Drawing/ImageProcessor.cs | 13 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 4 + MediaBrowser.Api/StartupWizardService.cs | 1 + MediaBrowser.Controller/Entities/BaseItem.cs | 28 ++- MediaBrowser.Controller/Entities/Game.cs | 2 +- MediaBrowser.Controller/Entities/IHasImages.cs | 6 +- MediaBrowser.Controller/Entities/Movies/Movie.cs | 6 +- MediaBrowser.Controller/Entities/MusicVideo.cs | 11 +- MediaBrowser.Controller/Entities/Trailer.cs | 4 +- MediaBrowser.Controller/Entities/Video.cs | 2 +- MediaBrowser.Controller/LiveTv/LiveStream.cs | 1 + .../MediaBrowser.Controller.csproj | 2 - MediaBrowser.Controller/Net/IHttpResultFactory.cs | 8 - .../Providers/IImageFileSaver.cs | 20 -- MediaBrowser.Controller/Providers/IImageSaver.cs | 11 - .../Providers/IProviderManager.cs | 7 - MediaBrowser.Controller/Providers/ItemInfo.cs | 2 +- MediaBrowser.Dlna/Eventing/EventManager.cs | 1 - .../Images/LocalImageProvider.cs | 2 +- MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs | 2 +- .../Encoder/EncodingUtils.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 4 +- .../Configuration/ServerConfiguration.cs | 1 + .../Notifications/NotificationType.cs | 1 - MediaBrowser.Providers/Manager/ImageSaver.cs | 10 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 10 +- .../TV/TheTVDB/TvdbSeriesProvider.cs | 3 +- .../EntryPoints/Notifications/Notifications.cs | 17 +- .../HttpServer/HttpListenerHost.cs | 10 +- .../HttpServer/HttpResultFactory.cs | 23 -- .../HttpServer/NativeWebSocket.cs | 240 ------------------ .../HttpServer/RangeRequestWriter.cs | 18 -- .../SocketSharp/WebSocketSharpResponse.cs | 18 +- .../Library/LibraryManager.cs | 2 +- .../Library/Resolvers/Audio/MusicArtistResolver.cs | 18 +- .../Library/Resolvers/Movies/MovieResolver.cs | 30 +-- .../Library/UserDataManager.cs | 24 +- .../LiveTv/EmbyTV/EmbyTV.cs | 33 ++- .../LiveTv/LiveTvManager.cs | 132 ++++------ .../LiveTv/LiveTvMediaSourceProvider.cs | 19 +- .../LiveTv/TunerHosts/BaseTunerHost.cs | 19 -- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 38 ++- .../TunerHosts/HdHomerun/HdHomerunLiveStream.cs | 196 ++++----------- .../LiveTv/TunerHosts/MulticastStream.cs | 96 ++++++++ .../LiveTv/TunerHosts/QueueStream.cs | 93 +++++++ .../Localization/Core/en-US.json | 1 - .../MediaBrowser.Server.Implementations.csproj | 3 +- .../Notifications/CoreNotificationTypes.cs | 7 - .../Persistence/SqliteItemRepository.cs | 7 +- .../ServerManager/WebSocketConnection.cs | 63 +---- .../ApplicationHost.cs | 15 +- MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs | 272 --------------------- .../MediaBrowser.XbmcMetadata.csproj | 1 - 53 files changed, 484 insertions(+), 1075 deletions(-) delete mode 100644 MediaBrowser.Controller/Providers/IImageFileSaver.cs delete mode 100644 MediaBrowser.Controller/Providers/IImageSaver.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs delete mode 100644 MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs (limited to 'MediaBrowser.Model/Configuration/ServerConfiguration.cs') diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 80ebbb719..e9f8f81f3 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -829,18 +829,7 @@ namespace Emby.Drawing // Run the enhancers sequentially in order of priority foreach (var enhancer in imageEnhancers) { - var typeName = enhancer.GetType().Name; - - try - { - await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("{0} failed enhancing {1}", ex, typeName, item.Name); - - throw; - } + await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false); // Feed the output into the next enhancer as input inputPath = outputPath; diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index a98637650..96d7874f0 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -197,6 +197,10 @@ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model + + {2e781478-814d-4a48-9d80-bff206441a65} + MediaBrowser.Server.Implementations + diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index ebb3204a4..4c5abc996 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -116,6 +116,7 @@ namespace MediaBrowser.Api config.EnableCaseSensitiveItemIds = true; //config.EnableFolderView = true; config.SchemaVersion = 109; + config.EnableSimpleArtistDetection = true; } public void Post(UpdateStartupConfiguration request) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index be88c535e..90a22b217 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -115,6 +115,22 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public bool IsInMixedFolder { get; set; } + [IgnoreDataMember] + protected virtual bool SupportsIsInMixedFolderDetection + { + get { return false; } + } + + public bool DetectIsInMixedFolder() + { + if (SupportsIsInMixedFolderDetection) + { + + } + + return IsInMixedFolder; + } + [IgnoreDataMember] public virtual bool SupportsRemoteImageDownloading { @@ -1116,7 +1132,7 @@ namespace MediaBrowser.Controller.Entities var hasThemeMedia = this as IHasThemeMedia; if (hasThemeMedia != null) { - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); @@ -1266,7 +1282,15 @@ namespace MediaBrowser.Controller.Entities { var current = this; - return current.IsInMixedFolder == newItem.IsInMixedFolder; + if (!SupportsIsInMixedFolderDetection) + { + if (current.IsInMixedFolder != newItem.IsInMixedFolder) + { + return false; + } + } + + return true; } public void AfterMetadataRefresh() diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs index 24910498f..a48b9f564 100644 --- a/MediaBrowser.Controller/Entities/Game.cs +++ b/MediaBrowser.Controller/Entities/Game.cs @@ -98,7 +98,7 @@ namespace MediaBrowser.Controller.Entities public override IEnumerable GetDeletePaths() { - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { return new[] { System.IO.Path.GetDirectoryName(Path) }; } diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index a38b7394d..1ab0566e0 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -150,11 +150,7 @@ namespace MediaBrowser.Controller.Entities /// true if [supports local metadata]; otherwise, false. bool SupportsLocalMetadata { get; } - /// - /// Gets a value indicating whether this instance is in mixed folder. - /// - /// true if this instance is in mixed folder; otherwise, false. - bool IsInMixedFolder { get; } + bool DetectIsInMixedFolder(); /// /// Gets a value indicating whether this instance is locked. diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index f0270497c..e1e336147 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -81,7 +81,7 @@ namespace MediaBrowser.Controller.Entities.Movies // Must have a parent to have special features // In other words, it must be part of the Parent/Child tree - if (LocationType == LocationType.FileSystem && GetParent() != null && !IsInMixedFolder) + if (LocationType == LocationType.FileSystem && GetParent() != null && !DetectIsInMixedFolder()) { var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); @@ -119,7 +119,7 @@ namespace MediaBrowser.Controller.Entities.Movies { var info = GetItemLookupInfo(); - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { info.Name = System.IO.Path.GetFileName(ContainingFolderPath); } @@ -145,7 +145,7 @@ namespace MediaBrowser.Controller.Entities.Movies else { // Try to get the year from the folder name - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath)); diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index 8b749b7a5..9254802dd 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -37,6 +37,15 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + protected override bool SupportsIsInMixedFolderDetection + { + get + { + return true; + } + } + public override UnratedItem GetBlockUnratedType() { return UnratedItem.Music; @@ -65,7 +74,7 @@ namespace MediaBrowser.Controller.Entities else { // Try to get the year from the folder name - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath)); diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index 7a987a68e..f68cd2c85 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Entities info.IsLocalTrailer = TrailerTypes.Contains(TrailerType.LocalTrailer); - if (!IsInMixedFolder && LocationType == LocationType.FileSystem) + if (!DetectIsInMixedFolder() && LocationType == LocationType.FileSystem) { info.Name = System.IO.Path.GetFileName(ContainingFolderPath); } @@ -90,7 +90,7 @@ namespace MediaBrowser.Controller.Entities else { // Try to get the year from the folder name - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath)); diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 1406a05ce..c64cdf57d 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -480,7 +480,7 @@ namespace MediaBrowser.Controller.Entities public override IEnumerable GetDeletePaths() { - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { return new[] { ContainingFolderPath }; } diff --git a/MediaBrowser.Controller/LiveTv/LiveStream.cs b/MediaBrowser.Controller/LiveTv/LiveStream.cs index 7d44fbd90..a5d432a54 100644 --- a/MediaBrowser.Controller/LiveTv/LiveStream.cs +++ b/MediaBrowser.Controller/LiveTv/LiveStream.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Controller.LiveTv public ITunerHost TunerHost { get; set; } public string OriginalStreamId { get; set; } public bool EnableStreamSharing { get; set; } + public string UniqueId = Guid.NewGuid().ToString("N"); public LiveStream(MediaSourceInfo mediaSource) { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index e9d2054da..7c1114e22 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -287,9 +287,7 @@ - - diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs index 8fdb1ce37..ca453840f 100644 --- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs +++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs @@ -11,14 +11,6 @@ namespace MediaBrowser.Controller.Net /// public interface IHttpResultFactory { - /// - /// Throws the error. - /// - /// The status code. - /// The error message. - /// The response headers. - void ThrowError(int statusCode, string errorMessage, IDictionary responseHeaders = null); - /// /// Gets the result. /// diff --git a/MediaBrowser.Controller/Providers/IImageFileSaver.cs b/MediaBrowser.Controller/Providers/IImageFileSaver.cs deleted file mode 100644 index 3e11d8bf8..000000000 --- a/MediaBrowser.Controller/Providers/IImageFileSaver.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Controller.Providers -{ - public interface IImageFileSaver : IImageSaver - { - /// - /// Gets the save paths. - /// - /// The item. - /// The type. - /// The format. - /// The index. - /// IEnumerable{System.String}. - IEnumerable GetSavePaths(IHasImages item, ImageType type, ImageFormat format, int index); - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/IImageSaver.cs b/MediaBrowser.Controller/Providers/IImageSaver.cs deleted file mode 100644 index 62017160f..000000000 --- a/MediaBrowser.Controller/Providers/IImageSaver.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MediaBrowser.Controller.Providers -{ - public interface IImageSaver - { - /// - /// Gets the name. - /// - /// The name. - string Name { get; } - } -} diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 3eefa9647..d3e5685bb 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -95,15 +95,8 @@ namespace MediaBrowser.Controller.Providers /// /// Adds the metadata providers. /// - /// The image providers. - /// The metadata services. - /// The metadata providers. - /// The savers. - /// The image savers. - /// The external ids. void AddParts(IEnumerable imageProviders, IEnumerable metadataServices, IEnumerable metadataProviders, IEnumerable savers, - IEnumerable imageSavers, IEnumerable externalIds); /// diff --git a/MediaBrowser.Controller/Providers/ItemInfo.cs b/MediaBrowser.Controller/Providers/ItemInfo.cs index 63cc48058..8de11b743 100644 --- a/MediaBrowser.Controller/Providers/ItemInfo.cs +++ b/MediaBrowser.Controller/Providers/ItemInfo.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Controller.Providers { Path = item.Path; ContainingFolderPath = item.ContainingFolderPath; - IsInMixedFolder = item.IsInMixedFolder; + IsInMixedFolder = item.DetectIsInMixedFolder(); var video = item as Video; if (video != null) diff --git a/MediaBrowser.Dlna/Eventing/EventManager.cs b/MediaBrowser.Dlna/Eventing/EventManager.cs index 68f012c3a..51c8d2d91 100644 --- a/MediaBrowser.Dlna/Eventing/EventManager.cs +++ b/MediaBrowser.Dlna/Eventing/EventManager.cs @@ -156,7 +156,6 @@ namespace MediaBrowser.Dlna.Eventing } catch (OperationCanceledException) { - throw; } catch { diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index fe61a7a46..ef9160b70 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -132,7 +132,7 @@ namespace MediaBrowser.LocalMetadata.Images } var imagePrefix = item.FileNameWithoutExtension + "-"; - var isInMixedFolder = item.IsInMixedFolder; + var isInMixedFolder = item.DetectIsInMixedFolder(); PopulatePrimaryImages(item, images, files, imagePrefix, isInMixedFolder); diff --git a/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs index a90789a3e..5592c068c 100644 --- a/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs @@ -99,7 +99,7 @@ namespace MediaBrowser.LocalMetadata.Savers public static string GetGameSavePath(Game item) { - if (item.IsInMixedFolder) + if (item.DetectIsInMixedFolder()) { return Path.ChangeExtension(item.Path, ".xml"); } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs index 33e90743a..5d0f1f075 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs @@ -76,7 +76,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public static string GetProbeSizeArgument(bool isDvd) { - return isDvd ? "-probesize 1G -analyzeduration 200M" : " -analyzeduration 2M"; + return isDvd ? "-probesize 1G -analyzeduration 200M" : ""; } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 25ad14fe8..5c3345008 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -426,8 +426,10 @@ namespace MediaBrowser.MediaEncoding.Encoder var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames); + var probeSizeArgument = GetProbeSizeArgument(inputFiles, request.Protocol); + return GetMediaInfoInternal(GetInputArgument(inputFiles, request.Protocol), request.InputPath, request.Protocol, extractChapters, - GetProbeSizeArgument(inputFiles, request.Protocol), request.MediaType == DlnaProfileType.Audio, request.VideoType, cancellationToken); + probeSizeArgument, request.MediaType == DlnaProfileType.Audio, request.VideoType, cancellationToken); } /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 1d2928f67..e7f8e6548 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -203,6 +203,7 @@ namespace MediaBrowser.Model.Configuration public string[] CodecsUsed { get; set; } public bool EnableChannelView { get; set; } public bool EnableExternalContentInSuggestions { get; set; } + public bool EnableSimpleArtistDetection { get; set; } public int ImageExtractionTimeoutMs { get; set; } /// diff --git a/MediaBrowser.Model/Notifications/NotificationType.cs b/MediaBrowser.Model/Notifications/NotificationType.cs index f5e3624f0..eefd15808 100644 --- a/MediaBrowser.Model/Notifications/NotificationType.cs +++ b/MediaBrowser.Model/Notifications/NotificationType.cs @@ -16,7 +16,6 @@ namespace MediaBrowser.Model.Notifications PluginUpdateInstalled, PluginUninstalled, NewLibraryContent, - NewLibraryContentMultiple, ServerRestartRequired, TaskFailed, CameraImageUploaded, diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index c9b3f22c5..5203adc9d 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -371,7 +371,7 @@ namespace MediaBrowser.Providers.Manager return Path.Combine(seriesFolder, imageFilename); } - if (item.IsInMixedFolder) + if (item.DetectIsInMixedFolder()) { return GetSavePathForItemInMixedFolder(item, type, "landscape", extension); } @@ -447,7 +447,7 @@ namespace MediaBrowser.Providers.Manager path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension); } - else if (item.IsInMixedFolder) + else if (item.DetectIsInMixedFolder()) { path = GetSavePathForItemInMixedFolder(item, type, filename, extension); } @@ -514,7 +514,7 @@ namespace MediaBrowser.Providers.Manager if (imageIndex.Value == 0) { - if (item.IsInMixedFolder) + if (item.DetectIsInMixedFolder()) { return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart", extension) }; } @@ -540,7 +540,7 @@ namespace MediaBrowser.Providers.Manager var outputIndex = imageIndex.Value; - if (item.IsInMixedFolder) + if (item.DetectIsInMixedFolder()) { return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) }; } @@ -583,7 +583,7 @@ namespace MediaBrowser.Providers.Manager return new[] { Path.Combine(seasonFolder, imageFilename) }; } - if (item.IsInMixedFolder || item is MusicVideo) + if (item.DetectIsInMixedFolder() || item is MusicVideo) { return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) }; } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 7e28254b0..ae1d60eb9 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -58,7 +58,6 @@ namespace MediaBrowser.Providers.Manager private IMetadataService[] _metadataServices = { }; private IMetadataProvider[] _metadataProviders = { }; private IEnumerable _savers; - private IImageSaver[] _imageSavers; private readonly IServerApplicationPaths _appPaths; private readonly IJsonSerializer _json; @@ -91,21 +90,14 @@ namespace MediaBrowser.Providers.Manager /// /// Adds the metadata providers. /// - /// The image providers. - /// The metadata services. - /// The metadata providers. - /// The metadata savers. - /// The image savers. - /// The external ids. public void AddParts(IEnumerable imageProviders, IEnumerable metadataServices, IEnumerable metadataProviders, IEnumerable metadataSavers, - IEnumerable imageSavers, IEnumerable externalIds) + IEnumerable externalIds) { ImageProviders = imageProviders.ToArray(); _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray(); _metadataProviders = metadataProviders.ToArray(); - _imageSavers = imageSavers.ToArray(); _externalIds = externalIds.OrderBy(i => i.Name).ToArray(); _savers = metadataSavers.Where(i => diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index ca4f1b956..2572a4f58 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -41,7 +41,7 @@ namespace MediaBrowser.Providers.TV private readonly ILibraryManager _libraryManager; private readonly IMemoryStreamProvider _memoryStreamProvider; - public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager) + public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager, IMemoryStreamProvider memoryStreamProvider) { _zipClient = zipClient; _httpClient = httpClient; @@ -49,6 +49,7 @@ namespace MediaBrowser.Providers.TV _config = config; _logger = logger; _libraryManager = libraryManager; + _memoryStreamProvider = memoryStreamProvider; Current = this; } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs index e84b66c5a..f7fe707da 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs @@ -377,10 +377,10 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications DisposeLibraryUpdateTimer(); } - if (items.Count == 1) - { - var item = items.First(); + items = items.Take(10).ToList(); + foreach (var item in items) + { var notification = new NotificationRequest { NotificationType = NotificationType.NewLibraryContent.ToString() @@ -388,17 +388,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications notification.Variables["Name"] = GetItemName(item); - await SendNotification(notification).ConfigureAwait(false); - } - else - { - var notification = new NotificationRequest - { - NotificationType = NotificationType.NewLibraryContentMultiple.ToString() - }; - - notification.Variables["ItemCount"] = items.Count.ToString(CultureInfo.InvariantCulture); - await SendNotification(notification).ConfigureAwait(false); } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 7dc6fbb25..2ebeb0d44 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -94,12 +94,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer // The Markdown feature causes slow startup times (5 mins+) on cold boots for some users // Custom format allows images - HostConfig.Instance.EnableFeatures = Feature.Csv | Feature.Html | Feature.Json | Feature.Jsv | Feature.Metadata | Feature.Xml | Feature.CustomFormat; + HostConfig.Instance.EnableFeatures = Feature.Html | Feature.Json | Feature.CustomFormat; container.Adapter = _containerAdapter; Plugins.RemoveAll(x => x is NativeTypesFeature); - Plugins.Add(new SwaggerFeature()); + //Plugins.Add(new SwaggerFeature()); Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization")); //Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { @@ -546,8 +546,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } } - - throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo); + else + { + httpRes.Close(); + } } /// diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 04085d3c7..10d6f7493 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -683,29 +683,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } - /// - /// Gets the error result. - /// - /// The status code. - /// The error message. - /// The response headers. - /// System.Object. - public void ThrowError(int statusCode, string errorMessage, IDictionary responseHeaders = null) - { - var error = new HttpError - { - Status = statusCode, - ErrorCode = errorMessage - }; - - if (responseHeaders != null) - { - AddResponseHeaders(error, responseHeaders); - } - - throw error; - } - public object GetAsyncStreamWriter(IAsyncStreamSource streamSource) { return new AsyncStreamWriter(streamSource); diff --git a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs b/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs deleted file mode 100644 index cac2f8e09..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs +++ /dev/null @@ -1,240 +0,0 @@ -using MediaBrowser.Common.Events; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Logging; -using System; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using WebSocketMessageType = MediaBrowser.Model.Net.WebSocketMessageType; -using WebSocketState = MediaBrowser.Model.Net.WebSocketState; - -namespace MediaBrowser.Server.Implementations.HttpServer -{ - /// - /// Class NativeWebSocket - /// - public class NativeWebSocket : IWebSocket - { - /// - /// The logger - /// - private readonly ILogger _logger; - - public event EventHandler Closed; - - /// - /// Gets or sets the web socket. - /// - /// The web socket. - private System.Net.WebSockets.WebSocket WebSocket { get; set; } - - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - /// - /// Initializes a new instance of the class. - /// - /// The socket. - /// The logger. - /// socket - public NativeWebSocket(WebSocket socket, ILogger logger) - { - if (socket == null) - { - throw new ArgumentNullException("socket"); - } - - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - - _logger = logger; - WebSocket = socket; - - Receive(); - } - - /// - /// Gets or sets the state. - /// - /// The state. - public WebSocketState State - { - get - { - WebSocketState commonState; - - if (!Enum.TryParse(WebSocket.State.ToString(), true, out commonState)) - { - _logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.State.ToString()); - } - - return commonState; - } - } - - /// - /// Receives this instance. - /// - private async void Receive() - { - while (true) - { - byte[] bytes; - - try - { - bytes = await ReceiveBytesAsync(_cancellationTokenSource.Token).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - break; - } - catch (WebSocketException ex) - { - _logger.ErrorException("Error receiving web socket message", ex); - - break; - } - - if (bytes == null) - { - // Connection closed - EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger); - break; - } - - if (OnReceiveBytes != null) - { - OnReceiveBytes(bytes); - } - } - } - - /// - /// Receives the async. - /// - /// The cancellation token. - /// Task{WebSocketMessageInfo}. - /// Connection closed - private async Task ReceiveBytesAsync(CancellationToken cancellationToken) - { - var bytes = new byte[4096]; - var buffer = new ArraySegment(bytes); - - var result = await WebSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false); - - if (result.CloseStatus.HasValue) - { - _logger.Info("Web socket connection closed by client. Reason: {0}", result.CloseStatus.Value); - return null; - } - - return buffer.Array; - } - - /// - /// Sends the async. - /// - /// The bytes. - /// The type. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken) - { - System.Net.WebSockets.WebSocketMessageType nativeType; - - if (!Enum.TryParse(type.ToString(), true, out nativeType)) - { - _logger.Warn("Unrecognized WebSocketMessageType: {0}", type.ToString()); - } - - var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token); - - return WebSocket.SendAsync(new ArraySegment(bytes), nativeType, true, linkedTokenSource.Token); - } - - public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) - { - var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token); - - return WebSocket.SendAsync(new ArraySegment(bytes), System.Net.WebSockets.WebSocketMessageType.Binary, true, linkedTokenSource.Token); - } - - public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) - { - var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token); - - var bytes = Encoding.UTF8.GetBytes(text); - - return WebSocket.SendAsync(new ArraySegment(bytes), System.Net.WebSockets.WebSocketMessageType.Text, true, linkedTokenSource.Token); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - _cancellationTokenSource.Cancel(); - - WebSocket.Dispose(); - } - } - - /// - /// Gets or sets the receive action. - /// - /// The receive action. - public Action OnReceiveBytes { get; set; } - - /// - /// Gets or sets the on receive. - /// - /// The on receive. - public Action OnReceive { get; set; } - - /// - /// The _supports native web socket - /// - private static bool? _supportsNativeWebSocket; - - /// - /// Gets a value indicating whether [supports web sockets]. - /// - /// true if [supports web sockets]; otherwise, false. - public static bool IsSupported - { - get - { - if (!_supportsNativeWebSocket.HasValue) - { - try - { - new ClientWebSocket(); - - _supportsNativeWebSocket = true; - } - catch (PlatformNotSupportedException) - { - _supportsNativeWebSocket = false; - } - } - - return _supportsNativeWebSocket.Value; - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs index 488c630fe..4b94095f5 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -191,15 +191,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } } - catch (IOException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error in range request writer", ex); - throw; - } finally { if (OnComplete != null) @@ -251,15 +242,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } } - catch (IOException ex) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error in range request writer", ex); - throw; - } finally { if (OnComplete != null) diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index e08be8bd1..a58645ec5 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -81,20 +81,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp public void Write(string text) { - try - { - var bOutput = System.Text.Encoding.UTF8.GetBytes(text); - response.ContentLength64 = bOutput.Length; + var bOutput = System.Text.Encoding.UTF8.GetBytes(text); + response.ContentLength64 = bOutput.Length; - var outputStream = response.OutputStream; - outputStream.Write(bOutput, 0, bOutput.Length); - Close(); - } - catch (Exception ex) - { - _logger.ErrorException("Could not WriteTextToResponse: " + ex.Message, ex); - throw; - } + var outputStream = response.OutputStream; + outputStream.Write(bOutput, 0, bOutput.Length); + Close(); } public void Close() diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index b2bddc70d..f7661f55b 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -2463,7 +2463,7 @@ namespace MediaBrowser.Server.Implementations.Library public IEnumerable