浏览代码

Merge pull request #2525 from MediaBrowser/dev

Dev
Luke 8 年之前
父节点
当前提交
afb4a08bfe
共有 35 个文件被更改,包括 328 次插入387 次删除
  1. 1 1
      Emby.Common.Implementations/Net/NetAcceptSocket.cs
  2. 24 3
      Emby.Common.Implementations/Net/SocketFactory.cs
  3. 10 8
      Emby.Common.Implementations/Net/UdpSocket.cs
  4. 10 10
      Emby.Drawing/ImageProcessor.cs
  5. 28 4
      Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
  6. 0 1
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  7. 5 2
      Emby.Server.Implementations/HttpServer/FileWriter.cs
  8. 3 2
      Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
  9. 2 2
      Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
  10. 17 10
      Emby.Server.Implementations/Library/LibraryManager.cs
  11. 54 0
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  12. 14 46
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  13. 1 1
      Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
  14. 1 1
      Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
  15. 0 158
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
  16. 71 7
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  17. 5 0
      Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
  18. 20 33
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  19. 3 73
      MediaBrowser.Api/StartupWizardService.cs
  20. 6 1
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  21. 6 1
      MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
  22. 6 1
      MediaBrowser.Controller/Entities/GameGenre.cs
  23. 6 1
      MediaBrowser.Controller/Entities/Genre.cs
  24. 6 1
      MediaBrowser.Controller/Entities/Person.cs
  25. 6 1
      MediaBrowser.Controller/Entities/Studio.cs
  26. 6 1
      MediaBrowser.Controller/Entities/Year.cs
  27. 2 8
      MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
  28. 2 0
      MediaBrowser.Controller/LiveTv/ITunerHost.cs
  29. 1 0
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  30. 1 2
      MediaBrowser.Model/LiveTv/LiveTvOptions.cs
  31. 2 0
      MediaBrowser.Model/Net/ISocketFactory.cs
  32. 2 1
      MediaBrowser.Model/Services/IRequest.cs
  33. 1 1
      SharedVersion.cs
  34. 2 2
      SocketHttpListener.Portable/Net/HttpListenerResponse.cs
  35. 4 4
      SocketHttpListener.Portable/Net/ResponseStream.cs

+ 1 - 1
Emby.Common.Implementations/Net/NetAcceptSocket.cs

@@ -100,7 +100,7 @@ namespace Emby.Common.Implementations.Net
 #if NET46
 #if NET46
         public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken)
         public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken)
         {
         {
-            var options = TransmitFileOptions.Disconnect | TransmitFileOptions.ReuseSocket | TransmitFileOptions.UseKernelApc;
+            var options = TransmitFileOptions.UseKernelApc;
 
 
             var completionSource = new TaskCompletionSource<bool>();
             var completionSource = new TaskCompletionSource<bool>();
 
 

+ 24 - 3
Emby.Common.Implementations/Net/SocketFactory.cs

@@ -97,10 +97,31 @@ namespace Emby.Common.Implementations.Net
             }
             }
         }
         }
 
 
+        public ISocket CreateUdpBroadcastSocket(int localPort)
+        {
+            if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
+
+            var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
+            try
+            {
+                retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+                retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
+
+                return new UdpSocket(retVal, localPort, IPAddress.Any);
+            }
+            catch
+            {
+                if (retVal != null)
+                    retVal.Dispose();
+
+                throw;
+            }
+        }
+        
         /// <summary>
         /// <summary>
-        /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
-        /// </summary>
-        /// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns>
+              /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
+              /// </summary>
+              /// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns>
         public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort)
         public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort)
         {
         {
             if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
             if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");

+ 10 - 8
Emby.Common.Implementations/Net/UdpSocket.cs

@@ -60,6 +60,8 @@ namespace Emby.Common.Implementations.Net
             var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
             var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
             state.TaskCompletionSource = tcs;
             state.TaskCompletionSource = tcs;
 
 
+            cancellationToken.Register(() => tcs.TrySetCanceled());
+
 #if NETSTANDARD1_6
 #if NETSTANDARD1_6
             _Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer), SocketFlags.None, state.RemoteEndPoint)
             _Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer), SocketFlags.None, state.RemoteEndPoint)
                 .ContinueWith((task, asyncState) =>
                 .ContinueWith((task, asyncState) =>
@@ -160,7 +162,7 @@ namespace Emby.Common.Implementations.Net
                 var bytesRead = receiveData();
                 var bytesRead = receiveData();
 
 
                 var ipEndPoint = state.RemoteEndPoint as IPEndPoint;
                 var ipEndPoint = state.RemoteEndPoint as IPEndPoint;
-                state.TaskCompletionSource.SetResult(
+                state.TaskCompletionSource.TrySetResult(
                     new SocketReceiveResult
                     new SocketReceiveResult
                     {
                     {
                         Buffer = state.Buffer,
                         Buffer = state.Buffer,
@@ -172,18 +174,18 @@ namespace Emby.Common.Implementations.Net
             }
             }
             catch (ObjectDisposedException)
             catch (ObjectDisposedException)
             {
             {
-                state.TaskCompletionSource.SetCanceled();
+                state.TaskCompletionSource.TrySetCanceled();
             }
             }
             catch (SocketException se)
             catch (SocketException se)
             {
             {
                 if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown)
                 if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown)
-                    state.TaskCompletionSource.SetException(se);
+                    state.TaskCompletionSource.TrySetException(se);
                 else
                 else
-                    state.TaskCompletionSource.SetCanceled();
+                    state.TaskCompletionSource.TrySetCanceled();
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
-                state.TaskCompletionSource.SetException(ex);
+                state.TaskCompletionSource.TrySetException(ex);
             }
             }
         }
         }
 
 
@@ -206,7 +208,7 @@ namespace Emby.Common.Implementations.Net
                 var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.RemoteEndPoint);
                 var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.RemoteEndPoint);
 
 
                 var ipEndPoint = state.RemoteEndPoint as IPEndPoint;
                 var ipEndPoint = state.RemoteEndPoint as IPEndPoint;
-                state.TaskCompletionSource.SetResult(
+                state.TaskCompletionSource.TrySetResult(
                     new SocketReceiveResult
                     new SocketReceiveResult
                     {
                     {
                         Buffer = state.Buffer,
                         Buffer = state.Buffer,
@@ -218,11 +220,11 @@ namespace Emby.Common.Implementations.Net
             }
             }
             catch (ObjectDisposedException)
             catch (ObjectDisposedException)
             {
             {
-                state.TaskCompletionSource.SetCanceled();
+                state.TaskCompletionSource.TrySetCanceled();
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
-                state.TaskCompletionSource.SetException(ex);
+                state.TaskCompletionSource.TrySetException(ex);
             }
             }
 #endif
 #endif
         }
         }

