diff options
Diffstat (limited to 'Emby.Server.Implementations/EntryPoints')
13 files changed, 360 insertions, 9 deletions
diff --git a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs index 561f5ee12..c2cee00c8 100644 --- a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs @@ -112,6 +112,7 @@ namespace Emby.Server.Implementations.EntryPoints _appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged; DisposeTimer(); + GC.SuppressFinalize(this); } private void DisposeTimer() diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs new file mode 100644 index 000000000..9b434d606 --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -0,0 +1,336 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Threading; +using Mono.Nat; +using MediaBrowser.Model.Extensions; + +namespace Emby.Server.Implementations.EntryPoints +{ + public class ExternalPortForwarding : IServerEntryPoint + { + private readonly IServerApplicationHost _appHost; + private readonly ILogger _logger; + private readonly IHttpClient _httpClient; + private readonly IServerConfigurationManager _config; + private readonly IDeviceDiscovery _deviceDiscovery; + + private ITimer _timer; + private bool _isStarted; + private readonly ITimerFactory _timerFactory; + + public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, ITimerFactory timerFactory) + { + _logger = logmanager.GetLogger("PortMapper"); + _appHost = appHost; + _config = config; + _deviceDiscovery = deviceDiscovery; + _httpClient = httpClient; + _timerFactory = timerFactory; + } + + private string _lastConfigIdentifier; + private string GetConfigIdentifier() + { + var values = new List<string>(); + var config = _config.Configuration; + + values.Add(config.EnableUPnP.ToString()); + values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture)); + values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture)); + values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture)); + values.Add(config.EnableHttps.ToString()); + values.Add(_appHost.EnableHttps.ToString()); + + return string.Join("|", values.ToArray(values.Count)); + } + + void _config_ConfigurationUpdated(object sender, EventArgs e) + { + if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase)) + { + if (_isStarted) + { + DisposeNat(); + } + + Run(); + } + } + + public void Run() + { + NatUtility.Logger = _logger; + NatUtility.HttpClient = _httpClient; + + if (_config.Configuration.EnableUPnP) + { + Start(); + } + + _config.ConfigurationUpdated -= _config_ConfigurationUpdated; + _config.ConfigurationUpdated += _config_ConfigurationUpdated; + } + + private void Start() + { + _logger.Debug("Starting NAT discovery"); + NatUtility.EnabledProtocols = new List<NatProtocol> + { + NatProtocol.Pmp + }; + NatUtility.DeviceFound += NatUtility_DeviceFound; + + // Mono.Nat does never rise this event. The event is there however it is useless. + // You could remove it with no risk. + NatUtility.DeviceLost += NatUtility_DeviceLost; + + + NatUtility.StartDiscovery(); + + _timer = _timerFactory.Create(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); + + _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; + + _lastConfigIdentifier = GetConfigIdentifier(); + + _isStarted = true; + } + + private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e) + { + if (_disposed) + { + return; + } + + var info = e.Argument; + + string usn; + if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + + string nt; + if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + + // Filter device type + if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && + nt.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && + usn.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && + nt.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1) + { + return; + } + + var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn; + + if (info.Location == null) + { + return; + } + + lock (_usnsHandled) + { + if (_usnsHandled.Contains(identifier)) + { + return; + } + _usnsHandled.Add(identifier); + } + + _logger.Debug("Found NAT device: " + identifier); + + 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); + + Uri uri; + if (Uri.TryCreate(localAddressString, UriKind.Absolute, out uri)) + { + localAddressString = uri.Host; + + if (!IPAddress.TryParse(localAddressString, out localAddress)) + { + return; + } + } + } + catch (Exception ex) + { + return; + } + + if (_disposed) + { + return; + } + + _logger.Debug("Calling Nat.Handle on " + identifier); + NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp); + } + } + + private void ClearCreatedRules(object state) + { + lock (_createdRules) + { + _createdRules.Clear(); + } + lock (_usnsHandled) + { + _usnsHandled.Clear(); + } + } + + void NatUtility_DeviceFound(object sender, DeviceEventArgs e) + { + if (_disposed) + { + return; + } + + try + { + var device = e.Device; + _logger.Debug("NAT device found: {0}", device.LocalAddress.ToString()); + + CreateRules(device); + } + catch + { + // I think it could be a good idea to log the exception because + // you are using permanent portmapping here (never expire) and that means that next time + // CreatePortMap is invoked it can fails with a 718-ConflictInMappingEntry or not. That depends + // on the router's upnp implementation (specs says it should fail however some routers don't do it) + // It also can fail with others like 727-ExternalPortOnlySupportsWildcard, 728-NoPortMapsAvailable + // and those errors (upnp errors) could be useful for diagnosting. + + // Commenting out because users are reporting problems out of our control + //_logger.ErrorException("Error creating port forwarding rules", ex); + } + } + + private List<string> _createdRules = new List<string>(); + private List<string> _usnsHandled = new List<string>(); + private async void CreateRules(INatDevice device) + { + if (_disposed) + { + throw new ObjectDisposedException("PortMapper"); + } + + // On some systems the device discovered event seems to fire repeatedly + // This check will help ensure we're not trying to port map the same device over and over + + var address = device.LocalAddress.ToString(); + + lock (_createdRules) + { + if (!_createdRules.Contains(address)) + { + _createdRules.Add(address); + } + else + { + return; + } + } + + var success = await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false); + + if (success) + { + await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false); + } + } + + private async Task<bool> CreatePortMap(INatDevice device, int privatePort, int publicPort) + { + _logger.Debug("Creating port map on port {0}", privatePort); + + try + { + await device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort) + { + Description = _appHost.Name + + }).ConfigureAwait(false); + + return true; + } + catch (Exception ex) + { + _logger.Error("Error creating port map: " + ex.Message); + + return false; + } + } + + // As I said before, this method will be never invoked. You can remove it. + void NatUtility_DeviceLost(object sender, DeviceEventArgs e) + { + var device = e.Device; + _logger.Debug("NAT device lost: {0}", device.LocalAddress.ToString()); + } + + private bool _disposed = false; + public void Dispose() + { + _disposed = true; + DisposeNat(); + GC.SuppressFinalize(this); + } + + private void DisposeNat() + { + _logger.Debug("Stopping NAT discovery"); + + if (_timer != null) + { + _timer.Dispose(); + _timer = null; + } + + _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; + + try + { + // This is not a significant improvement + NatUtility.StopDiscovery(); + NatUtility.DeviceFound -= NatUtility_DeviceFound; + NatUtility.DeviceLost -= NatUtility_DeviceLost; + } + // Statements in try-block will no fail because StopDiscovery is a one-line + // method that was no chances to fail. + // public static void StopDiscovery () + // { + // searching.Reset(); + // } + // IMO you could remove the catch-block + catch (Exception ex) + { + _logger.ErrorException("Error stopping NAT Discovery", ex); + } + finally + { + _isStarted = false; + } + } + } +}
\ No newline at end of file diff --git a/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs b/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs index 8ae85e390..221580681 100644 --- a/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs +++ b/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs @@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.EntryPoints _timer.Dispose(); _timer = null; } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 69a205dda..796d8cf48 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -367,15 +367,15 @@ namespace Emby.Server.Implementations.EntryPoints return new LibraryUpdateInfo { - ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList(), + ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), - ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList(), + ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), - ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N")).Distinct().ToList(), + ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), - FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList(), + FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), - FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList() + FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray() }; } @@ -426,6 +426,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs b/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs index 0203b5192..21e075cf5 100644 --- a/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs +++ b/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs @@ -68,6 +68,7 @@ namespace Emby.Server.Implementations.EntryPoints _timer.Dispose(); _timer = null; } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index b674fc39b..f73b40b46 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -72,6 +72,7 @@ namespace Emby.Server.Implementations.EntryPoints _liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled; _liveTvManager.TimerCreated -= _liveTvManager_TimerCreated; _liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs index 77de849a1..13e14be36 100644 --- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs +++ b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Library; +using System; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using System.Threading; @@ -36,6 +37,7 @@ namespace Emby.Server.Implementations.EntryPoints /// </summary> public void Dispose() { + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 4d640bc95..514321e20 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -174,6 +174,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 424153f22..614c04fd2 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -1,4 +1,5 @@ -using Emby.Server.Implementations.Browser; +using System; +using Emby.Server.Implementations.Browser; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; @@ -54,6 +55,7 @@ namespace Emby.Server.Implementations.EntryPoints /// </summary> public void Dispose() { + GC.SuppressFinalize(this); } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/EntryPoints/SystemEvents.cs b/Emby.Server.Implementations/EntryPoints/SystemEvents.cs index 4ab6d32f3..aa63dae27 100644 --- a/Emby.Server.Implementations/EntryPoints/SystemEvents.cs +++ b/Emby.Server.Implementations/EntryPoints/SystemEvents.cs @@ -43,6 +43,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { _systemEvents.SystemShutdown -= _systemEvents_SystemShutdown; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index df5a7c985..d04df0d2b 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -65,6 +65,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs index 9fbe06673..fb9402986 100644 --- a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.EntryPoints { @@ -58,7 +59,7 @@ namespace Emby.Server.Implementations.EntryPoints session.ApplicationVersion }; - var key = string.Join("_", keys.ToArray()).GetMD5(); + var key = string.Join("_", keys.ToArray(keys.Count)).GetMD5(); _apps.GetOrAdd(key, guid => GetNewClientInfo(session)); } @@ -129,6 +130,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { _sessionManager.SessionStarted -= _sessionManager_SessionStarted; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 71e31d4d4..4a7182a43 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.EntryPoints dto.ItemId = i.Id.ToString("N"); return dto; }) - .ToList(); + .ToArray(); var info = new UserDataChangeInfo { @@ -160,6 +160,7 @@ namespace Emby.Server.Implementations.EntryPoints } _userDataManager.UserDataSaved -= _userDataManager_UserDataSaved; + GC.SuppressFinalize(this); } } } |
