aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Dlna/PlayTo/SsdpHttpClient.cs36
-rw-r--r--Emby.Notifications/NotificationManager.cs2
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs27
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs4
-rw-r--r--Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs4
-rw-r--r--Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs44
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs358
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs184
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs128
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs85
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs78
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs19
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs21
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs8
-rw-r--r--MediaBrowser.Api/UserService.cs11
-rw-r--r--MediaBrowser.Controller/Authentication/AuthenticationException.cs1
-rw-r--r--MediaBrowser.Controller/Entities/User.cs66
-rw-r--r--MediaBrowser.Controller/Library/IUserManager.cs10
-rw-r--r--MediaBrowser.Model/IO/StreamDefaults.cs2
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs32
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs170
21 files changed, 656 insertions, 634 deletions
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index 217ea3a4b..66c634150 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -16,6 +16,8 @@ namespace Emby.Dlna.PlayTo
private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
private const string FriendlyName = "Jellyfin";
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
private readonly IHttpClient _httpClient;
private readonly IServerConfigurationManager _config;
@@ -25,7 +27,8 @@ namespace Emby.Dlna.PlayTo
_config = config;
}
- public async Task<XDocument> SendCommandAsync(string baseUrl,
+ public async Task<XDocument> SendCommandAsync(
+ string baseUrl,
DeviceService service,
string command,
string postData,
@@ -35,12 +38,20 @@ namespace Emby.Dlna.PlayTo
var cancellationToken = CancellationToken.None;
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
- using (var response = await PostSoapDataAsync(url, '\"' + service.ServiceType + '#' + command + '\"', postData, header, logRequest, cancellationToken)
+ using (var response = await PostSoapDataAsync(
+ url,
+ $"\"{service.ServiceType}#{command}\"",
+ postData,
+ header,
+ logRequest,
+ cancellationToken)
.ConfigureAwait(false))
using (var stream = response.Content)
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
- return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
+ return XDocument.Parse(
+ await reader.ReadToEndAsync().ConfigureAwait(false),
+ LoadOptions.PreserveWhitespace);
}
}
@@ -58,9 +69,8 @@ namespace Emby.Dlna.PlayTo
return baseUrl + serviceUrl;
}
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
- public async Task SubscribeAsync(string url,
+ public async Task SubscribeAsync(
+ string url,
string ip,
int port,
string localIp,
@@ -101,14 +111,12 @@ namespace Emby.Dlna.PlayTo
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
+ using (var stream = response.Content)
+ using (var reader = new StreamReader(stream, Encoding.UTF8))
{
- using (var stream = response.Content)
- {
- using (var reader = new StreamReader(stream, Encoding.UTF8))
- {
- return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
- }
- }
+ return XDocument.Parse(
+ await reader.ReadToEndAsync().ConfigureAwait(false),
+ LoadOptions.PreserveWhitespace);
}
}
@@ -122,7 +130,7 @@ namespace Emby.Dlna.PlayTo
{
if (soapAction[0] != '\"')
{
- soapAction = '\"' + soapAction + '\"';
+ soapAction = $"\"{soapAction}\"";
}
var options = new HttpRequestOptions
diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs
index a767e541e..eecbbea07 100644
--- a/Emby.Notifications/NotificationManager.cs
+++ b/Emby.Notifications/NotificationManager.cs
@@ -89,7 +89,7 @@ namespace Emby.Notifications
return _userManager.Users.Where(i => i.Policy.IsAdministrator)
.Select(i => i.Id);
case SendToUserType.All:
- return _userManager.Users.Select(i => i.Id);
+ return _userManager.UsersIds;
case SendToUserType.Custom:
return request.UserIds;
default:
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 0b3b81f94..6ab3d1bb1 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -315,8 +315,6 @@ namespace Emby.Server.Implementations
private IMediaSourceManager MediaSourceManager { get; set; }
- private IPlaylistManager PlaylistManager { get; set; }
-
private readonly IConfiguration _configuration;
/// <summary>
@@ -325,14 +323,6 @@ namespace Emby.Server.Implementations
/// <value>The installation manager.</value>
protected IInstallationManager InstallationManager { get; private set; }
- /// <summary>
- /// Gets or sets the zip client.
- /// </summary>
- /// <value>The zip client.</value>
- protected IZipClient ZipClient { get; private set; }
-
- protected IHttpResultFactory HttpResultFactory { get; private set; }
-
protected IAuthService AuthService { get; private set; }
public IStartupOptions StartupOptions { get; }
@@ -680,8 +670,6 @@ namespace Emby.Server.Implementations
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
}
- public static IStreamHelper StreamHelper { get; set; }
-
/// <summary>
/// Registers resources that classes will depend on
/// </summary>
@@ -725,8 +713,7 @@ namespace Emby.Server.Implementations
ProcessFactory = new ProcessFactory();
serviceCollection.AddSingleton(ProcessFactory);
- ApplicationHost.StreamHelper = new StreamHelper();
- serviceCollection.AddSingleton(StreamHelper);
+ serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
@@ -735,11 +722,9 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager));
- ZipClient = new ZipClient();
- serviceCollection.AddSingleton(ZipClient);
+ serviceCollection.AddSingleton(typeof(IZipClient), typeof(ZipClient));
- HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, StreamHelper);
- serviceCollection.AddSingleton(HttpResultFactory);
+ serviceCollection.AddSingleton(typeof(IHttpResultFactory), typeof(HttpResultFactory));
serviceCollection.AddSingleton<IServerApplicationHost>(this);
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
@@ -770,7 +755,8 @@ namespace Emby.Server.Implementations
_userRepository = GetUserRepository();
- UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
+ UserManager = new UserManager(LoggerFactory.CreateLogger<UserManager>(), _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
+
serviceCollection.AddSingleton(UserManager);
LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager);
@@ -836,8 +822,7 @@ namespace Emby.Server.Implementations
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
serviceCollection.AddSingleton(CollectionManager);
- PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager);
- serviceCollection.AddSingleton(PlaylistManager);
+ serviceCollection.AddSingleton(typeof(IPlaylistManager), typeof(PlaylistManager));
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
serviceCollection.AddSingleton(LiveTvManager);
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index 4035bb99d..9d4855bcf 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Data
var userDatasTableExists = TableExists(connection, "UserDatas");
var userDataTableExists = TableExists(connection, "userdata");
- var users = userDatasTableExists ? null : userManager.Users.ToArray();
+ var users = userDatasTableExists ? null : userManager.Users;
connection.RunInTransaction(db =>
{
@@ -84,7 +84,7 @@ namespace Emby.Server.Implementations.Data
}
}
- private void ImportUserIds(IDatabaseConnection db, User[] users)
+ private void ImportUserIds(IDatabaseConnection db, IEnumerable<User> users)
{
var userIdsWithUserData = GetAllUserIdsWithUserData(db);
diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
index b7565adec..b2328121e 100644
--- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
+++ b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
@@ -50,9 +50,7 @@ namespace Emby.Server.Implementations.EntryPoints
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
- var users = _userManager.Users.ToList();
-
- foreach (var user in users)
+ foreach (var user in _userManager.Users)
{
cancellationToken.ThrowIfCancellationRequested();
diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
index c7044820c..fa6bbcf91 100644
--- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
@@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.IO;
-using System.Linq;
-using System.Text;
+using System.Security.Cryptography;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Users;
@@ -17,32 +14,37 @@ namespace Emby.Server.Implementations.Library
{
public class DefaultPasswordResetProvider : IPasswordResetProvider
{
- public string Name => "Default Password Reset Provider";
+ private const string BaseResetFileName = "passwordreset";
- public bool IsEnabled => true;
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IUserManager _userManager;
private readonly string _passwordResetFileBase;
private readonly string _passwordResetFileBaseDir;
- private readonly string _passwordResetFileBaseName = "passwordreset";
- private readonly IJsonSerializer _jsonSerializer;
- private readonly IUserManager _userManager;
- private readonly ICryptoProvider _crypto;
-
- public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
+ public DefaultPasswordResetProvider(
+ IServerConfigurationManager configurationManager,
+ IJsonSerializer jsonSerializer,
+ IUserManager userManager)
{
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
- _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
+ _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName);
_jsonSerializer = jsonSerializer;
_userManager = userManager;
- _crypto = cryptoProvider;
}
+ /// <inheritdoc />
+ public string Name => "Default Password Reset Provider";
+
+ /// <inheritdoc />
+ public bool IsEnabled => true;
+
+ /// <inheritdoc />
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
{
SerializablePasswordReset spr;
- HashSet<string> usersreset = new HashSet<string>();
- foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
+ List<string> usersreset = new List<string>();
+ foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
{
using (var str = File.OpenRead(resetfile))
{
@@ -53,12 +55,15 @@ namespace Emby.Server.Implementations.Library
{
File.Delete(resetfile);
}
- else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
+ else if (string.Equals(
+ spr.Pin.Replace("-", string.Empty),
+ pin.Replace("-", string.Empty),
+ StringComparison.InvariantCultureIgnoreCase))
{
var resetUser = _userManager.GetUserByName(spr.UserName);
if (resetUser == null)
{
- throw new Exception($"User with a username of {spr.UserName} not found");
+ throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
}
await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
@@ -81,10 +86,11 @@ namespace Emby.Server.Implementations.Library
}
}
+ /// <inheritdoc />
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
{
string pin = string.Empty;
- using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
+ using (var cryptoRandom = RandomNumberGenerator.Create())
{
byte[] bytes = new byte[4];
cryptoRandom.GetBytes(bytes);
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index c8c8a108d..086527883 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -11,13 +12,11 @@ using MediaBrowser.Common.Events;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
@@ -41,34 +40,19 @@ namespace Emby.Server.Implementations.Library
public class UserManager : IUserManager
{
/// <summary>
- /// Gets the users.
- /// </summary>
- /// <value>The users.</value>
- public IEnumerable<User> Users => _users;
-
- private User[] _users;
-
- /// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
- /// <summary>
- /// Gets or sets the configuration manager.
- /// </summary>
- /// <value>The configuration manager.</value>
- private IServerConfigurationManager ConfigurationManager { get; set; }
+ private readonly object _policySyncLock = new object();
/// <summary>
/// Gets the active user repository
/// </summary>
/// <value>The user repository.</value>
- private IUserRepository UserRepository { get; set; }
- public event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
-
+ private readonly IUserRepository _userRepository;
private readonly IXmlSerializer _xmlSerializer;
private readonly IJsonSerializer _jsonSerializer;
-
private readonly INetworkManager _networkManager;
private readonly Func<IImageProcessor> _imageProcessorFactory;
@@ -76,6 +60,8 @@ namespace Emby.Server.Implementations.Library
private readonly IServerApplicationHost _appHost;
private readonly IFileSystem _fileSystem;
+ private ConcurrentDictionary<Guid, User> _users;
+
private IAuthenticationProvider[] _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider;
@@ -85,8 +71,7 @@ namespace Emby.Server.Implementations.Library
private DefaultPasswordResetProvider _defaultPasswordResetProvider;
public UserManager(
- ILoggerFactory loggerFactory,
- IServerConfigurationManager configurationManager,
+ ILogger<UserManager> logger,
IUserRepository userRepository,
IXmlSerializer xmlSerializer,
INetworkManager networkManager,
@@ -96,8 +81,8 @@ namespace Emby.Server.Implementations.Library
IJsonSerializer jsonSerializer,
IFileSystem fileSystem)
{
- _logger = loggerFactory.CreateLogger(nameof(UserManager));
- UserRepository = userRepository;
+ _logger = logger;
+ _userRepository = userRepository;
_xmlSerializer = xmlSerializer;
_networkManager = networkManager;
_imageProcessorFactory = imageProcessorFactory;
@@ -105,8 +90,51 @@ namespace Emby.Server.Implementations.Library
_appHost = appHost;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
- ConfigurationManager = configurationManager;
- _users = Array.Empty<User>();
+ _users = null;
+ }
+
+ public event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
+
+ /// <summary>
+ /// Occurs when [user updated].
+ /// </summary>
+ public event EventHandler<GenericEventArgs<User>> UserUpdated;
+
+ public event EventHandler<GenericEventArgs<User>> UserPolicyUpdated;
+
+ public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
+
+ public event EventHandler<GenericEventArgs<User>> UserLockedOut;
+
+ public event EventHandler<GenericEventArgs<User>> UserCreated;
+
+ /// <summary>
+ /// Occurs when [user deleted].
+ /// </summary>
+ public event EventHandler<GenericEventArgs<User>> UserDeleted;
+
+ /// <inheritdoc />
+ public IEnumerable<User> Users => _users.Values;
+
+ /// <inheritdoc />
+ public IEnumerable<Guid> UsersIds => _users.Keys;
+
+ /// <summary>
+ /// Called when [user updated].
+ /// </summary>
+ /// <param name="user">The user.</param>
+ private void OnUserUpdated(User user)
+ {
+ UserUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
+ }
+
+ /// <summary>
+ /// Called when [user deleted].
+ /// </summary>
+ /// <param name="user">The user.</param>
+ private void OnUserDeleted(User user)
+ {
+ UserDeleted?.Invoke(this, new GenericEventArgs<User> { Argument = user });
}
public NameIdPair[] GetAuthenticationProviders()
@@ -137,7 +165,7 @@ namespace Emby.Server.Implementations.Library
.ToArray();
}
- public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders,IEnumerable<IPasswordResetProvider> passwordResetProviders)
+ public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders)
{
_authenticationProviders = authenticationProviders.ToArray();
@@ -150,54 +178,21 @@ namespace Emby.Server.Implementations.Library
_defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
}
- #region UserUpdated Event
/// <summary>
- /// Occurs when [user updated].
- /// </summary>
- public event EventHandler<GenericEventArgs<User>> UserUpdated;
- public event EventHandler<GenericEventArgs<User>> UserPolicyUpdated;
- public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
- public event EventHandler<GenericEventArgs<User>> UserLockedOut;
-
- /// <summary>
- /// Called when [user updated].
- /// </summary>
- /// <param name="user">The user.</param>
- private void OnUserUpdated(User user)
- {
- UserUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
- }
- #endregion
-
- #region UserDeleted Event
- /// <summary>
- /// Occurs when [user deleted].
- /// </summary>
- public event EventHandler<GenericEventArgs<User>> UserDeleted;
- /// <summary>
- /// Called when [user deleted].
- /// </summary>
- /// <param name="user">The user.</param>
- private void OnUserDeleted(User user)
- {
- UserDeleted?.Invoke(this, new GenericEventArgs<User> { Argument = user });
- }
- #endregion
-
- /// <summary>
- /// Gets a User by Id
+ /// Gets a User by Id.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>User.</returns>
- /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentException"></exception>
public User GetUserById(Guid id)
{
if (id == Guid.Empty)
{
- throw new ArgumentException(nameof(id), "Guid can't be empty");
+ throw new ArgumentException("Guid can't be empty", nameof(id));
}
- return Users.FirstOrDefault(u => u.Id == id);
+ _users.TryGetValue(id, out User user);
+ return user;
}
/// <summary>
@@ -206,15 +201,13 @@ namespace Emby.Server.Implementations.Library
/// <param name="id">The identifier.</param>
/// <returns>User.</returns>
public User GetUserById(string id)
- {
- return GetUserById(new Guid(id));
- }
+ => GetUserById(new Guid(id));
public User GetUserByName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
- throw new ArgumentNullException(nameof(name));
+ throw new ArgumentException("Invalid username", nameof(name));
}
return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase));
@@ -222,8 +215,9 @@ namespace Emby.Server.Implementations.Library
public void Initialize()
{
- var users = LoadUsers();
- _users = users.ToArray();
+ LoadUsers();
+
+ var users = Users;
// If there are no local users with admin rights, make them all admins
if (!users.Any(i => i.Policy.IsAdministrator))
@@ -240,14 +234,12 @@ namespace Emby.Server.Implementations.Library
{
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
- // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.)
+ // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.)
return Regex.IsMatch(username, @"^[\w\-'._@]*$");
}
private static bool IsValidUsernameCharacter(char i)
- {
- return IsValidUsername(i.ToString());
- }
+ => IsValidUsername(i.ToString(CultureInfo.InvariantCulture));
public string MakeValidUsername(string username)
{
@@ -277,8 +269,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(username));
}
- var user = Users
- .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
+ var user = Users.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
var success = false;
string updatedUsername = null;
@@ -299,13 +290,12 @@ namespace Emby.Server.Implementations.Library
updatedUsername = authResult.username;
success = authResult.success;
- if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider))
+ if (success
+ && authenticationProvider != null
+ && !(authenticationProvider is DefaultAuthenticationProvider))
{
// We should trust the user that the authprovider says, not what was typed
- if (updatedUsername != username)
- {
- username = updatedUsername;
- }
+ username = updatedUsername;
// Search the database for the user again; the authprovider might have created it
user = Users
@@ -337,10 +327,11 @@ namespace Emby.Server.Implementations.Library
if (user.Policy.IsDisabled)
{
- throw new AuthenticationException(string.Format(
- CultureInfo.InvariantCulture,
- "The {0} account is currently disabled. Please consult with your administrator.",
- user.Name));
+ throw new AuthenticationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "The {0} account is currently disabled. Please consult with your administrator.",
+ user.Name));
}
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
@@ -386,7 +377,7 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider GetAuthenticationProvider(User user)
{
- return GetAuthenticationProviders(user).First();
+ return GetAuthenticationProviders(user)[0];
}
private IPasswordResetProvider GetPasswordResetProvider(User user)
@@ -396,7 +387,7 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider[] GetAuthenticationProviders(User user)
{
- var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId;
+ var authenticationProviderId = user?.Policy.AuthenticationProviderId;
var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray();
@@ -438,16 +429,10 @@ namespace Emby.Server.Implementations.Library
{
try
{
- var requiresResolvedUser = provider as IRequiresResolvedUser;
- ProviderAuthenticationResult authenticationResult = null;
- if (requiresResolvedUser != null)
- {
- authenticationResult = await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false);
- }
- else
- {
- authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false);
- }
+
+ var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
+ ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false)
+ : await provider.Authenticate(username, password).ConfigureAwait(false);
if (authenticationResult.Username != username)
{
@@ -467,7 +452,6 @@ namespace Emby.Server.Implementations.Library
private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
{
- string updatedUsername = null;
bool success = false;
IAuthenticationProvider authenticationProvider = null;
@@ -487,7 +471,7 @@ namespace Emby.Server.Implementations.Library
foreach (var provider in GetAuthenticationProviders(user))
{
var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
- updatedUsername = providerAuthResult.username;
+ var updatedUsername = providerAuthResult.username;
success = providerAuthResult.success;
if (success)
@@ -499,25 +483,32 @@ namespace Emby.Server.Implementations.Library
}
}
- if (user != null)
+ if (user != null
+ && !success
+ && _networkManager.IsInLocalNetwork(remoteEndPoint)
+ && user.Configuration.EnableLocalPassword)
{
- if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword)
+ if (password == null)
{
- if (password == null)
- {
- // legacy
- success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
- }
- else
- {
- success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
- }
+ // legacy
+ success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
+ }
+ else
+ {
+ success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
}
}
return (authenticationProvider, username, success);
}
+ private string GetLocalPasswordHash(User user)
+ {
+ return string.IsNullOrEmpty(user.EasyPassword)
+ ? null
+ : PasswordHash.ConvertToByteString(new PasswordHash(user.EasyPassword).Hash);
+ }
+
private void UpdateInvalidLoginAttemptCount(User user, int newValue)
{
if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0)
@@ -556,17 +547,17 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Loads the users from the repository
+ /// Loads the users from the repository.
/// </summary>
- /// <returns>IEnumerable{User}.</returns>
- private List<User> LoadUsers()
+ private void LoadUsers()
{
- var users = UserRepository.RetrieveAllUsers();
+ var users = _userRepository.RetrieveAllUsers();
// There always has to be at least one user.
if (users.Count != 0)
{
- return users;
+ _users = new ConcurrentDictionary<Guid, User>(
+ users.Select(x => new KeyValuePair<Guid, User>(x.Id, x)));
}
var defaultName = Environment.UserName;
@@ -581,14 +572,15 @@ namespace Emby.Server.Implementations.Library
user.DateLastSaved = DateTime.UtcNow;
- UserRepository.CreateUser(user);
+ _userRepository.CreateUser(user);
user.Policy.IsAdministrator = true;
user.Policy.EnableContentDeletion = true;
user.Policy.EnableRemoteControlOfOtherUsers = true;
UpdateUserPolicy(user, user.Policy, false);
- return new List<User> { user };
+ _users = new ConcurrentDictionary<Guid, User>();
+ _users[user.Id] = user;
}
public UserDto GetUserDto(User user, string remoteEndPoint = null)
@@ -619,7 +611,7 @@ namespace Emby.Server.Implementations.Library
Policy = user.Policy
};
- if (!hasPassword && Users.Count() == 1)
+ if (!hasPassword && _users.Count == 1)
{
dto.EnableAutoLogin = true;
}
@@ -694,22 +686,26 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- if (string.IsNullOrEmpty(newName))
+ if (string.IsNullOrWhiteSpace(newName))
{
- throw new ArgumentNullException(nameof(newName));
+ throw new ArgumentException("Invalid username", nameof(newName));
}
- if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)))
+ if (user.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))
{
- throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName));
+ throw new ArgumentException("The new and old names must be different.");
}
- if (user.Name.Equals(newName, StringComparison.Ordinal))
+ if (Users.Any(
+ u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)))
{
- throw new ArgumentException("The new and old names must be different.");
+ throw new ArgumentException(string.Format(
+ CultureInfo.InvariantCulture,
+ "A user with the name '{0}' already exists.",
+ newName));
}
- await user.Rename(newName);
+ await user.Rename(newName).ConfigureAwait(false);
OnUserUpdated(user);
}
@@ -727,23 +723,30 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id)))
+ if (user.Id == Guid.Empty)
{
- throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id));
+ throw new ArgumentException("Id can't be empty.", nameof(user));
+ }
+
+ if (!_users.ContainsKey(user.Id))
+ {
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "A user '{0}' with Id {1} does not exist.",
+ user.Name,
+ user.Id),
+ nameof(user));
}
user.DateModified = DateTime.UtcNow;
user.DateLastSaved = DateTime.UtcNow;
- UserRepository.UpdateUser(user);
+ _userRepository.UpdateUser(user);
OnUserUpdated(user);
}
- public event EventHandler<GenericEventArgs<User>> UserCreated;
-
- private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1);
-
/// <summary>
/// Creates the user.
/// </summary>
@@ -751,7 +754,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>User.</returns>
/// <exception cref="ArgumentNullException">name</exception>
/// <exception cref="ArgumentException"></exception>
- public async Task<User> CreateUser(string name)
+ public User CreateUser(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -768,28 +771,17 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name));
}
- await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
-
- try
- {
- var user = InstantiateNewUser(name);
+ var user = InstantiateNewUser(name);
- var list = Users.ToList();
- list.Add(user);
- _users = list.ToArray();
+ _users[user.Id] = user;
- user.DateLastSaved = DateTime.UtcNow;
+ user.DateLastSaved = DateTime.UtcNow;
- UserRepository.CreateUser(user);
+ _userRepository.CreateUser(user);
- EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger);
+ EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger);
- return user;
- }
- finally
- {
- _userListLock.Release();
- }
+ return user;
}
/// <summary>
@@ -799,57 +791,59 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">user</exception>
/// <exception cref="ArgumentException"></exception>
- public async Task DeleteUser(User user)
+ public void DeleteUser(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
- var allUsers = Users.ToList();
-
- if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null)
+ if (!_users.ContainsKey(user.Id))
{
- throw new ArgumentException(string.Format("The user cannot be deleted because there is no user with the Name {0} and Id {1}.", user.Name, user.Id));
+ throw new ArgumentException(string.Format(
+ CultureInfo.InvariantCulture,
+ "The user cannot be deleted because there is no user with the Name {0} and Id {1}.",
+ user.Name,
+ user.Id));
}
- if (allUsers.Count == 1)
+ if (_users.Count == 1)
{
- throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one user in the system.", user.Name));
+ throw new ArgumentException(string.Format(
+ CultureInfo.InvariantCulture,
+ "The user '{0}' cannot be deleted because there must be at least one user in the system.",
+ user.Name));
}
- if (user.Policy.IsAdministrator && allUsers.Count(i => i.Policy.IsAdministrator) == 1)
+ if (user.Policy.IsAdministrator
+ && Users.Count(i => i.Policy.IsAdministrator) == 1)
{
- throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Name));
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
+ user.Name),
+ nameof(user));
}
- await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
+ var configPath = GetConfigurationFilePath(user);
+
+ _userRepository.DeleteUser(user);
try
{
- var configPath = GetConfigurationFilePath(user);
-
- UserRepository.DeleteUser(user);
-
- try
- {
- _fileSystem.DeleteFile(configPath);
- }
- catch (IOException ex)
- {
- _logger.LogError(ex, "Error deleting file {path}", configPath);
- }
-
- DeleteUserPolicy(user);
-
- _users = allUsers.Where(i => i.Id != user.Id).ToArray();
-
- OnUserDeleted(user);
+ _fileSystem.DeleteFile(configPath);
}
- finally
+ catch (IOException ex)
{
- _userListLock.Release();
+ _logger.LogError(ex, "Error deleting file {path}", configPath);
}
+
+ DeleteUserPolicy(user);
+
+ _users.TryRemove(user.Id, out _);
+
+ OnUserDeleted(user);
}
/// <summary>
@@ -906,8 +900,7 @@ namespace Emby.Server.Implementations.Library
Name = name,
Id = Guid.NewGuid(),
DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- UsesIdForConfigurationPath = true
+ DateModified = DateTime.UtcNow
};
}
@@ -989,7 +982,6 @@ namespace Emby.Server.Implementations.Library
};
}
- private readonly object _policySyncLock = new object();
public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy)
{
var user = GetUserById(userId);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index ed254accb..85754ca8b 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
@@ -31,6 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
+ private readonly IStreamHelper _streamHelper;
public HdHomerunHost(
IServerConfigurationManager config,
@@ -40,29 +42,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
IHttpClient httpClient,
IServerApplicationHost appHost,
ISocketFactory socketFactory,
- INetworkManager networkManager)
+ INetworkManager networkManager,
+ IStreamHelper streamHelper)
: base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_socketFactory = socketFactory;
_networkManager = networkManager;
+ _streamHelper = streamHelper;
}
public string Name => "HD Homerun";
- public override string Type => DeviceType;
-
- public static string DeviceType => "hdhomerun";
+ public override string Type => "hdhomerun";
protected override string ChannelIdPrefix => "hdhr_";
private string GetChannelId(TunerHostInfo info, Channels i)
- {
- var id = ChannelIdPrefix + i.GuideNumber;
-
- return id;
- }
+ => ChannelIdPrefix + i.GuideNumber;
private async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
{
@@ -74,19 +72,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
CancellationToken = cancellationToken,
BufferContent = false
};
- using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
- {
- using (var stream = response.Content)
- {
- var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
- if (info.ImportFavoritesOnly)
- {
- lineup = lineup.Where(i => i.Favorite).ToList();
- }
+ using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
+ using (var stream = response.Content)
+ {
+ var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
- return lineup.Where(i => !i.DRM).ToList();
+ if (info.ImportFavoritesOnly)
+ {
+ lineup = lineup.Where(i => i.Favorite).ToList();
}
+
+ return lineup.Where(i => !i.DRM).ToList();
}
}
@@ -139,23 +136,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Url = string.Format("{0}/discover.json", GetApiUrl(info)),
CancellationToken = cancellationToken,
BufferContent = false
-
- }, "GET").ConfigureAwait(false))
+ }, HttpMethod.Get).ConfigureAwait(false))
+ using (var stream = response.Content)
{
- using (var stream = response.Content)
- {
- var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
+ var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
- if (!string.IsNullOrEmpty(cacheKey))
+ if (!string.IsNullOrEmpty(cacheKey))
+ {
+ lock (_modelCache)
{
- lock (_modelCache)
- {
- _modelCache[cacheKey] = discoverResponse;
- }
+ _modelCache[cacheKey] = discoverResponse;
}
-
- return discoverResponse;
}
+
+ return discoverResponse;
}
}
catch (HttpException ex)
@@ -186,36 +180,36 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- using (var stream = await _httpClient.Get(new HttpRequestOptions()
+ using (var response = await _httpClient.SendAsync(new HttpRequestOptions()
{
Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
CancellationToken = cancellationToken,
BufferContent = false
- }))
+ }, HttpMethod.Get))
+ using (var stream = response.Content)
+ using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
{
var tuners = new List<LiveTvTunerInfo>();
- using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
+ while (!sr.EndOfStream)
{
- while (!sr.EndOfStream)
+ string line = StripXML(sr.ReadLine());
+ if (line.Contains("Channel"))
{
- string line = StripXML(sr.ReadLine());
- if (line.Contains("Channel"))
+ LiveTvTunerStatus status;
+ var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
+ var name = line.Substring(0, index - 1);
+ var currentChannel = line.Substring(index + 7);
+ if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; }
+ tuners.Add(new LiveTvTunerInfo
{
- LiveTvTunerStatus status;
- var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
- var name = line.Substring(0, index - 1);
- var currentChannel = line.Substring(index + 7);
- if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; }
- tuners.Add(new LiveTvTunerInfo
- {
- Name = name,
- SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
- ProgramName = currentChannel,
- Status = status
- });
- }
+ Name = name,
+ SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
+ ProgramName = currentChannel,
+ Status = status
+ });
}
}
+
return tuners;
}
}
@@ -245,6 +239,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
bufferIndex++;
}
}
+
return new string(buffer, 0, bufferIndex);
}
@@ -256,7 +251,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var uri = new Uri(GetApiUrl(info));
- using (var manager = new HdHomerunManager(Logger))
+ using (var manager = new HdHomerunManager())
{
// Legacy HdHomeruns are IPv4 only
var ipInfo = IPAddress.Parse(uri.Host);
@@ -276,6 +271,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
});
}
}
+
return tuners;
}
@@ -434,12 +430,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
videoCodec = channelInfo.VideoCodec;
}
+
string audioCodec = channelInfo.AudioCodec;
if (!videoBitrate.HasValue)
{
videoBitrate = isHd ? 15000000 : 2000000;
}
+
int? audioBitrate = isHd ? 448000 : 192000;
// normalize
@@ -461,6 +459,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
id = "native";
}
+
id += "_" + channelId.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_" + url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
var mediaSource = new MediaSourceInfo
@@ -527,29 +526,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
else
{
- try
- {
- var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+ var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- if (modelInfo != null && modelInfo.SupportsTranscoding)
+ if (modelInfo != null && modelInfo.SupportsTranscoding)
+ {
+ if (info.AllowHWTranscoding)
{
- if (info.AllowHWTranscoding)
- {
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
-
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
- }
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
}
- }
- catch
- {
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
}
if (list.Count == 0)
@@ -582,7 +574,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
{
- return new HdHomerunUdpStream(mediaSource, info, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
+ return new HdHomerunUdpStream(
+ mediaSource,
+ info,
+ streamId,
+ new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path),
+ modelInfo.TunerCount,
+ FileSystem,
+ Logger,
+ Config.ApplicationPaths,
+ _appHost,
+ _socketFactory,
+ _networkManager,
+ _streamHelper);
}
var enableHttpStream = true;
@@ -599,10 +603,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
mediaSource.Path = httpUrl;
- return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
- }
-
- return new HdHomerunUdpStream(mediaSource, info, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
+ return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
+ }
+
+ return new HdHomerunUdpStream(
+ mediaSource,
+ info,
+ streamId,
+ new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
+ modelInfo.TunerCount,
+ FileSystem,
+ Logger,
+ Config.ApplicationPaths,
+ _appHost,
+ _socketFactory,
+ _networkManager,
+ _streamHelper);
}
public async Task Validate(TunerHostInfo info)
@@ -701,9 +717,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
catch (OperationCanceledException)
{
}
- catch
+ catch (Exception ex)
{
// Socket timeout indicates all messages have been received.
+ Logger.LogError(ex, "Error while sending discovery message");
}
}
@@ -718,21 +735,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Url = url
};
- try
- {
- var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
-
- hostInfo.DeviceId = modelInfo.DeviceID;
- hostInfo.FriendlyName = modelInfo.FriendlyName;
+ var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
- return hostInfo;
- }
- catch
- {
- // logged at lower levels
- }
+ hostInfo.DeviceId = modelInfo.DeviceID;
+ hostInfo.FriendlyName = modelInfo.FriendlyName;
- return null;
+ return hostInfo;
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index c19552428..3699b988c 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
+using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;
@@ -8,13 +9,12 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.LiveTv;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public interface IHdHomerunChannelCommands
{
- IEnumerable<Tuple<string, string>> GetCommands();
+ IEnumerable<(string, string)> GetCommands();
}
public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
@@ -33,16 +33,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- public IEnumerable<Tuple<string, string>> GetCommands()
+ public IEnumerable<(string, string)> GetCommands()
{
- var commands = new List<Tuple<string, string>>();
-
if (!string.IsNullOrEmpty(_channel))
- commands.Add(Tuple.Create("channel", _channel));
+ {
+ yield return ("channel", _channel);
+ }
if (!string.IsNullOrEmpty(_program))
- commands.Add(Tuple.Create("program", _program));
- return commands;
+ {
+ yield return ("program", _program);
+ }
}
}
@@ -57,29 +58,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_profile = profile;
}
- public IEnumerable<Tuple<string, string>> GetCommands()
+ public IEnumerable<(string, string)> GetCommands()
{
- var commands = new List<Tuple<string, string>>();
-
if (!string.IsNullOrEmpty(_channel))
{
- if (!string.IsNullOrEmpty(_profile) && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(_profile)
+ && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
{
- commands.Add(Tuple.Create("vchannel", string.Format("{0} transcode={1}", _channel, _profile)));
+ yield return ("vchannel", $"{_channel} transcode={_profile}");
}
else
{
- commands.Add(Tuple.Create("vchannel", _channel));
+ yield return ("vchannel", _channel);
}
}
-
- return commands;
}
}
public class HdHomerunManager : IDisposable
{
public const int HdHomeRunPort = 65001;
+
// Message constants
private const byte GetSetName = 3;
private const byte GetSetValue = 4;
@@ -87,19 +86,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private const ushort GetSetRequest = 4;
private const ushort GetSetReply = 5;
- private readonly ILogger _logger;
-
private uint? _lockkey = null;
private int _activeTuner = -1;
private IPEndPoint _remoteEndPoint;
private TcpClient _tcpClient;
- public HdHomerunManager(ILogger logger)
- {
- _logger = logger;
- }
-
public void Dispose()
{
using (var socket = _tcpClient)
@@ -108,8 +100,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
_tcpClient = null;
- var task = StopStreaming(socket);
- Task.WaitAll(task);
+ StopStreaming(socket).GetAwaiter().GetResult();
}
}
}
@@ -173,20 +164,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out var returnVal))
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
{
continue;
}
var commandList = commands.GetCommands();
- foreach (Tuple<string, string> command in commandList)
+ foreach (var command in commandList)
{
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out returnVal))
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
{
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
continue;
@@ -198,8 +191,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out returnVal))
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
{
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
continue;
@@ -231,13 +225,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
- foreach (Tuple<string, string> command in commandList)
+ foreach (var command in commandList)
{
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out string returnVal))
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
{
return;
}
@@ -264,21 +259,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private async Task ReleaseLockkey(TcpClient client, uint lockKeyValue)
{
- _logger.LogInformation("HdHomerunManager.ReleaseLockkey {0}", lockKeyValue);
-
var stream = client.GetStream();
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
- await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length, CancellationToken.None).ConfigureAwait(false);
+ await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length).ConfigureAwait(false);
var buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
- await stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).ConfigureAwait(false);
+ await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
_lockkey = null;
- await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, CancellationToken.None).ConfigureAwait(false);
- await stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).ConfigureAwait(false);
+ await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length).ConfigureAwait(false);
+ await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
}
finally
{
@@ -288,7 +281,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private static byte[] CreateGetMessage(int tuner, string name)
{
- var byteName = Encoding.UTF8.GetBytes(string.Format("/tuner{0}/{1}\0", tuner, name));
+ var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name));
int messageLength = byteName.Length + 10; // 4 bytes for header + 4 bytes for crc + 2 bytes for tag name and length
var message = new byte[messageLength];
@@ -311,12 +304,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private static byte[] CreateSetMessage(int tuner, string name, string value, uint? lockkey)
{
- var byteName = Encoding.UTF8.GetBytes(string.Format("/tuner{0}/{1}\0", tuner, name));
- var byteValue = Encoding.UTF8.GetBytes(string.Format("{0}\0", value));
+ var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name));
+ var byteValue = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\0", value));
int messageLength = byteName.Length + byteValue.Length + 12;
if (lockkey.HasValue)
+ {
messageLength += 6;
+ }
var message = new byte[messageLength];
@@ -324,21 +319,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
bool flipEndian = BitConverter.IsLittleEndian;
- message[offset] = GetSetValue;
- offset++;
- message[offset] = Convert.ToByte(byteValue.Length);
- offset++;
+ message[offset++] = GetSetValue;
+ message[offset++] = Convert.ToByte(byteValue.Length);
Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
offset += byteValue.Length;
if (lockkey.HasValue)
{
- message[offset] = GetSetLockkey;
- offset++;
- message[offset] = (byte)4;
- offset++;
+ message[offset++] = GetSetLockkey;
+ message[offset++] = 4;
var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
if (flipEndian)
+ {
Array.Reverse(lockKeyBytes);
+ }
+
Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
offset += 4;
}
@@ -346,7 +340,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// calculate crc and insert at the end of the message
var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
if (flipEndian)
+ {
Array.Reverse(crcBytes);
+ }
+
Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
return message;
@@ -375,10 +372,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
offset += 2;
// insert tag name and length
- message[offset] = GetSetName;
- offset++;
- message[offset] = Convert.ToByte(byteName.Length);
- offset++;
+ message[offset++] = GetSetName;
+ message[offset++] = Convert.ToByte(byteName.Length);
// insert name string
Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
@@ -392,7 +387,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
returnVal = string.Empty;
if (numBytes < 4)
+ {
return false;
+ }
var flipEndian = BitConverter.IsLittleEndian;
int offset = 0;
@@ -400,45 +397,49 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length);
if (flipEndian)
+ {
Array.Reverse(msgTypeBytes);
+ }
var msgType = BitConverter.ToUInt16(msgTypeBytes, 0);
offset += 2;
if (msgType != GetSetReply)
+ {
return false;
+ }
byte[] msgLengthBytes = new byte[2];
Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length);
if (flipEndian)
+ {
Array.Reverse(msgLengthBytes);
+ }
var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0);
offset += 2;
if (numBytes < msgLength + 8)
+ {
return false;
+ }
- var nameTag = buf[offset];
- offset++;
+ var nameTag = buf[offset++];
- var nameLength = buf[offset];
- offset++;
+ var nameLength = buf[offset++];
// skip the name field to get to value for return
offset += nameLength;
- var valueTag = buf[offset];
- offset++;
+ var valueTag = buf[offset++];
- var valueLength = buf[offset];
- offset++;
+ var valueLength = buf[offset++];
returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
return true;
}
- private class HdHomerunCrc
+ private static class HdHomerunCrc
{
private static uint[] crc_table = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
@@ -510,15 +511,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var hash = 0xffffffff;
for (var i = 0; i < numBytes; i++)
+ {
hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
+ }
var tmp = ~hash & 0xffffffff;
var b0 = tmp & 0xff;
var b1 = (tmp >> 8) & 0xff;
var b2 = (tmp >> 16) & 0xff;
var b3 = (tmp >> 24) & 0xff;
- hash = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
- return hash;
+ return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index 1d79a5f96..fbbab07f8 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -1,4 +1,5 @@
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Net;
@@ -18,6 +19,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
{
+ private const int RtpHeaderBytes = 12;
+
private readonly IServerApplicationHost _appHost;
private readonly MediaBrowser.Model.Net.ISocketFactory _socketFactory;
@@ -32,13 +35,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
IHdHomerunChannelCommands channelCommands,
int numTuners,
IFileSystem fileSystem,
- IHttpClient httpClient,
ILogger logger,
IServerApplicationPaths appPaths,
IServerApplicationHost appHost,
MediaBrowser.Model.Net.ISocketFactory socketFactory,
- INetworkManager networkManager)
- : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
+ INetworkManager networkManager,
+ IStreamHelper streamHelper)
+ : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
{
_appHost = appHost;
_socketFactory = socketFactory;
@@ -80,12 +83,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
var udpClient = _socketFactory.CreateUdpSocket(localPort);
- var hdHomerunManager = new HdHomerunManager(Logger);
+ var hdHomerunManager = new HdHomerunManager();
try
{
// send url to start streaming
- await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, openCancellationToken).ConfigureAwait(false);
+ await hdHomerunManager.StartStreaming(
+ remoteAddress,
+ localAddress,
+ localPort,
+ _channelCommands,
+ _numTuners,
+ openCancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -103,7 +112,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var taskCompletionSource = new TaskCompletionSource<bool>();
- await StartStreaming(udpClient, hdHomerunManager, remoteAddress, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
+ await StartStreaming(
+ udpClient,
+ hdHomerunManager,
+ remoteAddress,
+ taskCompletionSource,
+ LiveStreamCancellationTokenSource.Token).ConfigureAwait(false);
//OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile;
@@ -148,50 +162,43 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
});
}
- private static void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
- {
- Task.Run(() =>
- {
- openTaskCompletionSource.TrySetResult(true);
- });
- }
-
- private const int RtpHeaderBytes = 12;
-
private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
- var bufferSize = 81920;
-
- byte[] buffer = new byte[bufferSize];
- int read;
- var resolved = false;
-
- using (var source = _socketFactory.CreateNetworkStream(udpClient, false))
- using (var fileStream = FileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
+ byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamDefaults.DefaultCopyToBufferSize);
+ try
{
- var currentCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token).Token;
-
- while ((read = await source.ReadAsync(buffer, 0, buffer.Length, currentCancellationToken).ConfigureAwait(false)) != 0)
+ using (var source = _socketFactory.CreateNetworkStream(udpClient, false))
+ using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
{
- cancellationToken.ThrowIfCancellationRequested();
+ var currentCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token).Token;
+ int read;
+ var resolved = false;
+ while ((read = await source.ReadAsync(buffer, 0, buffer.Length, currentCancellationToken).ConfigureAwait(false)) != 0)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
- currentCancellationToken = cancellationToken;
+ currentCancellationToken = cancellationToken;
- read -= RtpHeaderBytes;
+ read -= RtpHeaderBytes;
- if (read > 0)
- {
- fileStream.Write(buffer, RtpHeaderBytes, read);
- }
+ if (read > 0)
+ {
+ await fileStream.WriteAsync(buffer, RtpHeaderBytes, read).ConfigureAwait(false);
+ }
- if (!resolved)
- {
- resolved = true;
- DateOpened = DateTime.UtcNow;
- Resolve(openTaskCompletionSource);
+ if (!resolved)
+ {
+ resolved = true;
+ DateOpened = DateTime.UtcNow;
+ openTaskCompletionSource.TrySetResult(true);
+ }
}
}
}
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buffer);
+ }
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
index b4395e2e1..d12c96392 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
@@ -16,27 +16,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class LiveStream : ILiveStream
{
- public MediaSourceInfo OriginalMediaSource { get; set; }
- public MediaSourceInfo MediaSource { get; set; }
-
- public int ConsumerCount { get; set; }
-
- public string OriginalStreamId { get; set; }
- public bool EnableStreamSharing { get; set; }
- public string UniqueId { get; }
-
protected readonly IFileSystem FileSystem;
protected readonly IServerApplicationPaths AppPaths;
+ protected readonly IStreamHelper StreamHelper;
protected string TempFilePath;
protected readonly ILogger Logger;
protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
- public string TunerHostId { get; }
-
- public DateTime DateOpened { get; protected set; }
-
- public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
+ public LiveStream(
+ MediaSourceInfo mediaSource,
+ TunerHostInfo tuner,
+ IFileSystem fileSystem,
+ ILogger logger,
+ IServerApplicationPaths appPaths,
+ IStreamHelper streamHelper)
{
OriginalMediaSource = mediaSource;
FileSystem = fileSystem;
@@ -51,11 +45,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
AppPaths = appPaths;
+ StreamHelper = streamHelper;
ConsumerCount = 1;
SetTempFilePath("ts");
}
+ protected virtual int EmptyReadLimit => 1000;
+
+ public MediaSourceInfo OriginalMediaSource { get; set; }
+ public MediaSourceInfo MediaSource { get; set; }
+
+ public int ConsumerCount { get; set; }
+
+ public string OriginalStreamId { get; set; }
+ public bool EnableStreamSharing { get; set; }
+ public string UniqueId { get; }
+
+ public string TunerHostId { get; }
+
+ public DateTime DateOpened { get; protected set; }
+
protected void SetTempFilePath(string extension)
{
TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
@@ -71,24 +81,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
EnableStreamSharing = false;
- Logger.LogInformation("Closing " + GetType().Name);
+ Logger.LogInformation("Closing {Type}", GetType().Name);
LiveStreamCancellationTokenSource.Cancel();
return Task.CompletedTask;
}
- protected Stream GetInputStream(string path, bool allowAsyncFileRead)
- {
- var fileOpenOptions = FileOpenOptions.SequentialScan;
-
- if (allowAsyncFileRead)
- {
- fileOpenOptions |= FileOpenOptions.Asynchronous;
- }
-
- return FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, fileOpenOptions);
- }
+ protected FileStream GetInputStream(string path, bool allowAsyncFileRead)
+ => new FileStream(
+ path,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite,
+ StreamDefaults.DefaultFileStreamBufferSize,
+ allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan);
public Task DeleteTempFiles()
{
@@ -144,8 +151,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
var nextFileInfo = GetNextFile(null);
- var nextFile = nextFileInfo.Item1;
- var isLastFile = nextFileInfo.Item2;
+ var nextFile = nextFileInfo.file;
+ var isLastFile = nextFileInfo.isLastFile;
while (!string.IsNullOrEmpty(nextFile))
{
@@ -155,8 +162,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
seekFile = false;
nextFileInfo = GetNextFile(nextFile);
- nextFile = nextFileInfo.Item1;
- isLastFile = nextFileInfo.Item2;
+ nextFile = nextFileInfo.file;
+ isLastFile = nextFileInfo.isLastFile;
}
Logger.LogInformation("Live Stream ended.");
@@ -180,19 +187,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken)
{
- using (var inputStream = (FileStream)GetInputStream(path, allowAsync))
+ using (var inputStream = GetInputStream(path, allowAsync))
{
if (seekFile)
{
TrySeek(inputStream, -20000);
}
- await ApplicationHost.StreamHelper.CopyToAsync(inputStream, stream, 81920, emptyReadLimit, cancellationToken).ConfigureAwait(false);
+ await StreamHelper.CopyToAsync(
+ inputStream,
+ stream,
+ StreamDefaults.DefaultCopyToBufferSize,
+ emptyReadLimit,
+ cancellationToken).ConfigureAwait(false);
}
}
- protected virtual int EmptyReadLimit => 1000;
-
private void TrySeek(FileStream stream, long offset)
{
if (!stream.CanSeek)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 6c5c80827..a02a9ade4 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -28,14 +28,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private readonly IServerApplicationHost _appHost;
private readonly INetworkManager _networkManager;
private readonly IMediaSourceManager _mediaSourceManager;
-
- public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, INetworkManager networkManager)
+ private readonly IStreamHelper _streamHelper;
+
+ public M3UTunerHost(
+ IServerConfigurationManager config,
+ IMediaSourceManager mediaSourceManager,
+ ILogger logger,
+ IJsonSerializer jsonSerializer,
+ IFileSystem fileSystem,
+ IHttpClient httpClient,
+ IServerApplicationHost appHost,
+ INetworkManager networkManager,
+ IStreamHelper streamHelper)
: base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_networkManager = networkManager;
_mediaSourceManager = mediaSourceManager;
+ _streamHelper = streamHelper;
}
public override string Type => "m3u";
@@ -103,11 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
+ return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
}
}
- return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths);
+ return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths, _streamHelper);
}
public async Task Validate(TunerHostInfo info)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index 7de9931c7..c6e894560 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -19,8 +19,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost;
- public SharedHttpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
- : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
+ public SharedHttpStream(
+ MediaSourceInfo mediaSource,
+ TunerHostInfo tunerHostInfo,
+ string originalStreamId,
+ IFileSystem fileSystem,
+ IHttpClient httpClient,
+ ILogger logger,
+ IServerApplicationPaths appPaths,
+ IServerApplicationHost appHost,
+ IStreamHelper streamHelper)
+ : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
{
_httpClient = httpClient;
_appHost = appHost;
@@ -118,7 +127,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
using (var stream = response.Content)
using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
{
- await ApplicationHost.StreamHelper.CopyToAsync(stream, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken).ConfigureAwait(false);
+ await StreamHelper.CopyToAsync(
+ stream,
+ fileStream,
+ StreamDefaults.DefaultCopyToBufferSize,
+ () => Resolve(openTaskCompletionSource),
+ cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
@@ -128,6 +142,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
Logger.LogError(ex, "Error copying live stream.");
}
+
EnableStreamSharing = false;
await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
});
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 0347100a4..61329160a 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -1375,16 +1375,14 @@ namespace Emby.Server.Implementations.Session
CheckDisposed();
User user = null;
- if (!request.UserId.Equals(Guid.Empty))
+ if (request.UserId != Guid.Empty)
{
- user = _userManager.Users
- .FirstOrDefault(i => i.Id == request.UserId);
+ user = _userManager.GetUserById(request.UserId);
}
if (user == null)
{
- user = _userManager.Users
- .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
+ user = _userManager.GetUserByName(request.Username);
}
if (user != null)
diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs
index fa70a52aa..21a94a4e0 100644
--- a/MediaBrowser.Api/UserService.cs
+++ b/MediaBrowser.Api/UserService.cs
@@ -365,8 +365,8 @@ namespace MediaBrowser.Api
}
_sessionMananger.RevokeUserTokens(user.Id, null);
-
- return _userManager.DeleteUser(user);
+ _userManager.DeleteUser(user);
+ return Task.CompletedTask;
}
/// <summary>
@@ -503,9 +503,14 @@ namespace MediaBrowser.Api
}
}
+ /// <summary>
+ /// Posts the specified request.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>System.Object.</returns>
public async Task<object> Post(CreateUserByName request)
{
- var newUser = await _userManager.CreateUser(request.Name).ConfigureAwait(false);
+ var newUser = _userManager.CreateUser(request.Name);
// no need to authenticate password for new user
if (request.Password != null)
diff --git a/MediaBrowser.Controller/Authentication/AuthenticationException.cs b/MediaBrowser.Controller/Authentication/AuthenticationException.cs
index 045cbcdae..62eca3ea9 100644
--- a/MediaBrowser.Controller/Authentication/AuthenticationException.cs
+++ b/MediaBrowser.Controller/Authentication/AuthenticationException.cs
@@ -1,4 +1,5 @@
using System;
+
namespace MediaBrowser.Controller.Authentication
{
/// <summary>
diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs
index 968d72579..7d245d4aa 100644
--- a/MediaBrowser.Controller/Entities/User.cs
+++ b/MediaBrowser.Controller/Entities/User.cs
@@ -17,13 +17,6 @@ namespace MediaBrowser.Controller.Entities
public class User : BaseItem
{
public static IUserManager UserManager { get; set; }
- public static IXmlSerializer XmlSerializer { get; set; }
-
- /// <summary>
- /// From now on all user paths will be Id-based.
- /// This is for backwards compatibility.
- /// </summary>
- public bool UsesIdForConfigurationPath { get; set; }
/// <summary>
/// Gets or sets the password.
@@ -31,7 +24,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The password.</value>
public string Password { get; set; }
public string EasyPassword { get; set; }
- public string Salt { get; set; }
// Strictly to remove IgnoreDataMember
public override ItemImageInfo[] ImageInfos
@@ -148,46 +140,23 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException"></exception>
public Task Rename(string newName)
{
- if (string.IsNullOrEmpty(newName))
- {
- throw new ArgumentNullException(nameof(newName));
- }
-
- // If only the casing is changing, leave the file system alone
- if (!UsesIdForConfigurationPath && !string.Equals(newName, Name, StringComparison.OrdinalIgnoreCase))
+ if (string.IsNullOrWhiteSpace(newName))
{
- UsesIdForConfigurationPath = true;
-
- // Move configuration
- var newConfigDirectory = GetConfigurationDirectoryPath(newName);
- var oldConfigurationDirectory = ConfigurationDirectoryPath;
-
- // Exceptions will be thrown if these paths already exist
- if (Directory.Exists(newConfigDirectory))
- {
- Directory.Delete(newConfigDirectory, true);
- }
-
- if (Directory.Exists(oldConfigurationDirectory))
- {
- Directory.Move(oldConfigurationDirectory, newConfigDirectory);
- }
- else
- {
- Directory.CreateDirectory(newConfigDirectory);
- }
+ throw new ArgumentException("Username can't be empty", nameof(newName));
}
Name = newName;
- return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem))
- {
- ReplaceAllMetadata = true,
- ImageRefreshMode = MetadataRefreshMode.FullRefresh,
- MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
- ForceSave = true
+ return RefreshMetadata(
+ new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem))
+ {
+ ReplaceAllMetadata = true,
+ ImageRefreshMode = MetadataRefreshMode.FullRefresh,
+ MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
+ ForceSave = true
- }, CancellationToken.None);
+ },
+ CancellationToken.None);
}
public override void UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken)
@@ -216,19 +185,6 @@ namespace MediaBrowser.Controller.Entities
{
var parentPath = ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath;
- // Legacy
- if (!UsesIdForConfigurationPath)
- {
- if (string.IsNullOrEmpty(username))
- {
- throw new ArgumentNullException(nameof(username));
- }
-
- var safeFolderName = FileSystem.GetValidFilename(username);
-
- return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, safeFolderName);
- }
-
// TODO: Remove idPath and just use usernamePath for future releases
var usernamePath = System.IO.Path.Combine(parentPath, username);
var idPath = System.IO.Path.Combine(parentPath, Id.ToString("N", CultureInfo.InvariantCulture));
diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs
index 7f7370893..bbedc0442 100644
--- a/MediaBrowser.Controller/Library/IUserManager.cs
+++ b/MediaBrowser.Controller/Library/IUserManager.cs
@@ -23,6 +23,12 @@ namespace MediaBrowser.Controller.Library
IEnumerable<User> Users { get; }
/// <summary>
+ /// Gets the user ids.
+ /// </summary>
+ /// <value>The users ids.</value>
+ IEnumerable<Guid> UsersIds { get; }
+
+ /// <summary>
/// Occurs when [user updated].
/// </summary>
event EventHandler<GenericEventArgs<User>> UserUpdated;
@@ -92,7 +98,7 @@ namespace MediaBrowser.Controller.Library
/// <returns>User.</returns>
/// <exception cref="ArgumentNullException">name</exception>
/// <exception cref="ArgumentException"></exception>
- Task<User> CreateUser(string name);
+ User CreateUser(string name);
/// <summary>
/// Deletes the user.
@@ -101,7 +107,7 @@ namespace MediaBrowser.Controller.Library
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">user</exception>
/// <exception cref="ArgumentException"></exception>
- Task DeleteUser(User user);
+ void DeleteUser(User user);
/// <summary>
/// Resets the password.
diff --git a/MediaBrowser.Model/IO/StreamDefaults.cs b/MediaBrowser.Model/IO/StreamDefaults.cs
index bef20e74f..1dc29e06e 100644
--- a/MediaBrowser.Model/IO/StreamDefaults.cs
+++ b/MediaBrowser.Model/IO/StreamDefaults.cs
@@ -13,6 +13,6 @@ namespace MediaBrowser.Model.IO
/// <summary>
/// The default file stream buffer size
/// </summary>
- public const int DefaultFileStreamBufferSize = 81920;
+ public const int DefaultFileStreamBufferSize = 4096;
}
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
index c04e98e64..eaebc13e3 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
@@ -50,27 +50,25 @@ namespace MediaBrowser.Providers.TV.TheTVDB
var language = item.GetPreferredMetadataLanguage();
if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
{
- var episodeTvdbId = episode.GetProviderId(MetadataProviders.Tvdb);
-
// Process images
try
{
+ var episodeInfo = new EpisodeInfo
+ {
+ IndexNumber = episode.IndexNumber.Value,
+ ParentIndexNumber = episode.ParentIndexNumber.Value,
+ SeriesProviderIds = series.ProviderIds
+ };
+ string episodeTvdbId = await _tvDbClientManager
+ .GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(episodeTvdbId))
{
- var episodeInfo = new EpisodeInfo
- {
- IndexNumber = episode.IndexNumber.Value,
- ParentIndexNumber = episode.ParentIndexNumber.Value,
- SeriesProviderIds = series.ProviderIds
- };
- episodeTvdbId = await _tvDbClientManager
- .GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false);
- if (string.IsNullOrEmpty(episodeTvdbId))
- {
- _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
- episodeInfo.ParentIndexNumber, episodeInfo.IndexNumber, series.GetProviderId(MetadataProviders.Tvdb));
- return imageResult;
- }
+ _logger.LogError(
+ "Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
+ episodeInfo.ParentIndexNumber,
+ episodeInfo.IndexNumber,
+ series.GetProviderId(MetadataProviders.Tvdb));
+ return imageResult;
}
var episodeResult =
@@ -86,7 +84,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
}
catch (TvDbServerException e)
{
- _logger.LogError(e, "Failed to retrieve episode images for {TvDbId}", episodeTvdbId);
+ _logger.LogError(e, "Failed to retrieve episode images for series {TvDbId}", series.GetProviderId(MetadataProviders.Tvdb));
}
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
index 302d40c6b..e5287048d 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
@@ -36,57 +36,33 @@ namespace MediaBrowser.Providers.TV.TheTVDB
var list = new List<RemoteSearchResult>();
// The search query must either provide an episode number or date
- if (!searchInfo.IndexNumber.HasValue || !searchInfo.PremiereDate.HasValue)
+ if (!searchInfo.IndexNumber.HasValue
+ || !searchInfo.PremiereDate.HasValue
+ || !TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds))
{
return list;
}
- if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds))
+ var metadataResult = await GetEpisode(searchInfo, cancellationToken).ConfigureAwait(false);
+
+ if (!metadataResult.HasMetadata)
{
- try
- {
- var episodeTvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb);
- if (string.IsNullOrEmpty(episodeTvdbId))
- {
- searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(),
- out var seriesTvdbId);
- episodeTvdbId = await _tvDbClientManager
- .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
- .ConfigureAwait(false);
- if (string.IsNullOrEmpty(episodeTvdbId))
- {
- _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
- searchInfo.ParentIndexNumber, searchInfo.IndexNumber, seriesTvdbId);
- return list;
- }
- }
+ return list;
+ }
- var episodeResult = await _tvDbClientManager.GetEpisodesAsync(Convert.ToInt32(episodeTvdbId),
- searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
- var metadataResult = MapEpisodeToResult(searchInfo, episodeResult.Data);
+ var item = metadataResult.Item;
- if (metadataResult.HasMetadata)
- {
- var item = metadataResult.Item;
-
- list.Add(new RemoteSearchResult
- {
- IndexNumber = item.IndexNumber,
- Name = item.Name,
- ParentIndexNumber = item.ParentIndexNumber,
- PremiereDate = item.PremiereDate,
- ProductionYear = item.ProductionYear,
- ProviderIds = item.ProviderIds,
- SearchProviderName = Name,
- IndexNumberEnd = item.IndexNumberEnd
- });
- }
- }
- catch (TvDbServerException e)
- {
- _logger.LogError(e, "Failed to retrieve episode with id {TvDbId}", searchInfo.IndexNumber);
- }
- }
+ list.Add(new RemoteSearchResult
+ {
+ IndexNumber = item.IndexNumber,
+ Name = item.Name,
+ ParentIndexNumber = item.ParentIndexNumber,
+ PremiereDate = item.PremiereDate,
+ ProductionYear = item.ProductionYear,
+ ProviderIds = item.ProviderIds,
+ SearchProviderName = Name,
+ IndexNumberEnd = item.IndexNumberEnd
+ });
return list;
}
@@ -103,36 +79,46 @@ namespace MediaBrowser.Providers.TV.TheTVDB
if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) &&
(searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue))
{
- var tvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb);
- try
- {
- if (string.IsNullOrEmpty(tvdbId))
- {
- tvdbId = await _tvDbClientManager
- .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
- .ConfigureAwait(false);
- if (string.IsNullOrEmpty(tvdbId))
- {
- _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
- searchInfo.ParentIndexNumber, searchInfo.IndexNumber, tvdbId);
- return result;
- }
- }
+ result = await GetEpisode(searchInfo, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ _logger.LogDebug("No series identity found for {EpisodeName}", searchInfo.Name);
+ }
- var episodeResult = await _tvDbClientManager.GetEpisodesAsync(
- Convert.ToInt32(tvdbId), searchInfo.MetadataLanguage,
- cancellationToken).ConfigureAwait(false);
+ return result;
+ }
- result = MapEpisodeToResult(searchInfo, episodeResult.Data);
- }
- catch (TvDbServerException e)
+ private async Task<MetadataResult<Episode>> GetEpisode(EpisodeInfo searchInfo, CancellationToken cancellationToken)
+ {
+ var result = new MetadataResult<Episode>
+ {
+ QueriedById = true
+ };
+
+ string seriesTvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb);
+ string episodeTvdbId = null;
+ try
+ {
+ episodeTvdbId = await _tvDbClientManager
+ .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
+ .ConfigureAwait(false);
+ if (string.IsNullOrEmpty(episodeTvdbId))
{
- _logger.LogError(e, "Failed to retrieve episode with id {TvDbId}", tvdbId);
+ _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
+ searchInfo.ParentIndexNumber, searchInfo.IndexNumber, seriesTvdbId);
+ return result;
}
+
+ var episodeResult = await _tvDbClientManager.GetEpisodesAsync(
+ Convert.ToInt32(episodeTvdbId), searchInfo.MetadataLanguage,
+ cancellationToken).ConfigureAwait(false);
+
+ result = MapEpisodeToResult(searchInfo, episodeResult.Data);
}
- else
+ catch (TvDbServerException e)
{
- _logger.LogDebug("No series identity found for {EpisodeName}", searchInfo.Name);
+ _logger.LogError(e, "Failed to retrieve episode with id {EpisodeTvDbId}, series id {SeriesTvdbId}", episodeTvdbId, seriesTvdbId);
}
return result;
@@ -193,24 +179,54 @@ namespace MediaBrowser.Providers.TV.TheTVDB
});
}
- foreach (var person in episode.GuestStars)
+ // GuestStars is a weird list of names and roles
+ // Example:
+ // 1: Some Actor (Role1
+ // 2: Role2
+ // 3: Role3)
+ // 4: Another Actor (Role1
+ // ...
+ for (var i = 0; i < episode.GuestStars.Length; ++i)
{
- var index = person.IndexOf('(');
- string role = null;
- var name = person;
+ var currentActor = episode.GuestStars[i];
+ var roleStartIndex = currentActor.IndexOf('(');
- if (index != -1)
+ if (roleStartIndex == -1)
{
- role = person.Substring(index + 1).Trim().TrimEnd(')');
+ result.AddPerson(new PersonInfo
+ {
+ Type = PersonType.GuestStar,
+ Name = currentActor,
+ Role = string.Empty
+ });
+ continue;
+ }
+
+ var roles = new List<string> {currentActor.Substring(roleStartIndex + 1)};
+
+ // Fetch all roles
+ for (var j = i + 1; j < episode.GuestStars.Length; ++j)
+ {
+ var currentRole = episode.GuestStars[j];
+ var roleEndIndex = currentRole.IndexOf(')');
+
+ if (roleEndIndex == -1)
+ {
+ roles.Add(currentRole);
+ continue;
+ }
- name = person.Substring(0, index).Trim();
+ roles.Add(currentRole.TrimEnd(')'));
+ // Update the outer index (keep in mind it adds 1 after the iteration)
+ i = j;
+ break;
}
result.AddPerson(new PersonInfo
{
Type = PersonType.GuestStar,
- Name = name,
- Role = role
+ Name = currentActor.Substring(0, roleStartIndex).Trim(),
+ Role = string.Join(", ", roles)
});
}