+ 10 - 10
Emby.Drawing/ImageProcessor.cs

@@ -238,7 +238,7 @@ namespace Emby.Drawing
             var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
             var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
             var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer);
             var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer);
 
 
-            var imageProcessingLockTaken = false;
+            //var imageProcessingLockTaken = false;
 
 
             try
             try
             {
             {
@@ -253,9 +253,9 @@ namespace Emby.Drawing
                     var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath));
                     var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath));
                     _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
                     _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
 
 
-                    await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
+                    //await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
 
 
-                    imageProcessingLockTaken = true;
+                    //imageProcessingLockTaken = true;
 
 
                     _imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
                     _imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
                     CopyFile(tmpPath, cacheFilePath);
                     CopyFile(tmpPath, cacheFilePath);
@@ -273,13 +273,13 @@ namespace Emby.Drawing
                 // Just spit out the original file if all the options are default
                 // Just spit out the original file if all the options are default
                 return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
                 return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
             }
             }
-            finally
-            {
-                if (imageProcessingLockTaken)
-                {
-                    _imageProcessingSemaphore.Release();
-                }
-            }
+            //finally
+            //{
+            //    if (imageProcessingLockTaken)
+            //    {
+            //        _imageProcessingSemaphore.Release();
+            //    }
+            //}
         }
         }
 
 
         private void CopyFile(string src, string destination)
         private void CopyFile(string src, string destination)

+ 28 - 4
Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs

@@ -9,6 +9,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
@@ -22,13 +23,15 @@ namespace Emby.Server.Implementations.Data
         private readonly IItemRepository _itemRepo;
         private readonly IItemRepository _itemRepo;
         private readonly ILogger _logger;
         private readonly ILogger _logger;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
+        private readonly IApplicationPaths _appPaths;
 
 
-        public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem)
+        public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
             _itemRepo = itemRepo;
             _itemRepo = itemRepo;
             _logger = logger;
             _logger = logger;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
+            _appPaths = appPaths;
         }
         }
 
 
         public string Name
         public string Name
@@ -150,13 +153,27 @@ namespace Emby.Server.Implementations.Data
 
 
                 try
                 try
                 {
                 {
-                    if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path))
+                    var isPathInLibrary = false;
+
+                    if (allLibraryPaths.Any(i => path.StartsWith(i, StringComparison.Ordinal)) || 
+                        allLibraryPaths.Contains(path, StringComparer.Ordinal) || 
+                        path.StartsWith(_appPaths.ProgramDataPath, StringComparison.Ordinal))
                     {
                     {
-                        continue;
+                        isPathInLibrary = true;
+
+                        if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path))
+                        {
+                            continue;
+                        }
                     }
                     }
 
 
                     var libraryItem = _libraryManager.GetItemById(item.Item1);
                     var libraryItem = _libraryManager.GetItemById(item.Item1);
 
 
+                    if (libraryItem == null)
+                    {
+                        continue;
+                    }
+
                     if (libraryItem.IsTopParent)
                     if (libraryItem.IsTopParent)
                     {
                     {
                         continue;
                         continue;
@@ -180,7 +197,14 @@ namespace Emby.Server.Implementations.Data
                         continue;
                         continue;
                     }
                     }
 
 
-                    _logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
+                    if (isPathInLibrary)
+                    {
+                        _logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
+                    }
+                    else
+                    {
+                        _logger.Info("Deleting item from database {0} because path is no longer in the server library. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
+                    }
 
 
                     await libraryItem.OnFileDeleted().ConfigureAwait(false);
                     await libraryItem.OnFileDeleted().ConfigureAwait(false);
                 }
                 }

+ 0 - 1
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -171,7 +171,6 @@
     <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
     <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
     <Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
     <Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunManager.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunManager.cs" />
-    <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHttpStream.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHttpStream.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunUdpStream.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunUdpStream.cs" />

+ 5 - 2
Emby.Server.Implementations/HttpServer/FileWriter.cs

@@ -27,6 +27,8 @@ namespace Emby.Server.Implementations.HttpServer
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
         public List<Cookie> Cookies { get; private set; }
         public List<Cookie> Cookies { get; private set; }
 
 
+        public FileShareMode FileShare { get; set; }
+
         /// <summary>
         /// <summary>
         /// The _options
         /// The _options
         /// </summary>
         /// </summary>
@@ -69,6 +71,7 @@ namespace Emby.Server.Implementations.HttpServer
                 SetRangeValues();
                 SetRangeValues();
             }
             }
 
 
+            FileShare = FileShareMode.Read;
             Cookies = new List<Cookie>();
             Cookies = new List<Cookie>();
         }
         }
 
 
@@ -153,11 +156,11 @@ namespace Emby.Server.Implementations.HttpServer
                 if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1))
                 if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1))
                 {
                 {
                     Logger.Info("Transmit file {0}", Path);
                     Logger.Info("Transmit file {0}", Path);
-                    await response.TransmitFile(Path, 0, 0, cancellationToken).ConfigureAwait(false);
+                    await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false);
                     return;
                     return;
                 }
                 }
 
 
-                await response.TransmitFile(Path, RangeStart, RangeEnd, cancellationToken).ConfigureAwait(false);
+                await response.TransmitFile(Path, RangeStart, RangeEnd, FileShare, cancellationToken).ConfigureAwait(false);
             }
             }
             finally
             finally
             {
             {

+ 3 - 2
Emby.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -556,12 +556,13 @@ namespace Emby.Server.Implementations.HttpServer
             {
             {
                 var rangeHeader = requestContext.Headers.Get("Range");
                 var rangeHeader = requestContext.Headers.Get("Range");
 
 
-                if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path) && options.FileShare == FileShareMode.Read)
+                if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path))
                 {
                 {
                     return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
                     return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
                     {
                     {
                         OnComplete = options.OnComplete,
                         OnComplete = options.OnComplete,
-                        OnError = options.OnError
+                        OnError = options.OnError,
+                        FileShare = options.FileShare
                     };
                     };
                 }
                 }
 
 

+ 2 - 2
Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs

@@ -193,9 +193,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
         {
         {
         }
         }
 
 
-        public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken)
+        public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
         {
         {
-            return _response.TransmitFile(path, offset, count, cancellationToken);
+            return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 17 - 10
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -513,6 +513,11 @@ namespace Emby.Server.Implementations.Library
         }
         }
 
 
         public Guid GetNewItemId(string key, Type type)
         public Guid GetNewItemId(string key, Type type)
