mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-04 03:27:21 -05:00 
			
		
		
		
	
						commit
						afb4a08bfe
					
				@ -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>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -97,6 +97,27 @@ 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.
 | 
					              /// 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>
 | 
					              /// </summary>
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
					            //finally
 | 
				
			||||||
            {
 | 
					            //{
 | 
				
			||||||
                if (imageProcessingLockTaken)
 | 
					            //    if (imageProcessingLockTaken)
 | 
				
			||||||
                {
 | 
					            //    {
 | 
				
			||||||
                    _imageProcessingSemaphore.Release();
 | 
					            //        _imageProcessingSemaphore.Release();
 | 
				
			||||||
                }
 | 
					            //    }
 | 
				
			||||||
            }
 | 
					            //}
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void CopyFile(string src, string destination)
 | 
					        private void CopyFile(string src, string destination)
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
 | 
					                    var isPathInLibrary = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (allLibraryPaths.Any(i => path.StartsWith(i, StringComparison.Ordinal)) || 
 | 
				
			||||||
 | 
					                        allLibraryPaths.Contains(path, StringComparer.Ordinal) || 
 | 
				
			||||||
 | 
					                        path.StartsWith(_appPaths.ProgramDataPath, StringComparison.Ordinal))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        isPathInLibrary = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path))
 | 
					                        if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path))
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            continue;
 | 
					                            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;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    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);
 | 
					                        _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);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -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" />
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
				
			|||||||
@ -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));
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -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
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@ -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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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>());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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 ?
 | 
				
			||||||
 | 
				
			|||||||
@ -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 ?
 | 
				
			||||||
 | 
				
			|||||||
@ -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 ?
 | 
				
			||||||
 | 
				
			|||||||
@ -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 ?
 | 
				
			||||||
 | 
				
			|||||||
@ -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 ?
 | 
				
			||||||
 | 
				
			|||||||
@ -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 ?
 | 
				
			||||||
 | 
				
			|||||||
@ -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 ?
 | 
				
			||||||
 | 
				
			|||||||
@ -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<NameValuePair>.</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;
 | 
				
			||||||
 | 
				
			|||||||
@ -44,6 +44,8 @@ namespace MediaBrowser.Controller.LiveTv
 | 
				
			|||||||
        /// <param name="cancellationToken">The cancellation token.</param>
 | 
					        /// <param name="cancellationToken">The cancellation token.</param>
 | 
				
			||||||
        /// <returns>Task<List<MediaSourceInfo>>.</returns>
 | 
					        /// <returns>Task<List<MediaSourceInfo>>.</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
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
				
			|||||||
@ -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..
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -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>
 | 
				
			||||||
 | 
				
			|||||||
@ -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,3 +1,3 @@
 | 
				
			|||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[assembly: AssemblyVersion("3.2.7.3")]
 | 
					[assembly: AssemblyVersion("3.2.7.4")]
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -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)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user