+        {
+            return GetNewItemIdInternal(key, type, false);
+        }
+
+        private Guid GetNewItemIdInternal(string key, Type type, bool forceCaseInsensitive)
         {
         {
             if (string.IsNullOrWhiteSpace(key))
             if (string.IsNullOrWhiteSpace(key))
             {
             {
@@ -531,7 +536,7 @@ namespace Emby.Server.Implementations.Library
                     .Replace("/", "\\");
                     .Replace("/", "\\");
             }
             }
 
 
-            if (!ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
+            if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
             {
             {
                 key = key.ToLower();
                 key = key.ToLower();
             }
             }
@@ -865,7 +870,7 @@ namespace Emby.Server.Implementations.Library
         /// <returns>Task{Person}.</returns>
         /// <returns>Task{Person}.</returns>
         public Person GetPerson(string name)
         public Person GetPerson(string name)
         {
         {
-            return CreateItemByName<Person>(Person.GetPath(name), name);
+            return CreateItemByName<Person>(Person.GetPath, name);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -875,7 +880,7 @@ namespace Emby.Server.Implementations.Library
         /// <returns>Task{Studio}.</returns>
         /// <returns>Task{Studio}.</returns>
         public Studio GetStudio(string name)
         public Studio GetStudio(string name)
         {
         {
-            return CreateItemByName<Studio>(Studio.GetPath(name), name);
+            return CreateItemByName<Studio>(Studio.GetPath, name);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -885,7 +890,7 @@ namespace Emby.Server.Implementations.Library
         /// <returns>Task{Genre}.</returns>
         /// <returns>Task{Genre}.</returns>
         public Genre GetGenre(string name)
         public Genre GetGenre(string name)
         {
         {
-            return CreateItemByName<Genre>(Genre.GetPath(name), name);
+            return CreateItemByName<Genre>(Genre.GetPath, name);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -895,7 +900,7 @@ namespace Emby.Server.Implementations.Library
         /// <returns>Task{MusicGenre}.</returns>
         /// <returns>Task{MusicGenre}.</returns>
         public MusicGenre GetMusicGenre(string name)
         public MusicGenre GetMusicGenre(string name)
         {
         {
-            return CreateItemByName<MusicGenre>(MusicGenre.GetPath(name), name);
+            return CreateItemByName<MusicGenre>(MusicGenre.GetPath, name);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -905,7 +910,7 @@ namespace Emby.Server.Implementations.Library
         /// <returns>Task{GameGenre}.</returns>
         /// <returns>Task{GameGenre}.</returns>
         public GameGenre GetGameGenre(string name)
         public GameGenre GetGameGenre(string name)
         {
         {
-            return CreateItemByName<GameGenre>(GameGenre.GetPath(name), name);
+            return CreateItemByName<GameGenre>(GameGenre.GetPath, name);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -923,7 +928,7 @@ namespace Emby.Server.Implementations.Library
 
 
             var name = value.ToString(CultureInfo.InvariantCulture);
             var name = value.ToString(CultureInfo.InvariantCulture);
 
 
-            return CreateItemByName<Year>(Year.GetPath(name), name);
+            return CreateItemByName<Year>(Year.GetPath, name);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -933,10 +938,10 @@ namespace Emby.Server.Implementations.Library
         /// <returns>Task{Genre}.</returns>
         /// <returns>Task{Genre}.</returns>
         public MusicArtist GetArtist(string name)
         public MusicArtist GetArtist(string name)
         {
         {
-            return CreateItemByName<MusicArtist>(MusicArtist.GetPath(name), name);
+            return CreateItemByName<MusicArtist>(MusicArtist.GetPath, name);
         }
         }
 
 
-        private T CreateItemByName<T>(string path, string name)
+        private T CreateItemByName<T>(Func<string,string> getPathFn, string name)
             where T : BaseItem, new()
             where T : BaseItem, new()
         {
         {
             if (typeof(T) == typeof(MusicArtist))
             if (typeof(T) == typeof(MusicArtist))
@@ -957,7 +962,9 @@ namespace Emby.Server.Implementations.Library
                 }
                 }
             }
             }
 
 
-            var id = GetNewItemId(path, typeof(T));
+            var path = getPathFn(name);
+            var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds;
+            var id = GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
 
 
             var item = GetItemById(id) as T;
             var item = GetItemById(id) as T;
 
 

+ 54 - 0
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -2542,6 +2542,60 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             public ProgramInfo Program { get; set; }
             public ProgramInfo Program { get; set; }
             public CancellationTokenSource CancellationTokenSource { get; set; }
             public CancellationTokenSource CancellationTokenSource { get; set; }
         }
         }
+
+        public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken)
+        {
+            foreach (var host in _liveTvManager.TunerHosts)
+            {
+                await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false);
+            }
+        }
+
+        private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken)
+        {
+            var discoveredDevices = await DiscoverDevices(host, 3000, cancellationToken).ConfigureAwait(false);
+
+            var configuredDevices = GetConfiguration().TunerHosts
+                .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase))
+                .ToList();
+
+            foreach (var device in discoveredDevices)
+            {
+                var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase));
+
+                if (configuredDevice != null)
+                {
+                    if (!string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase))
+                    {
+                        _logger.Info("Tuner url has changed from {0} to {1}", configuredDevice.Url, device.Url);
+
+                        configuredDevice.Url = device.Url;
+                        await _liveTvManager.SaveTunerHost(configuredDevice).ConfigureAwait(false);
+                    }
+                }
+            }
+        }
+
+        private async Task<List<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDuationMs, CancellationToken cancellationToken)
+        {
+            try
+            {
+                var discoveredDevices = await host.DiscoverDevices(discoveryDuationMs, cancellationToken).ConfigureAwait(false);
+
+                foreach (var device in discoveredDevices)
+                {
+                    _logger.Info("Discovered tuner device {0} at {1}", host.Name, device.Url);
+                }
+
+                return discoveredDevices;
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error discovering tuner devices", ex);
+
+                return new List<TunerHostInfo>();
+            }
+        }
     }
     }
     public static class ConfigurationExtension
     public static class ConfigurationExtension
     {
     {

+ 14 - 46
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -150,6 +150,16 @@ namespace Emby.Server.Implementations.LiveTv
             get { return _listingProviders; }
             get { return _listingProviders; }
         }
         }
 
 
+        public List<NameIdPair> GetTunerHostTypes()
+        {
+            return _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair
+            {
+                Name = i.Name,
+                Id = i.Type
+
+            }).ToList();
+        }
+
         void service_DataSourceChanged(object sender, EventArgs e)
         void service_DataSourceChanged(object sender, EventArgs e)
         {
         {
             if (!_isDisposed)
             if (!_isDisposed)
@@ -1180,6 +1190,8 @@ namespace Emby.Server.Implementations.LiveTv
         {
         {
             EmbyTV.EmbyTV.Current.CreateRecordingFolders();
             EmbyTV.EmbyTV.Current.CreateRecordingFolders();
 
 
+            await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
+
             var numComplete = 0;
             var numComplete = 0;
             double progressPerService = _services.Count == 0
             double progressPerService = _services.Count == 0
                 ? 0
                 ? 0
@@ -2748,7 +2760,7 @@ namespace Emby.Server.Implementations.LiveTv
 
 
         private bool IsLiveTvEnabled(User user)
         private bool IsLiveTvEnabled(User user)
         {
         {
-            return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count(i => i.IsEnabled) > 0);
+            return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count > 0);
         }
         }
 
 
         public IEnumerable<User> GetEnabledUsers()
         public IEnumerable<User> GetEnabledUsers()
@@ -2986,7 +2998,7 @@ namespace Emby.Server.Implementations.LiveTv
             if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase))
             if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase))
             {
             {
                 var config = GetConfiguration();
                 var config = GetConfiguration();
-                if (config.TunerHosts.Count(i => i.IsEnabled) > 0 &&
+                if (config.TunerHosts.Count > 0 &&
                     config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0)
                     config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0)
                 {
                 {
                     return Task.FromResult(new MBRegistrationRecord
                     return Task.FromResult(new MBRegistrationRecord
@@ -3000,50 +3012,6 @@ namespace Emby.Server.Implementations.LiveTv
             return _security.GetRegistrationStatus(feature);
             return _security.GetRegistrationStatus(feature);
         }
         }
 
 
-        public List<NameValuePair> GetSatIniMappings()
-        {
-            return new List<NameValuePair>();
-            //var names = GetType().Assembly.GetManifestResourceNames().Where(i => i.IndexOf("SatIp.ini", StringComparison.OrdinalIgnoreCase) != -1).ToList();
-
-            //return names.Select(GetSatIniMappings).Where(i => i != null).DistinctBy(i => i.Value.Split('|')[0]).ToList();
-        }
-
-        public NameValuePair GetSatIniMappings(string resource)
-        {
-            return new NameValuePair();
-            //using (var stream = GetType().Assembly.GetManifestResourceStream(resource))
-            //{
-            //    using (var reader = new StreamReader(stream))
-            //    {
-            //        var parser = new StreamIniDataParser();
-            //        IniData data = parser.ReadData(reader);
-
-            //        var satType1 = data["SATTYPE"]["1"];
-            //        var satType2 = data["SATTYPE"]["2"];
-
-            //        if (string.IsNullOrWhiteSpace(satType2))
-            //        {
-            //            return null;
-            //        }
-
-            //        var srch = "SatIp.ini.";
-            //        var filename = Path.GetFileName(resource);
-
-            //        return new NameValuePair
-            //        {
-            //            Name = satType1 + " " + satType2,
-            //            Value = satType2 + "|" + filename.Substring(filename.IndexOf(srch) + srch.Length)
-            //        };
-            //    }
-            //}
-        }
-
-        public Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken)
-        {
-            return Task.FromResult(new List<ChannelInfo>());
-            //return new TunerHosts.SatIp.ChannelScan(_logger).Scan(info, cancellationToken);
-        }
-
         public Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken)
         public Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken)
         {
         {
             var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
             var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));

+ 1 - 1
Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs

@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.LiveTv
 
 
         public bool IsHidden
         public bool IsHidden
         {
         {
-            get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count(i => i.IsEnabled) == 0; }
+            get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count == 0; }
         }
         }
 
 
         public bool IsEnabled
         public bool IsEnabled

+ 1 - 1
Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs

@@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         protected virtual List<TunerHostInfo> GetTunerHosts()
         protected virtual List<TunerHostInfo> GetTunerHosts()
         {
         {
             return GetConfiguration().TunerHosts
             return GetConfiguration().TunerHosts
-                .Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
+                .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
                 .ToList();
                 .ToList();
         }
         }
 
 

+ 0 - 158
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs

@@ -1,158 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.LiveTv;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Linq;
-using System.Threading;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Serialization;
-
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
-{
-    public class HdHomerunDiscovery : IServerEntryPoint
-    {
-        private readonly IDeviceDiscovery _deviceDiscovery;
-        private readonly IServerConfigurationManager _config;
-        private readonly ILogger _logger;
-        private readonly ILiveTvManager _liveTvManager;
-        private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
-        private readonly IHttpClient _httpClient;
-        private readonly IJsonSerializer _json;
-
-        public HdHomerunDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient, IJsonSerializer json)
-        {
-            _deviceDiscovery = deviceDiscovery;
-            _config = config;
-            _logger = logger;
-            _liveTvManager = liveTvManager;
-            _httpClient = httpClient;
-            _json = json;
-        }
-
-        public void Run()
-        {
-            _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
-        }
-
-        void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
-        {
-            string server = null;
-            var info = e.Argument;
-
-            if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
-            {
-                string location;
-                if (info.Headers.TryGetValue("Location", out location))
-                {
-                    //_logger.Debug("HdHomerun found at {0}", location);
-
-                    // Just get the beginning of the url
-                    Uri uri;
-                    if (Uri.TryCreate(location, UriKind.Absolute, out uri))
-                    {
-                        var apiUrl = location.Replace(uri.LocalPath, String.Empty, StringComparison.OrdinalIgnoreCase)
-                                .TrimEnd('/');
-
-                        //_logger.Debug("HdHomerun api url: {0}", apiUrl);
-                        AddDevice(apiUrl);
-                    }
-                }
-            }
-        }
-
-        private async void AddDevice(string url)
-        {
-            await _semaphore.WaitAsync().ConfigureAwait(false);
-
-            try
-            {
-                var options = GetConfiguration();
-
-                if (options.TunerHosts.Any(i =>
-                            string.Equals(i.Type, HdHomerunHost.DeviceType, StringComparison.OrdinalIgnoreCase) &&
-                            UriEquals(i.Url, url)))
-                {
-                    return;
-                }
-
-                // Strip off the port
-                url = new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped).TrimEnd('/');
-
-                // Test it by pulling down the lineup
-                using (var stream = await _httpClient.Get(new HttpRequestOptions
-                {
-                    Url = string.Format("{0}/discover.json", url),
-                    CancellationToken = CancellationToken.None,
-                    BufferContent = false
-                }))
-                {
-                    var response = _json.DeserializeFromStream<HdHomerunHost.DiscoverResponse>(stream);
-
-                    var existing = GetConfiguration().TunerHosts
-                        .FirstOrDefault(i => string.Equals(i.Type, HdHomerunHost.DeviceType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.DeviceId, response.DeviceID, StringComparison.OrdinalIgnoreCase));
-
-                    if (existing == null)
-                    {
-                        await _liveTvManager.SaveTunerHost(new TunerHostInfo
-                        {
-                            Type = HdHomerunHost.DeviceType,
-                            Url = url,
-                            DeviceId = response.DeviceID
-
-                        }).ConfigureAwait(false);
-                    }
-                    else
-                    {
-                        if (!string.Equals(existing.Url, url, StringComparison.OrdinalIgnoreCase))
-                        {
-                            existing.Url = url;
-                            await _liveTvManager.SaveTunerHost(existing).ConfigureAwait(false);
-                        }
-                    }
-                }
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error saving device", ex);
-            }
-            finally
-            {
-                _semaphore.Release();
-            }
-        }
-
-        private bool UriEquals(string savedUri, string location)
-        {
-            return string.Equals(NormalizeUrl(location), NormalizeUrl(savedUri), StringComparison.OrdinalIgnoreCase);
-        }
-
-        private string NormalizeUrl(string url)
-        {
-            if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
-            {
-                url = "http://" + url;
-            }
-
-            url = url.TrimEnd('/');
-
-            // Strip off the port
-            return new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped);
-        }
-
-        private LiveTvOptions GetConfiguration()
-        {
-            return _config.GetConfiguration<LiveTvOptions>("livetv");
-        }
-
-        public void Dispose()
-        {
-        }
-    }
-}

+ 71 - 7
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

@@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             var list = new List<LiveTvTunerInfo>();
             var list = new List<LiveTvTunerInfo>();
 
 
             foreach (var host in GetConfiguration().TunerHosts
             foreach (var host in GetConfiguration().TunerHosts
-                .Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)))
+                .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)))
             {
             {
                 try
                 try
                 {
                 {
@@ -587,7 +587,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
             var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
 
 
             if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
             if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
-            {              
+            {
                 var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo);
                 var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo);
                 var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
                 var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
 
 
@@ -605,11 +605,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
         public async Task Validate(TunerHostInfo info)
         public async Task Validate(TunerHostInfo info)
         {
         {
-            if (!info.IsEnabled)
-            {
-                return;
-            }
-
             lock (_modelCache)
             lock (_modelCache)
             {
             {
                 _modelCache.Clear();
                 _modelCache.Clear();
@@ -652,5 +647,74 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             public string LineupURL { get; set; }
             public string LineupURL { get; set; }
             public int TunerCount { get; set; }
             public int TunerCount { get; set; }
         }
         }
+
+        public async Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
+        {
+            cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token;
+            var list = new List<TunerHostInfo>();
+
+            // Create udp broadcast discovery message
+            byte[] discBytes = { 0, 2, 0, 12, 1, 4, 255, 255, 255, 255, 2, 4, 255, 255, 255, 255, 115, 204, 125, 143 };
+            using (var udpClient = _socketFactory.CreateUdpBroadcastSocket(0))
+            {
+                // Need a way to set the Receive timeout on the socket otherwise this might never timeout?
+                try
+                {
+                    await udpClient.SendAsync(discBytes, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken);
+                    while (!cancellationToken.IsCancellationRequested)
+                    {
+                        var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+                        var deviceIp = response.RemoteEndPoint.IpAddress.Address;
+
+                        // check to make sure we have enough bytes received to be a valid message and make sure the 2nd byte is the discover reply byte
+                        if (response.ReceivedBytes > 13 && response.Buffer[1] == 3)
+                        {
+                            var deviceAddress = "http://" + deviceIp;
+
+                            var info = await TryGetTunerHostInfo(deviceAddress, cancellationToken).ConfigureAwait(false);
+
+                            if (info != null)
+                            {
+                                list.Add(info);
+                            }
+                        }
+                    }
+
+                }
+                catch (OperationCanceledException)
+                {
+                }
+                catch
+                {
+                    // Socket timeout indicates all messages have been received.
+                }
+            }
+
+            return list;
+        }
+
+        private async Task<TunerHostInfo> TryGetTunerHostInfo(string url, CancellationToken cancellationToken)
+        {
+            var hostInfo = new TunerHostInfo
+            {
+                Type = Type,
+                Url = url
+            };
+
+            try
+            {
+                var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
+
+                hostInfo.DeviceId = modelInfo.DeviceID;
+
+                return hostInfo;
+            }
+            catch
+            {
+                // logged at lower levels
+            }
+
+            return null;
+        }
     }
     }
 }
 }

+ 5 - 0
Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs

@@ -176,5 +176,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         {
         {
             return Task.FromResult(true);
             return Task.FromResult(true);
         }
         }
+
+        public Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
+        {
+            return Task.FromResult(new List<TunerHostInfo>());
+        }
     }
     }
 }
 }

+ 20 - 33
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -582,13 +582,13 @@ namespace MediaBrowser.Api.LiveTv
     }
     }
 
 
     [Route("/LiveTv/ListingProviders/Default", "GET")]
     [Route("/LiveTv/ListingProviders/Default", "GET")]
-    [Authenticated(AllowBeforeStartupWizard = true)]
+    [Authenticated]
     public class GetDefaultListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
     public class GetDefaultListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
     {
     {
     }
     }
 
 
     [Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")]
     [Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")]
-    [Authenticated(AllowBeforeStartupWizard = true)]
+    [Authenticated]
     public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
     public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
     {
     {
         public bool ValidateLogin { get; set; }
         public bool ValidateLogin { get; set; }
@@ -596,7 +596,7 @@ namespace MediaBrowser.Api.LiveTv
     }
     }
 
 
     [Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")]
     [Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")]
-    [Authenticated(AllowBeforeStartupWizard = true)]
+    [Authenticated]
     public class DeleteListingProvider : IReturnVoid
     public class DeleteListingProvider : IReturnVoid
     {
     {
         [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")]
         [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")]
@@ -604,7 +604,7 @@ namespace MediaBrowser.Api.LiveTv
     }
     }
 
 
     [Route("/LiveTv/ListingProviders/Lineups", "GET", Summary = "Gets available lineups")]
     [Route("/LiveTv/ListingProviders/Lineups", "GET", Summary = "Gets available lineups")]
-    [Authenticated(AllowBeforeStartupWizard = true)]
+    [Authenticated]
     public class GetLineups : IReturn<List<NameIdPair>>
     public class GetLineups : IReturn<List<NameIdPair>>
     {
     {
         [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -621,13 +621,13 @@ namespace MediaBrowser.Api.LiveTv
     }
     }
 
 
     [Route("/LiveTv/ListingProviders/SchedulesDirect/Countries", "GET", Summary = "Gets available lineups")]
     [Route("/LiveTv/ListingProviders/SchedulesDirect/Countries", "GET", Summary = "Gets available lineups")]
-    [Authenticated(AllowBeforeStartupWizard = true)]
+    [Authenticated]
     public class GetSchedulesDirectCountries
     public class GetSchedulesDirectCountries
     {
     {
     }
     }
 
 
     [Route("/LiveTv/ChannelMappingOptions")]
     [Route("/LiveTv/ChannelMappingOptions")]
-    [Authenticated(AllowBeforeStartupWizard = true)]
+    [Authenticated]
     public class GetChannelMappingOptions
     public class GetChannelMappingOptions
     {
     {
         [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -635,7 +635,7 @@ namespace MediaBrowser.Api.LiveTv
     }
     }
 
 
     [Route("/LiveTv/ChannelMappings")]
     [Route("/LiveTv/ChannelMappings")]
-    [Authenticated(AllowBeforeStartupWizard = true)]
+    [Authenticated]
     public class SetChannelMapping
     public class SetChannelMapping
     {
     {
         [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -660,20 +660,6 @@ namespace MediaBrowser.Api.LiveTv
         public string Feature { get; set; }
         public string Feature { get; set; }
     }
     }
 
 
-    [Route("/LiveTv/TunerHosts/Satip/IniMappings", "GET", Summary = "Gets available mappings")]
-    [Authenticated(AllowBeforeStartupWizard = true)]
-    public class GetSatIniMappings : IReturn<List<NameValuePair>>
-    {
-
-    }
-
-    [Route("/LiveTv/TunerHosts/Satip/ChannelScan", "GET", Summary = "Scans for available channels")]
-    [Authenticated(AllowBeforeStartupWizard = true)]
-    public class GetSatChannnelScanResult : TunerHostInfo
-    {
-
-    }
-
     [Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")]
     [Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")]
     public class GetLiveStreamFile
     public class GetLiveStreamFile
     {
     {
@@ -687,6 +673,13 @@ namespace MediaBrowser.Api.LiveTv
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
+    [Route("/LiveTv/TunerHosts/Types", "GET")]
+    [Authenticated]
+    public class GetTunerHostTypes : IReturn<List<NameIdPair>>
+    {
+        
+    }
+
     public class LiveTvService : BaseApiService
     public class LiveTvService : BaseApiService
     {
     {
         private readonly ILiveTvManager _liveTvManager;
         private readonly ILiveTvManager _liveTvManager;
@@ -712,6 +705,12 @@ namespace MediaBrowser.Api.LiveTv
             _sessionContext = sessionContext;
             _sessionContext = sessionContext;
         }
         }
 
 
+        public object Get(GetTunerHostTypes request)
+        {
+            var list = _liveTvManager.GetTunerHostTypes();
+            return ToOptimizedResult(list);
+        }
+
         public object Get(GetLiveRecordingFile request)
         public object Get(GetLiveRecordingFile request)
         {
         {
             var path = _liveTvManager.GetEmbyTvActiveRecordingPath(request.Id);
             var path = _liveTvManager.GetEmbyTvActiveRecordingPath(request.Id);
@@ -749,13 +748,6 @@ namespace MediaBrowser.Api.LiveTv
             return ToOptimizedResult(new ListingsProviderInfo());
             return ToOptimizedResult(new ListingsProviderInfo());
         }
         }
 
 
-        public async Task<object> Get(GetSatChannnelScanResult request)
-        {
-            var result = await _liveTvManager.GetSatChannelScanResult(request, CancellationToken.None).ConfigureAwait(false);
-
-            return ToOptimizedResult(result);
-        }
-
         public async Task<object> Get(GetLiveTvRegistrationInfo request)
         public async Task<object> Get(GetLiveTvRegistrationInfo request)
         {
         {
             var result = await _liveTvManager.GetRegistrationInfo(request.Feature).ConfigureAwait(false);
             var result = await _liveTvManager.GetRegistrationInfo(request.Feature).ConfigureAwait(false);
@@ -803,11 +795,6 @@ namespace MediaBrowser.Api.LiveTv
             return ToOptimizedResult(result);
             return ToOptimizedResult(result);
         }
         }
 
 
-        public object Get(GetSatIniMappings request)
-        {
-            return ToOptimizedResult(_liveTvManager.GetSatIniMappings());
-        }
-
         public async Task<object> Get(GetSchedulesDirectCountries request)
         public async Task<object> Get(GetSchedulesDirectCountries request)
         {
         {
             // https://json.schedulesdirect.org/20141201/available/countries
             // https://json.schedulesdirect.org/20141201/available/countries

+ 3 - 73
MediaBrowser.Api/StartupWizardService.cs

@@ -1,12 +1,9 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Connect;
 using MediaBrowser.Controller.Connect;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.LiveTv;
 using System;
 using System;
 using System.Linq;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -52,16 +49,14 @@ namespace MediaBrowser.Api
         private readonly IServerApplicationHost _appHost;
         private readonly IServerApplicationHost _appHost;
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
         private readonly IConnectManager _connectManager;
         private readonly IConnectManager _connectManager;
-        private readonly ILiveTvManager _liveTvManager;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IMediaEncoder _mediaEncoder;
 
 
-        public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager, IMediaEncoder mediaEncoder)
+        public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, IMediaEncoder mediaEncoder)
         {
         {
             _config = config;
             _config = config;
             _appHost = appHost;
             _appHost = appHost;
             _userManager = userManager;
             _userManager = userManager;
             _connectManager = connectManager;
             _connectManager = connectManager;
-            _liveTvManager = liveTvManager;
             _mediaEncoder = mediaEncoder;
             _mediaEncoder = mediaEncoder;
         }
         }
 
 
@@ -92,20 +87,6 @@ namespace MediaBrowser.Api
                 PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
                 PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
             };
             };
 
 
-            var tvConfig = GetLiveTVConfiguration();
-
-            if (tvConfig.TunerHosts.Count > 0)
-            {
-                result.LiveTvTunerPath = tvConfig.TunerHosts[0].Url;
-                result.LiveTvTunerType = tvConfig.TunerHosts[0].Type;
-            }
-
-            if (tvConfig.ListingProviders.Count > 0)
-            {
-                result.LiveTvGuideProviderId = tvConfig.ListingProviders[0].Id;
-                result.LiveTvGuideProviderType = tvConfig.ListingProviders[0].Type;
-            }
-
             return result;
             return result;
         }
         }
 
 
@@ -120,6 +101,7 @@ namespace MediaBrowser.Api
             config.EnableSeriesPresentationUniqueKey = true;
             config.EnableSeriesPresentationUniqueKey = true;
             config.EnableLocalizedGuids = true;
             config.EnableLocalizedGuids = true;
             config.EnableSimpleArtistDetection = true;
             config.EnableSimpleArtistDetection = true;
+            config.EnableNormalizedItemByNameIds = true;
         }
         }
 
 
         public void Post(UpdateStartupConfiguration request)
         public void Post(UpdateStartupConfiguration request)
@@ -128,9 +110,6 @@ namespace MediaBrowser.Api
             _config.Configuration.MetadataCountryCode = request.MetadataCountryCode;
             _config.Configuration.MetadataCountryCode = request.MetadataCountryCode;
             _config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
             _config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
             _config.SaveConfiguration();
             _config.SaveConfiguration();
-
-            var task = UpdateTuners(request);
-            Task.WaitAll(task);
         }
         }
 
 
         public object Get(GetStartupUser request)
         public object Get(GetStartupUser request)
@@ -165,51 +144,6 @@ namespace MediaBrowser.Api
 
 
             return result;
             return result;
         }
         }
-
-        private async Task UpdateTuners(UpdateStartupConfiguration request)
-        {
-            var config = GetLiveTVConfiguration();
-            var save = false;
-
-            if (string.IsNullOrWhiteSpace(request.LiveTvTunerPath) ||
-                string.IsNullOrWhiteSpace(request.LiveTvTunerType))
-            {
-                if (config.TunerHosts.Count > 0)
-                {
-                    config.TunerHosts.Clear();
-                    save = true;
-                }
-            }
-            else
-            {
-                if (!config.TunerHosts.Any(i => string.Equals(i.Type, request.LiveTvTunerType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.Url, request.LiveTvTunerPath, StringComparison.OrdinalIgnoreCase)))
-                {
-                    // Add tuner
-                    await _liveTvManager.SaveTunerHost(new TunerHostInfo
-                    {
-                        IsEnabled = true,
-                        Type = request.LiveTvTunerType,
-                        Url = request.LiveTvTunerPath
-
-                    }).ConfigureAwait(false);
-                }
-            }
-
-            if (save)
-            {
-                SaveLiveTVConfiguration(config);
-            }
-        }
-
-        private void SaveLiveTVConfiguration(LiveTvOptions config)
-        {
-            _config.SaveConfiguration("livetv", config);
-        }
-
-        private LiveTvOptions GetLiveTVConfiguration()
-        {
-            return _config.GetConfiguration<LiveTvOptions>("livetv");
-        }
     }
     }
 
 
     public class StartupConfiguration
     public class StartupConfiguration
@@ -217,10 +151,6 @@ namespace MediaBrowser.Api
         public string UICulture { get; set; }
         public string UICulture { get; set; }
         public string MetadataCountryCode { get; set; }
         public string MetadataCountryCode { get; set; }
         public string PreferredMetadataLanguage { get; set; }
         public string PreferredMetadataLanguage { get; set; }
-        public string LiveTvTunerType { get; set; }
-        public string LiveTvTunerPath { get; set; }
-        public string LiveTvGuideProviderId { get; set; }
-        public string LiveTvGuideProviderType { get; set; }
     }
     }
 
 
     public class StartupInfo
     public class StartupInfo

+ 6 - 1
MediaBrowser.Controller/Entities/Audio/MusicArtist.cs

@@ -289,7 +289,12 @@ namespace MediaBrowser.Controller.Entities.Audio
             }
             }
         }
         }
 
 
-        public static string GetPath(string name, bool normalizeName = true)
+        public static string GetPath(string name)
+        {
+            return GetPath(name, true);
+        }
+
+        public static string GetPath(string name, bool normalizeName)
         {
         {
             // Trim the period at the end because windows will have a hard time with that
             // Trim the period at the end because windows will have a hard time with that
             var validName = normalizeName ?
             var validName = normalizeName ?

+ 6 - 1
MediaBrowser.Controller/Entities/Audio/MusicGenre.cs

@@ -118,7 +118,12 @@ namespace MediaBrowser.Controller.Entities.Audio
             return LibraryManager.GetItemList(query);
             return LibraryManager.GetItemList(query);
         }
         }
 
 
-        public static string GetPath(string name, bool normalizeName = true)
+        public static string GetPath(string name)
+        {
+            return GetPath(name, true);
+        }
+
+        public static string GetPath(string name, bool normalizeName)
         {
         {
             // Trim the period at the end because windows will have a hard time with that
             // Trim the period at the end because windows will have a hard time with that
             var validName = normalizeName ?
             var validName = normalizeName ?

+ 6 - 1
MediaBrowser.Controller/Entities/GameGenre.cs

@@ -96,7 +96,12 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        public static string GetPath(string name, bool normalizeName = true)
+        public static string GetPath(string name)
+        {
+            return GetPath(name, true);
+        }
+
+        public static string GetPath(string name, bool normalizeName)
         {
         {
             // Trim the period at the end because windows will have a hard time with that
             // Trim the period at the end because windows will have a hard time with that
             var validName = normalizeName ?
             var validName = normalizeName ?

+ 6 - 1
MediaBrowser.Controller/Entities/Genre.cs

@@ -108,7 +108,12 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        public static string GetPath(string name, bool normalizeName = true)
+        public static string GetPath(string name)
+        {
+            return GetPath(name, true);
+        }
+
+        public static string GetPath(string name, bool normalizeName)
         {
         {
             // Trim the period at the end because windows will have a hard time with that
             // Trim the period at the end because windows will have a hard time with that
             var validName = normalizeName ?
             var validName = normalizeName ?

+ 6 - 1
MediaBrowser.Controller/Entities/Person.cs

@@ -133,7 +133,12 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        public static string GetPath(string name, bool normalizeName = true)
+        public static string GetPath(string name)
+        {
+            return GetPath(name, true);
+        }
+
+        public static string GetPath(string name, bool normalizeName)
         {
         {
             // Trim the period at the end because windows will have a hard time with that
             // Trim the period at the end because windows will have a hard time with that
             var validFilename = normalizeName ?
             var validFilename = normalizeName ?

+ 6 - 1
MediaBrowser.Controller/Entities/Studio.cs

@@ -114,7 +114,12 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        public static string GetPath(string name, bool normalizeName = true)
+        public static string GetPath(string name)
+        {
+            return GetPath(name, true);
+        }
+
+        public static string GetPath(string name, bool normalizeName)
         {
         {
             // Trim the period at the end because windows will have a hard time with that
             // Trim the period at the end because windows will have a hard time with that
             var validName = normalizeName ?
             var validName = normalizeName ?

+ 6 - 1
MediaBrowser.Controller/Entities/Year.cs

@@ -122,7 +122,12 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        public static string GetPath(string name, bool normalizeName = true)
+        public static string GetPath(string name)
+        {
+            return GetPath(name, true);
+        }
+
+        public static string GetPath(string name, bool normalizeName)
         {
         {
             // Trim the period at the end because windows will have a hard time with that
             // Trim the period at the end because windows will have a hard time with that
             var validName = normalizeName ?
             var validName = normalizeName ?

+ 2 - 8
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -376,19 +376,13 @@ namespace MediaBrowser.Controller.LiveTv
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         Task OnRecordingFileDeleted(BaseItem recording);
         Task OnRecordingFileDeleted(BaseItem recording);
 
 
-        /// <summary>
-        /// Gets the sat ini mappings.
-        /// </summary>
-        /// <returns>List&lt;NameValuePair&gt;.</returns>
-        List<NameValuePair> GetSatIniMappings();
-
-        Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken);
-
         Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken);
         Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken);
         Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
         Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
 
 
         List<IListingsProvider> ListingProviders { get; }
         List<IListingsProvider> ListingProviders { get; }
 
 
+        List<NameIdPair> GetTunerHostTypes();
+
         event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
         event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
         event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
         event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
         event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;
         event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;

+ 2 - 0
MediaBrowser.Controller/LiveTv/ITunerHost.cs

@@ -44,6 +44,8 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns>
         /// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns>
         Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
         Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
+
+        Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken);
     }
     }
     public interface IConfigurableTunerHost
     public interface IConfigurableTunerHost
     {
     {

+ 1 - 0
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -48,6 +48,7 @@ namespace MediaBrowser.Model.Configuration
         public bool EnableHttps { get; set; }
         public bool EnableHttps { get; set; }
         public bool EnableSeriesPresentationUniqueKey { get; set; }
         public bool EnableSeriesPresentationUniqueKey { get; set; }
         public bool EnableLocalizedGuids { get; set; }
         public bool EnableLocalizedGuids { get; set; }
+        public bool EnableNormalizedItemByNameIds { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the value pointing to the file system where the ssl certiifcate is located..
         /// Gets or sets the value pointing to the file system where the ssl certiifcate is located..

+ 1 - 2
MediaBrowser.Model/LiveTv/LiveTvOptions.cs

@@ -46,14 +46,13 @@ namespace MediaBrowser.Model.LiveTv
         public string Url { get; set; }
         public string Url { get; set; }
         public string Type { get; set; }
         public string Type { get; set; }
         public string DeviceId { get; set; }
         public string DeviceId { get; set; }
+        public string FriendlyName { get; set; }
         public bool ImportFavoritesOnly { get; set; }
         public bool ImportFavoritesOnly { get; set; }
         public bool AllowHWTranscoding { get; set; }
         public bool AllowHWTranscoding { get; set; }
-        public bool IsEnabled { get; set; }
         public bool EnableTvgId { get; set; }
         public bool EnableTvgId { get; set; }
 
 
         public TunerHostInfo()
         public TunerHostInfo()
         {
         {
-            IsEnabled = true;
             AllowHWTranscoding = true;
             AllowHWTranscoding = true;
         }
         }
     }
     }

+ 2 - 0
MediaBrowser.Model/Net/ISocketFactory.cs

@@ -14,6 +14,8 @@ namespace MediaBrowser.Model.Net
 		/// <returns>A <see cref="ISocket"/> implementation.</returns>
 		/// <returns>A <see cref="ISocket"/> implementation.</returns>
 		ISocket CreateUdpSocket(int localPort);
 		ISocket CreateUdpSocket(int localPort);
 
 
+        ISocket CreateUdpBroadcastSocket(int localPort);
+
         ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort);
         ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort);
 
 
         /// <summary>
         /// <summary>

+ 2 - 1
MediaBrowser.Model/Services/IRequest.cs

@@ -4,6 +4,7 @@ using System.IO;
 using System.Net;
 using System.Net;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
 
 
 namespace MediaBrowser.Model.Services
 namespace MediaBrowser.Model.Services
 {
 {
@@ -154,6 +155,6 @@ namespace MediaBrowser.Model.Services
         //Add Metadata to Response
         //Add Metadata to Response
         Dictionary<string, object> Items { get; }
         Dictionary<string, object> Items { get; }
 
 
-        Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken);
+        Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken);
     }
     }
 }
 }

+ 1 - 1
SharedVersion.cs

@@ -1,3 +1,3 @@
 using System.Reflection;
 using System.Reflection;
 
 
-[assembly: AssemblyVersion("3.2.7.3")]
+[assembly: AssemblyVersion("3.2.7.4")]

+ 2 - 2
SocketHttpListener.Portable/Net/HttpListenerResponse.cs

@@ -515,9 +515,9 @@ namespace SocketHttpListener.Net
             cookies.Add(cookie);
             cookies.Add(cookie);
         }
         }
 
 
-        public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken)
+        public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
         {
         {
-            return ((ResponseStream)OutputStream).TransmitFile(path, offset, count, cancellationToken);
+            return ((ResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 4 - 4
SocketHttpListener.Portable/Net/ResponseStream.cs

@@ -307,13 +307,13 @@ namespace SocketHttpListener.Net
             throw new NotSupportedException();
             throw new NotSupportedException();
         }
         }
 
 
-        public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken)
+        public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
         {
         {
             //if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !response.SendChunked)
             //if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !response.SendChunked)
             //{
             //{
             //    return TransmitFileOverSocket(path, offset, count, cancellationToken);
             //    return TransmitFileOverSocket(path, offset, count, cancellationToken);
             //}
             //}
-            return TransmitFileManaged(path, offset, count, cancellationToken);
+            return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken);
         }
         }
 
 
         private readonly byte[] _emptyBuffer = new byte[] { };
         private readonly byte[] _emptyBuffer = new byte[] { };
@@ -334,7 +334,7 @@ namespace SocketHttpListener.Net
             await _socket.SendFile(path, buffer, _emptyBuffer, cancellationToken).ConfigureAwait(false);
             await _socket.SendFile(path, buffer, _emptyBuffer, cancellationToken).ConfigureAwait(false);
         }
         }
 
 
-        private async Task TransmitFileManaged(string path, long offset, long count, CancellationToken cancellationToken)
+        private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
         {
         {
             var chunked = response.SendChunked;
             var chunked = response.SendChunked;
 
 
@@ -343,7 +343,7 @@ namespace SocketHttpListener.Net
                 await WriteAsync(_emptyBuffer, 0, 0, cancellationToken).ConfigureAwait(false);
                 await WriteAsync(_emptyBuffer, 0, 0, cancellationToken).ConfigureAwait(false);
             }
             }
 
 
-            using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true))
+            using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, true))
             {
             {
                 if (offset > 0)
                 if (offset > 0)
                 {
                 {