From 4519ce26e2250cb233836296d292ddb7b3cf6346 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 31 Jan 2019 00:24:53 -0800 Subject: Upgrade crypto provider, retarget better framework --- .../Cryptography/CryptographyProvider.cs | 171 ++++++++++++++++----- .../Emby.Server.Implementations.csproj | 2 +- Emby.Server.Implementations/Library/UserManager.cs | 31 ++-- 3 files changed, 147 insertions(+), 57 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 09fdbc856d..ca6ae2bb2d 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,40 +1,131 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Cryptography -{ - public class CryptographyProvider : ICryptoProvider - { - public Guid GetMD5(string str) - { - return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); - } - - public byte[] ComputeSHA1(byte[] bytes) - { - using (var provider = SHA1.Create()) - { - return provider.ComputeHash(bytes); - } - } - - public byte[] ComputeMD5(Stream str) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(str); - } - } - - public byte[] ComputeMD5(byte[] bytes) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(bytes); - } - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Cryptography +{ + public class CryptographyProvider : ICryptoProvider + { + private List SupportedHashMethods = new List(); + private string DefaultHashMethod = "SHA256"; + private RandomNumberGenerator rng; + private int defaultiterations = 1000; + public CryptographyProvider() + { + //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 + //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one + SupportedHashMethods = new List + { + "MD5" + ,"System.Security.Cryptography.MD5" + ,"SHA" + ,"SHA1" + ,"System.Security.Cryptography.SHA1" + ,"SHA256" + ,"SHA-256" + ,"System.Security.Cryptography.SHA256" + ,"SHA384" + ,"SHA-384" + ,"System.Security.Cryptography.SHA384" + ,"SHA512" + ,"SHA-512" + ,"System.Security.Cryptography.SHA512" + }; + rng = RandomNumberGenerator.Create(); + } + + public Guid GetMD5(string str) + { + return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); + } + + public byte[] ComputeSHA1(byte[] bytes) + { + using (var provider = SHA1.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public byte[] ComputeMD5(Stream str) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(str); + } + } + + public byte[] ComputeMD5(byte[] bytes) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public IEnumerable GetSupportedHashMethods() + { + return SupportedHashMethods; + } + + private byte[] PBKDF2(string method, byte[] bytes, byte[] salt) + { + using (var r = new Rfc2898DeriveBytes(bytes, salt, defaultiterations, new HashAlgorithmName(method))) + { + return r.GetBytes(32); + } + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes) + { + return ComputeHash(HashMethod, bytes, new byte[0]); + } + + public byte[] ComputeHashWithDefaultMethod(byte[] bytes) + { + return ComputeHash(DefaultHashMethod, bytes); + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) + { + if (SupportedHashMethods.Contains(HashMethod)) + { + if (salt.Length == 0) + { + using (var h = HashAlgorithm.Create(HashMethod)) + { + return h.ComputeHash(bytes); + } + } + else + { + return PBKDF2(HashMethod, bytes, salt); + } + } + else + { + throw new CryptographicException(String.Format("Requested hash method is not supported: {0}", HashMethod)); + } + } + + public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) + { + return PBKDF2(DefaultHashMethod, bytes, salt); + } + + public byte[] ComputeHash(PasswordHash hash) + { + return ComputeHash(hash.Id, hash.HashBytes, hash.SaltBytes); + } + + public byte[] GenerateSalt() + { + byte[] salt = new byte[8]; + rng.GetBytes(salt); + return salt; + } + } +} diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 3aa617b021..df7963b023 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -35,7 +35,7 @@ - netstandard2.0 + netcoreapp2.1 false diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 05fce4542f..70639dad5f 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Events; @@ -220,22 +221,20 @@ namespace Emby.Server.Implementations.Library } } - public bool IsValidUsername(string username) - { - // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) - foreach (var currentChar in username) - { - if (!IsValidUsernameCharacter(currentChar)) - { - return false; - } - } - return true; - } - - private static bool IsValidUsernameCharacter(char i) - { - return !char.Equals(i, '<') && !char.Equals(i, '>'); + public bool IsValidUsername(string username) + { + //The old way was dumb, we should make it less dumb, lets do so. + //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 + string UserNameRegex = "^[\\w-'._@]*$"; + // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) + return Regex.IsMatch(username, UserNameRegex); + } + + private static bool IsValidUsernameCharacter(char i) + { + string UserNameRegex = "^[\\w-'._@]*$"; + return Regex.IsMatch(i.ToString(), UserNameRegex); } public string MakeValidUsername(string username) -- cgit v1.2.3 From 05bbf71b6d97614888efe103f763753e4487cc2c Mon Sep 17 00:00:00 2001 From: Phallacy Date: Tue, 12 Feb 2019 02:16:03 -0800 Subject: sha256 with salt auth and sha1 interop --- .../Cryptography/CryptographyProvider.cs | 2 +- .../Library/DefaultAuthenticationProvider.cs | 167 +- Emby.Server.Implementations/Library/UserManager.cs | 2416 ++++++++++---------- MediaBrowser.Model/Cryptography/ICryptoProvider.cs | 29 +- MediaBrowser.Model/Cryptography/PasswordHash.cs | 37 +- 5 files changed, 1388 insertions(+), 1263 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index ca6ae2bb2d..4f2bc1b030 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -10,7 +10,7 @@ namespace Emby.Server.Implementations.Cryptography public class CryptographyProvider : ICryptoProvider { private List SupportedHashMethods = new List(); - private string DefaultHashMethod = "SHA256"; + public string DefaultHashMethod => "SHA256"; private RandomNumberGenerator rng; private int defaultiterations = 1000; public CryptographyProvider() diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 4013ac0c80..92346c65aa 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Text; using System.Threading.Tasks; using MediaBrowser.Controller.Authentication; @@ -19,31 +20,110 @@ namespace Emby.Server.Implementations.Library public bool IsEnabled => true; + + //This is dumb and an artifact of the backwards way auth providers were designed. + //This version of authenticate was never meant to be called, but needs to be here for interface compat + //Only the providers that don't provide local user support use this public Task Authenticate(string username, string password) { throw new NotImplementedException(); } - public Task Authenticate(string username, string password, User resolvedUser) - { - if (resolvedUser == null) - { - throw new Exception("Invalid username or password"); - } - - var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); - if (!success) - { - throw new Exception("Invalid username or password"); - } + //This is the verson that we need to use for local users. Because reasons. + public Task Authenticate(string username, string password, User resolvedUser) + { + ConvertPasswordFormat(resolvedUser); + byte[] passwordbytes = Encoding.UTF8.GetBytes(password); + bool success = false; + if (resolvedUser == null) + { + success = false; + throw new Exception("Invalid username or password"); + } + if (!resolvedUser.Password.Contains("$")) + { + ConvertPasswordFormat(resolvedUser); + } + PasswordHash ReadyHash = new PasswordHash(resolvedUser.Password); + byte[] CalculatedHash; + string CalculatedHashString; + if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == ReadyHash.Id)) + { + if (String.IsNullOrEmpty(ReadyHash.Salt)) + { + CalculatedHash = _cryptographyProvider.ComputeHash(ReadyHash.Id, passwordbytes); + CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); + } + else + { + CalculatedHash = _cryptographyProvider.ComputeHash(ReadyHash.Id, passwordbytes, ReadyHash.SaltBytes); + CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); + } + if (CalculatedHashString == ReadyHash.Hash) + { + success = true; + //throw new Exception("Invalid username or password"); + } + } + else + { + success = false; + throw new Exception(String.Format("Requested crypto method not available in provider: {0}", ReadyHash.Id)); + } + + //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + + if (!success) + { + throw new Exception("Invalid username or password"); + } + + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); + //This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change + //but at least they are in the new format. + private void ConvertPasswordFormat(User user) + { + if (!string.IsNullOrEmpty(user.Password)) + { + if (!user.Password.Contains("$")) + { + string hash = user.Password; + user.Password = String.Format("$SHA1${0}", hash); + } + if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) + { + string hash = user.EasyPassword; + user.EasyPassword = String.Format("$SHA1${0}", hash); + } + } } + // OLD VERSION //public Task Authenticate(string username, string password, User resolvedUser) + // OLD VERSION //{ + // OLD VERSION // if (resolvedUser == null) + // OLD VERSION // { + // OLD VERSION // throw new Exception("Invalid username or password"); + // OLD VERSION // } + // OLD VERSION // + // OLD VERSION // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + // OLD VERSION // + // OLD VERSION // if (!success) + // OLD VERSION // { + // OLD VERSION // throw new Exception("Invalid username or password"); + // OLD VERSION // } + // OLD VERSION // + // OLD VERSION // return Task.FromResult(new ProviderAuthenticationResult + // OLD VERSION // { + // OLD VERSION // Username = username + // OLD VERSION // }); + // OLD VERSION //} + public Task HasPassword(User user) { var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); @@ -57,19 +137,26 @@ namespace Emby.Server.Implementations.Library public Task ChangePassword(User user, string newPassword) { - string newPasswordHash = null; - - if (newPassword != null) + //string newPasswordHash = null; + ConvertPasswordFormat(user); + PasswordHash passwordHash = new PasswordHash(user.Password); + if(passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) { - newPasswordHash = GetHashedString(user, newPassword); + passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); + passwordHash.Salt = BitConverter.ToString(passwordHash.SaltBytes).Replace("-",""); + passwordHash.Id = _cryptographyProvider.DefaultHashMethod; + passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); + }else if (newPassword != null) + { + passwordHash.Hash = GetHashedString(user, newPassword); } - if (string.IsNullOrWhiteSpace(newPasswordHash)) + if (string.IsNullOrWhiteSpace(passwordHash.Hash)) { - throw new ArgumentNullException(nameof(newPasswordHash)); + throw new ArgumentNullException(nameof(passwordHash.Hash)); } - user.Password = newPasswordHash; + user.Password = passwordHash.ToString(); return Task.CompletedTask; } @@ -86,19 +173,39 @@ namespace Emby.Server.Implementations.Library return GetHashedString(user, string.Empty); } + public string GetHashedStringChangeAuth(string NewPassword, PasswordHash passwordHash) + { + return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(NewPassword), passwordHash.SaltBytes)).Replace("-", string.Empty); + } + /// /// Gets the hashed string. /// - public string GetHashedString(User user, string str) - { - var salt = user.Salt; - if (salt != null) + public string GetHashedString(User user, string str) + { + //This is legacy. Deprecated in the auth method. + //return BitConverter.ToString(_cryptoProvider2.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + PasswordHash passwordHash; + if (String.IsNullOrEmpty(user.Password)) + { + passwordHash = new PasswordHash(_cryptographyProvider); + } + else { - // return BCrypt.HashPassword(str, salt); + ConvertPasswordFormat(user); + passwordHash = new PasswordHash(user.Password); + } + if (passwordHash.SaltBytes != null) + { + return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str), passwordHash.SaltBytes)).Replace("-",string.Empty); + } + else + { + return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + //throw new Exception("User does not have a hash, this should not be possible"); } - // legacy - return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + } } } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 40eda52c6a..a139c4e736 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -1,222 +1,222 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -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; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.Library -{ - /// - /// Class UserManager - /// - public class UserManager : IUserManager - { - /// - /// Gets the users. - /// - /// The users. - public IEnumerable Users => _users; - - private User[] _users; - - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// Gets or sets the configuration manager. - /// - /// The configuration manager. - private IServerConfigurationManager ConfigurationManager { get; set; } - - /// - /// Gets the active user repository - /// - /// The user repository. - private IUserRepository UserRepository { get; set; } - public event EventHandler> UserPasswordChanged; - - private readonly IXmlSerializer _xmlSerializer; - private readonly IJsonSerializer _jsonSerializer; - - private readonly INetworkManager _networkManager; - - private readonly Func _imageProcessorFactory; - private readonly Func _dtoServiceFactory; - private readonly IServerApplicationHost _appHost; - private readonly IFileSystem _fileSystem; - private readonly ICryptoProvider _cryptographyProvider; - - private IAuthenticationProvider[] _authenticationProviders; - private DefaultAuthenticationProvider _defaultAuthenticationProvider; - - public UserManager( - ILoggerFactory loggerFactory, - IServerConfigurationManager configurationManager, - IUserRepository userRepository, - IXmlSerializer xmlSerializer, - INetworkManager networkManager, - Func imageProcessorFactory, - Func dtoServiceFactory, - IServerApplicationHost appHost, - IJsonSerializer jsonSerializer, - IFileSystem fileSystem, - ICryptoProvider cryptographyProvider) - { - _logger = loggerFactory.CreateLogger(nameof(UserManager)); - UserRepository = userRepository; - _xmlSerializer = xmlSerializer; - _networkManager = networkManager; - _imageProcessorFactory = imageProcessorFactory; - _dtoServiceFactory = dtoServiceFactory; - _appHost = appHost; - _jsonSerializer = jsonSerializer; - _fileSystem = fileSystem; - _cryptographyProvider = cryptographyProvider; - ConfigurationManager = configurationManager; - _users = Array.Empty(); - - DeletePinFile(); - } - - public NameIdPair[] GetAuthenticationProviders() - { - return _authenticationProviders - .Where(i => i.IsEnabled) - .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) - .ThenBy(i => i.Name) - .Select(i => new NameIdPair - { - Name = i.Name, - Id = GetAuthenticationProviderId(i) - }) - .ToArray(); - } - - public void AddParts(IEnumerable authenticationProviders) - { - _authenticationProviders = authenticationProviders.ToArray(); - - _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); - } - - #region UserUpdated Event - /// - /// Occurs when [user updated]. - /// - public event EventHandler> UserUpdated; - public event EventHandler> UserPolicyUpdated; - public event EventHandler> UserConfigurationUpdated; - public event EventHandler> UserLockedOut; - - /// - /// Called when [user updated]. - /// - /// The user. - private void OnUserUpdated(User user) - { - UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - #region UserDeleted Event - /// - /// Occurs when [user deleted]. - /// - public event EventHandler> UserDeleted; - /// - /// Called when [user deleted]. - /// - /// The user. - private void OnUserDeleted(User user) - { - UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - /// - /// Gets a User by Id - /// - /// The id. - /// User. - /// - public User GetUserById(Guid id) - { - if (id.Equals(Guid.Empty)) - { - throw new ArgumentNullException(nameof(id)); - } - - return Users.FirstOrDefault(u => u.Id == id); - } - - /// - /// Gets the user by identifier. - /// - /// The identifier. - /// User. - public User GetUserById(string id) - { - return GetUserById(new Guid(id)); - } - - public User GetUserByName(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase)); - } - - public void Initialize() - { - _users = LoadUsers(); - - var users = Users.ToList(); - - // If there are no local users with admin rights, make them all admins - if (!users.Any(i => i.Policy.IsAdministrator)) - { - foreach (var user in users) - { - user.Policy.IsAdministrator = true; - UpdateUserPolicy(user, user.Policy, false); - } - } - } - +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +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; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library +{ + /// + /// Class UserManager + /// + public class UserManager : IUserManager + { + /// + /// Gets the users. + /// + /// The users. + public IEnumerable Users => _users; + + private User[] _users; + + /// + /// The _logger + /// + private readonly ILogger _logger; + + /// + /// Gets or sets the configuration manager. + /// + /// The configuration manager. + private IServerConfigurationManager ConfigurationManager { get; set; } + + /// + /// Gets the active user repository + /// + /// The user repository. + private IUserRepository UserRepository { get; set; } + public event EventHandler> UserPasswordChanged; + + private readonly IXmlSerializer _xmlSerializer; + private readonly IJsonSerializer _jsonSerializer; + + private readonly INetworkManager _networkManager; + + private readonly Func _imageProcessorFactory; + private readonly Func _dtoServiceFactory; + private readonly IServerApplicationHost _appHost; + private readonly IFileSystem _fileSystem; + private readonly ICryptoProvider _cryptographyProvider; + + private IAuthenticationProvider[] _authenticationProviders; + private DefaultAuthenticationProvider _defaultAuthenticationProvider; + + public UserManager( + ILoggerFactory loggerFactory, + IServerConfigurationManager configurationManager, + IUserRepository userRepository, + IXmlSerializer xmlSerializer, + INetworkManager networkManager, + Func imageProcessorFactory, + Func dtoServiceFactory, + IServerApplicationHost appHost, + IJsonSerializer jsonSerializer, + IFileSystem fileSystem, + ICryptoProvider cryptographyProvider) + { + _logger = loggerFactory.CreateLogger(nameof(UserManager)); + UserRepository = userRepository; + _xmlSerializer = xmlSerializer; + _networkManager = networkManager; + _imageProcessorFactory = imageProcessorFactory; + _dtoServiceFactory = dtoServiceFactory; + _appHost = appHost; + _jsonSerializer = jsonSerializer; + _fileSystem = fileSystem; + _cryptographyProvider = cryptographyProvider; + ConfigurationManager = configurationManager; + _users = Array.Empty(); + + DeletePinFile(); + } + + public NameIdPair[] GetAuthenticationProviders() + { + return _authenticationProviders + .Where(i => i.IsEnabled) + .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = GetAuthenticationProviderId(i) + }) + .ToArray(); + } + + public void AddParts(IEnumerable authenticationProviders) + { + _authenticationProviders = authenticationProviders.ToArray(); + + _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); + } + + #region UserUpdated Event + /// + /// Occurs when [user updated]. + /// + public event EventHandler> UserUpdated; + public event EventHandler> UserPolicyUpdated; + public event EventHandler> UserConfigurationUpdated; + public event EventHandler> UserLockedOut; + + /// + /// Called when [user updated]. + /// + /// The user. + private void OnUserUpdated(User user) + { + UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + #endregion + + #region UserDeleted Event + /// + /// Occurs when [user deleted]. + /// + public event EventHandler> UserDeleted; + /// + /// Called when [user deleted]. + /// + /// The user. + private void OnUserDeleted(User user) + { + UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); + } + #endregion + + /// + /// Gets a User by Id + /// + /// The id. + /// User. + /// + public User GetUserById(Guid id) + { + if (id.Equals(Guid.Empty)) + { + throw new ArgumentNullException(nameof(id)); + } + + return Users.FirstOrDefault(u => u.Id == id); + } + + /// + /// Gets the user by identifier. + /// + /// The identifier. + /// User. + public User GetUserById(string id) + { + return GetUserById(new Guid(id)); + } + + public User GetUserByName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase)); + } + + public void Initialize() + { + _users = LoadUsers(); + + var users = Users.ToList(); + + // If there are no local users with admin rights, make them all admins + if (!users.Any(i => i.Policy.IsAdministrator)) + { + foreach (var user in users) + { + user.Policy.IsAdministrator = true; + UpdateUserPolicy(user, user.Policy, false); + } + } + } + public bool IsValidUsername(string username) { //The old way was dumb, we should make it less dumb, lets do so. @@ -231,992 +231,992 @@ namespace Emby.Server.Implementations.Library { string UserNameRegex = "^[\\w-'._@]*$"; return Regex.IsMatch(i.ToString(), UserNameRegex); - } - - public string MakeValidUsername(string username) - { - if (IsValidUsername(username)) - { - return username; - } - - // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) - var builder = new StringBuilder(); - - foreach (var c in username) - { - if (IsValidUsernameCharacter(c)) - { - builder.Append(c); - } - } - return builder.ToString(); - } - - public async Task AuthenticateUser(string username, string password, string hashedPassword, string remoteEndPoint, bool isUserSession) - { - if (string.IsNullOrWhiteSpace(username)) - { - throw new ArgumentNullException(nameof(username)); - } - - var user = Users - .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - - var success = false; - IAuthenticationProvider authenticationProvider = null; - - if (user != null) - { - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.Item1; - success = authResult.Item2; - } - else - { - // user is null - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.Item1; - success = authResult.Item2; - - if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) - { - user = await CreateUser(username).ConfigureAwait(false); - - var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; - if (hasNewUserPolicy != null) - { - var policy = hasNewUserPolicy.GetNewUserPolicy(); - UpdateUserPolicy(user, policy, true); - } - } - } - - if (success && user != null && authenticationProvider != null) - { - var providerId = GetAuthenticationProviderId(authenticationProvider); - - if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) - { - user.Policy.AuthenticationProviderId = providerId; - UpdateUserPolicy(user, user.Policy, true); - } - } - - if (user == null) - { - throw new SecurityException("Invalid username or password entered."); - } - - if (user.Policy.IsDisabled) - { - throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); - } - - if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) - { - throw new SecurityException("Forbidden."); - } - - if (!user.IsParentalScheduleAllowed()) - { - throw new SecurityException("User is not allowed access at this time."); - } - - // Update LastActivityDate and LastLoginDate, then save - if (success) - { - if (isUserSession) - { - user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - UpdateUser(user); - } - UpdateInvalidLoginAttemptCount(user, 0); - } - else - { - UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1); - } - - _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied"); - - return success ? user : null; - } - - private static string GetAuthenticationProviderId(IAuthenticationProvider provider) - { - return provider.GetType().FullName; - } - - private IAuthenticationProvider GetAuthenticationProvider(User user) - { - return GetAuthenticationProviders(user).First(); - } - - private IAuthenticationProvider[] GetAuthenticationProviders(User user) - { - var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; - - var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); - - if (!string.IsNullOrEmpty(authenticationProviderId)) - { - providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); - } - - if (providers.Length == 0) - { - providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; - } - - return providers; - } - - private async Task AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) - { - try - { - var requiresResolvedUser = provider as IRequiresResolvedUser; - if (requiresResolvedUser != null) - { - await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); - } - else - { - await provider.Authenticate(username, password).ConfigureAwait(false); - } - - return true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name); - - return false; - } - } - - private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) - { - bool success = false; - IAuthenticationProvider authenticationProvider = null; - - if (password != null && user != null) - { - // Doesn't look like this is even possible to be used, because of password == null checks below - hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password); - } - - if (password == null) - { - // legacy - success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } - else - { - foreach (var provider in GetAuthenticationProviders(user)) - { - success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - - if (success) - { - authenticationProvider = provider; - break; - } - } - } - - if (user != null) - { - if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) - { - if (password == null) - { - // 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 new Tuple(authenticationProvider, success); - } - - private void UpdateInvalidLoginAttemptCount(User user, int newValue) - { - if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) - { - return; - } - - user.Policy.InvalidLoginAttemptCount = newValue; - - var maxCount = user.Policy.IsAdministrator ? 3 : 5; - - var fireLockout = false; - - if (newValue >= maxCount) - { - _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue); - user.Policy.IsDisabled = true; - - fireLockout = true; - } - - UpdateUserPolicy(user, user.Policy, false); - - if (fireLockout) - { - UserLockedOut?.Invoke(this, new GenericEventArgs(user)); - } - } - - private string GetLocalPasswordHash(User user) - { - return string.IsNullOrEmpty(user.EasyPassword) - ? _defaultAuthenticationProvider.GetEmptyHashedString(user) - : user.EasyPassword; - } - - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.Equals(passwordHash, _defaultAuthenticationProvider.GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); - } - - /// - /// Loads the users from the repository - /// - /// IEnumerable{User}. - private User[] LoadUsers() - { - var users = UserRepository.RetrieveAllUsers(); - - // There always has to be at least one user. - if (users.Count == 0) - { - var defaultName = Environment.UserName; - if (string.IsNullOrWhiteSpace(defaultName)) - { - defaultName = "MyJellyfinUser"; - } - var name = MakeValidUsername(defaultName); - - var user = InstantiateNewUser(name); - - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.CreateUser(user); - - users.Add(user); - - user.Policy.IsAdministrator = true; - user.Policy.EnableContentDeletion = true; - user.Policy.EnableRemoteControlOfOtherUsers = true; - UpdateUserPolicy(user, user.Policy, false); - } - - return users.ToArray(); - } - - public UserDto GetUserDto(User user, string remoteEndPoint = null) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; - var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); - - var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? - hasConfiguredEasyPassword : - hasConfiguredPassword; - - var dto = new UserDto - { - Id = user.Id, - Name = user.Name, - HasPassword = hasPassword, - HasConfiguredPassword = hasConfiguredPassword, - HasConfiguredEasyPassword = hasConfiguredEasyPassword, - LastActivityDate = user.LastActivityDate, - LastLoginDate = user.LastLoginDate, - Configuration = user.Configuration, - ServerId = _appHost.SystemId, - Policy = user.Policy - }; - - if (!hasPassword && Users.Count() == 1) - { - dto.EnableAutoLogin = true; - } - - var image = user.GetImageInfo(ImageType.Primary, 0); - - if (image != null) - { - dto.PrimaryImageTag = GetImageCacheTag(user, image); - - try - { - _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user); - } - catch (Exception ex) - { - // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions - _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name); - } - } - - return dto; - } - - public UserDto GetOfflineUserDto(User user) - { - var dto = GetUserDto(user); - - dto.ServerName = _appHost.FriendlyName; - - return dto; - } - - private string GetImageCacheTag(BaseItem item, ItemImageInfo image) - { - try - { - return _imageProcessorFactory().GetImageCacheTag(item, image); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path); - return null; - } - } - - /// - /// Refreshes metadata for each user - /// - /// The cancellation token. - /// Task. - public async Task RefreshUsersMetadata(CancellationToken cancellationToken) - { - foreach (var user in Users) - { - await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Renames the user. - /// - /// The user. - /// The new name. - /// Task. - /// user - /// - public async Task RenameUser(User user, string newName) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrEmpty(newName)) - { - throw new ArgumentNullException(nameof(newName)); - } - - if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName)); - } - - if (user.Name.Equals(newName, StringComparison.Ordinal)) - { - throw new ArgumentException("The new and old names must be different."); - } - - await user.Rename(newName); - - OnUserUpdated(user); - } - - /// - /// Updates the user. - /// - /// The user. - /// user - /// - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id))) - { - throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id)); - } - - user.DateModified = DateTime.UtcNow; - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.UpdateUser(user); - - OnUserUpdated(user); - } - - public event EventHandler> UserCreated; - - private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); - - /// - /// Creates the user. - /// - /// The name. - /// User. - /// name - /// - public async Task CreateUser(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - if (!IsValidUsername(name)) - { - throw new ArgumentException("Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); - } - - if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - 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 list = Users.ToList(); - list.Add(user); - _users = list.ToArray(); - - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.CreateUser(user); - - EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); - - return user; - } - finally - { - _userListLock.Release(); - } - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - /// - public async Task DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var allUsers = Users.ToList(); - - if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null) - { - 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)); - } - - if (allUsers.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)); - } - - if (user.Policy.IsAdministrator && allUsers.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)); - } - - await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); - - 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); - } - finally - { - _userListLock.Release(); - } - } - - /// - /// Resets the password by clearing it. - /// - /// Task. - public Task ResetPassword(User user) - { - return ChangePassword(user, string.Empty); - } - - public void ResetEasyPassword(User user) - { - ChangeEasyPassword(user, string.Empty, null); - } - - public async Task ChangePassword(User user, string newPassword) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (newPassword != null) - { - newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword); - } - - if (string.IsNullOrWhiteSpace(newPasswordHash)) - { - throw new ArgumentNullException(nameof(newPasswordHash)); - } - - user.EasyPassword = newPasswordHash; - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - /// - /// Instantiates the new user. - /// - /// The name. - /// User. - private static User InstantiateNewUser(string name) - { - return new User - { - Name = name, - Id = Guid.NewGuid(), - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - UsesIdForConfigurationPath = true, - //Salt = BCrypt.GenerateSalt() - }; - } - - private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); - - private string _lastPin; - private PasswordPinCreationResult _lastPasswordPinCreationResult; - private int _pinAttempts; - - private async Task CreatePasswordResetPin() - { - var num = new Random().Next(1, 9999); - - var path = PasswordResetFile; - - var pin = num.ToString("0000", CultureInfo.InvariantCulture); - _lastPin = pin; - - var time = TimeSpan.FromMinutes(5); - var expiration = DateTime.UtcNow.Add(time); - - var text = new StringBuilder(); - - var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty; - - text.AppendLine("Use your web browser to visit:"); - text.AppendLine(string.Empty); - text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html"); - text.AppendLine(string.Empty); - text.AppendLine("Enter the following pin code:"); - text.AppendLine(string.Empty); - text.AppendLine(pin); - text.AppendLine(string.Empty); - - var localExpirationTime = expiration.ToLocalTime(); - // Tuesday, 22 August 2006 06:30 AM - text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture)); - - File.WriteAllText(path, text.ToString(), Encoding.UTF8); - - var result = new PasswordPinCreationResult - { - PinFile = path, - ExpirationDate = expiration - }; - - _lastPasswordPinCreationResult = result; - _pinAttempts = 0; - - return result; - } - - public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) - { - DeletePinFile(); - - var user = string.IsNullOrWhiteSpace(enteredUsername) ? - null : - GetUserByName(enteredUsername); - - var action = ForgotPasswordAction.InNetworkRequired; - string pinFile = null; - DateTime? expirationDate = null; - - if (user != null && !user.Policy.IsAdministrator) - { - action = ForgotPasswordAction.ContactAdmin; - } - else - { - if (isInNetwork) - { - action = ForgotPasswordAction.PinCode; - } - - var result = await CreatePasswordResetPin().ConfigureAwait(false); - pinFile = result.PinFile; - expirationDate = result.ExpirationDate; - } - - return new ForgotPasswordResult - { - Action = action, - PinFile = pinFile, - PinExpirationDate = expirationDate - }; - } - - public async Task RedeemPasswordResetPin(string pin) - { - DeletePinFile(); - - var usersReset = new List(); - - var valid = !string.IsNullOrWhiteSpace(_lastPin) && - string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) && - _lastPasswordPinCreationResult != null && - _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow; - - if (valid) - { - _lastPin = null; - _lastPasswordPinCreationResult = null; - - foreach (var user in Users) - { - await ResetPassword(user).ConfigureAwait(false); - - if (user.Policy.IsDisabled) - { - user.Policy.IsDisabled = false; - UpdateUserPolicy(user, user.Policy, true); - } - usersReset.Add(user.Name); - } - } - else - { - _pinAttempts++; - if (_pinAttempts >= 3) - { - _lastPin = null; - _lastPasswordPinCreationResult = null; - } - } - - return new PinRedeemResult - { - Success = valid, - UsersReset = usersReset.ToArray() - }; - } - - private void DeletePinFile() - { - try - { - _fileSystem.DeleteFile(PasswordResetFile); - } - catch - { - - } - } - - class PasswordPinCreationResult - { - public string PinFile { get; set; } - public DateTime ExpirationDate { get; set; } - } - - public UserPolicy GetUserPolicy(User user) - { - var path = GetPolicyFilePath(user); - - if (!File.Exists(path)) - { - return GetDefaultPolicy(user); - } - - try - { - lock (_policySyncLock) - { - return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path); - } - } - catch (IOException) - { - return GetDefaultPolicy(user); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {path}", path); - - return GetDefaultPolicy(user); - } - } - - private static UserPolicy GetDefaultPolicy(User user) - { - return new UserPolicy - { - EnableContentDownloading = true, - EnableSyncTranscoding = true - }; - } - - private readonly object _policySyncLock = new object(); - public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) - { - var user = GetUserById(userId); - UpdateUserPolicy(user, userPolicy, true); - } - - private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent) - { - // The xml serializer will output differently if the type is not exact - if (userPolicy.GetType() != typeof(UserPolicy)) - { - var json = _jsonSerializer.SerializeToString(userPolicy); - userPolicy = _jsonSerializer.DeserializeFromString(json); - } - - var path = GetPolicyFilePath(user); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_policySyncLock) - { - _xmlSerializer.SerializeToFile(userPolicy, path); - user.Policy = userPolicy; - } - - if (fireEvent) - { - UserPolicyUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - } - - private void DeleteUserPolicy(User user) - { - var path = GetPolicyFilePath(user); - - try - { - lock (_policySyncLock) - { - _fileSystem.DeleteFile(path); - } - } - catch (IOException) - { - - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deleting policy file"); - } - } - - private static string GetPolicyFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml"); - } - - private static string GetConfigurationFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "config.xml"); - } - - public UserConfiguration GetUserConfiguration(User user) - { - var path = GetConfigurationFilePath(user); - - if (!File.Exists(path)) - { - return new UserConfiguration(); - } - - try - { - lock (_configSyncLock) - { - return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path); - } - } - catch (IOException) - { - return new UserConfiguration(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {path}", path); - - return new UserConfiguration(); - } - } - - private readonly object _configSyncLock = new object(); - public void UpdateConfiguration(Guid userId, UserConfiguration config) - { - var user = GetUserById(userId); - UpdateConfiguration(user, config); - } - - public void UpdateConfiguration(User user, UserConfiguration config) - { - UpdateConfiguration(user, config, true); - } - - private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent) - { - var path = GetConfigurationFilePath(user); - - // The xml serializer will output differently if the type is not exact - if (config.GetType() != typeof(UserConfiguration)) - { - var json = _jsonSerializer.SerializeToString(config); - config = _jsonSerializer.DeserializeFromString(json); - } - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_configSyncLock) - { - _xmlSerializer.SerializeToFile(config, path); - user.Configuration = config; - } - - if (fireEvent) - { - UserConfigurationUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - } - } - - public class DeviceAccessEntryPoint : IServerEntryPoint - { - private IUserManager _userManager; - private IAuthenticationRepository _authRepo; - private IDeviceManager _deviceManager; - private ISessionManager _sessionManager; - - public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) - { - _userManager = userManager; - _authRepo = authRepo; - _deviceManager = deviceManager; - _sessionManager = sessionManager; - } - - public Task RunAsync() - { - _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; - - return Task.CompletedTask; - } - - private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) - { - var user = e.Argument; - if (!user.Policy.EnableAllDevices) - { - UpdateDeviceAccess(user); - } - } - - private void UpdateDeviceAccess(User user) - { - var existing = _authRepo.Get(new AuthenticationInfoQuery - { - UserId = user.Id - - }).Items; - - foreach (var authInfo in existing) - { - if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) - { - _sessionManager.Logout(authInfo); - } - } - } - - public void Dispose() - { - - } - } -} + } + + public string MakeValidUsername(string username) + { + if (IsValidUsername(username)) + { + return username; + } + + // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) + var builder = new StringBuilder(); + + foreach (var c in username) + { + if (IsValidUsernameCharacter(c)) + { + builder.Append(c); + } + } + return builder.ToString(); + } + + public async Task AuthenticateUser(string username, string password, string hashedPassword, string remoteEndPoint, bool isUserSession) + { + if (string.IsNullOrWhiteSpace(username)) + { + throw new ArgumentNullException(nameof(username)); + } + + var user = Users + .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + + var success = false; + IAuthenticationProvider authenticationProvider = null; + + if (user != null) + { + var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); + authenticationProvider = authResult.Item1; + success = authResult.Item2; + } + else + { + // user is null + var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); + authenticationProvider = authResult.Item1; + success = authResult.Item2; + + if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) + { + user = await CreateUser(username).ConfigureAwait(false); + + var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; + if (hasNewUserPolicy != null) + { + var policy = hasNewUserPolicy.GetNewUserPolicy(); + UpdateUserPolicy(user, policy, true); + } + } + } + + if (success && user != null && authenticationProvider != null) + { + var providerId = GetAuthenticationProviderId(authenticationProvider); + + if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) + { + user.Policy.AuthenticationProviderId = providerId; + UpdateUserPolicy(user, user.Policy, true); + } + } + + if (user == null) + { + throw new SecurityException("Invalid username or password entered."); + } + + if (user.Policy.IsDisabled) + { + throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); + } + + if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) + { + throw new SecurityException("Forbidden."); + } + + if (!user.IsParentalScheduleAllowed()) + { + throw new SecurityException("User is not allowed access at this time."); + } + + // Update LastActivityDate and LastLoginDate, then save + if (success) + { + if (isUserSession) + { + user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; + UpdateUser(user); + } + UpdateInvalidLoginAttemptCount(user, 0); + } + else + { + UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1); + } + + _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied"); + + return success ? user : null; + } + + private static string GetAuthenticationProviderId(IAuthenticationProvider provider) + { + return provider.GetType().FullName; + } + + private IAuthenticationProvider GetAuthenticationProvider(User user) + { + return GetAuthenticationProviders(user).First(); + } + + private IAuthenticationProvider[] GetAuthenticationProviders(User user) + { + var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; + + var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(authenticationProviderId)) + { + providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); + } + + if (providers.Length == 0) + { + providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; + } + + return providers; + } + + private async Task AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) + { + try + { + var requiresResolvedUser = provider as IRequiresResolvedUser; + if (requiresResolvedUser != null) + { + await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); + } + else + { + await provider.Authenticate(username, password).ConfigureAwait(false); + } + + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name); + + return false; + } + } + + private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) + { + bool success = false; + IAuthenticationProvider authenticationProvider = null; + + if (password != null && user != null) + { + // Doesn't look like this is even possible to be used, because of password == null checks below + hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password); + } + + if (password == null) + { + // legacy + success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + foreach (var provider in GetAuthenticationProviders(user)) + { + success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + + if (success) + { + authenticationProvider = provider; + break; + } + } + } + + if (user != null) + { + if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) + { + if (password == null) + { + // 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 new Tuple(authenticationProvider, success); + } + + private void UpdateInvalidLoginAttemptCount(User user, int newValue) + { + if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) + { + return; + } + + user.Policy.InvalidLoginAttemptCount = newValue; + + var maxCount = user.Policy.IsAdministrator ? 3 : 5; + + var fireLockout = false; + + if (newValue >= maxCount) + { + _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue); + user.Policy.IsDisabled = true; + + fireLockout = true; + } + + UpdateUserPolicy(user, user.Policy, false); + + if (fireLockout) + { + UserLockedOut?.Invoke(this, new GenericEventArgs(user)); + } + } + + private string GetLocalPasswordHash(User user) + { + return string.IsNullOrEmpty(user.EasyPassword) + ? _defaultAuthenticationProvider.GetEmptyHashedString(user) + : user.EasyPassword; + } + + private bool IsPasswordEmpty(User user, string passwordHash) + { + return string.Equals(passwordHash, _defaultAuthenticationProvider.GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + } + + /// + /// Loads the users from the repository + /// + /// IEnumerable{User}. + private User[] LoadUsers() + { + var users = UserRepository.RetrieveAllUsers(); + + // There always has to be at least one user. + if (users.Count == 0) + { + var defaultName = Environment.UserName; + if (string.IsNullOrWhiteSpace(defaultName)) + { + defaultName = "MyJellyfinUser"; + } + var name = MakeValidUsername(defaultName); + + var user = InstantiateNewUser(name); + + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.CreateUser(user); + + users.Add(user); + + user.Policy.IsAdministrator = true; + user.Policy.EnableContentDeletion = true; + user.Policy.EnableRemoteControlOfOtherUsers = true; + UpdateUserPolicy(user, user.Policy, false); + } + + return users.ToArray(); + } + + public UserDto GetUserDto(User user, string remoteEndPoint = null) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; + var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); + + var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? + hasConfiguredEasyPassword : + hasConfiguredPassword; + + var dto = new UserDto + { + Id = user.Id, + Name = user.Name, + HasPassword = hasPassword, + HasConfiguredPassword = hasConfiguredPassword, + HasConfiguredEasyPassword = hasConfiguredEasyPassword, + LastActivityDate = user.LastActivityDate, + LastLoginDate = user.LastLoginDate, + Configuration = user.Configuration, + ServerId = _appHost.SystemId, + Policy = user.Policy + }; + + if (!hasPassword && Users.Count() == 1) + { + dto.EnableAutoLogin = true; + } + + var image = user.GetImageInfo(ImageType.Primary, 0); + + if (image != null) + { + dto.PrimaryImageTag = GetImageCacheTag(user, image); + + try + { + _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user); + } + catch (Exception ex) + { + // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions + _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name); + } + } + + return dto; + } + + public UserDto GetOfflineUserDto(User user) + { + var dto = GetUserDto(user); + + dto.ServerName = _appHost.FriendlyName; + + return dto; + } + + private string GetImageCacheTag(BaseItem item, ItemImageInfo image) + { + try + { + return _imageProcessorFactory().GetImageCacheTag(item, image); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path); + return null; + } + } + + /// + /// Refreshes metadata for each user + /// + /// The cancellation token. + /// Task. + public async Task RefreshUsersMetadata(CancellationToken cancellationToken) + { + foreach (var user in Users) + { + await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Renames the user. + /// + /// The user. + /// The new name. + /// Task. + /// user + /// + public async Task RenameUser(User user, string newName) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrEmpty(newName)) + { + throw new ArgumentNullException(nameof(newName)); + } + + if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName)); + } + + if (user.Name.Equals(newName, StringComparison.Ordinal)) + { + throw new ArgumentException("The new and old names must be different."); + } + + await user.Rename(newName); + + OnUserUpdated(user); + } + + /// + /// Updates the user. + /// + /// The user. + /// user + /// + public void UpdateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id))) + { + throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id)); + } + + user.DateModified = DateTime.UtcNow; + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.UpdateUser(user); + + OnUserUpdated(user); + } + + public event EventHandler> UserCreated; + + private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); + + /// + /// Creates the user. + /// + /// The name. + /// User. + /// name + /// + public async Task CreateUser(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + if (!IsValidUsername(name)) + { + throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); + } + + if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + 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 list = Users.ToList(); + list.Add(user); + _users = list.ToArray(); + + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.CreateUser(user); + + EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); + + return user; + } + finally + { + _userListLock.Release(); + } + } + + /// + /// Deletes the user. + /// + /// The user. + /// Task. + /// user + /// + public async Task DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var allUsers = Users.ToList(); + + if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null) + { + 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)); + } + + if (allUsers.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)); + } + + if (user.Policy.IsAdministrator && allUsers.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)); + } + + await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + + 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); + } + finally + { + _userListLock.Release(); + } + } + + /// + /// Resets the password by clearing it. + /// + /// Task. + public Task ResetPassword(User user) + { + return ChangePassword(user, string.Empty); + } + + public void ResetEasyPassword(User user) + { + ChangeEasyPassword(user, string.Empty, null); + } + + public async Task ChangePassword(User user, string newPassword) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); + + UpdateUser(user); + + UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (newPassword != null) + { + newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException(nameof(newPasswordHash)); + } + + user.EasyPassword = newPasswordHash; + + UpdateUser(user); + + UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + /// + /// Instantiates the new user. + /// + /// The name. + /// User. + private static User InstantiateNewUser(string name) + { + return new User + { + Name = name, + Id = Guid.NewGuid(), + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow, + UsesIdForConfigurationPath = true, + //Salt = BCrypt.GenerateSalt() + }; + } + + private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); + + private string _lastPin; + private PasswordPinCreationResult _lastPasswordPinCreationResult; + private int _pinAttempts; + + private async Task CreatePasswordResetPin() + { + var num = new Random().Next(1, 9999); + + var path = PasswordResetFile; + + var pin = num.ToString("0000", CultureInfo.InvariantCulture); + _lastPin = pin; + + var time = TimeSpan.FromMinutes(5); + var expiration = DateTime.UtcNow.Add(time); + + var text = new StringBuilder(); + + var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty; + + text.AppendLine("Use your web browser to visit:"); + text.AppendLine(string.Empty); + text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html"); + text.AppendLine(string.Empty); + text.AppendLine("Enter the following pin code:"); + text.AppendLine(string.Empty); + text.AppendLine(pin); + text.AppendLine(string.Empty); + + var localExpirationTime = expiration.ToLocalTime(); + // Tuesday, 22 August 2006 06:30 AM + text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture)); + + File.WriteAllText(path, text.ToString(), Encoding.UTF8); + + var result = new PasswordPinCreationResult + { + PinFile = path, + ExpirationDate = expiration + }; + + _lastPasswordPinCreationResult = result; + _pinAttempts = 0; + + return result; + } + + public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) + { + DeletePinFile(); + + var user = string.IsNullOrWhiteSpace(enteredUsername) ? + null : + GetUserByName(enteredUsername); + + var action = ForgotPasswordAction.InNetworkRequired; + string pinFile = null; + DateTime? expirationDate = null; + + if (user != null && !user.Policy.IsAdministrator) + { + action = ForgotPasswordAction.ContactAdmin; + } + else + { + if (isInNetwork) + { + action = ForgotPasswordAction.PinCode; + } + + var result = await CreatePasswordResetPin().ConfigureAwait(false); + pinFile = result.PinFile; + expirationDate = result.ExpirationDate; + } + + return new ForgotPasswordResult + { + Action = action, + PinFile = pinFile, + PinExpirationDate = expirationDate + }; + } + + public async Task RedeemPasswordResetPin(string pin) + { + DeletePinFile(); + + var usersReset = new List(); + + var valid = !string.IsNullOrWhiteSpace(_lastPin) && + string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) && + _lastPasswordPinCreationResult != null && + _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow; + + if (valid) + { + _lastPin = null; + _lastPasswordPinCreationResult = null; + + foreach (var user in Users) + { + await ResetPassword(user).ConfigureAwait(false); + + if (user.Policy.IsDisabled) + { + user.Policy.IsDisabled = false; + UpdateUserPolicy(user, user.Policy, true); + } + usersReset.Add(user.Name); + } + } + else + { + _pinAttempts++; + if (_pinAttempts >= 3) + { + _lastPin = null; + _lastPasswordPinCreationResult = null; + } + } + + return new PinRedeemResult + { + Success = valid, + UsersReset = usersReset.ToArray() + }; + } + + private void DeletePinFile() + { + try + { + _fileSystem.DeleteFile(PasswordResetFile); + } + catch + { + + } + } + + class PasswordPinCreationResult + { + public string PinFile { get; set; } + public DateTime ExpirationDate { get; set; } + } + + public UserPolicy GetUserPolicy(User user) + { + var path = GetPolicyFilePath(user); + + if (!File.Exists(path)) + { + return GetDefaultPolicy(user); + } + + try + { + lock (_policySyncLock) + { + return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path); + } + } + catch (IOException) + { + return GetDefaultPolicy(user); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading policy file: {path}", path); + + return GetDefaultPolicy(user); + } + } + + private static UserPolicy GetDefaultPolicy(User user) + { + return new UserPolicy + { + EnableContentDownloading = true, + EnableSyncTranscoding = true + }; + } + + private readonly object _policySyncLock = new object(); + public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) + { + var user = GetUserById(userId); + UpdateUserPolicy(user, userPolicy, true); + } + + private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent) + { + // The xml serializer will output differently if the type is not exact + if (userPolicy.GetType() != typeof(UserPolicy)) + { + var json = _jsonSerializer.SerializeToString(userPolicy); + userPolicy = _jsonSerializer.DeserializeFromString(json); + } + + var path = GetPolicyFilePath(user); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_policySyncLock) + { + _xmlSerializer.SerializeToFile(userPolicy, path); + user.Policy = userPolicy; + } + + if (fireEvent) + { + UserPolicyUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + } + + private void DeleteUserPolicy(User user) + { + var path = GetPolicyFilePath(user); + + try + { + lock (_policySyncLock) + { + _fileSystem.DeleteFile(path); + } + } + catch (IOException) + { + + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting policy file"); + } + } + + private static string GetPolicyFilePath(User user) + { + return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml"); + } + + private static string GetConfigurationFilePath(User user) + { + return Path.Combine(user.ConfigurationDirectoryPath, "config.xml"); + } + + public UserConfiguration GetUserConfiguration(User user) + { + var path = GetConfigurationFilePath(user); + + if (!File.Exists(path)) + { + return new UserConfiguration(); + } + + try + { + lock (_configSyncLock) + { + return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path); + } + } + catch (IOException) + { + return new UserConfiguration(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading policy file: {path}", path); + + return new UserConfiguration(); + } + } + + private readonly object _configSyncLock = new object(); + public void UpdateConfiguration(Guid userId, UserConfiguration config) + { + var user = GetUserById(userId); + UpdateConfiguration(user, config); + } + + public void UpdateConfiguration(User user, UserConfiguration config) + { + UpdateConfiguration(user, config, true); + } + + private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent) + { + var path = GetConfigurationFilePath(user); + + // The xml serializer will output differently if the type is not exact + if (config.GetType() != typeof(UserConfiguration)) + { + var json = _jsonSerializer.SerializeToString(config); + config = _jsonSerializer.DeserializeFromString(json); + } + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_configSyncLock) + { + _xmlSerializer.SerializeToFile(config, path); + user.Configuration = config; + } + + if (fireEvent) + { + UserConfigurationUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + } + } + + public class DeviceAccessEntryPoint : IServerEntryPoint + { + private IUserManager _userManager; + private IAuthenticationRepository _authRepo; + private IDeviceManager _deviceManager; + private ISessionManager _sessionManager; + + public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) + { + _userManager = userManager; + _authRepo = authRepo; + _deviceManager = deviceManager; + _sessionManager = sessionManager; + } + + public Task RunAsync() + { + _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; + + return Task.CompletedTask; + } + + private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) + { + var user = e.Argument; + if (!user.Policy.EnableAllDevices) + { + UpdateDeviceAccess(user); + } + } + + private void UpdateDeviceAccess(User user) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + UserId = user.Id + + }).Items; + + foreach (var authInfo in existing) + { + if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) + { + _sessionManager.Logout(authInfo); + } + } + } + + public void Dispose() + { + + } + } +} diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index ec7e57fec5..8accc696e3 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -1,15 +1,15 @@ -using System; -using System.IO; -using System.Collections.Generic; - -namespace MediaBrowser.Model.Cryptography -{ - public interface ICryptoProvider - { - Guid GetMD5(string str); - byte[] ComputeMD5(Stream str); - byte[] ComputeMD5(byte[] bytes); - byte[] ComputeSHA1(byte[] bytes); +using System; +using System.IO; +using System.Collections.Generic; + +namespace MediaBrowser.Model.Cryptography +{ + public interface ICryptoProvider + { + Guid GetMD5(string str); + byte[] ComputeMD5(Stream str); + byte[] ComputeMD5(byte[] bytes); + byte[] ComputeSHA1(byte[] bytes); IEnumerable GetSupportedHashMethods(); byte[] ComputeHash(string HashMethod, byte[] bytes); byte[] ComputeHashWithDefaultMethod(byte[] bytes); @@ -17,5 +17,6 @@ namespace MediaBrowser.Model.Cryptography byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); byte[] ComputeHash(PasswordHash hash); byte[] GenerateSalt(); - } -} + string DefaultHashMethod { get; } + } +} diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index d37220ab2f..524484b107 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -33,15 +33,15 @@ namespace MediaBrowser.Model.Cryptography if (a.Length == 4) { Salt = a[2]; - SaltBytes = Convert.FromBase64CharArray(Salt.ToCharArray(), 0, Salt.Length); + SaltBytes = FromByteString(Salt); Hash = a[3]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } else { Salt = string.Empty; Hash = a[3]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } } else @@ -49,15 +49,15 @@ namespace MediaBrowser.Model.Cryptography if (a.Length == 4) { Salt = a[2]; - SaltBytes = Convert.FromBase64CharArray(Salt.ToCharArray(), 0, Salt.Length); + SaltBytes = FromByteString(Salt); Hash = a[3]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } else { Salt = string.Empty; Hash = a[2]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } } @@ -68,7 +68,17 @@ namespace MediaBrowser.Model.Cryptography { Id = "SHA256"; SaltBytes = cryptoProvider2.GenerateSalt(); - Salt = Convert.ToBase64String(SaltBytes); + Salt = BitConverter.ToString(SaltBytes).Replace("-", ""); + } + + private byte[] FromByteString(string ByteString) + { + List Bytes = new List(); + for (int i = 0; i < ByteString.Length; i += 2) + { + Bytes.Add(Convert.ToByte(ByteString.Substring(i, 2),16)); + } + return Bytes.ToArray(); } private string SerializeParameters() { @@ -77,7 +87,7 @@ namespace MediaBrowser.Model.Cryptography { ReturnString += String.Format(",{0}={1}", KVP.Key, KVP.Value); } - if (ReturnString[0] == ',') + if ((!string.IsNullOrEmpty(ReturnString)) && ReturnString[0] == ',') { ReturnString = ReturnString.Remove(0, 1); } @@ -85,8 +95,15 @@ namespace MediaBrowser.Model.Cryptography } public override string ToString() - { - return String.Format("${0}${1}${2}${3}", Id, SerializeParameters(), Salt, Hash); + { + string OutString = "$"; + OutString += Id; + if (!string.IsNullOrEmpty(SerializeParameters())) + OutString += $"${SerializeParameters()}"; + if (!string.IsNullOrEmpty(Salt)) + OutString += $"${Salt}"; + OutString += $"${Hash}"; + return OutString; } } -- cgit v1.2.3 From 1ffd443d5aaec408170eaec31923a1cbbe1bb929 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Tue, 12 Feb 2019 22:30:26 -0800 Subject: fixed nul user check to be first per justaman --- .../Library/DefaultAuthenticationProvider.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 92346c65aa..255fd82527 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -32,15 +32,16 @@ namespace Emby.Server.Implementations.Library //This is the verson that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) - { - ConvertPasswordFormat(resolvedUser); - byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - bool success = false; + { + bool success = false; if (resolvedUser == null) { success = false; throw new Exception("Invalid username or password"); } + ConvertPasswordFormat(resolvedUser); + byte[] passwordbytes = Encoding.UTF8.GetBytes(password); + if (!resolvedUser.Password.Contains("$")) { ConvertPasswordFormat(resolvedUser); -- cgit v1.2.3 From 77602aff889e605f8178ecf95592c0d75102e59f Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 13 Feb 2019 00:33:00 -0800 Subject: Minor fixes re:PR870, added null checks from PR876 --- .../Cryptography/CryptographyProvider.cs | 38 +++++++---- .../Data/SqliteUserRepository.cs | 32 +++++++++ .../Library/DefaultAuthenticationProvider.cs | 71 +++++++------------- Emby.Server.Implementations/Library/UserManager.cs | 6 +- MediaBrowser.Model/Cryptography/PasswordHash.cs | 75 +++++++++++++--------- 5 files changed, 124 insertions(+), 98 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 4f2bc1b030..7817989e7f 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Cryptography { public class CryptographyProvider : ICryptoProvider { - private List SupportedHashMethods = new List(); + private HashSet SupportedHashMethods; public string DefaultHashMethod => "SHA256"; private RandomNumberGenerator rng; private int defaultiterations = 1000; @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Cryptography { //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one - SupportedHashMethods = new List + SupportedHashMethods = new HashSet() { "MD5" ,"System.Security.Cryptography.MD5" @@ -71,9 +71,9 @@ namespace Emby.Server.Implementations.Cryptography return SupportedHashMethods; } - private byte[] PBKDF2(string method, byte[] bytes, byte[] salt) - { - using (var r = new Rfc2898DeriveBytes(bytes, salt, defaultiterations, new HashAlgorithmName(method))) + private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) + { + using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations, new HashAlgorithmName(method))) { return r.GetBytes(32); } @@ -102,30 +102,40 @@ namespace Emby.Server.Implementations.Cryptography } else { - return PBKDF2(HashMethod, bytes, salt); + return PBKDF2(HashMethod, bytes, salt,defaultiterations); } } else { throw new CryptographicException(String.Format("Requested hash method is not supported: {0}", HashMethod)); } - } + } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) { - return PBKDF2(DefaultHashMethod, bytes, salt); + return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations); } public byte[] ComputeHash(PasswordHash hash) - { - return ComputeHash(hash.Id, hash.HashBytes, hash.SaltBytes); - } - + { + int iterations = defaultiterations; + if (!hash.Parameters.ContainsKey("iterations")) + { + hash.Parameters.Add("iterations", defaultiterations.ToString()); + } + else + { + try { iterations = int.Parse(hash.Parameters["iterations"]); } + catch (Exception e) { iterations = defaultiterations; throw new Exception($"Couldn't successfully parse iterations value from string:{hash.Parameters["iterations"]}", e); } + } + return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes,iterations); + } + public byte[] GenerateSalt() { - byte[] salt = new byte[8]; + byte[] salt = new byte[64]; rng.GetBytes(salt); return salt; - } + } } } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index db359d7ddc..b3d4573427 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -55,6 +55,7 @@ namespace Emby.Server.Implementations.Data { TryMigrateToLocalUsersTable(connection); } + RemoveEmptyPasswordHashes(); } } @@ -73,6 +74,37 @@ namespace Emby.Server.Implementations.Data } } + private void RemoveEmptyPasswordHashes() + { + foreach (var user in RetrieveAllUsers()) + { + // If the user password is the sha1 hash of the empty string, remove it + if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709") || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709")) + { + continue; + } + + user.Password = null; + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) + { + statement.TryBind("@InternalId", user.InternalId); + statement.TryBind("@data", serialized); + statement.MoveNext(); + } + + }, TransactionMode); + } + } + + } + /// /// Save a user in the repo /// diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 255fd82527..ca6217016b 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -36,32 +36,27 @@ namespace Emby.Server.Implementations.Library bool success = false; if (resolvedUser == null) { - success = false; throw new Exception("Invalid username or password"); } ConvertPasswordFormat(resolvedUser); byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - - if (!resolvedUser.Password.Contains("$")) - { - ConvertPasswordFormat(resolvedUser); - } - PasswordHash ReadyHash = new PasswordHash(resolvedUser.Password); + + PasswordHash readyHash = new PasswordHash(resolvedUser.Password); byte[] CalculatedHash; string CalculatedHashString; - if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == ReadyHash.Id)) + if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == readyHash.Id)) { - if (String.IsNullOrEmpty(ReadyHash.Salt)) + if (String.IsNullOrEmpty(readyHash.Salt)) { - CalculatedHash = _cryptographyProvider.ComputeHash(ReadyHash.Id, passwordbytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } else { - CalculatedHash = _cryptographyProvider.ComputeHash(ReadyHash.Id, passwordbytes, ReadyHash.SaltBytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } - if (CalculatedHashString == ReadyHash.Hash) + if (CalculatedHashString == readyHash.Hash) { success = true; //throw new Exception("Invalid username or password"); @@ -69,8 +64,7 @@ namespace Emby.Server.Implementations.Library } else { - success = false; - throw new Exception(String.Format("Requested crypto method not available in provider: {0}", ReadyHash.Id)); + throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); } //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); @@ -105,26 +99,6 @@ namespace Emby.Server.Implementations.Library } } - // OLD VERSION //public Task Authenticate(string username, string password, User resolvedUser) - // OLD VERSION //{ - // OLD VERSION // if (resolvedUser == null) - // OLD VERSION // { - // OLD VERSION // throw new Exception("Invalid username or password"); - // OLD VERSION // } - // OLD VERSION // - // OLD VERSION // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); - // OLD VERSION // - // OLD VERSION // if (!success) - // OLD VERSION // { - // OLD VERSION // throw new Exception("Invalid username or password"); - // OLD VERSION // } - // OLD VERSION // - // OLD VERSION // return Task.FromResult(new ProviderAuthenticationResult - // OLD VERSION // { - // OLD VERSION // Username = username - // OLD VERSION // }); - // OLD VERSION //} - public Task HasPassword(User user) { var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); @@ -133,7 +107,7 @@ namespace Emby.Server.Implementations.Library private bool IsPasswordEmpty(User user, string passwordHash) { - return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + return string.IsNullOrEmpty(passwordHash); } public Task ChangePassword(User user, string newPassword) @@ -144,7 +118,7 @@ namespace Emby.Server.Implementations.Library if(passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) { passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); - passwordHash.Salt = BitConverter.ToString(passwordHash.SaltBytes).Replace("-",""); + passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); passwordHash.Id = _cryptographyProvider.DefaultHashMethod; passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); }else if (newPassword != null) @@ -164,19 +138,18 @@ namespace Emby.Server.Implementations.Library public string GetPasswordHash(User user) { - return string.IsNullOrEmpty(user.Password) - ? GetEmptyHashedString(user) - : user.Password; + return user.Password; } public string GetEmptyHashedString(User user) { - return GetHashedString(user, string.Empty); + return null; } - public string GetHashedStringChangeAuth(string NewPassword, PasswordHash passwordHash) + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) { - return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(NewPassword), passwordHash.SaltBytes)).Replace("-", string.Empty); + passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); } /// @@ -184,8 +157,6 @@ namespace Emby.Server.Implementations.Library /// public string GetHashedString(User user, string str) { - //This is legacy. Deprecated in the auth method. - //return BitConverter.ToString(_cryptoProvider2.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); PasswordHash passwordHash; if (String.IsNullOrEmpty(user.Password)) { @@ -197,13 +168,15 @@ namespace Emby.Server.Implementations.Library passwordHash = new PasswordHash(user.Password); } if (passwordHash.SaltBytes != null) - { - return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str), passwordHash.SaltBytes)).Replace("-",string.Empty); + { + //the password is modern format with PBKDF and we should take advantage of that + passwordHash.HashBytes = Encoding.UTF8.GetBytes(str); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); } else - { - return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); - //throw new Exception("User does not have a hash, this should not be possible"); + { + //the password has no salt and should be called with the older method for safety + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))); } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index a139c4e736..b8777a4802 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -217,9 +217,8 @@ namespace Emby.Server.Implementations.Library } } - public bool IsValidUsername(string username) + public static bool IsValidUsername(string username) { - //The old way was dumb, we should make it less dumb, lets do so. //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 string UserNameRegex = "^[\\w-'._@]*$"; @@ -229,8 +228,7 @@ namespace Emby.Server.Implementations.Library private static bool IsValidUsernameCharacter(char i) { - string UserNameRegex = "^[\\w-'._@]*$"; - return Regex.IsMatch(i.ToString(), UserNameRegex); + return IsValidUsername(i.ToString()); } public string MakeValidUsername(string username) diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 524484b107..cd61657c18 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -16,70 +16,78 @@ namespace MediaBrowser.Model.Cryptography public byte[] SaltBytes; public string Hash; public byte[] HashBytes; - public PasswordHash(string StorageString) + public PasswordHash(string storageString) { - string[] a = StorageString.Split('$'); - Id = a[1]; - if (a[2].Contains("=")) + string[] SplitStorageString = storageString.Split('$'); + Id = SplitStorageString[1]; + if (SplitStorageString[2].Contains("=")) { - foreach (string paramset in (a[2].Split(','))) + foreach (string paramset in (SplitStorageString[2].Split(','))) { if (!String.IsNullOrEmpty(paramset)) { - string[] fields = paramset.Split('='); - Parameters.Add(fields[0], fields[1]); + string[] fields = paramset.Split('='); + if(fields.Length == 2) + { + Parameters.Add(fields[0], fields[1]); + } } } - if (a.Length == 4) + if (SplitStorageString.Length == 5) { - Salt = a[2]; - SaltBytes = FromByteString(Salt); - Hash = a[3]; - HashBytes = FromByteString(Hash); + Salt = SplitStorageString[3]; + SaltBytes = ConvertFromByteString(Salt); + Hash = SplitStorageString[4]; + HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = a[3]; - HashBytes = FromByteString(Hash); + Hash = SplitStorageString[3]; + HashBytes = ConvertFromByteString(Hash); } } else { - if (a.Length == 4) + if (SplitStorageString.Length == 4) { - Salt = a[2]; - SaltBytes = FromByteString(Salt); - Hash = a[3]; - HashBytes = FromByteString(Hash); + Salt = SplitStorageString[2]; + SaltBytes = ConvertFromByteString(Salt); + Hash = SplitStorageString[3]; + HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = a[2]; - HashBytes = FromByteString(Hash); + Hash = SplitStorageString[2]; + HashBytes = ConvertFromByteString(Hash); } } } - public PasswordHash(ICryptoProvider cryptoProvider2) + public PasswordHash(ICryptoProvider cryptoProvider) { - Id = "SHA256"; - SaltBytes = cryptoProvider2.GenerateSalt(); - Salt = BitConverter.ToString(SaltBytes).Replace("-", ""); + Id = cryptoProvider.DefaultHashMethod; + SaltBytes = cryptoProvider.GenerateSalt(); + Salt = ConvertToByteString(SaltBytes); } - private byte[] FromByteString(string ByteString) + public static byte[] ConvertFromByteString(string byteString) { List Bytes = new List(); - for (int i = 0; i < ByteString.Length; i += 2) + for (int i = 0; i < byteString.Length; i += 2) { - Bytes.Add(Convert.ToByte(ByteString.Substring(i, 2),16)); + Bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); } return Bytes.ToArray(); - } + } + public static string ConvertToByteString(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", ""); + } + private string SerializeParameters() { string ReturnString = String.Empty; @@ -98,10 +106,15 @@ namespace MediaBrowser.Model.Cryptography { string OutString = "$"; OutString += Id; - if (!string.IsNullOrEmpty(SerializeParameters())) - OutString += $"${SerializeParameters()}"; + string paramstring = SerializeParameters(); + if (!string.IsNullOrEmpty(paramstring)) + { + OutString += $"${paramstring}"; + } if (!string.IsNullOrEmpty(Salt)) + { OutString += $"${Salt}"; + } OutString += $"${Hash}"; return OutString; } -- cgit v1.2.3 From 9e58e31de08b4d8e7922038a9f291e720a778b8f Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 13 Feb 2019 00:43:48 -0800 Subject: Update Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs fix to styling Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Library/DefaultAuthenticationProvider.cs | 226 ++++++++++----------- 1 file changed, 113 insertions(+), 113 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index ca6217016b..750807b10c 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -1,39 +1,39 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Library -{ - public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser - { - private readonly ICryptoProvider _cryptographyProvider; - public DefaultAuthenticationProvider(ICryptoProvider crypto) - { - _cryptographyProvider = crypto; - } - - public string Name => "Default"; - - public bool IsEnabled => true; - - - //This is dumb and an artifact of the backwards way auth providers were designed. - //This version of authenticate was never meant to be called, but needs to be here for interface compat - //Only the providers that don't provide local user support use this - public Task Authenticate(string username, string password) - { - throw new NotImplementedException(); - } - - - //This is the verson that we need to use for local users. Because reasons. +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Library +{ + public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser + { + private readonly ICryptoProvider _cryptographyProvider; + public DefaultAuthenticationProvider(ICryptoProvider crypto) + { + _cryptographyProvider = crypto; + } + + public string Name => "Default"; + + public bool IsEnabled => true; + + + //This is dumb and an artifact of the backwards way auth providers were designed. + //This version of authenticate was never meant to be called, but needs to be here for interface compat + //Only the providers that don't provide local user support use this + public Task Authenticate(string username, string password) + { + throw new NotImplementedException(); + } + + + //This is the verson that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) - { - bool success = false; + { + bool success = false; if (resolvedUser == null) { throw new Exception("Invalid username or password"); @@ -42,18 +42,18 @@ namespace Emby.Server.Implementations.Library byte[] passwordbytes = Encoding.UTF8.GetBytes(password); PasswordHash readyHash = new PasswordHash(resolvedUser.Password); - byte[] CalculatedHash; + byte[] CalculatedHash; string CalculatedHashString; if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == readyHash.Id)) { if (String.IsNullOrEmpty(readyHash.Salt)) { - CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } else { - CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } if (CalculatedHashString == readyHash.Hash) @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library } else { - throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); + throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); } //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); @@ -78,10 +78,10 @@ namespace Emby.Server.Implementations.Library { Username = username }); - } - - //This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change - //but at least they are in the new format. + } + + //This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change + //but at least they are in the new format. private void ConvertPasswordFormat(User user) { if (!string.IsNullOrEmpty(user.Password)) @@ -90,71 +90,71 @@ namespace Emby.Server.Implementations.Library { string hash = user.Password; user.Password = String.Format("$SHA1${0}", hash); - } + } if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) { string hash = user.EasyPassword; user.EasyPassword = String.Format("$SHA1${0}", hash); } } - } - - public Task HasPassword(User user) - { - var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); - return Task.FromResult(hasConfiguredPassword); - } - - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.IsNullOrEmpty(passwordHash); - } - - public Task ChangePassword(User user, string newPassword) - { - //string newPasswordHash = null; - ConvertPasswordFormat(user); - PasswordHash passwordHash = new PasswordHash(user.Password); - if(passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) - { - passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); - passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); - passwordHash.Id = _cryptographyProvider.DefaultHashMethod; - passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); - }else if (newPassword != null) - { - passwordHash.Hash = GetHashedString(user, newPassword); - } - - if (string.IsNullOrWhiteSpace(passwordHash.Hash)) - { - throw new ArgumentNullException(nameof(passwordHash.Hash)); - } - - user.Password = passwordHash.ToString(); - - return Task.CompletedTask; - } - - public string GetPasswordHash(User user) - { - return user.Password; - } - - public string GetEmptyHashedString(User user) - { - return null; - } - - public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) - { - passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); - } - - /// - /// Gets the hashed string. - /// + } + + public Task HasPassword(User user) + { + var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); + return Task.FromResult(hasConfiguredPassword); + } + + private bool IsPasswordEmpty(User user, string passwordHash) + { + return string.IsNullOrEmpty(passwordHash); + } + + public Task ChangePassword(User user, string newPassword) + { + //string newPasswordHash = null; + ConvertPasswordFormat(user); + PasswordHash passwordHash = new PasswordHash(user.Password); + if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) + { + passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); + passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); + passwordHash.Id = _cryptographyProvider.DefaultHashMethod; + passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); + }else if (newPassword != null) + { + passwordHash.Hash = GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(passwordHash.Hash)) + { + throw new ArgumentNullException(nameof(passwordHash.Hash)); + } + + user.Password = passwordHash.ToString(); + + return Task.CompletedTask; + } + + public string GetPasswordHash(User user) + { + return user.Password; + } + + public string GetEmptyHashedString(User user) + { + return null; + } + + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) + { + passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); + } + + /// + /// Gets the hashed string. + /// public string GetHashedString(User user, string str) { PasswordHash passwordHash; @@ -163,23 +163,23 @@ namespace Emby.Server.Implementations.Library passwordHash = new PasswordHash(_cryptographyProvider); } else - { + { ConvertPasswordFormat(user); passwordHash = new PasswordHash(user.Password); } if (passwordHash.SaltBytes != null) - { - //the password is modern format with PBKDF and we should take advantage of that - passwordHash.HashBytes = Encoding.UTF8.GetBytes(str); + { + //the password is modern format with PBKDF and we should take advantage of that + passwordHash.HashBytes = Encoding.UTF8.GetBytes(str); return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); } else - { + { //the password has no salt and should be called with the older method for safety return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))); - } - - - } - } -} + } + + + } + } +} -- cgit v1.2.3 From d8e6808d77eb70025b4a26538a1deb814cbe2831 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 13 Feb 2019 00:44:07 -0800 Subject: Update Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs fix to styling Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 750807b10c..33428c05ee 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -91,6 +91,7 @@ namespace Emby.Server.Implementations.Library string hash = user.Password; user.Password = String.Format("$SHA1${0}", hash); } + if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) { string hash = user.EasyPassword; -- cgit v1.2.3 From bca569da420075030fd7de6f9ed8d3abcb7a67cb Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 22:10:37 +0100 Subject: Reduce the amount of db calls during the post scan event --- .../Channels/ChannelPostScanTask.cs | 48 ++++++++-------------- 1 file changed, 18 insertions(+), 30 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs index ad6c537ef0..ec85ffa4d4 100644 --- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs +++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs @@ -35,64 +35,52 @@ namespace Emby.Server.Implementations.Channels public static string GetUserDistinctValue(User user) { var channels = user.Policy.EnabledChannels - .OrderBy(i => i) - .ToList(); + .OrderBy(i => i); - return string.Join("|", channels.ToArray()); + return string.Join("|", channels); } private void CleanDatabase(CancellationToken cancellationToken) { var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds(); - var databaseIds = _libraryManager.GetItemIds(new InternalItemsQuery + var databaseIds = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(Channel).Name } + IncludeItemTypes = new[] { typeof(Channel).Name }, + ExcludeItemIds = installedChannelIds.ToArray() }); - var invalidIds = databaseIds - .Except(installedChannelIds) - .ToList(); - - foreach (var id in invalidIds) + foreach (var channel in databaseIds.Cast()) { cancellationToken.ThrowIfCancellationRequested(); - CleanChannel(id, cancellationToken); + CleanChannel(channel, cancellationToken); } } - private void CleanChannel(Guid id, CancellationToken cancellationToken) + private void CleanChannel(Channel channel, CancellationToken cancellationToken) { - _logger.LogInformation("Cleaning channel {0} from database", id); + _logger.LogInformation("Cleaning channel {0} from database", channel.Id); // Delete all channel items - var allIds = _libraryManager.GetItemIds(new InternalItemsQuery + var items = _libraryManager.GetItemList(new InternalItemsQuery { - ChannelIds = new[] { id } + ChannelIds = new[] { channel.Id } }); - foreach (var deleteId in allIds) + foreach (var item in items) { cancellationToken.ThrowIfCancellationRequested(); - DeleteItem(deleteId); - } - - // Finally, delete the channel itself - DeleteItem(id); - } + _libraryManager.DeleteItem(item, new DeleteOptions + { + DeleteFileLocation = false - private void DeleteItem(Guid id) - { - var item = _libraryManager.GetItemById(id); - - if (item == null) - { - return; + }, false); } - _libraryManager.DeleteItem(item, new DeleteOptions + // Finally, delete the channel itself + _libraryManager.DeleteItem(channel, new DeleteOptions { DeleteFileLocation = false -- cgit v1.2.3 From 0fbc4545d1b9f8b3e5af3324d92ab0c0ef0fafe2 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 14 Feb 2019 17:02:46 +0100 Subject: Address comments --- Emby.Server.Implementations/Channels/ChannelPostScanTask.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs index ec85ffa4d4..3c7cbb1159 100644 --- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs +++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs @@ -44,17 +44,17 @@ namespace Emby.Server.Implementations.Channels { var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds(); - var databaseIds = _libraryManager.GetItemList(new InternalItemsQuery + var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Channel).Name }, ExcludeItemIds = installedChannelIds.ToArray() }); - foreach (var channel in databaseIds.Cast()) + foreach (var channel in uninstalledChannels) { cancellationToken.ThrowIfCancellationRequested(); - CleanChannel(channel, cancellationToken); + CleanChannel((Channel)channel, cancellationToken); } } -- cgit v1.2.3 From bb503638123bafb42594456fa440e71b80088fb5 Mon Sep 17 00:00:00 2001 From: bobberb Date: Sun, 17 Feb 2019 00:21:55 +0000 Subject: Translated using Weblate (Hebrew) Currently translated at 97.8% (92 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/ --- Emby.Server.Implementations/Localization/Core/he.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index fff1d1f0ec..0ed998c4bd 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -1,8 +1,8 @@ { - "Albums": "Albums", + "Albums": "אלבומים", "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", - "Artists": "Artists", + "Application": "אפליקציה", + "Artists": "אמנים", "AuthenticationSucceededWithUserName": "{0} successfully authenticated", "Books": "ספרים", "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", -- cgit v1.2.3 From 9f3aa2cead95ec0a66a518919c179eea4cad5d9c Mon Sep 17 00:00:00 2001 From: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> Date: Mon, 18 Feb 2019 00:31:03 -0800 Subject: Apply suggestions from code review Adding minor stylistic suggestions from Bond-009 Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Cryptography/CryptographyProvider.cs | 41 +- .../Data/SqliteUserRepository.cs | 469 +++++++++++---------- .../Library/DefaultAuthenticationProvider.cs | 9 +- 3 files changed, 264 insertions(+), 255 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 7817989e7f..436443f066 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.Cryptography } private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) - { + { using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations, new HashAlgorithmName(method))) { return r.GetBytes(32); @@ -107,9 +107,9 @@ namespace Emby.Server.Implementations.Cryptography } else { - throw new CryptographicException(String.Format("Requested hash method is not supported: {0}", HashMethod)); + throw new CryptographicException($"Requested hash method is not supported: {HashMethod}")); } - } + } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) { @@ -117,25 +117,32 @@ namespace Emby.Server.Implementations.Cryptography } public byte[] ComputeHash(PasswordHash hash) - { - int iterations = defaultiterations; - if (!hash.Parameters.ContainsKey("iterations")) - { - hash.Parameters.Add("iterations", defaultiterations.ToString()); - } - else - { - try { iterations = int.Parse(hash.Parameters["iterations"]); } - catch (Exception e) { iterations = defaultiterations; throw new Exception($"Couldn't successfully parse iterations value from string:{hash.Parameters["iterations"]}", e); } + { + int iterations = defaultiterations; + if (!hash.Parameters.ContainsKey("iterations")) + { + hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture)); + } + else + { + try + { + iterations = int.Parse(hash.Parameters["iterations"]); + } + catch (Exception e) + { + iterations = defaultiterations; + throw new Exception($"Couldn't successfully parse iterations value from string:{hash.Parameters["iterations"]}", e); + } } - return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes,iterations); - } - + return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations); + } + public byte[] GenerateSalt() { byte[] salt = new byte[64]; rng.GetBytes(salt); return salt; - } + } } } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index b3d4573427..1b6deae7d3 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -1,85 +1,86 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// - /// Class SQLiteUserRepository - /// - public class SqliteUserRepository : BaseSqliteRepository, IUserRepository - { - private readonly IJsonSerializer _jsonSerializer; - - public SqliteUserRepository( - ILoggerFactory loggerFactory, - IServerApplicationPaths appPaths, - IJsonSerializer jsonSerializer) - : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) - { - _jsonSerializer = jsonSerializer; - - DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); - } - - /// - /// Gets the name of the repository - /// - /// The name. - public string Name => "SQLite"; - - /// - /// Opens the connection to the database - /// - /// Task. - public void Initialize() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - var localUsersTableExists = TableExists(connection, "LocalUsersv2"); - - connection.RunQueries(new[] { - "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", - "drop index if exists idx_users" - }); - - if (!localUsersTableExists && TableExists(connection, "Users")) - { - TryMigrateToLocalUsersTable(connection); - } - RemoveEmptyPasswordHashes(); - } - } - - private void TryMigrateToLocalUsersTable(ManagedConnection connection) - { - try - { - connection.RunQueries(new[] - { - "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" - }); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating users database"); - } - } - +using System; +using System.Collections.Generic; +using System.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + /// + /// Class SQLiteUserRepository + /// + public class SqliteUserRepository : BaseSqliteRepository, IUserRepository + { + private readonly IJsonSerializer _jsonSerializer; + + public SqliteUserRepository( + ILoggerFactory loggerFactory, + IServerApplicationPaths appPaths, + IJsonSerializer jsonSerializer) + : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) + { + _jsonSerializer = jsonSerializer; + + DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); + } + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name => "SQLite"; + + /// + /// Opens the connection to the database + /// + /// Task. + public void Initialize() + { + using (var connection = CreateConnection()) + { + RunDefaultInitialization(connection); + + var localUsersTableExists = TableExists(connection, "LocalUsersv2"); + + connection.RunQueries(new[] { + "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", + "drop index if exists idx_users" + }); + + if (!localUsersTableExists && TableExists(connection, "Users")) + { + TryMigrateToLocalUsersTable(connection); + } + RemoveEmptyPasswordHashes(); + } + } + + private void TryMigrateToLocalUsersTable(ManagedConnection connection) + { + try + { + connection.RunQueries(new[] + { + "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" + }); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error migrating users database"); + } + } + private void RemoveEmptyPasswordHashes() { foreach (var user in RetrieveAllUsers()) { // If the user password is the sha1 hash of the empty string, remove it - if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709") || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709")) + if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) + || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) { continue; } @@ -103,160 +104,160 @@ namespace Emby.Server.Implementations.Data } } - } - - /// - /// Save a user in the repo - /// - public void CreateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) - { - statement.TryBind("@guid", user.Id.ToGuidBlob()); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - - var createdUser = GetUser(user.Id, false); - - if (createdUser == null) - { - throw new ApplicationException("created user should never be null"); - } - - user.InternalId = createdUser.InternalId; - - }, TransactionMode); - } - } - } - - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - - }, TransactionMode); - } - } - } - - private User GetUser(Guid guid, bool openLock) - { - using (openLock ? WriteLock.Read() : null) - { - using (var connection = CreateConnection(true)) - { - using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) - { - statement.TryBind("@guid", guid); - - foreach (var row in statement.ExecuteQuery()) - { - return GetUser(row); - } - } - } - } - - return null; - } - - private User GetUser(IReadOnlyList row) - { - var id = row[0].ToInt64(); - var guid = row[1].ReadGuidFromBlob(); - - using (var stream = new MemoryStream(row[2].ToBlob())) - { - stream.Position = 0; - var user = _jsonSerializer.DeserializeFromStream(stream); - user.InternalId = id; - user.Id = guid; - return user; - } - } - - /// - /// Retrieve all users from the database - /// - /// IEnumerable{User}. - public List RetrieveAllUsers() - { - var list = new List(); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) - { - list.Add(GetUser(row)); - } - } - } - - return list; - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - public void DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) - { - statement.TryBind("@id", user.InternalId); - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - } -} + } + + /// + /// Save a user in the repo + /// + public void CreateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) + { + statement.TryBind("@guid", user.Id.ToGuidBlob()); + statement.TryBind("@data", serialized); + + statement.MoveNext(); + } + + var createdUser = GetUser(user.Id, false); + + if (createdUser == null) + { + throw new ApplicationException("created user should never be null"); + } + + user.InternalId = createdUser.InternalId; + + }, TransactionMode); + } + } + } + + public void UpdateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) + { + statement.TryBind("@InternalId", user.InternalId); + statement.TryBind("@data", serialized); + statement.MoveNext(); + } + + }, TransactionMode); + } + } + } + + private User GetUser(Guid guid, bool openLock) + { + using (openLock ? WriteLock.Read() : null) + { + using (var connection = CreateConnection(true)) + { + using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) + { + statement.TryBind("@guid", guid); + + foreach (var row in statement.ExecuteQuery()) + { + return GetUser(row); + } + } + } + } + + return null; + } + + private User GetUser(IReadOnlyList row) + { + var id = row[0].ToInt64(); + var guid = row[1].ReadGuidFromBlob(); + + using (var stream = new MemoryStream(row[2].ToBlob())) + { + stream.Position = 0; + var user = _jsonSerializer.DeserializeFromStream(stream); + user.InternalId = id; + user.Id = guid; + return user; + } + } + + /// + /// Retrieve all users from the database + /// + /// IEnumerable{User}. + public List RetrieveAllUsers() + { + var list = new List(); + + using (WriteLock.Read()) + { + using (var connection = CreateConnection(true)) + { + foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) + { + list.Add(GetUser(row)); + } + } + } + + return list; + } + + /// + /// Deletes the user. + /// + /// The user. + /// Task. + /// user + public void DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) + { + statement.TryBind("@id", user.InternalId); + statement.MoveNext(); + } + }, TransactionMode); + } + } + } + } +} diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 33428c05ee..016de6db78 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Library PasswordHash readyHash = new PasswordHash(resolvedUser.Password); byte[] CalculatedHash; string CalculatedHashString; - if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == readyHash.Id)) + if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) { if (String.IsNullOrEmpty(readyHash.Salt)) { @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library } else { - throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); + throw new Exception(String.Format($"Requested crypto method not available in provider: {readyHash.Id}")); } //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); @@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Library if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) { string hash = user.EasyPassword; - user.EasyPassword = String.Format("$SHA1${0}", hash); + user.EasyPassword = string.Format("$SHA1${0}", hash); } } } @@ -122,7 +122,8 @@ namespace Emby.Server.Implementations.Library passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); passwordHash.Id = _cryptographyProvider.DefaultHashMethod; passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); - }else if (newPassword != null) + } + else if (newPassword != null) { passwordHash.Hash = GetHashedString(user, newPassword); } -- cgit v1.2.3 From 48e7274d3783e57f89f6e1cc76fcd8696e987ec5 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 18 Feb 2019 01:26:01 -0800 Subject: added justaman notes, fixed new bug from emty has removals --- .../Cryptography/CryptographyProvider.cs | 5 +-- .../Library/DefaultAuthenticationProvider.cs | 42 ++++++++++++++++------ Emby.Server.Implementations/Library/UserManager.cs | 4 +-- MediaBrowser.Model/Cryptography/PasswordHash.cs | 42 ++++++++++++---------- 4 files changed, 59 insertions(+), 34 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 436443f066..c4f0346314 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Text; @@ -102,12 +103,12 @@ namespace Emby.Server.Implementations.Cryptography } else { - return PBKDF2(HashMethod, bytes, salt,defaultiterations); + return PBKDF2(HashMethod, bytes, salt, defaultiterations); } } else { - throw new CryptographicException($"Requested hash method is not supported: {HashMethod}")); + throw new CryptographicException($"Requested hash method is not supported: {HashMethod}"); } } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 016de6db78..80026d97c9 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -37,7 +37,17 @@ namespace Emby.Server.Implementations.Library if (resolvedUser == null) { throw new Exception("Invalid username or password"); - } + } + + //As long as jellyfin supports passwordless users, we need this little block here to accomodate + if (IsPasswordEmpty(resolvedUser, password)) + { + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + ConvertPasswordFormat(resolvedUser); byte[] passwordbytes = Encoding.UTF8.GetBytes(password); @@ -106,15 +116,30 @@ namespace Emby.Server.Implementations.Library return Task.FromResult(hasConfiguredPassword); } - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.IsNullOrEmpty(passwordHash); + private bool IsPasswordEmpty(User user, string password) + { + if (string.IsNullOrEmpty(user.Password)) + { + return string.IsNullOrEmpty(password); + } + return false; } public Task ChangePassword(User user, string newPassword) { - //string newPasswordHash = null; - ConvertPasswordFormat(user); + ConvertPasswordFormat(user); + //This is needed to support changing a no password user to a password user + if (string.IsNullOrEmpty(user.Password)) + { + PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider); + newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); + newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes); + newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod; + newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash); + user.Password = newPasswordHash.ToString(); + return Task.CompletedTask; + } + PasswordHash passwordHash = new PasswordHash(user.Password); if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) { @@ -143,11 +168,6 @@ namespace Emby.Server.Implementations.Library return user.Password; } - public string GetEmptyHashedString(User user) - { - return null; - } - public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) { passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index b8777a4802..3daed0c085 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -475,13 +475,13 @@ namespace Emby.Server.Implementations.Library private string GetLocalPasswordHash(User user) { return string.IsNullOrEmpty(user.EasyPassword) - ? _defaultAuthenticationProvider.GetEmptyHashedString(user) + ? null : user.EasyPassword; } private bool IsPasswordEmpty(User user, string passwordHash) { - return string.Equals(passwordHash, _defaultAuthenticationProvider.GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + return string.IsNullOrEmpty(passwordHash); } /// diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index cd61657c18..3a817543bc 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -18,48 +18,52 @@ namespace MediaBrowser.Model.Cryptography public byte[] HashBytes; public PasswordHash(string storageString) { - string[] SplitStorageString = storageString.Split('$'); - Id = SplitStorageString[1]; - if (SplitStorageString[2].Contains("=")) + string[] splitted = storageString.Split('$'); + Id = splitted[1]; + if (splitted[2].Contains("=")) { - foreach (string paramset in (SplitStorageString[2].Split(','))) + foreach (string paramset in (splitted[2].Split(','))) { if (!String.IsNullOrEmpty(paramset)) { string[] fields = paramset.Split('='); - if(fields.Length == 2) + if (fields.Length == 2) { Parameters.Add(fields[0], fields[1]); + } + else + { + throw new Exception($"Malformed parameter in password hash string {paramset}"); } } } - if (SplitStorageString.Length == 5) + if (splitted.Length == 5) { - Salt = SplitStorageString[3]; + Salt = splitted[3]; SaltBytes = ConvertFromByteString(Salt); - Hash = SplitStorageString[4]; + Hash = splitted[4]; HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = SplitStorageString[3]; + Hash = splitted[3]; HashBytes = ConvertFromByteString(Hash); } } else { - if (SplitStorageString.Length == 4) + if (splitted.Length == 4) { - Salt = SplitStorageString[2]; + Salt = splitted[2]; SaltBytes = ConvertFromByteString(Salt); - Hash = SplitStorageString[3]; + Hash = splitted[3]; HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = SplitStorageString[2]; + Hash = splitted[2]; HashBytes = ConvertFromByteString(Hash); } @@ -83,6 +87,7 @@ namespace MediaBrowser.Model.Cryptography } return Bytes.ToArray(); } + public static string ConvertToByteString(byte[] bytes) { return BitConverter.ToString(bytes).Replace("-", ""); @@ -104,19 +109,18 @@ namespace MediaBrowser.Model.Cryptography public override string ToString() { - string OutString = "$"; - OutString += Id; + string outString = "$" +Id; string paramstring = SerializeParameters(); if (!string.IsNullOrEmpty(paramstring)) { - OutString += $"${paramstring}"; + outString += $"${paramstring}"; } if (!string.IsNullOrEmpty(Salt)) { - OutString += $"${Salt}"; + outString += $"${Salt}"; } - OutString += $"${Hash}"; - return OutString; + outString += $"${Hash}"; + return outString; } } -- cgit v1.2.3 From 56e306334201e06f2066e1e7ca1246508346bfa9 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 18 Feb 2019 10:56:01 -0800 Subject: little fixes for JustAMan --- Emby.Server.Implementations/Cryptography/CryptographyProvider.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index c4f0346314..dc528c280a 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -132,8 +132,7 @@ namespace Emby.Server.Implementations.Cryptography } catch (Exception e) { - iterations = defaultiterations; - throw new Exception($"Couldn't successfully parse iterations value from string:{hash.Parameters["iterations"]}", e); + throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e); } } return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations); -- cgit v1.2.3 From c6188e26afa0034c5c255a19b2fc71aa42311e26 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 18 Feb 2019 22:47:02 +0100 Subject: Got to start somewhere --- Emby.Server.Implementations/ApplicationHost.cs | 43 +++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7d77500abc..1b88fae0e5 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -181,11 +181,17 @@ namespace Emby.Server.Implementations /// The logger. protected ILogger Logger { get; set; } + private IPlugin[] _plugins; + /// /// Gets or sets the plugins. /// /// The plugins. - public IPlugin[] Plugins { get; protected set; } + public IPlugin[] Plugins + { + get => _plugins; + protected set => _plugins = value; + } /// /// Gets or sets the logger factory. @@ -1047,6 +1053,41 @@ namespace Emby.Server.Implementations CollectionFolder.JsonSerializer = JsonSerializer; CollectionFolder.ApplicationHost = this; AuthenticatedAttribute.AuthService = AuthService; + + InstallationManager.PluginInstalled += PluginInstalled; + } + + private async void PluginInstalled(object sender, GenericEventArgs args) + { + string dir = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(args.Argument.targetFilename)); + var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.TopDirectoryOnly) + .Select(x => Assembly.LoadFrom(x)) + .SelectMany(x => x.ExportedTypes) + .Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType) + .ToList(); + + types.AddRange(types); + + var plugins = types.Where(x => x.IsAssignableFrom(typeof(IPlugin))) + .Select(CreateInstanceSafe) + .Where(x => x != null) + .Cast() + .Select(LoadPlugin) + .Where(x => x != null) + .ToArray(); + + int oldLen = _plugins.Length; + Array.Resize(ref _plugins, _plugins.Length + plugins.Length); + plugins.CopyTo(_plugins, oldLen); + + var entries = types.Where(x => x.IsAssignableFrom(typeof(IServerEntryPoint))) + .Select(CreateInstanceSafe) + .Where(x => x != null) + .Cast() + .ToList(); + + await Task.WhenAll(StartEntryPoints(entries, true)); + await Task.WhenAll(StartEntryPoints(entries, false)); } /// -- cgit v1.2.3 From 6bbb968b578fe42224227b70e78825bbed5cfc6f Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 20 Feb 2019 00:00:26 -0800 Subject: minor changes and return to netstandard --- .../Cryptography/CryptographyProvider.cs | 5 +- .../Data/SqliteUserRepository.cs | 3 +- .../Emby.Server.Implementations.csproj | 2 +- .../Library/DefaultAuthenticationProvider.cs | 33 +++++---- Emby.Server.Implementations/Library/UserManager.cs | 3 +- MediaBrowser.Model/Cryptography/PasswordHash.cs | 81 ++++++++++++---------- 6 files changed, 72 insertions(+), 55 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index dc528c280a..2f2fd95922 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -73,8 +73,9 @@ namespace Emby.Server.Implementations.Cryptography } private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) - { - using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations, new HashAlgorithmName(method))) + { + //downgrading for now as we need this library to be dotnetstandard compliant + using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) { return r.GetBytes(32); } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index 1b6deae7d3..3df91f71cb 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -54,7 +54,8 @@ namespace Emby.Server.Implementations.Data if (!localUsersTableExists && TableExists(connection, "Users")) { TryMigrateToLocalUsersTable(connection); - } + } + RemoveEmptyPasswordHashes(); } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 86b2efe54d..8356a9501b 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -34,7 +34,7 @@ - netcoreapp2.1 + netstandard2.0 false diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 80026d97c9..2ac3ef424e 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.Library string CalculatedHashString; if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) { - if (String.IsNullOrEmpty(readyHash.Salt)) + if (string.IsNullOrEmpty(readyHash.Salt)) { CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); @@ -65,7 +65,8 @@ namespace Emby.Server.Implementations.Library { CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); - } + } + if (CalculatedHashString == readyHash.Hash) { success = true; @@ -95,18 +96,20 @@ namespace Emby.Server.Implementations.Library private void ConvertPasswordFormat(User user) { if (!string.IsNullOrEmpty(user.Password)) + { + return; + } + + if (!user.Password.Contains("$")) { - if (!user.Password.Contains("$")) - { - string hash = user.Password; - user.Password = String.Format("$SHA1${0}", hash); - } - - if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) - { - string hash = user.EasyPassword; - user.EasyPassword = string.Format("$SHA1${0}", hash); - } + string hash = user.Password; + user.Password = String.Format("$SHA1${0}", hash); + } + + if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) + { + string hash = user.EasyPassword; + user.EasyPassword = string.Format("$SHA1${0}", hash); } } @@ -122,6 +125,7 @@ namespace Emby.Server.Implementations.Library { return string.IsNullOrEmpty(password); } + return false; } @@ -188,7 +192,8 @@ namespace Emby.Server.Implementations.Library { ConvertPasswordFormat(user); passwordHash = new PasswordHash(user.Password); - } + } + if (passwordHash.SaltBytes != null) { //the password is modern format with PBKDF and we should take advantage of that diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 3daed0c085..b74006233e 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -221,9 +221,8 @@ 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 - string UserNameRegex = "^[\\w-'._@]*$"; // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) - return Regex.IsMatch(username, UserNameRegex); + return Regex.IsMatch(username, "^[\\w-'._@]*$"); } private static bool IsValidUsernameCharacter(char i) diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 3a817543bc..49bd510e96 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -10,26 +10,33 @@ namespace MediaBrowser.Model.Cryptography //https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md //$[$=(,=)*][$[$]] - public string Id; - public Dictionary Parameters = new Dictionary(); - public string Salt; - public byte[] SaltBytes; - public string Hash; - public byte[] HashBytes; + private string id; + private Dictionary parameters = new Dictionary(); + private string salt; + private byte[] saltBytes; + private string hash; + private byte[] hashBytes; + public string Id { get => id; set => id = value; } + public Dictionary Parameters { get => parameters; set => parameters = value; } + public string Salt { get => salt; set => salt = value; } + public byte[] SaltBytes { get => saltBytes; set => saltBytes = value; } + public string Hash { get => hash; set => hash = value; } + public byte[] HashBytes { get => hashBytes; set => hashBytes = value; } + public PasswordHash(string storageString) { string[] splitted = storageString.Split('$'); - Id = splitted[1]; + id = splitted[1]; if (splitted[2].Contains("=")) { foreach (string paramset in (splitted[2].Split(','))) { - if (!String.IsNullOrEmpty(paramset)) + if (!string.IsNullOrEmpty(paramset)) { string[] fields = paramset.Split('='); if (fields.Length == 2) { - Parameters.Add(fields[0], fields[1]); + parameters.Add(fields[0], fields[1]); } else { @@ -39,32 +46,32 @@ namespace MediaBrowser.Model.Cryptography } if (splitted.Length == 5) { - Salt = splitted[3]; - SaltBytes = ConvertFromByteString(Salt); - Hash = splitted[4]; - HashBytes = ConvertFromByteString(Hash); + salt = splitted[3]; + saltBytes = ConvertFromByteString(salt); + hash = splitted[4]; + hashBytes = ConvertFromByteString(hash); } else { - Salt = string.Empty; - Hash = splitted[3]; - HashBytes = ConvertFromByteString(Hash); + salt = string.Empty; + hash = splitted[3]; + hashBytes = ConvertFromByteString(hash); } } else { if (splitted.Length == 4) { - Salt = splitted[2]; - SaltBytes = ConvertFromByteString(Salt); - Hash = splitted[3]; - HashBytes = ConvertFromByteString(Hash); + salt = splitted[2]; + saltBytes = ConvertFromByteString(salt); + hash = splitted[3]; + hashBytes = ConvertFromByteString(hash); } else { - Salt = string.Empty; - Hash = splitted[2]; - HashBytes = ConvertFromByteString(Hash); + salt = string.Empty; + hash = splitted[2]; + hashBytes = ConvertFromByteString(hash); } } @@ -73,9 +80,9 @@ namespace MediaBrowser.Model.Cryptography public PasswordHash(ICryptoProvider cryptoProvider) { - Id = cryptoProvider.DefaultHashMethod; - SaltBytes = cryptoProvider.GenerateSalt(); - Salt = ConvertToByteString(SaltBytes); + id = cryptoProvider.DefaultHashMethod; + saltBytes = cryptoProvider.GenerateSalt(); + salt = ConvertToByteString(SaltBytes); } public static byte[] ConvertFromByteString(string byteString) @@ -95,31 +102,35 @@ namespace MediaBrowser.Model.Cryptography private string SerializeParameters() { - string ReturnString = String.Empty; - foreach (var KVP in Parameters) + string ReturnString = string.Empty; + foreach (var KVP in parameters) { - ReturnString += String.Format(",{0}={1}", KVP.Key, KVP.Value); - } + ReturnString += $",{KVP.Key}={KVP.Value}"; + } + if ((!string.IsNullOrEmpty(ReturnString)) && ReturnString[0] == ',') { ReturnString = ReturnString.Remove(0, 1); - } + } + return ReturnString; } public override string ToString() { - string outString = "$" +Id; + string outString = "$" +id; string paramstring = SerializeParameters(); if (!string.IsNullOrEmpty(paramstring)) { outString += $"${paramstring}"; } - if (!string.IsNullOrEmpty(Salt)) + + if (!string.IsNullOrEmpty(salt)) { - outString += $"${Salt}"; + outString += $"${salt}"; } - outString += $"${Hash}"; + + outString += $"${hash}"; return outString; } } -- cgit v1.2.3 From 098de6b0501eaa0375fc5bfd7cff369815b57718 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 20 Feb 2019 01:17:30 -0800 Subject: made newlines into linux newlines --- .../Cryptography/CryptographyProvider.cs | 294 ++++++------ .../Data/SqliteUserRepository.cs | 526 ++++++++++----------- .../Library/DefaultAuthenticationProvider.cs | 362 +++++++------- 3 files changed, 590 insertions(+), 592 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 2f2fd95922..ea719309cd 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,149 +1,149 @@ -using System; -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Cryptography -{ - public class CryptographyProvider : ICryptoProvider - { - private HashSet SupportedHashMethods; - public string DefaultHashMethod => "SHA256"; - private RandomNumberGenerator rng; - private int defaultiterations = 1000; - public CryptographyProvider() - { - //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 - //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one - SupportedHashMethods = new HashSet() - { - "MD5" - ,"System.Security.Cryptography.MD5" - ,"SHA" - ,"SHA1" - ,"System.Security.Cryptography.SHA1" - ,"SHA256" - ,"SHA-256" - ,"System.Security.Cryptography.SHA256" - ,"SHA384" - ,"SHA-384" - ,"System.Security.Cryptography.SHA384" - ,"SHA512" - ,"SHA-512" - ,"System.Security.Cryptography.SHA512" - }; - rng = RandomNumberGenerator.Create(); - } - - public Guid GetMD5(string str) - { - return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); - } - - public byte[] ComputeSHA1(byte[] bytes) - { - using (var provider = SHA1.Create()) - { - return provider.ComputeHash(bytes); - } - } - - public byte[] ComputeMD5(Stream str) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(str); - } - } - - public byte[] ComputeMD5(byte[] bytes) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(bytes); - } - } - - public IEnumerable GetSupportedHashMethods() - { - return SupportedHashMethods; - } - - private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) +using System.IO; +using System.Security.Cryptography; +using System.Text; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Cryptography +{ + public class CryptographyProvider : ICryptoProvider + { + private HashSet SupportedHashMethods; + public string DefaultHashMethod => "SHA256"; + private RandomNumberGenerator rng; + private int defaultiterations = 1000; + public CryptographyProvider() { - //downgrading for now as we need this library to be dotnetstandard compliant - using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) - { - return r.GetBytes(32); - } - } - - public byte[] ComputeHash(string HashMethod, byte[] bytes) - { - return ComputeHash(HashMethod, bytes, new byte[0]); - } - - public byte[] ComputeHashWithDefaultMethod(byte[] bytes) - { - return ComputeHash(DefaultHashMethod, bytes); - } - - public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) - { - if (SupportedHashMethods.Contains(HashMethod)) - { - if (salt.Length == 0) - { - using (var h = HashAlgorithm.Create(HashMethod)) - { - return h.ComputeHash(bytes); - } - } - else - { - return PBKDF2(HashMethod, bytes, salt, defaultiterations); - } - } - else - { - throw new CryptographicException($"Requested hash method is not supported: {HashMethod}"); - } - } - - public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) - { - return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations); - } - - public byte[] ComputeHash(PasswordHash hash) - { - int iterations = defaultiterations; - if (!hash.Parameters.ContainsKey("iterations")) - { - hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture)); - } - else - { - try - { - iterations = int.Parse(hash.Parameters["iterations"]); - } - catch (Exception e) - { - throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e); - } - } - return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations); - } - - public byte[] GenerateSalt() - { - byte[] salt = new byte[64]; - rng.GetBytes(salt); - return salt; - } - } -} + //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 + //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one + SupportedHashMethods = new HashSet() + { + "MD5" + ,"System.Security.Cryptography.MD5" + ,"SHA" + ,"SHA1" + ,"System.Security.Cryptography.SHA1" + ,"SHA256" + ,"SHA-256" + ,"System.Security.Cryptography.SHA256" + ,"SHA384" + ,"SHA-384" + ,"System.Security.Cryptography.SHA384" + ,"SHA512" + ,"SHA-512" + ,"System.Security.Cryptography.SHA512" + }; + rng = RandomNumberGenerator.Create(); + } + + public Guid GetMD5(string str) + { + return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); + } + + public byte[] ComputeSHA1(byte[] bytes) + { + using (var provider = SHA1.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public byte[] ComputeMD5(Stream str) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(str); + } + } + + public byte[] ComputeMD5(byte[] bytes) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public IEnumerable GetSupportedHashMethods() + { + return SupportedHashMethods; + } + + private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) + { + //downgrading for now as we need this library to be dotnetstandard compliant + using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) + { + return r.GetBytes(32); + } + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes) + { + return ComputeHash(HashMethod, bytes, new byte[0]); + } + + public byte[] ComputeHashWithDefaultMethod(byte[] bytes) + { + return ComputeHash(DefaultHashMethod, bytes); + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) + { + if (SupportedHashMethods.Contains(HashMethod)) + { + if (salt.Length == 0) + { + using (var h = HashAlgorithm.Create(HashMethod)) + { + return h.ComputeHash(bytes); + } + } + else + { + return PBKDF2(HashMethod, bytes, salt, defaultiterations); + } + } + else + { + throw new CryptographicException($"Requested hash method is not supported: {HashMethod}"); + } + } + + public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) + { + return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations); + } + + public byte[] ComputeHash(PasswordHash hash) + { + int iterations = defaultiterations; + if (!hash.Parameters.ContainsKey("iterations")) + { + hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture)); + } + else + { + try + { + iterations = int.Parse(hash.Parameters["iterations"]); + } + catch (Exception e) + { + throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e); + } + } + return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations); + } + + public byte[] GenerateSalt() + { + byte[] salt = new byte[64]; + rng.GetBytes(salt); + return salt; + } + } +} diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index 3df91f71cb..182df0edc9 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -1,264 +1,264 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// - /// Class SQLiteUserRepository - /// - public class SqliteUserRepository : BaseSqliteRepository, IUserRepository - { - private readonly IJsonSerializer _jsonSerializer; - - public SqliteUserRepository( - ILoggerFactory loggerFactory, - IServerApplicationPaths appPaths, - IJsonSerializer jsonSerializer) - : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) - { - _jsonSerializer = jsonSerializer; - - DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); - } - - /// - /// Gets the name of the repository - /// - /// The name. - public string Name => "SQLite"; - - /// - /// Opens the connection to the database - /// - /// Task. - public void Initialize() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - var localUsersTableExists = TableExists(connection, "LocalUsersv2"); - - connection.RunQueries(new[] { - "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", - "drop index if exists idx_users" - }); - - if (!localUsersTableExists && TableExists(connection, "Users")) - { - TryMigrateToLocalUsersTable(connection); +using System; +using System.Collections.Generic; +using System.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + /// + /// Class SQLiteUserRepository + /// + public class SqliteUserRepository : BaseSqliteRepository, IUserRepository + { + private readonly IJsonSerializer _jsonSerializer; + + public SqliteUserRepository( + ILoggerFactory loggerFactory, + IServerApplicationPaths appPaths, + IJsonSerializer jsonSerializer) + : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) + { + _jsonSerializer = jsonSerializer; + + DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); + } + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name => "SQLite"; + + /// + /// Opens the connection to the database + /// + /// Task. + public void Initialize() + { + using (var connection = CreateConnection()) + { + RunDefaultInitialization(connection); + + var localUsersTableExists = TableExists(connection, "LocalUsersv2"); + + connection.RunQueries(new[] { + "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", + "drop index if exists idx_users" + }); + + if (!localUsersTableExists && TableExists(connection, "Users")) + { + TryMigrateToLocalUsersTable(connection); } - - RemoveEmptyPasswordHashes(); - } - } - - private void TryMigrateToLocalUsersTable(ManagedConnection connection) - { - try - { - connection.RunQueries(new[] - { - "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" - }); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating users database"); - } - } - - private void RemoveEmptyPasswordHashes() - { - foreach (var user in RetrieveAllUsers()) - { - // If the user password is the sha1 hash of the empty string, remove it - if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) - || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) - { - continue; - } - - user.Password = null; - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - - }, TransactionMode); - } - } - - } - - /// - /// Save a user in the repo - /// - public void CreateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) - { - statement.TryBind("@guid", user.Id.ToGuidBlob()); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - - var createdUser = GetUser(user.Id, false); - - if (createdUser == null) - { - throw new ApplicationException("created user should never be null"); - } - - user.InternalId = createdUser.InternalId; - - }, TransactionMode); - } - } - } - - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - - }, TransactionMode); - } - } - } - - private User GetUser(Guid guid, bool openLock) - { - using (openLock ? WriteLock.Read() : null) - { - using (var connection = CreateConnection(true)) - { - using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) - { - statement.TryBind("@guid", guid); - - foreach (var row in statement.ExecuteQuery()) - { - return GetUser(row); - } - } - } - } - - return null; - } - - private User GetUser(IReadOnlyList row) - { - var id = row[0].ToInt64(); - var guid = row[1].ReadGuidFromBlob(); - - using (var stream = new MemoryStream(row[2].ToBlob())) - { - stream.Position = 0; - var user = _jsonSerializer.DeserializeFromStream(stream); - user.InternalId = id; - user.Id = guid; - return user; - } - } - - /// - /// Retrieve all users from the database - /// - /// IEnumerable{User}. - public List RetrieveAllUsers() - { - var list = new List(); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) - { - list.Add(GetUser(row)); - } - } - } - - return list; - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - public void DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) - { - statement.TryBind("@id", user.InternalId); - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - } -} + + RemoveEmptyPasswordHashes(); + } + } + + private void TryMigrateToLocalUsersTable(ManagedConnection connection) + { + try + { + connection.RunQueries(new[] + { + "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" + }); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error migrating users database"); + } + } + + private void RemoveEmptyPasswordHashes() + { + foreach (var user in RetrieveAllUsers()) + { + // If the user password is the sha1 hash of the empty string, remove it + if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) + || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) + { + continue; + } + + user.Password = null; + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) + { + statement.TryBind("@InternalId", user.InternalId); + statement.TryBind("@data", serialized); + statement.MoveNext(); + } + + }, TransactionMode); + } + } + + } + + /// + /// Save a user in the repo + /// + public void CreateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) + { + statement.TryBind("@guid", user.Id.ToGuidBlob()); + statement.TryBind("@data", serialized); + + statement.MoveNext(); + } + + var createdUser = GetUser(user.Id, false); + + if (createdUser == null) + { + throw new ApplicationException("created user should never be null"); + } + + user.InternalId = createdUser.InternalId; + + }, TransactionMode); + } + } + } + + public void UpdateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) + { + statement.TryBind("@InternalId", user.InternalId); + statement.TryBind("@data", serialized); + statement.MoveNext(); + } + + }, TransactionMode); + } + } + } + + private User GetUser(Guid guid, bool openLock) + { + using (openLock ? WriteLock.Read() : null) + { + using (var connection = CreateConnection(true)) + { + using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) + { + statement.TryBind("@guid", guid); + + foreach (var row in statement.ExecuteQuery()) + { + return GetUser(row); + } + } + } + } + + return null; + } + + private User GetUser(IReadOnlyList row) + { + var id = row[0].ToInt64(); + var guid = row[1].ReadGuidFromBlob(); + + using (var stream = new MemoryStream(row[2].ToBlob())) + { + stream.Position = 0; + var user = _jsonSerializer.DeserializeFromStream(stream); + user.InternalId = id; + user.Id = guid; + return user; + } + } + + /// + /// Retrieve all users from the database + /// + /// IEnumerable{User}. + public List RetrieveAllUsers() + { + var list = new List(); + + using (WriteLock.Read()) + { + using (var connection = CreateConnection(true)) + { + foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) + { + list.Add(GetUser(row)); + } + } + } + + return list; + } + + /// + /// Deletes the user. + /// + /// The user. + /// Task. + /// user + public void DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) + { + statement.TryBind("@id", user.InternalId); + statement.MoveNext(); + } + }, TransactionMode); + } + } + } + } +} diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 2ac3ef424e..b58374adb4 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -1,42 +1,42 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Library -{ - public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser - { - private readonly ICryptoProvider _cryptographyProvider; - public DefaultAuthenticationProvider(ICryptoProvider crypto) - { - _cryptographyProvider = crypto; - } - - public string Name => "Default"; - - public bool IsEnabled => true; - - - //This is dumb and an artifact of the backwards way auth providers were designed. - //This version of authenticate was never meant to be called, but needs to be here for interface compat - //Only the providers that don't provide local user support use this - public Task Authenticate(string username, string password) - { - throw new NotImplementedException(); - } - - - //This is the verson that we need to use for local users. Because reasons. - public Task Authenticate(string username, string password, User resolvedUser) - { - bool success = false; - if (resolvedUser == null) - { - throw new Exception("Invalid username or password"); +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Library +{ + public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser + { + private readonly ICryptoProvider _cryptographyProvider; + public DefaultAuthenticationProvider(ICryptoProvider crypto) + { + _cryptographyProvider = crypto; + } + + public string Name => "Default"; + + public bool IsEnabled => true; + + + //This is dumb and an artifact of the backwards way auth providers were designed. + //This version of authenticate was never meant to be called, but needs to be here for interface compat + //Only the providers that don't provide local user support use this + public Task Authenticate(string username, string password) + { + throw new NotImplementedException(); + } + + + //This is the verson that we need to use for local users. Because reasons. + public Task Authenticate(string username, string password, User resolvedUser) + { + bool success = false; + if (resolvedUser == null) + { + throw new Exception("Invalid username or password"); } //As long as jellyfin supports passwordless users, we need this little block here to accomodate @@ -47,166 +47,164 @@ namespace Emby.Server.Implementations.Library Username = username }); } - - ConvertPasswordFormat(resolvedUser); - byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - - PasswordHash readyHash = new PasswordHash(resolvedUser.Password); - byte[] CalculatedHash; - string CalculatedHashString; - if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) - { - if (string.IsNullOrEmpty(readyHash.Salt)) - { - CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); - CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); - } - else - { - CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); - CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); + + ConvertPasswordFormat(resolvedUser); + byte[] passwordbytes = Encoding.UTF8.GetBytes(password); + + PasswordHash readyHash = new PasswordHash(resolvedUser.Password); + byte[] CalculatedHash; + string CalculatedHashString; + if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) + { + if (string.IsNullOrEmpty(readyHash.Salt)) + { + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); + CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); + } + else + { + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); + CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } - - if (CalculatedHashString == readyHash.Hash) - { - success = true; - //throw new Exception("Invalid username or password"); - } - } - else - { - throw new Exception(String.Format($"Requested crypto method not available in provider: {readyHash.Id}")); - } - - //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); - - if (!success) - { - throw new Exception("Invalid username or password"); - } - - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); - } - - //This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change - //but at least they are in the new format. - private void ConvertPasswordFormat(User user) - { - if (!string.IsNullOrEmpty(user.Password)) + + if (CalculatedHashString == readyHash.Hash) + { + success = true; + //throw new Exception("Invalid username or password"); + } + } + else + { + throw new Exception(String.Format($"Requested crypto method not available in provider: {readyHash.Id}")); + } + + //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + + if (!success) + { + throw new Exception("Invalid username or password"); + } + + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + + //This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change + //but at least they are in the new format. + private void ConvertPasswordFormat(User user) + { + if (!string.IsNullOrEmpty(user.Password)) { return; } - - if (!user.Password.Contains("$")) - { - string hash = user.Password; - user.Password = String.Format("$SHA1${0}", hash); - } - - if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) - { - string hash = user.EasyPassword; - user.EasyPassword = string.Format("$SHA1${0}", hash); - } - } - - public Task HasPassword(User user) - { - var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); - return Task.FromResult(hasConfiguredPassword); - } - - private bool IsPasswordEmpty(User user, string password) + + if (!user.Password.Contains("$")) + { + string hash = user.Password; + user.Password = String.Format("$SHA1${0}", hash); + } + + if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) + { + string hash = user.EasyPassword; + user.EasyPassword = string.Format("$SHA1${0}", hash); + } + } + + public Task HasPassword(User user) + { + var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); + return Task.FromResult(hasConfiguredPassword); + } + + private bool IsPasswordEmpty(User user, string password) { if (string.IsNullOrEmpty(user.Password)) { return string.IsNullOrEmpty(password); } - return false; - } - - public Task ChangePassword(User user, string newPassword) - { + return false; + } + + public Task ChangePassword(User user, string newPassword) + { ConvertPasswordFormat(user); //This is needed to support changing a no password user to a password user if (string.IsNullOrEmpty(user.Password)) { PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider); - newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); - newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes); - newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod; + newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); + newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes); + newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod; newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash); user.Password = newPasswordHash.ToString(); return Task.CompletedTask; } - PasswordHash passwordHash = new PasswordHash(user.Password); - if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) - { - passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); - passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); - passwordHash.Id = _cryptographyProvider.DefaultHashMethod; - passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); - } - else if (newPassword != null) - { - passwordHash.Hash = GetHashedString(user, newPassword); - } - - if (string.IsNullOrWhiteSpace(passwordHash.Hash)) - { - throw new ArgumentNullException(nameof(passwordHash.Hash)); - } - - user.Password = passwordHash.ToString(); - - return Task.CompletedTask; - } - - public string GetPasswordHash(User user) - { - return user.Password; - } - - public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) - { - passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); - } - - /// - /// Gets the hashed string. - /// - public string GetHashedString(User user, string str) - { - PasswordHash passwordHash; - if (String.IsNullOrEmpty(user.Password)) - { - passwordHash = new PasswordHash(_cryptographyProvider); - } - else - { - ConvertPasswordFormat(user); - passwordHash = new PasswordHash(user.Password); - } - - if (passwordHash.SaltBytes != null) - { - //the password is modern format with PBKDF and we should take advantage of that - passwordHash.HashBytes = Encoding.UTF8.GetBytes(str); - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); - } - else - { - //the password has no salt and should be called with the older method for safety - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))); - } - - - } - } -} + PasswordHash passwordHash = new PasswordHash(user.Password); + if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) + { + passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); + passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); + passwordHash.Id = _cryptographyProvider.DefaultHashMethod; + passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); + } + else if (newPassword != null) + { + passwordHash.Hash = GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(passwordHash.Hash)) + { + throw new ArgumentNullException(nameof(passwordHash.Hash)); + } + + user.Password = passwordHash.ToString(); + + return Task.CompletedTask; + } + + public string GetPasswordHash(User user) + { + return user.Password; + } + + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) + { + passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); + } + + /// + /// Gets the hashed string. + /// + public string GetHashedString(User user, string str) + { + PasswordHash passwordHash; + if (String.IsNullOrEmpty(user.Password)) + { + passwordHash = new PasswordHash(_cryptographyProvider); + } + else + { + ConvertPasswordFormat(user); + passwordHash = new PasswordHash(user.Password); + } + + if (passwordHash.SaltBytes != null) + { + //the password is modern format with PBKDF and we should take advantage of that + passwordHash.HashBytes = Encoding.UTF8.GetBytes(str); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); + } + else + { + //the password has no salt and should be called with the older method for safety + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))); + } + } + } +} -- cgit v1.2.3 From a23f04623ed2738ab1205717674614e9eed6b548 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 16 Feb 2019 12:39:53 +0100 Subject: Remove IEncryptionManager --- Emby.Server.Implementations/ApplicationHost.cs | 4 +- .../Security/EncryptionManager.cs | 57 ---------------------- .../Security/IEncryptionManager.cs | 19 -------- .../Subtitles/OpenSubtitleDownloader.cs | 19 ++++---- 4 files changed, 11 insertions(+), 88 deletions(-) delete mode 100644 Emby.Server.Implementations/Security/EncryptionManager.cs delete mode 100644 MediaBrowser.Controller/Security/IEncryptionManager.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8daba05850..dcf2098d49 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -739,10 +739,8 @@ namespace Emby.Server.Implementations TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); serviceCollection.AddSingleton(TVSeriesManager); - var encryptionManager = new EncryptionManager(); - serviceCollection.AddSingleton(encryptionManager); - DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager); + serviceCollection.AddSingleton(DeviceManager); MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); diff --git a/Emby.Server.Implementations/Security/EncryptionManager.cs b/Emby.Server.Implementations/Security/EncryptionManager.cs deleted file mode 100644 index fa8872ccc3..0000000000 --- a/Emby.Server.Implementations/Security/EncryptionManager.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Text; -using MediaBrowser.Controller.Security; - -namespace Emby.Server.Implementations.Security -{ - public class EncryptionManager : IEncryptionManager - { - /// - /// Encrypts the string. - /// - /// The value. - /// System.String. - /// value - public string EncryptString(string value) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - return EncryptStringUniversal(value); - } - - /// - /// Decrypts the string. - /// - /// The value. - /// System.String. - /// value - public string DecryptString(string value) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - return DecryptStringUniversal(value); - } - - private static string EncryptStringUniversal(string value) - { - // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now - - var bytes = Encoding.UTF8.GetBytes(value); - return Convert.ToBase64String(bytes); - } - - private static string DecryptStringUniversal(string value) - { - // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now - - var bytes = Convert.FromBase64String(value); - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } - } -} diff --git a/MediaBrowser.Controller/Security/IEncryptionManager.cs b/MediaBrowser.Controller/Security/IEncryptionManager.cs deleted file mode 100644 index 68680fdf3f..0000000000 --- a/MediaBrowser.Controller/Security/IEncryptionManager.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MediaBrowser.Controller.Security -{ - public interface IEncryptionManager - { - /// - /// Encrypts the string. - /// - /// The value. - /// System.String. - string EncryptString(string value); - - /// - /// Decrypts the string. - /// - /// The value. - /// System.String. - string DecryptString(string value); - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs index 6a5162b8d6..c76ff3feda 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; @@ -29,17 +30,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IServerConfigurationManager _config; - private readonly IEncryptionManager _encryption; private readonly IJsonSerializer _json; private readonly IFileSystem _fileSystem; - public OpenSubtitleDownloader(ILoggerFactory loggerFactory, IHttpClient httpClient, IServerConfigurationManager config, IEncryptionManager encryption, IJsonSerializer json, IFileSystem fileSystem) + public OpenSubtitleDownloader(ILoggerFactory loggerFactory, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem) { _logger = loggerFactory.CreateLogger(GetType().Name); _httpClient = httpClient; _config = config; - _encryption = encryption; _json = json; _fileSystem = fileSystem; @@ -63,16 +62,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles !string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash) && !options.OpenSubtitlesPasswordHash.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) { - options.OpenSubtitlesPasswordHash = EncryptPassword(options.OpenSubtitlesPasswordHash); + options.OpenSubtitlesPasswordHash = ToBase64EncodedString(options.OpenSubtitlesPasswordHash); } } - private string EncryptPassword(string password) + private static string ToBase64EncodedString(string password) { - return PasswordHashPrefix + _encryption.EncryptString(password); + var bytes = Encoding.UTF8.GetBytes(password); + return PasswordHashPrefix + bytes; } - private string DecryptPassword(string password) + private static string DecodeBase64EncodedString(string password) { if (password == null || !password.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) @@ -80,7 +80,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles return string.Empty; } - return _encryption.DecryptString(password.Substring(2)); + var bytes = Convert.FromBase64String(password.Substring(2)); + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); } public string Name => "Open Subtitles"; @@ -186,7 +187,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var options = GetOptions(); var user = options.OpenSubtitlesUsername ?? string.Empty; - var password = DecryptPassword(options.OpenSubtitlesPasswordHash); + var password = DecodeBase64EncodedString(options.OpenSubtitlesPasswordHash); var loginResponse = await OpenSubtitles.LogInAsync(user, password, "en", cancellationToken).ConfigureAwait(false); -- cgit v1.2.3 From 320707d44c403b167f6365299054b1a86a9d15cf Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 20 Feb 2019 16:49:03 +0100 Subject: Reduce string allocations at startup --- .../Services/ServicePath.cs | 161 +++++++++------------ .../Services/StringMapTypeDeserializer.cs | 52 ++++--- 2 files changed, 91 insertions(+), 122 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index f575baca30..ccb28e8df0 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Services private const char ComponentSeperator = '.'; private const string VariablePrefix = "{"; - readonly bool[] componentsWithSeparators; + private readonly bool[] componentsWithSeparators; private readonly string restPath; public bool IsWildCardPath { get; private set; } @@ -54,10 +54,6 @@ namespace Emby.Server.Implementations.Services public string Description { get; private set; } public bool IsHidden { get; private set; } - public int Priority { get; set; } //passed back to RouteAttribute - - public IEnumerable PathVariables => this.variablesNames.Where(e => !string.IsNullOrWhiteSpace(e)); - public static string[] GetPathPartsForMatching(string pathInfo) { return pathInfo.ToLowerInvariant().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries); @@ -83,9 +79,12 @@ namespace Emby.Server.Implementations.Services { list.Add(hashPrefix + part); - var subParts = part.Split(ComponentSeperator); - if (subParts.Length == 1) continue; + if (part.IndexOf(ComponentSeperator) == -1) + { + continue; + } + var subParts = part.Split(ComponentSeperator); foreach (var subPart in subParts) { list.Add(hashPrefix + subPart); @@ -114,7 +113,7 @@ namespace Emby.Server.Implementations.Services { if (string.IsNullOrEmpty(component)) continue; - if (StringContains(component, VariablePrefix) + if (component.IndexOf(VariablePrefix, StringComparison.OrdinalIgnoreCase) != -1 && component.IndexOf(ComponentSeperator) != -1) { hasSeparators.Add(true); @@ -165,7 +164,11 @@ namespace Emby.Server.Implementations.Services for (var i = 0; i < components.Length - 1; i++) { - if (!this.isWildcard[i]) continue; + if (!this.isWildcard[i]) + { + continue; + } + if (this.literalsToMatch[i + 1] == null) { throw new ArgumentException( @@ -173,7 +176,7 @@ namespace Emby.Server.Implementations.Services } } - this.wildcardCount = this.isWildcard.Count(x => x); + this.wildcardCount = this.isWildcard.Length; this.IsWildCardPath = this.wildcardCount > 0; this.FirstMatchHashKey = !this.IsWildCardPath @@ -181,19 +184,14 @@ namespace Emby.Server.Implementations.Services : WildCardChar + PathSeperator + firstLiteralMatch; this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType); - RegisterCaseInsenstivePropertyNameMappings(); - } - private void RegisterCaseInsenstivePropertyNameMappings() - { - foreach (var propertyInfo in GetSerializableProperties(RequestType)) - { - var propertyName = propertyInfo.Name; - propertyNamesMap.Add(propertyName.ToLowerInvariant(), propertyName); - } + _propertyNamesMap = new HashSet( + GetSerializableProperties(RequestType).Select(x => x.Name), + StringComparer.OrdinalIgnoreCase); } - internal static string[] IgnoreAttributesNamed = new[] { + internal static string[] IgnoreAttributesNamed = new[] + { "IgnoreDataMemberAttribute", "JsonIgnoreAttribute" }; @@ -201,19 +199,12 @@ namespace Emby.Server.Implementations.Services private static Type excludeType = typeof(Stream); - internal static List GetSerializableProperties(Type type) + internal static IEnumerable GetSerializableProperties(Type type) { - var list = new List(); - var props = GetPublicProperties(type); - - foreach (var prop in props) + foreach (var prop in GetPublicProperties(type)) { - if (prop.GetMethod == null) - { - continue; - } - - if (excludeType == prop.PropertyType) + if (prop.GetMethod == null + || excludeType == prop.PropertyType) { continue; } @@ -230,23 +221,21 @@ namespace Emby.Server.Implementations.Services if (!ignored) { - list.Add(prop); + yield return prop; } } - - // else return those properties that are not decorated with IgnoreDataMember - return list; } - private static List GetPublicProperties(Type type) + private static IEnumerable GetPublicProperties(Type type) { - if (type.GetTypeInfo().IsInterface) + if (type.IsInterface) { var propertyInfos = new List(); - - var considered = new List(); + var considered = new List() + { + type + }; var queue = new Queue(); - considered.Add(type); queue.Enqueue(type); while (queue.Count > 0) @@ -254,15 +243,16 @@ namespace Emby.Server.Implementations.Services var subType = queue.Dequeue(); foreach (var subInterface in subType.GetTypeInfo().ImplementedInterfaces) { - if (considered.Contains(subInterface)) continue; + if (considered.Contains(subInterface)) + { + continue; + } considered.Add(subInterface); queue.Enqueue(subInterface); } - var typeProperties = GetTypesPublicProperties(subType); - - var newPropertyInfos = typeProperties + var newPropertyInfos = GetTypesPublicProperties(subType) .Where(x => !propertyInfos.Contains(x)); propertyInfos.InsertRange(0, newPropertyInfos); @@ -271,28 +261,22 @@ namespace Emby.Server.Implementations.Services return propertyInfos; } - var list = new List(); - - foreach (var t in GetTypesPublicProperties(type)) - { - if (t.GetIndexParameters().Length == 0) - { - list.Add(t); - } - } - return list; + return GetTypesPublicProperties(type) + .Where(x => x.GetIndexParameters().Length == 0); } - private static PropertyInfo[] GetTypesPublicProperties(Type subType) + private static IEnumerable GetTypesPublicProperties(Type subType) { - var pis = new List(); foreach (var pi in subType.GetRuntimeProperties()) { var mi = pi.GetMethod ?? pi.SetMethod; - if (mi != null && mi.IsStatic) continue; - pis.Add(pi); + if (mi != null && mi.IsStatic) + { + continue; + } + + yield return pi; } - return pis.ToArray(); } /// @@ -302,7 +286,7 @@ namespace Emby.Server.Implementations.Services private readonly StringMapTypeDeserializer typeDeserializer; - private readonly Dictionary propertyNamesMap = new Dictionary(); + private readonly HashSet _propertyNamesMap; public int MatchScore(string httpMethod, string[] withPathInfoParts) { @@ -312,13 +296,10 @@ namespace Emby.Server.Implementations.Services return -1; } - var score = 0; - //Routes with least wildcard matches get the highest score - score += Math.Max((100 - wildcardMatchCount), 1) * 1000; - - //Routes with less variable (and more literal) matches - score += Math.Max((10 - VariableArgsCount), 1) * 100; + var score = Math.Max((100 - wildcardMatchCount), 1) * 1000 + //Routes with less variable (and more literal) matches + + Math.Max((10 - VariableArgsCount), 1) * 100; //Exact verb match is better than ANY if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase)) @@ -333,11 +314,6 @@ namespace Emby.Server.Implementations.Services return score; } - private bool StringContains(string str1, string str2) - { - return str1.IndexOf(str2, StringComparison.OrdinalIgnoreCase) != -1; - } - /// /// For performance withPathInfoParts should already be a lower case string /// to minimize redundant matching operations. @@ -374,7 +350,8 @@ namespace Emby.Server.Implementations.Services if (i < this.TotalComponentsCount - 1) { // Continue to consume up until a match with the next literal - while (pathIx < withPathInfoParts.Length && !LiteralsEqual(withPathInfoParts[pathIx], this.literalsToMatch[i + 1])) + while (pathIx < withPathInfoParts.Length + && !string.Equals(withPathInfoParts[pathIx], this.literalsToMatch[i + 1], StringComparison.InvariantCultureIgnoreCase)) { pathIx++; wildcardMatchCount++; @@ -403,10 +380,12 @@ namespace Emby.Server.Implementations.Services continue; } - if (withPathInfoParts.Length <= pathIx || !LiteralsEqual(withPathInfoParts[pathIx], literalToMatch)) + if (withPathInfoParts.Length <= pathIx + || !string.Equals(withPathInfoParts[pathIx], literalToMatch, StringComparison.InvariantCultureIgnoreCase)) { return false; } + pathIx++; } } @@ -414,35 +393,26 @@ namespace Emby.Server.Implementations.Services return pathIx == withPathInfoParts.Length; } - private static bool LiteralsEqual(string str1, string str2) - { - // Most cases - if (string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - // Handle turkish i - str1 = str1.ToUpperInvariant(); - str2 = str2.ToUpperInvariant(); - - // Invariant IgnoreCase would probably be better but it's not available in PCL - return string.Equals(str1, str2, StringComparison.CurrentCultureIgnoreCase); - } - private bool ExplodeComponents(ref string[] withPathInfoParts) { var totalComponents = new List(); for (var i = 0; i < withPathInfoParts.Length; i++) { var component = withPathInfoParts[i]; - if (string.IsNullOrEmpty(component)) continue; + if (string.IsNullOrEmpty(component)) + { + continue; + } if (this.PathComponentsCount != this.TotalComponentsCount && this.componentsWithSeparators[i]) { var subComponents = component.Split(ComponentSeperator); - if (subComponents.Length < 2) return false; + if (subComponents.Length < 2) + { + return false; + } + totalComponents.AddRange(subComponents); } else @@ -483,7 +453,7 @@ namespace Emby.Server.Implementations.Services continue; } - if (!this.propertyNamesMap.TryGetValue(variableName.ToLowerInvariant(), out var propertyNameOnRequest)) + if (!this._propertyNamesMap.Contains(variableName)) { if (string.Equals("ignore", variableName, StringComparison.OrdinalIgnoreCase)) { @@ -507,6 +477,7 @@ namespace Emby.Server.Implementations.Services { sb.Append(PathSeperatorChar + requestComponents[j]); } + value = sb.ToString(); } else @@ -517,13 +488,13 @@ namespace Emby.Server.Implementations.Services var stopLiteral = i == this.TotalComponentsCount - 1 ? null : this.literalsToMatch[i + 1]; if (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase)) { - var sb = new StringBuilder(); - sb.Append(value); + var sb = new StringBuilder(value); pathIx++; while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase)) { sb.Append(PathSeperatorChar + requestComponents[pathIx++]); } + value = sb.ToString(); } else @@ -538,7 +509,7 @@ namespace Emby.Server.Implementations.Services pathIx++; } - requestKeyValuesMap[propertyNameOnRequest] = value; + requestKeyValuesMap[variableName] = value; } if (queryStringAndFormData != null) diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index d13935fbab..f835aa1b5b 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -11,15 +11,16 @@ namespace Emby.Server.Implementations.Services { internal class PropertySerializerEntry { - public PropertySerializerEntry(Action propertySetFn, Func propertyParseStringFn) + public PropertySerializerEntry(Action propertySetFn, Func propertyParseStringFn, Type propertyType) { PropertySetFn = propertySetFn; PropertyParseStringFn = propertyParseStringFn; + PropertyType = PropertyType; } - public Action PropertySetFn; - public Func PropertyParseStringFn; - public Type PropertyType; + public Action PropertySetFn { get; private set; } + public Func PropertyParseStringFn { get; private set; } + public Type PropertyType { get; private set; } } private readonly Type type; @@ -29,7 +30,9 @@ namespace Emby.Server.Implementations.Services public Func GetParseFn(Type propertyType) { if (propertyType == typeof(string)) + { return s => s; + } return _GetParseFn(propertyType); } @@ -48,7 +51,7 @@ namespace Emby.Server.Implementations.Services var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo); var propertyType = propertyInfo.PropertyType; var propertyParseStringFn = GetParseFn(propertyType); - var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn) { PropertyType = propertyType }; + var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn, propertyType); propertySetterMap[propertyInfo.Name] = propertySerializer; } @@ -56,34 +59,21 @@ namespace Emby.Server.Implementations.Services public object PopulateFromMap(object instance, IDictionary keyValuePairs) { - string propertyName = null; - string propertyTextValue = null; PropertySerializerEntry propertySerializerEntry = null; if (instance == null) + { instance = _CreateInstanceFn(type); + } foreach (var pair in keyValuePairs) { - propertyName = pair.Key; - propertyTextValue = pair.Value; - - if (string.IsNullOrEmpty(propertyTextValue)) - { - continue; - } + string propertyName = pair.Key; + string propertyTextValue = pair.Value; - if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry)) - { - if (propertyName == "v") - { - continue; - } - - continue; - } - - if (propertySerializerEntry.PropertySetFn == null) + if (string.IsNullOrEmpty(propertyTextValue) + || !propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry) + || propertySerializerEntry.PropertySetFn == null) { continue; } @@ -99,6 +89,7 @@ namespace Emby.Server.Implementations.Services { continue; } + propertySerializerEntry.PropertySetFn(instance, value); } @@ -107,7 +98,11 @@ namespace Emby.Server.Implementations.Services public static string LeftPart(string strVal, char needle) { - if (strVal == null) return null; + if (strVal == null) + { + return null; + } + var pos = strVal.IndexOf(needle); return pos == -1 ? strVal @@ -119,7 +114,10 @@ namespace Emby.Server.Implementations.Services { public static Action GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) { - if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) return null; + if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) + { + return null; + } var setMethodInfo = propertyInfo.SetMethod; return (instance, value) => setMethodInfo.Invoke(instance, new[] { value }); -- cgit v1.2.3 From cf4e64f4309a40ff50607d418970bc2767a275bb Mon Sep 17 00:00:00 2001 From: Xu Fasheng Date: Thu, 21 Feb 2019 22:35:31 +0800 Subject: Add option to toggle if ignore virtual interfaces Some VPN like ZerotierOne owns IP address but no gateway, and there is no good idea in NetworkManager.GetIPsDefault() to filter such virtual interfaces, so just provide one option to let user decide it. --- Emby.Dlna/Main/DlnaEntryPoint.cs | 2 +- Emby.Server.Implementations/ApplicationHost.cs | 2 +- Emby.Server.Implementations/Networking/NetworkManager.cs | 12 ++++++------ MediaBrowser.Common/Net/INetworkManager.cs | 2 +- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 2 ++ RSSDP/RSSDP.csproj | 1 + RSSDP/SsdpCommunicationsServer.cs | 8 ++++++-- 7 files changed, 18 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index a200065782..4eb4cde81c 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -171,7 +171,7 @@ namespace Emby.Dlna.Main { var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows; - _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding) + _communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding) { IsShared = true }; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 042b04b3bb..e9f43a0eae 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1577,7 +1577,7 @@ namespace Emby.Server.Implementations if (addresses.Count == 0) { - addresses.AddRange(NetworkManager.GetLocalIpAddresses()); + addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces)); } var resultList = new List(); diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 60cc9b88eb..b7a125f207 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -79,13 +79,13 @@ namespace Emby.Server.Implementations.Networking private IpAddressInfo[] _localIpAddresses; private readonly object _localIpAddressSyncLock = new object(); - public IpAddressInfo[] GetLocalIpAddresses() + public IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface = true) { lock (_localIpAddressSyncLock) { if (_localIpAddresses == null) { - var addresses = GetLocalIpAddressesInternal().Result.Select(ToIpAddressInfo).ToArray(); + var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).Result.Select(ToIpAddressInfo).ToArray(); _localIpAddresses = addresses; @@ -95,9 +95,9 @@ namespace Emby.Server.Implementations.Networking } } - private async Task> GetLocalIpAddressesInternal() + private async Task> GetLocalIpAddressesInternal(bool ignoreVirtualInterface) { - var list = GetIPsDefault() + var list = GetIPsDefault(ignoreVirtualInterface) .ToList(); if (list.Count == 0) @@ -383,7 +383,7 @@ namespace Emby.Server.Implementations.Networking return Dns.GetHostAddressesAsync(hostName); } - private List GetIPsDefault() + private List GetIPsDefault(bool ignoreVirtualInterface) { NetworkInterface[] interfaces; @@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.Networking // Try to exclude virtual adapters // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms var addr = ipProperties.GatewayAddresses.FirstOrDefault(); - if (addr == null || string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase)) + if (addr == null || (ignoreVirtualInterface && string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))) { return new List(); } diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 72fb6e2b86..3364230d1d 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.Common.Net /// true if [is in local network] [the specified endpoint]; otherwise, false. bool IsInLocalNetwork(string endpoint); - IpAddressInfo[] GetLocalIpAddresses(); + IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface); IpAddressInfo ParseIpAddress(string ipAddress); diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index ed58003292..0ba36b4b9d 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -178,6 +178,7 @@ namespace MediaBrowser.Model.Configuration public string[] LocalNetworkSubnets { get; set; } public string[] LocalNetworkAddresses { get; set; } public string[] CodecsUsed { get; set; } + public bool IgnoreVirtualInterfaces { get; set; } public bool EnableExternalContentInSuggestions { get; set; } public bool RequireHttps { get; set; } public bool IsBehindProxy { get; set; } @@ -205,6 +206,7 @@ namespace MediaBrowser.Model.Configuration CodecsUsed = Array.Empty(); ImageExtractionTimeoutMs = 0; PathSubstitutions = Array.Empty(); + IgnoreVirtualInterfaces = false; EnableSimpleArtistDetection = true; DisplaySpecialsWithinSeasons = true; diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj index f06d4687b9..456a93aa80 100644 --- a/RSSDP/RSSDP.csproj +++ b/RSSDP/RSSDP.csproj @@ -3,6 +3,7 @@ + diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 04e76ef591..9da906b496 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Net; using Microsoft.Extensions.Logging; using MediaBrowser.Model.Net; +using MediaBrowser.Controller.Configuration; namespace Rssdp.Infrastructure { @@ -45,6 +46,7 @@ namespace Rssdp.Infrastructure private readonly ILogger _logger; private ISocketFactory _SocketFactory; private readonly INetworkManager _networkManager; + private readonly IServerConfigurationManager _config; private int _LocalPort; private int _MulticastTtl; @@ -74,9 +76,11 @@ namespace Rssdp.Infrastructure /// Minimum constructor. /// /// The argument is null. - public SsdpCommunicationsServer(ISocketFactory socketFactory, INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding) + public SsdpCommunicationsServer(IServerConfigurationManager config, ISocketFactory socketFactory, + INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding) : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding) { + _config = config; } /// @@ -363,7 +367,7 @@ namespace Rssdp.Infrastructure if (_enableMultiSocketBinding) { - foreach (var address in _networkManager.GetLocalIpAddresses()) + foreach (var address in _networkManager.GetLocalIpAddresses(_config.Configuration.IgnoreVirtualInterfaces)) { if (address.AddressFamily == IpAddressFamily.InterNetworkV6) { -- cgit v1.2.3 From cbd0e71c077e6233bcbc751f9a2f1ee742000ba3 Mon Sep 17 00:00:00 2001 From: Xu Fasheng Date: Fri, 22 Feb 2019 12:06:49 +0800 Subject: Send DLNA devices message to only the matched interface This will be the right way for multiple interfaces, or the client will receive all devices message with different IP addresses and could not detect which one could access. And provide one option DlnaOptions.SendOnlyMatchedHost to fallback to old behaviour if this commit missed something. --- Emby.Dlna/Configuration/DlnaOptions.cs | 2 + Emby.Dlna/Main/DlnaEntryPoint.cs | 4 +- .../Networking/NetworkManager.cs | 60 ++++++++++++++++++++++ MediaBrowser.Common/Net/INetworkManager.cs | 3 ++ MediaBrowser.Model/Net/IpAddressInfo.cs | 1 + RSSDP/ISsdpCommunicationsServer.cs | 6 +-- RSSDP/SsdpCommunicationsServer.cs | 13 ++--- RSSDP/SsdpDeviceLocator.cs | 2 +- RSSDP/SsdpDevicePublisher.cs | 30 +++++++++-- RSSDP/SsdpRootDevice.cs | 10 ++++ 10 files changed, 116 insertions(+), 15 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs index 0ebb490a1f..c7cb364a83 100644 --- a/Emby.Dlna/Configuration/DlnaOptions.cs +++ b/Emby.Dlna/Configuration/DlnaOptions.cs @@ -7,6 +7,7 @@ namespace Emby.Dlna.Configuration public bool EnableServer { get; set; } public bool EnableDebugLog { get; set; } public bool BlastAliveMessages { get; set; } + public bool SendOnlyMatchedHost { get; set; } public int ClientDiscoveryIntervalSeconds { get; set; } public int BlastAliveMessageIntervalSeconds { get; set; } public string DefaultUserId { get; set; } @@ -16,6 +17,7 @@ namespace Emby.Dlna.Configuration EnablePlayTo = true; EnableServer = true; BlastAliveMessages = true; + SendOnlyMatchedHost = true; ClientDiscoveryIntervalSeconds = 60; BlastAliveMessageIntervalSeconds = 1800; } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 8eff7f773d..427b3a5c0e 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -230,7 +230,7 @@ namespace Emby.Dlna.Main try { - _Publisher = new SsdpDevicePublisher(_communicationsServer, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion); + _Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion, _config.GetDlnaConfiguration().SendOnlyMatchedHost); _Publisher.LogFunction = LogMessage; _Publisher.SupportPnpRootDevice = false; @@ -269,6 +269,8 @@ namespace Emby.Dlna.Main { CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info. Location = uri, // Must point to the URL that serves your devices UPnP description document. + Address = address, + SubnetMask = _networkManager.GetLocalIpSubnetMask(address), FriendlyName = "Jellyfin", Manufacturer = "Jellyfin", ModelName = "Jellyfin Server", diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index b7a125f207..8696d18968 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -636,6 +636,66 @@ namespace Emby.Server.Implementations.Networking return false; } + public bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask) + { + IPAddress network1 = GetNetworkAddress(ToIPAddress(address1), ToIPAddress(subnetMask)); + IPAddress network2 = GetNetworkAddress(ToIPAddress(address2), ToIPAddress(subnetMask)); + return network1.Equals(network2); + } + + private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask) + { + byte[] ipAdressBytes = address.GetAddressBytes(); + byte[] subnetMaskBytes = subnetMask.GetAddressBytes(); + + if (ipAdressBytes.Length != subnetMaskBytes.Length) + { + throw new ArgumentException("Lengths of IP address and subnet mask do not match."); + } + + byte[] broadcastAddress = new byte[ipAdressBytes.Length]; + for (int i = 0; i < broadcastAddress.Length; i++) + { + broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i])); + } + return new IPAddress(broadcastAddress); + } + + public IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address) + { + NetworkInterface[] interfaces; + IPAddress ipaddress = ToIPAddress(address); + + try + { + var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown }; + + interfaces = NetworkInterface.GetAllNetworkInterfaces() + .Where(i => validStatuses.Contains(i.OperationalStatus)) + .ToArray(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error in GetAllNetworkInterfaces"); + return null; + } + + foreach (NetworkInterface ni in interfaces) + { + if (ni.GetIPProperties().GatewayAddresses.FirstOrDefault() != null) + { + foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) + { + if (ip.Address.Equals(ipaddress) && ip.IPv4Mask != null) + { + return ToIpAddressInfo(ip.IPv4Mask); + } + } + } + } + return null; + } + public static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint) { if (endpoint == null) diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 3364230d1d..34c6f58665 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -62,5 +62,8 @@ namespace MediaBrowser.Common.Net Task GetHostAddressesAsync(string host); bool IsAddressInSubnets(string addressString, string[] subnets); + + bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask); + IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address); } } diff --git a/MediaBrowser.Model/Net/IpAddressInfo.cs b/MediaBrowser.Model/Net/IpAddressInfo.cs index 7a278d4d41..87fa55bcae 100644 --- a/MediaBrowser.Model/Net/IpAddressInfo.cs +++ b/MediaBrowser.Model/Net/IpAddressInfo.cs @@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Net public static IpAddressInfo IPv6Loopback = new IpAddressInfo("::1", IpAddressFamily.InterNetworkV6); public string Address { get; set; } + public IpAddressInfo SubnetMask { get; set; } public IpAddressFamily AddressFamily { get; set; } public IpAddressInfo(string address, IpAddressFamily addressFamily) diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs index ef75f997fb..c99d684a13 100644 --- a/RSSDP/ISsdpCommunicationsServer.cs +++ b/RSSDP/ISsdpCommunicationsServer.cs @@ -45,8 +45,8 @@ namespace Rssdp.Infrastructure /// /// Sends a message to the SSDP multicast address and port. /// - Task SendMulticastMessage(string message, CancellationToken cancellationToken); - Task SendMulticastMessage(string message, int sendCount, CancellationToken cancellationToken); + Task SendMulticastMessage(string message, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken); + Task SendMulticastMessage(string message, int sendCount, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken); #endregion @@ -63,4 +63,4 @@ namespace Rssdp.Infrastructure #endregion } -} \ No newline at end of file +} diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 9da906b496..ea4d79c991 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -240,15 +240,15 @@ namespace Rssdp.Infrastructure } } - public Task SendMulticastMessage(string message, CancellationToken cancellationToken) + public Task SendMulticastMessage(string message, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken) { - return SendMulticastMessage(message, SsdpConstants.UdpResendCount, cancellationToken); + return SendMulticastMessage(message, SsdpConstants.UdpResendCount, fromLocalIpAddress, cancellationToken); } /// /// Sends a message to the SSDP multicast address and port. /// - public async Task SendMulticastMessage(string message, int sendCount, CancellationToken cancellationToken) + public async Task SendMulticastMessage(string message, int sendCount, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken) { if (message == null) throw new ArgumentNullException(nameof(message)); @@ -268,7 +268,7 @@ namespace Rssdp.Infrastructure IpAddress = new IpAddressInfo(SsdpConstants.MulticastLocalAdminAddress, IpAddressFamily.InterNetwork), Port = SsdpConstants.MulticastPort - }, cancellationToken).ConfigureAwait(false); + }, fromLocalIpAddress, cancellationToken).ConfigureAwait(false); await Task.Delay(100, cancellationToken).ConfigureAwait(false); } @@ -336,14 +336,15 @@ namespace Rssdp.Infrastructure #region Private Methods - private Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination, CancellationToken cancellationToken) + private Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken) { var sockets = _sendSockets; if (sockets != null) { sockets = sockets.ToList(); - var tasks = sockets.Select(s => SendFromSocket(s, messageData, destination, cancellationToken)); + var tasks = sockets.Where(s => (fromLocalIpAddress == null || fromLocalIpAddress.Equals(s.LocalIPAddress))) + .Select(s => SendFromSocket(s, messageData, destination, cancellationToken)); return Task.WhenAll(tasks); } diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 128bdfcbb4..e17e14c1a6 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -354,7 +354,7 @@ namespace Rssdp.Infrastructure var message = BuildMessage(header, values); - return _CommunicationsServer.SendMulticastMessage(message, cancellationToken); + return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken); } private void ProcessSearchResponseMessage(HttpResponseMessage message, IpAddressInfo localIpAddress) diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index ce64ba1176..076246b243 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; +using MediaBrowser.Common.Net; using Rssdp; namespace Rssdp.Infrastructure @@ -16,10 +17,12 @@ namespace Rssdp.Infrastructure /// public class SsdpDevicePublisher : DisposableManagedObjectBase, ISsdpDevicePublisher { + private readonly INetworkManager _networkManager; private ISsdpCommunicationsServer _CommsServer; private string _OSName; private string _OSVersion; + private bool _sendOnlyMatchedHost; private bool _SupportPnpRootDevice; @@ -37,9 +40,11 @@ namespace Rssdp.Infrastructure /// /// Default constructor. /// - public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, string osName, string osVersion) + public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, INetworkManager networkManager, + string osName, string osVersion, bool sendOnlyMatchedHost) { if (communicationsServer == null) throw new ArgumentNullException(nameof(communicationsServer)); + if (networkManager == null) throw new ArgumentNullException(nameof(networkManager)); if (osName == null) throw new ArgumentNullException(nameof(osName)); if (osName.Length == 0) throw new ArgumentException("osName cannot be an empty string.", nameof(osName)); if (osVersion == null) throw new ArgumentNullException(nameof(osVersion)); @@ -51,10 +56,12 @@ namespace Rssdp.Infrastructure _RecentSearchRequests = new Dictionary(StringComparer.OrdinalIgnoreCase); _Random = new Random(); + _networkManager = networkManager; _CommsServer = communicationsServer; _CommsServer.RequestReceived += CommsServer_RequestReceived; _OSName = osName; _OSVersion = osVersion; + _sendOnlyMatchedHost = sendOnlyMatchedHost; _CommsServer.BeginListeningForBroadcasts(); } @@ -250,7 +257,11 @@ namespace Rssdp.Infrastructure foreach (var device in deviceList) { - SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken); + if (!_sendOnlyMatchedHost || + _networkManager.IsInSameSubnet(device.ToRootDevice().Address, remoteEndPoint.IpAddress, device.ToRootDevice().SubnetMask)) + { + SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken); + } } } else @@ -427,7 +438,12 @@ namespace Rssdp.Infrastructure var message = BuildMessage(header, values); - _CommsServer.SendMulticastMessage(message, cancellationToken); + if (_sendOnlyMatchedHost) + { + _CommsServer.SendMulticastMessage(message, _sendOnlyMatchedHost ? rootDevice.Address : null, cancellationToken); + } else { + _CommsServer.SendMulticastMessage(message, null, cancellationToken); + } //WriteTrace(String.Format("Sent alive notification"), device); } @@ -472,7 +488,13 @@ namespace Rssdp.Infrastructure var sendCount = IsDisposed ? 1 : 3; WriteTrace(String.Format("Sent byebye notification"), device); - return _CommsServer.SendMulticastMessage(message, sendCount, cancellationToken); + if (_sendOnlyMatchedHost) + { + return _CommsServer.SendMulticastMessage(message, sendCount, + _sendOnlyMatchedHost ? device.ToRootDevice().Address : null, cancellationToken); + } else { + return _CommsServer.SendMulticastMessage(message, sendCount, null, cancellationToken); + } } private void DisposeRebroadcastTimer() diff --git a/RSSDP/SsdpRootDevice.cs b/RSSDP/SsdpRootDevice.cs index a2b0f60f54..d918b9040d 100644 --- a/RSSDP/SsdpRootDevice.cs +++ b/RSSDP/SsdpRootDevice.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text; using System.Xml; using Rssdp.Infrastructure; +using MediaBrowser.Model.Net; namespace Rssdp { @@ -52,6 +53,15 @@ namespace Rssdp /// public Uri Location { get; set; } + /// + /// Gets or sets the Address used to check if the received message from same interface with this device/tree. Required. + /// + public IpAddressInfo Address { get; set; } + + /// + /// Gets or sets the SubnetMask used to check if the received message from same interface with this device/tree. Required. + /// + public IpAddressInfo SubnetMask { get; set; } /// /// The base URL to use for all relative url's provided in other propertise (and those of child devices). Optional. -- cgit v1.2.3 From 0f9006c81fae1c9bd74c284b0affa76115a89ad2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 25 Feb 2019 18:26:17 +0100 Subject: Use stopwatch for more accurate measurements and reduce log spam DateTime.Now is suitible for small timespans Replaced the needlessly complex and verbose logging for the httpserver --- Emby.Server.Implementations/ApplicationHost.cs | 10 +-- .../HttpServer/HttpListenerHost.cs | 82 ++++++---------------- .../HttpServer/LoggerUtils.cs | 55 --------------- .../ScheduledTasks/Triggers/DailyTrigger.cs | 2 +- 4 files changed, 27 insertions(+), 122 deletions(-) delete mode 100644 Emby.Server.Implementations/HttpServer/LoggerUtils.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 042b04b3bb..553d9b9dd8 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -550,16 +550,18 @@ namespace Emby.Server.Implementations var entryPoints = GetExports(); - var now = DateTime.UtcNow; + var stopWatch = new Stopwatch(); + stopWatch.Start(); await Task.WhenAll(StartEntryPoints(entryPoints, true)); - Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:fff} ms", DateTime.Now - now); + Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:fff} ms", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); HttpServer.GlobalResponse = null; - now = DateTime.UtcNow; + stopWatch.Restart(); await Task.WhenAll(StartEntryPoints(entryPoints, false)); - Logger.LogInformation("Executed all post-startup entry points in {Elapsed:fff} ms", DateTime.Now - now); + Logger.LogInformation("Executed all post-startup entry points in {Elapsed:fff} ms", stopWatch.Elapsed); + stopWatch.Stop(); } private IEnumerable StartEntryPoints(IEnumerable entryPoints, bool isBeforeStartup) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index d78891ac7c..29aab353d8 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -286,31 +287,6 @@ namespace Emby.Server.Implementations.HttpServer } } - private static readonly string[] _skipLogExtensions = - { - ".js", - ".css", - ".woff", - ".woff2", - ".ttf", - ".html" - }; - - private bool EnableLogging(string url, string localPath) - { - var extension = GetExtension(url); - - return ((string.IsNullOrEmpty(extension) || !_skipLogExtensions.Contains(extension)) - && (string.IsNullOrEmpty(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1)); - } - - private static string GetExtension(string url) - { - var parts = url.Split(new[] { '?' }, 2); - - return Path.GetExtension(parts[0]); - } - public static string RemoveQueryStringByKey(string url, string key) { var uri = new Uri(url); @@ -448,10 +424,9 @@ namespace Emby.Server.Implementations.HttpServer /// protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) { - var date = DateTime.Now; + var stopWatch = new Stopwatch(); + stopWatch.Start(); var httpRes = httpReq.Response; - bool enableLog = false; - bool logHeaders = false; string urlToLog = null; string remoteIp = httpReq.RemoteIp; @@ -498,18 +473,8 @@ namespace Emby.Server.Implementations.HttpServer return; } - var operationName = httpReq.OperationName; - - enableLog = EnableLogging(urlString, localPath); - urlToLog = urlString; - logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1; - - if (enableLog) - { - urlToLog = GetUrlToLog(urlString); - - LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null); - } + urlToLog = GetUrlToLog(urlString); + Logger.LogDebug("HTTP {HttpMethod} {Url} UserAgent: {UserAgent} \nHeaders: {@Headers}", urlToLog, httpReq.UserAgent ?? string.Empty, httpReq.HttpMethod, httpReq.Headers); if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) @@ -517,6 +482,7 @@ namespace Emby.Server.Implementations.HttpServer RedirectToUrl(httpRes, DefaultRedirectPath); return; } + if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) { @@ -562,16 +528,19 @@ namespace Emby.Server.Implementations.HttpServer RedirectToUrl(httpRes, DefaultRedirectPath); return; } + if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase)) { RedirectToUrl(httpRes, "../" + DefaultRedirectPath); return; } + if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { RedirectToUrl(httpRes, DefaultRedirectPath); return; } + if (string.IsNullOrEmpty(localPath)) { RedirectToUrl(httpRes, "/" + DefaultRedirectPath); @@ -607,33 +576,21 @@ namespace Emby.Server.Implementations.HttpServer if (handler != null) { - await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, operationName, cancellationToken).ConfigureAwait(false); + await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, httpReq.OperationName, cancellationToken).ConfigureAwait(false); } else { await ErrorHandler(new FileNotFoundException(), httpReq, false, false).ConfigureAwait(false); } } - catch (OperationCanceledException ex) - { - await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); - } - - catch (IOException ex) - { - await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); - } - - catch (SocketException ex) + catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException) { await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); } - catch (SecurityException ex) { await ErrorHandler(ex, httpReq, false, true).ConfigureAwait(false); } - catch (Exception ex) { var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase); @@ -644,13 +601,15 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.Close(); - if (enableLog) + stopWatch.Stop(); + var elapsed = stopWatch.Elapsed; + if (elapsed.Milliseconds > 500) { - var statusCode = httpRes.StatusCode; - - var duration = DateTime.Now - date; - - LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration, logHeaders ? httpRes.Headers : null); + _logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:ss.fff}. {Url}", httpRes.StatusCode, remoteIp, stopWatch.Elapsed, urlToLog); + } + else + { + _logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:ss.fff}. {Url}", httpRes.StatusCode, remoteIp, stopWatch.Elapsed, urlToLog); } } } @@ -663,12 +622,11 @@ namespace Emby.Server.Implementations.HttpServer var pathParts = pathInfo.TrimStart('/').Split('/'); if (pathParts.Length == 0) { - _logger.LogError("Path parts empty for PathInfo: {pathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl); + _logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl); return null; } var restPath = ServiceHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out string contentType); - if (restPath != null) { return new ServiceHandler diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs deleted file mode 100644 index d22d9db265..0000000000 --- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Globalization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.HttpServer -{ - public static class LoggerUtils - { - public static void LogRequest(ILogger logger, string url, string method, string userAgent, QueryParamCollection headers) - { - if (headers == null) - { - logger.LogInformation("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty); - } - else - { - var headerText = string.Empty; - var index = 0; - - foreach (var i in headers) - { - if (index > 0) - { - headerText += ", "; - } - - headerText += i.Name + "=" + i.Value; - - index++; - } - - logger.LogInformation("HTTP {0} {1}. {2}", method, url, headerText); - } - } - - /// - /// Logs the response. - /// - /// The logger. - /// The status code. - /// The URL. - /// The end point. - /// The duration. - public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration, QueryParamCollection headers) - { - var durationMs = duration.TotalMilliseconds; - var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms"; - - //var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray()); - var headerText = string.Empty; - logger.LogInformation("HTTP Response {0} to {1}. Time: {2}{3}. {4} {5}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url, headerText); - } - } -} diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index 98685cebea..d33ce8a1ea 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var dueTime = triggerDate - now; - logger.LogInformation("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate.ToString(), dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + logger.LogInformation("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate, dueTime); Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); } -- cgit v1.2.3 From 0804bed66d6b060ab550ab4ef878724a14764fed Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 Feb 2019 19:37:39 +0100 Subject: Log time in a standardized way --- Emby.Server.Implementations/ApplicationHost.cs | 4 ++-- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 +- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 8 ++++---- .../ScheduledTasks/Triggers/DailyTrigger.cs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 553d9b9dd8..45a819f260 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -553,14 +553,14 @@ namespace Emby.Server.Implementations var stopWatch = new Stopwatch(); stopWatch.Start(); await Task.WhenAll(StartEntryPoints(entryPoints, true)); - Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:fff} ms", stopWatch.Elapsed); + Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); HttpServer.GlobalResponse = null; stopWatch.Restart(); await Task.WhenAll(StartEntryPoints(entryPoints, false)); - Logger.LogInformation("Executed all post-startup entry points in {Elapsed:fff} ms", stopWatch.Elapsed); + Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); stopWatch.Stop(); } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 6502e4aeda..70e5fa6406 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2747,7 +2747,7 @@ namespace Emby.Server.Implementations.Data if (elapsed >= slowThreshold) { - Logger.LogWarning("{0} query time (slow): {1}ms. Query: {2}", + Logger.LogWarning("{0} query time (slow): {1:g}. Query: {2}", methodName, elapsed, commandText); diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 29aab353d8..ee746c6696 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -68,7 +68,7 @@ namespace Emby.Server.Implementations.HttpServer _networkManager = networkManager; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; - + _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); Instance = this; @@ -603,13 +603,13 @@ namespace Emby.Server.Implementations.HttpServer stopWatch.Stop(); var elapsed = stopWatch.Elapsed; - if (elapsed.Milliseconds > 500) + if (elapsed.TotalMilliseconds > 500) { - _logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:ss.fff}. {Url}", httpRes.StatusCode, remoteIp, stopWatch.Elapsed, urlToLog); + _logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); } else { - _logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:ss.fff}. {Url}", httpRes.StatusCode, remoteIp, stopWatch.Elapsed, urlToLog); + _logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index d33ce8a1ea..ec9466c4ab 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var dueTime = triggerDate - now; - logger.LogInformation("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate, dueTime); + logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:g}, which is {DueTime:g} from now.", taskName, triggerDate, dueTime); Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); } -- cgit v1.2.3 From 1731bf7372a13ea8c656eb9f895508b7b4c66784 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 Feb 2019 20:47:23 +0100 Subject: Remove ordering items --- Emby.Server.Implementations/Dto/DtoService.cs | 15 ++-------- .../Session/SessionManager.cs | 2 +- MediaBrowser.Api/BaseApiService.cs | 13 ++------ MediaBrowser.Api/FilterService.cs | 3 +- MediaBrowser.Api/UserLibrary/ItemsService.cs | 35 ++++++++-------------- MediaBrowser.Controller/Dto/DtoOptions.cs | 14 ++------- MediaBrowser.Controller/Dto/IDtoService.cs | 4 +-- MediaBrowser.Controller/Entities/Folder.cs | 24 ++------------- 8 files changed, 27 insertions(+), 83 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 2233d3d40c..7b28a22a84 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -5,8 +5,6 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Controller.Channels; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -21,8 +19,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; @@ -83,15 +79,8 @@ namespace Emby.Server.Implementations.Dto return GetBaseItemDto(item, options, user, owner); } - public BaseItemDto[] GetBaseItemDtos(List items, DtoOptions options, User user = null, BaseItem owner = null) - { - return GetBaseItemDtos(items, items.Count, options, user, owner); - } - - public BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null) - { - return GetBaseItemDtos(items, items.Length, options, user, owner); - } + public BaseItemDto[] GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null) + => GetBaseItemDtos(items, items.Count, options, user, owner); public BaseItemDto[] GetBaseItemDtos(IEnumerable items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index fa0ab62d32..03e7b26545 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1090,7 +1090,7 @@ namespace Emby.Server.Implementations.Session await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); } - private IList TranslateItemForPlayback(Guid id, User user) + private IEnumerable TranslateItemForPlayback(Guid id, User user) { var item = _libraryManager.GetItemById(id); diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index a037357ede..69673a49c1 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -172,16 +172,9 @@ namespace MediaBrowser.Api if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes)) { - if (string.IsNullOrEmpty(hasDtoOptions.EnableImageTypes)) - { - options.ImageTypes = Array.Empty(); - } - else - { - options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)) - .ToArray(); - } + options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)) + .ToArray(); } } diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index 9caf07cea2..201efe7371 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -180,7 +181,7 @@ namespace MediaBrowser.Api return ToOptimizedResult(filters); } - private QueryFiltersLegacy GetFilters(BaseItem[] items) + private QueryFiltersLegacy GetFilters(IReadOnlyCollection items) { var result = new QueryFiltersLegacy(); diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 84475467fe..3c7ad1d0a6 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using MediaBrowser.Controller.Dto; @@ -197,29 +198,27 @@ namespace MediaBrowser.Api.UserLibrary request.ParentId = null; } - var item = string.IsNullOrEmpty(request.ParentId) ? - null : - _libraryManager.GetItemById(request.ParentId); + BaseItem item = null; - if (item == null) + if (!string.IsNullOrEmpty(request.ParentId)) { - item = string.IsNullOrEmpty(request.ParentId) ? - user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder() : - _libraryManager.GetItemById(request.ParentId); + item = _libraryManager.GetItemById(request.ParentId); } - // Default list type = children + if (item == null) + { + item = _libraryManager.GetUserRootFolder(); + } - var folder = item as Folder; + Folder folder = item as Folder; if (folder == null) { - folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder(); + folder = _libraryManager.GetUserRootFolder(); } var hasCollectionType = folder as IHasCollectionType; - var isPlaylistQuery = (hasCollectionType != null && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)); - - if (isPlaylistQuery) + if (hasCollectionType != null + && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) { request.Recursive = true; request.IncludeItemTypes = "Playlist"; @@ -235,20 +234,12 @@ namespace MediaBrowser.Api.UserLibrary }; } - if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null) - { - return folder.GetItems(GetItemsQuery(request, dtoOptions, user)); - } - - var userRoot = item as UserRootFolder; - - if (userRoot == null) + if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || !(item is UserRootFolder)) { return folder.GetItems(GetItemsQuery(request, dtoOptions, user)); } var itemsArray = folder.GetChildren(user, true).ToArray(); - return new QueryResult { Items = itemsArray, diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs index aa99f6b58f..cdaf95f5ce 100644 --- a/MediaBrowser.Controller/Dto/DtoOptions.cs +++ b/MediaBrowser.Controller/Dto/DtoOptions.cs @@ -36,9 +36,7 @@ namespace MediaBrowser.Controller.Dto .ToArray(); public bool ContainsField(ItemFields field) - { - return AllItemFields.Contains(field); - } + => Fields.Contains(field); public DtoOptions(bool allFields) { @@ -47,15 +45,7 @@ namespace MediaBrowser.Controller.Dto EnableUserData = true; AddCurrentProgram = true; - if (allFields) - { - Fields = AllItemFields; - } - else - { - Fields = new ItemFields[] { }; - } - + Fields = allFields ? AllItemFields : Array.Empty(); ImageTypes = AllImageTypes; } diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index df5ec5dd0b..4b6fd58fea 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -57,9 +57,7 @@ namespace MediaBrowser.Controller.Dto /// The options. /// The user. /// The owner. - BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null); - - BaseItemDto[] GetBaseItemDtos(List items, DtoOptions options, User user = null, BaseItem owner = null); + BaseItemDto[] GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null); /// /// Gets the item by name dto. diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 8bfadbee6c..e49ff20baa 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -810,37 +810,19 @@ namespace MediaBrowser.Controller.Entities { if (query.ItemIds.Length > 0) { - var result = LibraryManager.GetItemsResult(query); - - if (query.OrderBy.Length == 0) - { - var ids = query.ItemIds.ToList(); - - // Try to preserve order - result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id)).ToArray(); - } - return result; + return LibraryManager.GetItemsResult(query); } return GetItemsInternal(query); } - public BaseItem[] GetItemList(InternalItemsQuery query) + public IReadOnlyList GetItemList(InternalItemsQuery query) { query.EnableTotalRecordCount = false; if (query.ItemIds.Length > 0) { - var result = LibraryManager.GetItemList(query); - - if (query.OrderBy.Length == 0) - { - var ids = query.ItemIds.ToList(); - - // Try to preserve order - return result.OrderBy(i => ids.IndexOf(i.Id)).ToArray(); - } - return result.ToArray(); + return LibraryManager.GetItemList(query); } return GetItemsInternal(query).Items; -- cgit v1.2.3 From 4db31acff940dc9cabbd39649103faf4dc2e2c47 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 21 Feb 2019 10:07:21 +0100 Subject: Begin removing System.Net sources --- .../HttpClientManager/HttpClientManager.cs | 2 +- .../HttpServer/FileWriter.cs | 16 +- .../HttpServer/HttpListenerHost.cs | 3 + .../HttpServer/HttpResultFactory.cs | 2 +- .../HttpServer/RangeRequestWriter.cs | 2 +- .../HttpServer/ResponseFilter.cs | 2 +- .../HttpServer/StreamWriter.cs | 4 +- .../SocketSharp/WebSocketSharpListener.cs | 54 ++- .../SocketSharp/WebSocketSharpRequest.cs | 3 +- .../SocketSharp/WebSocketSharpResponse.cs | 46 +- .../Progressive/BaseProgressiveStreamingService.cs | 18 +- MediaBrowser.Model/Services/IRequest.cs | 2 +- .../Services/QueryParamCollection.cs | 31 ++ SocketHttpListener/Ext.cs | 1 - SocketHttpListener/HttpResponse.cs | 243 +++++----- .../Net/AuthenticationSchemeSelector.cs | 6 - SocketHttpListener/Net/AuthenticationTypes.cs | 9 - SocketHttpListener/Net/BoundaryType.cs | 11 - SocketHttpListener/Net/ChunkStream.cs | 385 --------------- SocketHttpListener/Net/ChunkedInputStream.cs | 178 ------- SocketHttpListener/Net/EntitySendFormat.cs | 8 - SocketHttpListener/Net/HttpConnection.cs | 528 -------------------- SocketHttpListener/Net/HttpEndPointListener.cs | 526 -------------------- SocketHttpListener/Net/HttpEndPointManager.cs | 192 -------- SocketHttpListener/Net/HttpKnownHeaderNames.cs | 91 ---- SocketHttpListener/Net/HttpListener.cs | 284 ----------- .../Net/HttpListenerBasicIdentity.cs | 49 -- .../Net/HttpListenerContext.Managed.cs | 99 ---- SocketHttpListener/Net/HttpListenerContext.cs | 74 --- .../Net/HttpListenerPrefixCollection.cs | 117 ----- .../Net/HttpListenerRequest.Managed.cs | 325 ------------- SocketHttpListener/Net/HttpListenerRequest.cs | 537 --------------------- .../Net/HttpListenerRequestUriBuilder.cs | 443 ----------------- .../Net/HttpListenerResponse.Managed.cs | 333 ------------- SocketHttpListener/Net/HttpListenerResponse.cs | 294 ----------- .../Net/HttpRequestStream.Managed.cs | 210 -------- SocketHttpListener/Net/HttpRequestStream.cs | 129 ----- .../Net/HttpResponseStream.Managed.cs | 329 ------------- SocketHttpListener/Net/HttpResponseStream.cs | 124 ----- SocketHttpListener/Net/HttpStatusCode.cs | 321 ------------ SocketHttpListener/Net/HttpStatusDescription.cs | 69 --- SocketHttpListener/Net/HttpStreamAsyncResult.cs | 79 --- SocketHttpListener/Net/HttpVersion.cs | 16 - SocketHttpListener/Net/ListenerPrefix.cs | 89 ---- SocketHttpListener/Net/UriScheme.cs | 20 - SocketHttpListener/Net/WebHeaderCollection.cs | 360 -------------- SocketHttpListener/Net/WebHeaderEncoding.cs | 84 ---- .../Net/WebSockets/HttpListenerWebSocketContext.cs | 92 ---- .../Net/WebSockets/HttpWebSocket.Managed.cs | 81 ---- SocketHttpListener/Net/WebSockets/HttpWebSocket.cs | 159 ------ .../Net/WebSockets/WebSocketCloseStatus.cs | 27 -- .../Net/WebSockets/WebSocketContext.cs | 24 - .../Net/WebSockets/WebSocketValidate.cs | 141 ------ SocketHttpListener/WebSocket.cs | 3 +- 54 files changed, 263 insertions(+), 7012 deletions(-) delete mode 100644 SocketHttpListener/Net/AuthenticationSchemeSelector.cs delete mode 100644 SocketHttpListener/Net/AuthenticationTypes.cs delete mode 100644 SocketHttpListener/Net/BoundaryType.cs delete mode 100644 SocketHttpListener/Net/ChunkStream.cs delete mode 100644 SocketHttpListener/Net/ChunkedInputStream.cs delete mode 100644 SocketHttpListener/Net/EntitySendFormat.cs delete mode 100644 SocketHttpListener/Net/HttpConnection.cs delete mode 100644 SocketHttpListener/Net/HttpEndPointListener.cs delete mode 100644 SocketHttpListener/Net/HttpEndPointManager.cs delete mode 100644 SocketHttpListener/Net/HttpKnownHeaderNames.cs delete mode 100644 SocketHttpListener/Net/HttpListener.cs delete mode 100644 SocketHttpListener/Net/HttpListenerBasicIdentity.cs delete mode 100644 SocketHttpListener/Net/HttpListenerContext.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpListenerContext.cs delete mode 100644 SocketHttpListener/Net/HttpListenerPrefixCollection.cs delete mode 100644 SocketHttpListener/Net/HttpListenerRequest.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpListenerRequest.cs delete mode 100644 SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs delete mode 100644 SocketHttpListener/Net/HttpListenerResponse.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpListenerResponse.cs delete mode 100644 SocketHttpListener/Net/HttpRequestStream.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpRequestStream.cs delete mode 100644 SocketHttpListener/Net/HttpResponseStream.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpResponseStream.cs delete mode 100644 SocketHttpListener/Net/HttpStatusCode.cs delete mode 100644 SocketHttpListener/Net/HttpStatusDescription.cs delete mode 100644 SocketHttpListener/Net/HttpStreamAsyncResult.cs delete mode 100644 SocketHttpListener/Net/HttpVersion.cs delete mode 100644 SocketHttpListener/Net/ListenerPrefix.cs delete mode 100644 SocketHttpListener/Net/UriScheme.cs delete mode 100644 SocketHttpListener/Net/WebHeaderCollection.cs delete mode 100644 SocketHttpListener/Net/WebHeaderEncoding.cs delete mode 100644 SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs delete mode 100644 SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs delete mode 100644 SocketHttpListener/Net/WebSockets/HttpWebSocket.cs delete mode 100644 SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs delete mode 100644 SocketHttpListener/Net/WebSockets/WebSocketContext.cs delete mode 100644 SocketHttpListener/Net/WebSockets/WebSocketValidate.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 2e07281369..834a494f81 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.HttpClientManager } httpWebRequest.ContentType = contentType; - httpWebRequest.ContentLength = bytes.Length; + // httpWebRequest.ContentLength = bytes.Length; (await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length); } catch (Exception ex) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 7aedba9b31..835c6f52ef 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; +using Emby.Server.Implementations.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; @@ -14,6 +15,7 @@ namespace Emby.Server.Implementations.HttpServer public class FileWriter : IHttpResult { private ILogger Logger { get; set; } + public IFileSystem FileSystem { get; } private string RangeHeader { get; set; } private bool IsHeadRequest { get; set; } @@ -51,6 +53,7 @@ namespace Emby.Server.Implementations.HttpServer Path = path; Logger = logger; + FileSystem = fileSystem; RangeHeader = rangeHeader; Headers["Content-Type"] = contentType; @@ -60,7 +63,8 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(rangeHeader)) { - Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); + // TODO + //Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); StatusCode = HttpStatusCode.OK; } else @@ -95,7 +99,7 @@ namespace Emby.Server.Implementations.HttpServer // Content-Length is the length of what we're serving, not the original content var lengthString = RangeLength.ToString(UsCulture); - Headers["Content-Length"] = lengthString; + // TODO Headers["Content-Length"] = lengthString; var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); Headers["Content-Range"] = rangeString; @@ -174,12 +178,12 @@ namespace Emby.Server.Implementations.HttpServer } //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0; - - await response.TransmitFile(path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false); + // TODO not DI friendly lol + await response.TransmitFile(path, 0, 0, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); return; } - - await response.TransmitFile(path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false); + // TODO not DI friendly lol + await response.TransmitFile(path, RangeStart, RangeLength, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); } finally { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index ee746c6696..fb42460f1b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -644,6 +644,9 @@ namespace Emby.Server.Implementations.HttpServer { var bOutput = Encoding.UTF8.GetBytes(text); response.SetContentLength(bOutput.Length); + // TODO + response.Headers.Remove("Content-Length"); // DO NOT SET THIS, IT'S DONE AUTOMATICALLY BECAUSE MS ARE NOT STUPID + response.SendChunked = true; return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length); } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 070717d489..5e9d2b4c36 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -592,7 +592,7 @@ namespace Emby.Server.Implementations.HttpServer { if (totalContentLength.HasValue) { - responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture); + // TODO responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture); } if (isHeadRequest) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 891a76ec2a..9c8ab8d91c 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.HttpServer RangeLength = 1 + RangeEnd - RangeStart; // Content-Length is the length of what we're serving, not the original content - Headers["Content-Length"] = RangeLength.ToString(UsCulture); + // TODO Headers["Content-Length"] = RangeLength.ToString(UsCulture); Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); if (RangeStart > 0 && SourceStream.CanSeek) diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index da2bf983a0..dbce3250d4 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.HttpServer public void FilterResponse(IRequest req, IResponse res, object dto) { // Try to prevent compatibility view - res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); + res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); res.AddHeader("Access-Control-Allow-Origin", "*"); diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index cb2e3580b2..750b0f7958 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer if (source.CanSeek) { - Headers["Content-Length"] = source.Length.ToString(UsCulture); + // TODO Headers["Content-Length"] = source.Length.ToString(UsCulture); } } @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.HttpServer Headers["Content-Type"] = contentType; - Headers["Content-Length"] = contentLength.ToString(UsCulture); + // TODO Headers["Content-Length"] = contentLength.ToString(UsCulture); } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index 693c2328c6..64856f04af 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; + using System.Net; + using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; @@ -13,9 +14,8 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; -using SocketHttpListener.Net; -namespace Jellyfin.Server.SocketSharp + namespace Jellyfin.Server.SocketSharp { public class WebSocketSharpListener : IHttpListener { @@ -67,24 +67,44 @@ namespace Jellyfin.Server.SocketSharp public void Start(IEnumerable urlPrefixes) { - if (_listener == null) - { - _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); - } + // TODO + //if (_listener == null) + //{ + // _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); + //} + + //_listener.EnableDualMode = _enableDualMode; + + //if (_certificate != null) + //{ + // _listener.LoadCert(_certificate); + //} - _listener.EnableDualMode = _enableDualMode; + //_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); + //_listener.Prefixes.AddRange(urlPrefixes); - if (_certificate != null) + //_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); + + //_listener.Start(); + + if (_listener == null) { - _listener.LoadCert(_certificate); + _listener = new HttpListener(); } - + _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); - _listener.Prefixes.AddRange(urlPrefixes); - _listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); + //foreach (var urlPrefix in urlPrefixes) + //{ + // _listener.Prefixes.Add(urlPrefix); + //} + _listener.Prefixes.Add("http://localhost:8096/"); _listener.Start(); + + // TODO how to do this in netcore? + _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), + null); } private static void LogRequest(ILogger logger, HttpListenerRequest request) @@ -98,8 +118,10 @@ namespace Jellyfin.Server.SocketSharp request.UserAgent ?? string.Empty); } - private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken) + private Task InitTask(IAsyncResult asyncResult, CancellationToken cancellationToken) { + var context = _listener.EndGetContext(asyncResult); + _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), null); IHttpRequest httpReq = null; var request = context.Request; @@ -134,7 +156,7 @@ namespace Jellyfin.Server.SocketSharp var endpoint = ctx.Request.RemoteEndPoint.ToString(); var url = ctx.Request.RawUrl; - var queryString = ctx.Request.QueryString; + var queryString = new QueryParamCollection(ctx.Request.QueryString); var connectingArgs = new WebSocketConnectingEventArgs { @@ -153,7 +175,7 @@ namespace Jellyfin.Server.SocketSharp if (WebSocketConnected != null) { - var socket = new SharpWebSocket(webSocketContext.WebSocket, _logger); + SharpWebSocket socket = null; //new SharpWebSocket(webSocketContext.WebSocket, _logger); await socket.ConnectAsServerAsync().ConfigureAwait(false); WebSocketConnected(new WebSocketConnectEventArgs diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs index 6458707d92..6a00e283c6 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Net; using System.Text; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; @@ -438,7 +439,7 @@ namespace Jellyfin.Server.SocketSharp public string UserAgent => request.UserAgent; - public QueryParamCollection Headers => request.Headers; + public QueryParamCollection Headers => new QueryParamCollection(request.Headers); private QueryParamCollection queryString; public QueryParamCollection QueryString => queryString ?? (queryString = MyHttpUtility.ParseQueryString(request.Url.Query)); diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs index cf5aee5d40..552ae1754d 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; +using Emby.Server.Implementations; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; -using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IRequest = MediaBrowser.Model.Services.IRequest; @@ -53,7 +54,7 @@ namespace Jellyfin.Server.SocketSharp set => _response.ContentType = value; } - public QueryParamCollection Headers => _response.Headers; + public QueryParamCollection Headers => new QueryParamCollection(_response.Headers); private static string AsHeaderValue(Cookie cookie) { @@ -152,7 +153,7 @@ namespace Jellyfin.Server.SocketSharp // you can happily set the Content-Length header in Asp.Net // but HttpListener will complain if you do - you have to set ContentLength64 on the response. // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header - _response.ContentLength64 = contentLength; + //_response.ContentLength64 = contentLength; } public void SetCookie(Cookie cookie) @@ -172,10 +173,43 @@ namespace Jellyfin.Server.SocketSharp public void ClearCookies() { } - - public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) + const int StreamCopyToBufferSize = 81920; + public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) { - return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken); + // TODO + // return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken); + var allowAsync = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + //if (count <= 0) + //{ + // allowAsync = true; + //} + + var fileOpenOptions = FileOpenOptions.SequentialScan; + + if (allowAsync) + { + fileOpenOptions |= FileOpenOptions.Asynchronous; + } + + // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + + using (var fs = fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions)) + { + if (offset > 0) + { + fs.Position = offset; + } + + if (count > 0) + { + await streamHelper.CopyToAsync(fs, OutputStream, count, cancellationToken).ConfigureAwait(false); + } + else + { + await fs.CopyToAsync(OutputStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false); + } + } } } } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 6a98c5e8a6..3e74d59db7 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -324,7 +324,8 @@ namespace MediaBrowser.Api.Playback.Progressive // Seeing cases of -1 here if (response.ContentLength.HasValue && response.ContentLength.Value >= 0) { - responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); + // TODO + //responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); } if (isHeadRequest) @@ -382,17 +383,18 @@ namespace MediaBrowser.Api.Playback.Progressive var hasHeaders = streamResult as IHasHeaders; if (hasHeaders != null) { - if (contentLength.HasValue) - { - hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); - } - else - { + // TODO + //if (contentLength.HasValue) + //{ + // hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); + //} + //else + //{ if (hasHeaders.Headers.ContainsKey("Content-Length")) { hasHeaders.Headers.Remove("Content-Length"); } - } + //} } return streamResult; diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index ac9b981b98..909cebce3d 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -152,7 +152,7 @@ namespace MediaBrowser.Model.Services QueryParamCollection Headers { get; } - Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken); + Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken); bool SendChunked { get; set; } } diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 4297b97c66..0e0ebf848b 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; +using System.Net; using MediaBrowser.Model.Dto; namespace MediaBrowser.Model.Services { + // Remove this garbage class, it's just a bastard copy of NameValueCollection public class QueryParamCollection : List { public QueryParamCollection() @@ -20,6 +23,30 @@ namespace MediaBrowser.Model.Services } } + // TODO remove this shit + public QueryParamCollection(WebHeaderCollection webHeaderCollection) + { + foreach (var key in webHeaderCollection.AllKeys) + { + foreach (var value in webHeaderCollection.GetValues(key) ?? Array.Empty()) + { + Add(key, value); + } + } + } + + // TODO remove this shit + public QueryParamCollection(NameValueCollection nameValueCollection) + { + foreach (var key in nameValueCollection.AllKeys) + { + foreach (var value in nameValueCollection.GetValues(key) ?? Array.Empty()) + { + Add(key, value); + } + } + } + private static StringComparison GetStringComparison() { return StringComparison.OrdinalIgnoreCase; @@ -50,6 +77,10 @@ namespace MediaBrowser.Model.Services /// public virtual void Add(string key, string value) { + if (string.Equals(key, "content-length", StringComparison.OrdinalIgnoreCase)) + { + return; + } Add(new NameValuePair(key, value)); } diff --git a/SocketHttpListener/Ext.cs b/SocketHttpListener/Ext.cs index 2b3c67071c..697c5d5b70 100644 --- a/SocketHttpListener/Ext.cs +++ b/SocketHttpListener/Ext.cs @@ -6,7 +6,6 @@ using System.Net; using System.Text; using System.Threading.Tasks; using MediaBrowser.Model.Services; -using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode; using WebSocketState = System.Net.WebSockets.WebSocketState; namespace SocketHttpListener diff --git a/SocketHttpListener/HttpResponse.cs b/SocketHttpListener/HttpResponse.cs index d5d9b4a1c8..a33b942959 100644 --- a/SocketHttpListener/HttpResponse.cs +++ b/SocketHttpListener/HttpResponse.cs @@ -1,123 +1,122 @@ -using System; -using System.Linq; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; -using SocketHttpListener.Net; -using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode; -using HttpVersion = SocketHttpListener.Net.HttpVersion; - -namespace SocketHttpListener +using System; +using System.Linq; +using System.Net; +using System.Text; +using MediaBrowser.Model.Services; +using SocketHttpListener.Net; + +namespace SocketHttpListener { - internal class HttpResponse : HttpBase - { - #region Private Fields - - private string _code; - private string _reason; - - #endregion - - #region Private Constructors - - private HttpResponse(string code, string reason, Version version, QueryParamCollection headers) - : base(version, headers) - { - _code = code; - _reason = reason; - } - - #endregion - - #region Internal Constructors - - internal HttpResponse(HttpStatusCode code) - : this(code, code.GetDescription()) - { - } - - internal HttpResponse(HttpStatusCode code, string reason) - : this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection()) - { - Headers["Server"] = "websocket-sharp/1.0"; - } - - #endregion - - #region Public Properties - - public CookieCollection Cookies => GetCookies(Headers, true); - - private static CookieCollection GetCookies(QueryParamCollection headers, bool response) - { - var name = response ? "Set-Cookie" : "Cookie"; - return headers == null || !headers.Contains(name) - ? new CookieCollection() - : CookieHelper.Parse(headers[name], response); - } - - public bool IsProxyAuthenticationRequired => _code == "407"; - - public bool IsUnauthorized => _code == "401"; - - public bool IsWebSocketResponse - { - get - { - var headers = Headers; - return ProtocolVersion > HttpVersion.Version10 && - _code == "101" && - headers.Contains("Upgrade", "websocket") && - headers.Contains("Connection", "Upgrade"); - } - } - - public string Reason => _reason; - - public string StatusCode => _code; - - #endregion - - #region Internal Methods - - internal static HttpResponse CreateCloseResponse(HttpStatusCode code) - { - var res = new HttpResponse(code); - res.Headers["Connection"] = "close"; - - return res; - } - - #endregion - - #region Public Methods - - public void SetCookies(CookieCollection cookies) - { - if (cookies == null || cookies.Count == 0) - return; - - var headers = Headers; - var sorted = cookies.OfType().OrderBy(i => i.Name).ToList(); - - foreach (var cookie in sorted) - headers.Add("Set-Cookie", cookie.ToString()); - } - - public override string ToString() - { - var output = new StringBuilder(64); - output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf); - - var headers = Headers; - foreach (var key in headers.Keys) - output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf); - - output.Append(CrLf); - - return output.ToString(); - } - - #endregion - } -} + // TODO what is the point of this class? + internal class HttpResponse : HttpBase + { + #region Private Fields + + private string _code; + private string _reason; + + #endregion + + #region Private Constructors + + private HttpResponse(string code, string reason, Version version, QueryParamCollection headers) + : base(version, headers) + { + _code = code; + _reason = reason; + } + + #endregion + + #region Internal Constructors + + internal HttpResponse(HttpStatusCode code) + : this(code, code.GetDescription()) + { + } + + internal HttpResponse(HttpStatusCode code, string reason) + : this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection()) + { + Headers["Server"] = "websocket-sharp/1.0"; + } + + #endregion + + #region Public Properties + + public CookieCollection Cookies => GetCookies(Headers, true); + + private static CookieCollection GetCookies(QueryParamCollection headers, bool response) + { + var name = response ? "Set-Cookie" : "Cookie"; + return headers == null || !headers.Contains(name) + ? new CookieCollection() + : CookieHelper.Parse(headers[name], response); + } + + public bool IsProxyAuthenticationRequired => _code == "407"; + + public bool IsUnauthorized => _code == "401"; + + public bool IsWebSocketResponse + { + get + { + var headers = Headers; + return ProtocolVersion > HttpVersion.Version10 && + _code == "101" && + headers.Contains("Upgrade", "websocket") && + headers.Contains("Connection", "Upgrade"); + } + } + + public string Reason => _reason; + + public string StatusCode => _code; + + #endregion + + #region Internal Methods + + internal static HttpResponse CreateCloseResponse(HttpStatusCode code) + { + var res = new HttpResponse(code); + res.Headers["Connection"] = "close"; + + return res; + } + + #endregion + + #region Public Methods + + public void SetCookies(CookieCollection cookies) + { + if (cookies == null || cookies.Count == 0) + return; + + var headers = Headers; + var sorted = cookies.OfType().OrderBy(i => i.Name).ToList(); + + foreach (var cookie in sorted) + headers.Add("Set-Cookie", cookie.ToString()); + } + + public override string ToString() + { + var output = new StringBuilder(64); + output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf); + + var headers = Headers; + foreach (var key in headers.Keys) + output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf); + + output.Append(CrLf); + + return output.ToString(); + } + + #endregion + } +} diff --git a/SocketHttpListener/Net/AuthenticationSchemeSelector.cs b/SocketHttpListener/Net/AuthenticationSchemeSelector.cs deleted file mode 100644 index 77e6d0b8ab..0000000000 --- a/SocketHttpListener/Net/AuthenticationSchemeSelector.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Net; - -namespace SocketHttpListener.Net -{ - public delegate AuthenticationSchemes AuthenticationSchemeSelector(HttpListenerRequest httpRequest); -} diff --git a/SocketHttpListener/Net/AuthenticationTypes.cs b/SocketHttpListener/Net/AuthenticationTypes.cs deleted file mode 100644 index a3dbe9dfff..0000000000 --- a/SocketHttpListener/Net/AuthenticationTypes.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal class AuthenticationTypes - { - internal const string NTLM = "NTLM"; - internal const string Negotiate = "Negotiate"; - internal const string Basic = "Basic"; - } -} diff --git a/SocketHttpListener/Net/BoundaryType.cs b/SocketHttpListener/Net/BoundaryType.cs deleted file mode 100644 index da0b22910e..0000000000 --- a/SocketHttpListener/Net/BoundaryType.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal enum BoundaryType - { - ContentLength = 0, // Content-Length: XXX - Chunked = 1, // Transfer-Encoding: chunked - Multipart = 3, - None = 4, - Invalid = 5, - } -} diff --git a/SocketHttpListener/Net/ChunkStream.cs b/SocketHttpListener/Net/ChunkStream.cs deleted file mode 100644 index 3836947d4c..0000000000 --- a/SocketHttpListener/Net/ChunkStream.cs +++ /dev/null @@ -1,385 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal sealed class ChunkStream - { - private enum State - { - None, - PartialSize, - Body, - BodyFinished, - Trailer - } - - private class Chunk - { - public byte[] Bytes; - public int Offset; - - public Chunk(byte[] chunk) - { - Bytes = chunk; - } - - public int Read(byte[] buffer, int offset, int size) - { - int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size; - Buffer.BlockCopy(Bytes, Offset, buffer, offset, nread); - Offset += nread; - return nread; - } - } - - internal WebHeaderCollection _headers; - private int _chunkSize; - private int _chunkRead; - private int _totalWritten; - private State _state; - private StringBuilder _saved; - private bool _sawCR; - private bool _gotit; - private int _trailerState; - private List _chunks; - - public ChunkStream(WebHeaderCollection headers) - { - _headers = headers; - _saved = new StringBuilder(); - _chunks = new List(); - _chunkSize = -1; - _totalWritten = 0; - } - - public void ResetBuffer() - { - _chunkSize = -1; - _chunkRead = 0; - _totalWritten = 0; - _chunks.Clear(); - } - - public int Read(byte[] buffer, int offset, int size) - { - return ReadFromChunks(buffer, offset, size); - } - - private int ReadFromChunks(byte[] buffer, int offset, int size) - { - int count = _chunks.Count; - int nread = 0; - - var chunksForRemoving = new List(count); - for (int i = 0; i < count; i++) - { - var chunk = _chunks[i]; - - if (chunk.Offset == chunk.Bytes.Length) - { - chunksForRemoving.Add(chunk); - continue; - } - - nread += chunk.Read(buffer, offset + nread, size - nread); - if (nread == size) - break; - } - - foreach (var chunk in chunksForRemoving) - _chunks.Remove(chunk); - - return nread; - } - - public void Write(byte[] buffer, int offset, int size) - { - // Note, the logic here only works when offset is 0 here. - // Otherwise, it would treat "size" as the end offset instead of an actual byte count from offset. - - if (offset < size) - InternalWrite(buffer, ref offset, size); - } - - private void InternalWrite(byte[] buffer, ref int offset, int size) - { - if (_state == State.None || _state == State.PartialSize) - { - _state = GetChunkSize(buffer, ref offset, size); - if (_state == State.PartialSize) - return; - - _saved.Length = 0; - _sawCR = false; - _gotit = false; - } - - if (_state == State.Body && offset < size) - { - _state = ReadBody(buffer, ref offset, size); - if (_state == State.Body) - return; - } - - if (_state == State.BodyFinished && offset < size) - { - _state = ReadCRLF(buffer, ref offset, size); - if (_state == State.BodyFinished) - return; - - _sawCR = false; - } - - if (_state == State.Trailer && offset < size) - { - _state = ReadTrailer(buffer, ref offset, size); - if (_state == State.Trailer) - return; - - _saved.Length = 0; - _sawCR = false; - _gotit = false; - } - - if (offset < size) - InternalWrite(buffer, ref offset, size); - } - - public bool WantMore => (_chunkRead != _chunkSize || _chunkSize != 0 || _state != State.None); - - public bool DataAvailable - { - get - { - int count = _chunks.Count; - for (int i = 0; i < count; i++) - { - var ch = _chunks[i]; - if (ch == null || ch.Bytes == null) - continue; - if (ch.Bytes.Length > 0 && ch.Offset < ch.Bytes.Length) - return (_state != State.Body); - } - return false; - } - } - - public int TotalDataSize => _totalWritten; - - public int ChunkLeft => _chunkSize - _chunkRead; - - private State ReadBody(byte[] buffer, ref int offset, int size) - { - if (_chunkSize == 0) - return State.BodyFinished; - - int diff = size - offset; - if (diff + _chunkRead > _chunkSize) - diff = _chunkSize - _chunkRead; - - byte[] chunk = new byte[diff]; - Buffer.BlockCopy(buffer, offset, chunk, 0, diff); - _chunks.Add(new Chunk(chunk)); - offset += diff; - _chunkRead += diff; - _totalWritten += diff; - - return (_chunkRead == _chunkSize) ? State.BodyFinished : State.Body; - } - - private State GetChunkSize(byte[] buffer, ref int offset, int size) - { - _chunkRead = 0; - _chunkSize = 0; - char c = '\0'; - while (offset < size) - { - c = (char)buffer[offset++]; - if (c == '\r') - { - if (_sawCR) - ThrowProtocolViolation("2 CR found"); - - _sawCR = true; - continue; - } - - if (_sawCR && c == '\n') - break; - - if (c == ' ') - _gotit = true; - - if (!_gotit) - _saved.Append(c); - - if (_saved.Length > 20) - ThrowProtocolViolation("chunk size too long."); - } - - if (!_sawCR || c != '\n') - { - if (offset < size) - ThrowProtocolViolation("Missing \\n"); - - try - { - if (_saved.Length > 0) - { - _chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber); - } - } - catch (Exception) - { - ThrowProtocolViolation("Cannot parse chunk size."); - } - - return State.PartialSize; - } - - _chunkRead = 0; - try - { - _chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber); - } - catch (Exception) - { - ThrowProtocolViolation("Cannot parse chunk size."); - } - - if (_chunkSize == 0) - { - _trailerState = 2; - return State.Trailer; - } - - return State.Body; - } - - private static string RemoveChunkExtension(string input) - { - int idx = input.IndexOf(';'); - if (idx == -1) - return input; - return input.Substring(0, idx); - } - - private State ReadCRLF(byte[] buffer, ref int offset, int size) - { - if (!_sawCR) - { - if ((char)buffer[offset++] != '\r') - ThrowProtocolViolation("Expecting \\r"); - - _sawCR = true; - if (offset == size) - return State.BodyFinished; - } - - if (_sawCR && (char)buffer[offset++] != '\n') - ThrowProtocolViolation("Expecting \\n"); - - return State.None; - } - - private State ReadTrailer(byte[] buffer, ref int offset, int size) - { - char c = '\0'; - - // short path - if (_trailerState == 2 && (char)buffer[offset] == '\r' && _saved.Length == 0) - { - offset++; - if (offset < size && (char)buffer[offset] == '\n') - { - offset++; - return State.None; - } - offset--; - } - - int st = _trailerState; - string stString = "\r\n\r"; - while (offset < size && st < 4) - { - c = (char)buffer[offset++]; - if ((st == 0 || st == 2) && c == '\r') - { - st++; - continue; - } - - if ((st == 1 || st == 3) && c == '\n') - { - st++; - continue; - } - - if (st > 0) - { - _saved.Append(stString.Substring(0, _saved.Length == 0 ? st - 2 : st)); - st = 0; - if (_saved.Length > 4196) - ThrowProtocolViolation("Error reading trailer (too long)."); - } - } - - if (st < 4) - { - _trailerState = st; - if (offset < size) - ThrowProtocolViolation("Error reading trailer."); - - return State.Trailer; - } - - var reader = new StringReader(_saved.ToString()); - string line; - while ((line = reader.ReadLine()) != null && line != "") - _headers.Add(line); - - return State.None; - } - - private static void ThrowProtocolViolation(string message) - { - var we = new WebException(message, null, WebExceptionStatus.ServerProtocolViolation, null); - throw we; - } - } -} diff --git a/SocketHttpListener/Net/ChunkedInputStream.cs b/SocketHttpListener/Net/ChunkedInputStream.cs deleted file mode 100644 index 7aa8529e38..0000000000 --- a/SocketHttpListener/Net/ChunkedInputStream.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.IO; -using System.Net; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal sealed class ChunkedInputStream : HttpRequestStream - { - private ChunkStream _decoder; - private readonly HttpListenerContext _context; - private bool _no_more_data; - - private class ReadBufferState - { - public byte[] Buffer; - public int Offset; - public int Count; - public int InitialCount; - public HttpStreamAsyncResult Ares; - public ReadBufferState(byte[] buffer, int offset, int count, HttpStreamAsyncResult ares) - { - Buffer = buffer; - Offset = offset; - Count = count; - InitialCount = count; - Ares = ares; - } - } - - public ChunkedInputStream(HttpListenerContext context, Stream stream, byte[] buffer, int offset, int length) - : base(stream, buffer, offset, length) - { - _context = context; - var coll = (WebHeaderCollection)context.Request.Headers; - _decoder = new ChunkStream(coll); - } - - public ChunkStream Decoder - { - get => _decoder; - set => _decoder = value; - } - - protected override int ReadCore(byte[] buffer, int offset, int count) - { - IAsyncResult ares = BeginReadCore(buffer, offset, count, null, null); - return EndRead(ares); - } - - protected override IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state) - { - var ares = new HttpStreamAsyncResult(this); - ares._callback = cback; - ares._state = state; - if (_no_more_data || size == 0 || _closed) - { - ares.Complete(); - return ares; - } - int nread = _decoder.Read(buffer, offset, size); - offset += nread; - size -= nread; - if (size == 0) - { - // got all we wanted, no need to bother the decoder yet - ares._count = nread; - ares.Complete(); - return ares; - } - if (!_decoder.WantMore) - { - _no_more_data = nread == 0; - ares._count = nread; - ares.Complete(); - return ares; - } - ares._buffer = new byte[8192]; - ares._offset = 0; - ares._count = 8192; - var rb = new ReadBufferState(buffer, offset, size, ares); - rb.InitialCount += nread; - base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb); - return ares; - } - - private void OnRead(IAsyncResult base_ares) - { - ReadBufferState rb = (ReadBufferState)base_ares.AsyncState; - var ares = rb.Ares; - try - { - int nread = base.EndRead(base_ares); - if (nread == 0) - { - _no_more_data = true; - ares._count = rb.InitialCount - rb.Count; - ares.Complete(); - return; - } - - _decoder.Write(ares._buffer, ares._offset, nread); - nread = _decoder.Read(rb.Buffer, rb.Offset, rb.Count); - rb.Offset += nread; - rb.Count -= nread; - if (rb.Count == 0 || !_decoder.WantMore) - { - _no_more_data = !_decoder.WantMore && nread == 0; - ares._count = rb.InitialCount - rb.Count; - ares.Complete(); - return; - } - ares._offset = 0; - ares._count = Math.Min(8192, _decoder.ChunkLeft + 6); - base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb); - } - catch (Exception e) - { - _context.Connection.SendError(e.Message, 400); - ares.Complete(e); - } - } - - public override int EndRead(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - var ares = asyncResult as HttpStreamAsyncResult; - if (ares == null || !ReferenceEquals(this, ares._parent)) - { - throw new ArgumentException("Invalid async result"); - } - if (ares._endCalled) - { - throw new InvalidOperationException("Invalid end call"); - } - ares._endCalled = true; - - if (!asyncResult.IsCompleted) - asyncResult.AsyncWaitHandle.WaitOne(); - - if (ares._error != null) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "Operation aborted"); - - return ares._count; - } - } -} diff --git a/SocketHttpListener/Net/EntitySendFormat.cs b/SocketHttpListener/Net/EntitySendFormat.cs deleted file mode 100644 index 3ed981084d..0000000000 --- a/SocketHttpListener/Net/EntitySendFormat.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal enum EntitySendFormat - { - ContentLength = 0, // Content-Length: XXX - Chunked = 1, // Transfer-Encoding: chunked - } -} diff --git a/SocketHttpListener/Net/HttpConnection.cs b/SocketHttpListener/Net/HttpConnection.cs deleted file mode 100644 index e89f4ed9b1..0000000000 --- a/SocketHttpListener/Net/HttpConnection.cs +++ /dev/null @@ -1,528 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; -namespace SocketHttpListener.Net -{ - sealed class HttpConnection - { - private static AsyncCallback s_onreadCallback = new AsyncCallback(OnRead); - const int BufferSize = 8192; - Socket _socket; - Stream _stream; - HttpEndPointListener _epl; - MemoryStream _memoryStream; - byte[] _buffer; - HttpListenerContext _context; - StringBuilder _currentLine; - ListenerPrefix _prefix; - HttpRequestStream _requestStream; - HttpResponseStream _responseStream; - bool _chunked; - int _reuses; - bool _contextBound; - bool secure; - IPEndPoint local_ep; - HttpListener _lastListener; - X509Certificate cert; - SslStream ssl_stream; - - private readonly ILogger _logger; - private readonly ICryptoProvider _cryptoProvider; - private readonly IStreamHelper _streamHelper; - private readonly IFileSystem _fileSystem; - private readonly IEnvironmentInfo _environment; - - public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure, - X509Certificate cert, ICryptoProvider cryptoProvider, IStreamHelper streamHelper, IFileSystem fileSystem, - IEnvironmentInfo environment) - { - _logger = logger; - this._socket = socket; - this._epl = epl; - this.secure = secure; - this.cert = cert; - _cryptoProvider = cryptoProvider; - _streamHelper = streamHelper; - _fileSystem = fileSystem; - _environment = environment; - - if (secure == false) - { - _stream = new SocketStream(_socket, false); - } - else - { - ssl_stream = new SslStream(new SocketStream(_socket, false), false, (t, c, ch, e) => - { - if (c == null) - { - return true; - } - - //var c2 = c as X509Certificate2; - //if (c2 == null) - //{ - // c2 = new X509Certificate2(c.GetRawCertData()); - //} - - //_clientCert = c2; - //_clientCertErrors = new int[] { (int)e }; - return true; - }); - - _stream = ssl_stream; - } - } - - public Stream Stream => _stream; - - public async Task Init() - { - if (ssl_stream != null) - { - var enableAsync = true; - if (enableAsync) - { - await ssl_stream.AuthenticateAsServerAsync(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false).ConfigureAwait(false); - } - else - { - ssl_stream.AuthenticateAsServer(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false); - } - } - - InitInternal(); - } - - private void InitInternal() - { - _contextBound = false; - _requestStream = null; - _responseStream = null; - _prefix = null; - _chunked = false; - _memoryStream = new MemoryStream(); - _position = 0; - _inputState = InputState.RequestLine; - _lineState = LineState.None; - _context = new HttpListenerContext(this); - } - - public bool IsClosed => (_socket == null); - - public int Reuses => _reuses; - - public IPEndPoint LocalEndPoint - { - get - { - if (local_ep != null) - return local_ep; - - local_ep = (IPEndPoint)_socket.LocalEndPoint; - return local_ep; - } - } - - public IPEndPoint RemoteEndPoint => _socket.RemoteEndPoint as IPEndPoint; - - public bool IsSecure => secure; - - public ListenerPrefix Prefix - { - get => _prefix; - set => _prefix = value; - } - - private void OnTimeout(object unused) - { - //_logger.LogInformation("HttpConnection timer fired"); - CloseSocket(); - Unbind(); - } - - public void BeginReadRequest() - { - if (_buffer == null) - _buffer = new byte[BufferSize]; - try - { - _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); - } - catch - { - CloseSocket(); - Unbind(); - } - } - - public HttpRequestStream GetRequestStream(bool chunked, long contentlength) - { - if (_requestStream == null) - { - byte[] buffer = _memoryStream.GetBuffer(); - int length = (int)_memoryStream.Length; - _memoryStream = null; - if (chunked) - { - _chunked = true; - //_context.Response.SendChunked = true; - _requestStream = new ChunkedInputStream(_context, _stream, buffer, _position, length - _position); - } - else - { - _requestStream = new HttpRequestStream(_stream, buffer, _position, length - _position, contentlength); - } - } - return _requestStream; - } - - public HttpResponseStream GetResponseStream(bool isExpect100Continue = false) - { - // TODO: can we get this _stream before reading the input? - if (_responseStream == null) - { - var supportsDirectSocketAccess = !_context.Response.SendChunked && !isExpect100Continue && !secure; - - _responseStream = new HttpResponseStream(_stream, _context.Response, false, _streamHelper, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger); - } - return _responseStream; - } - - private static void OnRead(IAsyncResult ares) - { - var cnc = (HttpConnection)ares.AsyncState; - cnc.OnReadInternal(ares); - } - - private void OnReadInternal(IAsyncResult ares) - { - int nread = -1; - try - { - nread = _stream.EndRead(ares); - _memoryStream.Write(_buffer, 0, nread); - if (_memoryStream.Length > 32768) - { - SendError("Bad Request", 400); - Close(true); - return; - } - } - catch - { - if (_memoryStream != null && _memoryStream.Length > 0) - SendError(); - if (_socket != null) - { - CloseSocket(); - Unbind(); - } - return; - } - - if (nread == 0) - { - CloseSocket(); - Unbind(); - return; - } - - if (ProcessInput(_memoryStream)) - { - if (!_context.HaveError) - _context.Request.FinishInitialization(); - - if (_context.HaveError) - { - SendError(); - Close(true); - return; - } - - if (!_epl.BindContext(_context)) - { - const int NotFoundErrorCode = 404; - SendError(HttpStatusDescription.Get(NotFoundErrorCode), NotFoundErrorCode); - Close(true); - return; - } - HttpListener listener = _epl.Listener; - if (_lastListener != listener) - { - RemoveConnection(); - listener.AddConnection(this); - _lastListener = listener; - } - - _contextBound = true; - listener.RegisterContext(_context); - return; - } - _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); - } - - private void RemoveConnection() - { - if (_lastListener == null) - _epl.RemoveConnection(this); - else - _lastListener.RemoveConnection(this); - } - - private enum InputState - { - RequestLine, - Headers - } - - private enum LineState - { - None, - CR, - LF - } - - InputState _inputState = InputState.RequestLine; - LineState _lineState = LineState.None; - int _position; - - // true -> done processing - // false -> need more input - private bool ProcessInput(MemoryStream ms) - { - byte[] buffer = ms.GetBuffer(); - int len = (int)ms.Length; - int used = 0; - string line; - - while (true) - { - if (_context.HaveError) - return true; - - if (_position >= len) - break; - - try - { - line = ReadLine(buffer, _position, len - _position, ref used); - _position += used; - } - catch - { - _context.ErrorMessage = "Bad request"; - _context.ErrorStatus = 400; - return true; - } - - if (line == null) - break; - - if (line == "") - { - if (_inputState == InputState.RequestLine) - continue; - _currentLine = null; - ms = null; - return true; - } - - if (_inputState == InputState.RequestLine) - { - _context.Request.SetRequestLine(line); - _inputState = InputState.Headers; - } - else - { - try - { - _context.Request.AddHeader(line); - } - catch (Exception e) - { - _context.ErrorMessage = e.Message; - _context.ErrorStatus = 400; - return true; - } - } - } - - if (used == len) - { - ms.SetLength(0); - _position = 0; - } - return false; - } - - private string ReadLine(byte[] buffer, int offset, int len, ref int used) - { - if (_currentLine == null) - _currentLine = new StringBuilder(128); - int last = offset + len; - used = 0; - for (int i = offset; i < last && _lineState != LineState.LF; i++) - { - used++; - byte b = buffer[i]; - if (b == 13) - { - _lineState = LineState.CR; - } - else if (b == 10) - { - _lineState = LineState.LF; - } - else - { - _currentLine.Append((char)b); - } - } - - string result = null; - if (_lineState == LineState.LF) - { - _lineState = LineState.None; - result = _currentLine.ToString(); - _currentLine.Length = 0; - } - - return result; - } - - public void SendError(string msg, int status) - { - try - { - HttpListenerResponse response = _context.Response; - response.StatusCode = status; - response.ContentType = "text/html"; - string description = HttpStatusDescription.Get(status); - string str; - if (msg != null) - str = string.Format("

{0} ({1})

", description, msg); - else - str = string.Format("

{0}

", description); - - byte[] error = Encoding.UTF8.GetBytes(str); - response.Close(error, false); - } - catch - { - // response was already closed - } - } - - public void SendError() - { - SendError(_context.ErrorMessage, _context.ErrorStatus); - } - - private void Unbind() - { - if (_contextBound) - { - _epl.UnbindContext(_context); - _contextBound = false; - } - } - - public void Close() - { - Close(false); - } - - private void CloseSocket() - { - if (_socket == null) - return; - - try - { - _socket.Close(); - } - catch { } - finally - { - _socket = null; - } - - RemoveConnection(); - } - - internal void Close(bool force) - { - if (_socket != null) - { - Stream st = GetResponseStream(); - if (st != null) - st.Close(); - - _responseStream = null; - } - - if (_socket != null) - { - force |= !_context.Request.KeepAlive; - if (!force) - force = (string.Equals(_context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase)); - - if (!force && _context.Request.FlushInput()) - { - if (_chunked && _context.Response.ForceCloseChunked == false) - { - // Don't close. Keep working. - _reuses++; - Unbind(); - InitInternal(); - BeginReadRequest(); - return; - } - - _reuses++; - Unbind(); - InitInternal(); - BeginReadRequest(); - return; - } - - Socket s = _socket; - _socket = null; - try - { - if (s != null) - s.Shutdown(SocketShutdown.Both); - } - catch - { - } - finally - { - if (s != null) - { - try - { - s.Close(); - } - catch { } - } - } - Unbind(); - RemoveConnection(); - return; - } - } - } -} diff --git a/SocketHttpListener/Net/HttpEndPointListener.cs b/SocketHttpListener/Net/HttpEndPointListener.cs deleted file mode 100644 index 35d1af6486..0000000000 --- a/SocketHttpListener/Net/HttpEndPointListener.cs +++ /dev/null @@ -1,526 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - internal sealed class HttpEndPointListener - { - private HttpListener _listener; - private IPEndPoint _endpoint; - private Socket _socket; - private Dictionary _prefixes; - private List _unhandledPrefixes; // host = '*' - private List _allPrefixes; // host = '+' - private X509Certificate _cert; - private bool _secure; - private Dictionary _unregisteredConnections; - - private readonly ILogger _logger; - private bool _closed; - private bool _enableDualMode; - private readonly ICryptoProvider _cryptoProvider; - private readonly ISocketFactory _socketFactory; - private readonly IStreamHelper _streamHelper; - private readonly IFileSystem _fileSystem; - private readonly IEnvironmentInfo _environment; - - public HttpEndPointListener(HttpListener listener, IPAddress addr, int port, bool secure, X509Certificate cert, - ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IStreamHelper streamHelper, - IFileSystem fileSystem, IEnvironmentInfo environment) - { - this._listener = listener; - _logger = logger; - _cryptoProvider = cryptoProvider; - _socketFactory = socketFactory; - _streamHelper = streamHelper; - _fileSystem = fileSystem; - _environment = environment; - - this._secure = secure; - this._cert = cert; - - _enableDualMode = addr.Equals(IPAddress.IPv6Any); - _endpoint = new IPEndPoint(addr, port); - - _prefixes = new Dictionary(); - _unregisteredConnections = new Dictionary(); - - CreateSocket(); - } - - internal HttpListener Listener => _listener; - - private void CreateSocket() - { - try - { - _socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode); - } - catch (SocketCreateException ex) - { - if (_enableDualMode && _endpoint.Address.Equals(IPAddress.IPv6Any) && - (string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) || - // mono 4.8.1 and lower on bsd is throwing this - string.Equals(ex.ErrorCode, "ProtocolNotSupported", StringComparison.OrdinalIgnoreCase) || - // mono 5.2 on bsd is throwing this - string.Equals(ex.ErrorCode, "OperationNotSupported", StringComparison.OrdinalIgnoreCase))) - { - _endpoint = new IPEndPoint(IPAddress.Any, _endpoint.Port); - _enableDualMode = false; - _socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode); - } - else - { - throw; - } - } - - try - { - _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - } - catch (SocketException) - { - // This is not supported on all operating systems (qnap) - } - - _socket.Bind(_endpoint); - - // This is the number TcpListener uses. - _socket.Listen(2147483647); - - Accept(); - - _closed = false; - } - - private void Accept() - { - var acceptEventArg = new SocketAsyncEventArgs(); - acceptEventArg.UserToken = this; - acceptEventArg.Completed += new EventHandler(OnAccept); - - Accept(acceptEventArg); - } - - private static void TryCloseAndDispose(Socket socket) - { - try - { - using (socket) - { - socket.Close(); - } - } - catch - { - - } - } - - private static void TryClose(Socket socket) - { - try - { - socket.Close(); - } - catch - { - - } - } - - private void Accept(SocketAsyncEventArgs acceptEventArg) - { - // acceptSocket must be cleared since the context object is being reused - acceptEventArg.AcceptSocket = null; - - try - { - bool willRaiseEvent = _socket.AcceptAsync(acceptEventArg); - - if (!willRaiseEvent) - { - ProcessAccept(acceptEventArg); - } - } - catch (ObjectDisposedException) - { - // TODO Investigate or properly fix. - } - catch (Exception ex) - { - var epl = (HttpEndPointListener)acceptEventArg.UserToken; - - epl._logger.LogError(ex, "Error in socket.AcceptAsync"); - } - } - - // This method is the callback method associated with Socket.AcceptAsync - // operations and is invoked when an accept operation is complete - // - private static void OnAccept(object sender, SocketAsyncEventArgs e) - { - ProcessAccept(e); - } - - private static async void ProcessAccept(SocketAsyncEventArgs args) - { - var epl = (HttpEndPointListener)args.UserToken; - - if (epl._closed) - { - return; - } - - // http://msdn.microsoft.com/en-us/library/system.net.sockets.acceptSocket.acceptasync%28v=vs.110%29.aspx - // Under certain conditions ConnectionReset can occur - // Need to attept to re-accept - var socketError = args.SocketError; - var accepted = args.AcceptSocket; - - epl.Accept(args); - - if (socketError == SocketError.ConnectionReset) - { - epl._logger.LogError("SocketError.ConnectionReset reported. Attempting to re-accept."); - return; - } - - if (accepted == null) - { - return; - } - - if (epl._secure && epl._cert == null) - { - TryClose(accepted); - return; - } - - try - { - var remoteEndPointString = accepted.RemoteEndPoint == null ? string.Empty : accepted.RemoteEndPoint.ToString(); - var localEndPointString = accepted.LocalEndPoint == null ? string.Empty : accepted.LocalEndPoint.ToString(); - //_logger.LogInformation("HttpEndPointListener Accepting connection from {0} to {1} secure connection requested: {2}", remoteEndPointString, localEndPointString, _secure); - - var conn = new HttpConnection(epl._logger, accepted, epl, epl._secure, epl._cert, epl._cryptoProvider, epl._streamHelper, epl._fileSystem, epl._environment); - - await conn.Init().ConfigureAwait(false); - - //_logger.LogDebug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId); - lock (epl._unregisteredConnections) - { - epl._unregisteredConnections[conn] = conn; - } - conn.BeginReadRequest(); - } - catch (Exception ex) - { - epl._logger.LogError(ex, "Error in ProcessAccept"); - - TryClose(accepted); - epl.Accept(); - return; - } - } - - private Socket CreateSocket(AddressFamily addressFamily, bool dualMode) - { - try - { - var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); - - if (dualMode) - { - socket.DualMode = true; - } - - return socket; - } - catch (SocketException ex) - { - throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex); - } - catch (ArgumentException ex) - { - if (dualMode) - { - // Mono for BSD incorrectly throws ArgumentException instead of SocketException - throw new SocketCreateException("AddressFamilyNotSupported", ex); - } - else - { - throw; - } - } - } - - internal void RemoveConnection(HttpConnection conn) - { - lock (_unregisteredConnections) - { - _unregisteredConnections.Remove(conn); - } - } - - public bool BindContext(HttpListenerContext context) - { - var req = context.Request; - HttpListener listener = SearchListener(req.Url, out var prefix); - if (listener == null) - return false; - - context.Connection.Prefix = prefix; - return true; - } - - public void UnbindContext(HttpListenerContext context) - { - if (context == null || context.Request == null) - return; - - _listener.UnregisterContext(context); - } - - private HttpListener SearchListener(Uri uri, out ListenerPrefix prefix) - { - prefix = null; - if (uri == null) - return null; - - string host = uri.Host; - int port = uri.Port; - string path = WebUtility.UrlDecode(uri.AbsolutePath); - string pathSlash = path[path.Length - 1] == '/' ? path : path + "/"; - - HttpListener bestMatch = null; - int bestLength = -1; - - if (host != null && host != "") - { - Dictionary localPrefixes = _prefixes; - foreach (var p in localPrefixes.Keys) - { - string ppath = p.Path; - if (ppath.Length < bestLength) - continue; - - if (p.Host != host || p.Port != port) - continue; - - if (path.StartsWith(ppath) || pathSlash.StartsWith(ppath)) - { - bestLength = ppath.Length; - bestMatch = localPrefixes[p]; - prefix = p; - } - } - if (bestLength != -1) - return bestMatch; - } - - List list = _unhandledPrefixes; - bestMatch = MatchFromList(host, path, list, out prefix); - - if (path != pathSlash && bestMatch == null) - bestMatch = MatchFromList(host, pathSlash, list, out prefix); - - if (bestMatch != null) - return bestMatch; - - list = _allPrefixes; - bestMatch = MatchFromList(host, path, list, out prefix); - - if (path != pathSlash && bestMatch == null) - bestMatch = MatchFromList(host, pathSlash, list, out prefix); - - if (bestMatch != null) - return bestMatch; - - return null; - } - - private HttpListener MatchFromList(string host, string path, List list, out ListenerPrefix prefix) - { - prefix = null; - if (list == null) - return null; - - HttpListener bestMatch = null; - int bestLength = -1; - - foreach (ListenerPrefix p in list) - { - string ppath = p.Path; - if (ppath.Length < bestLength) - continue; - - if (path.StartsWith(ppath)) - { - bestLength = ppath.Length; - bestMatch = p._listener; - prefix = p; - } - } - - return bestMatch; - } - - private void AddSpecial(List list, ListenerPrefix prefix) - { - if (list == null) - return; - - foreach (ListenerPrefix p in list) - { - if (p.Path == prefix.Path) - throw new Exception("net_listener_already"); - } - list.Add(prefix); - } - - private bool RemoveSpecial(List list, ListenerPrefix prefix) - { - if (list == null) - return false; - - int c = list.Count; - for (int i = 0; i < c; i++) - { - ListenerPrefix p = list[i]; - if (p.Path == prefix.Path) - { - list.RemoveAt(i); - return true; - } - } - return false; - } - - private void CheckIfRemove() - { - if (_prefixes.Count > 0) - return; - - List list = _unhandledPrefixes; - if (list != null && list.Count > 0) - return; - - list = _allPrefixes; - if (list != null && list.Count > 0) - return; - - HttpEndPointManager.RemoveEndPoint(this, _endpoint); - } - - public void Close() - { - _closed = true; - _socket.Close(); - lock (_unregisteredConnections) - { - // Clone the list because RemoveConnection can be called from Close - var connections = new List(_unregisteredConnections.Keys); - - foreach (HttpConnection c in connections) - c.Close(true); - _unregisteredConnections.Clear(); - } - } - - public void AddPrefix(ListenerPrefix prefix, HttpListener listener) - { - List current; - List future; - if (prefix.Host == "*") - { - do - { - current = _unhandledPrefixes; - future = current != null ? new List(current) : new List(); - prefix._listener = listener; - AddSpecial(future, prefix); - } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current); - return; - } - - if (prefix.Host == "+") - { - do - { - current = _allPrefixes; - future = current != null ? new List(current) : new List(); - prefix._listener = listener; - AddSpecial(future, prefix); - } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current); - return; - } - - Dictionary prefs, p2; - do - { - prefs = _prefixes; - if (prefs.ContainsKey(prefix)) - { - throw new Exception("net_listener_already"); - } - p2 = new Dictionary(prefs); - p2[prefix] = listener; - } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs); - } - - public void RemovePrefix(ListenerPrefix prefix, HttpListener listener) - { - List current; - List future; - if (prefix.Host == "*") - { - do - { - current = _unhandledPrefixes; - future = current != null ? new List(current) : new List(); - if (!RemoveSpecial(future, prefix)) - break; // Prefix not found - } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current); - - CheckIfRemove(); - return; - } - - if (prefix.Host == "+") - { - do - { - current = _allPrefixes; - future = current != null ? new List(current) : new List(); - if (!RemoveSpecial(future, prefix)) - break; // Prefix not found - } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current); - CheckIfRemove(); - return; - } - - Dictionary prefs, p2; - do - { - prefs = _prefixes; - if (!prefs.ContainsKey(prefix)) - break; - - p2 = new Dictionary(prefs); - p2.Remove(prefix); - } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs); - CheckIfRemove(); - } - } -} diff --git a/SocketHttpListener/Net/HttpEndPointManager.cs b/SocketHttpListener/Net/HttpEndPointManager.cs deleted file mode 100644 index 27b4244a6c..0000000000 --- a/SocketHttpListener/Net/HttpEndPointManager.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - internal sealed class HttpEndPointManager - { - private static Dictionary> s_ipEndPoints = new Dictionary>(); - - private HttpEndPointManager() - { - } - - public static void AddListener(ILogger logger, HttpListener listener) - { - var added = new List(); - try - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - foreach (string prefix in listener.Prefixes) - { - AddPrefixInternal(logger, prefix, listener); - added.Add(prefix); - } - } - } - catch - { - foreach (string prefix in added) - { - RemovePrefix(logger, prefix, listener); - } - throw; - } - } - - public static void AddPrefix(ILogger logger, string prefix, HttpListener listener) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - AddPrefixInternal(logger, prefix, listener); - } - } - - private static void AddPrefixInternal(ILogger logger, string p, HttpListener listener) - { - int start = p.IndexOf(':') + 3; - int colon = p.IndexOf(':', start); - if (colon != -1) - { - // root can't be -1 here, since we've already checked for ending '/' in ListenerPrefix. - int root = p.IndexOf('/', colon, p.Length - colon); - string portString = p.Substring(colon + 1, root - colon - 1); - - if (!int.TryParse(portString, out var port) || port <= 0 || port >= 65536) - { - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_port"); - } - } - - var lp = new ListenerPrefix(p); - if (lp.Host != "*" && lp.Host != "+" && Uri.CheckHostName(lp.Host) == UriHostNameType.Unknown) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_listener_host"); - - if (lp.Path.IndexOf('%') != -1) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path"); - - if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path"); - - // listens on all the interfaces if host name cannot be parsed by IPAddress. - HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure); - epl.AddPrefix(lp, listener); - } - - private static IPAddress GetIpAnyAddress(HttpListener listener) - { - return listener.EnableDualMode ? IPAddress.IPv6Any : IPAddress.Any; - } - - private static HttpEndPointListener GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure) - { - IPAddress addr; - if (host == "*" || host == "+") - { - addr = GetIpAnyAddress(listener); - } - else - { - const int NotSupportedErrorCode = 50; - try - { - addr = Dns.GetHostAddresses(host)[0]; - } - catch - { - // Throw same error code as windows, request is not supported. - throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported"); - } - - if (IPAddress.Any.Equals(addr)) - { - // Don't support listening to 0.0.0.0, match windows behavior. - throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported"); - } - } - - Dictionary p = null; - if (s_ipEndPoints.ContainsKey(addr)) - { - p = s_ipEndPoints[addr]; - } - else - { - p = new Dictionary(); - s_ipEndPoints[addr] = p; - } - - HttpEndPointListener epl = null; - if (p.ContainsKey(port)) - { - epl = p[port]; - } - else - { - try - { - epl = new HttpEndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.SocketFactory, listener.StreamHelper, listener.FileSystem, listener.EnvironmentInfo); - } - catch (SocketException ex) - { - throw new HttpListenerException(ex.ErrorCode, ex.Message); - } - p[port] = epl; - } - - return epl; - } - - public static void RemoveEndPoint(HttpEndPointListener epl, IPEndPoint ep) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - Dictionary p = null; - p = s_ipEndPoints[ep.Address]; - p.Remove(ep.Port); - if (p.Count == 0) - { - s_ipEndPoints.Remove(ep.Address); - } - epl.Close(); - } - } - - public static void RemoveListener(ILogger logger, HttpListener listener) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - foreach (string prefix in listener.Prefixes) - { - RemovePrefixInternal(logger, prefix, listener); - } - } - } - - public static void RemovePrefix(ILogger logger, string prefix, HttpListener listener) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - RemovePrefixInternal(logger, prefix, listener); - } - } - - private static void RemovePrefixInternal(ILogger logger, string prefix, HttpListener listener) - { - var lp = new ListenerPrefix(prefix); - if (lp.Path.IndexOf('%') != -1) - return; - - if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) - return; - - HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure); - epl.RemovePrefix(lp, listener); - } - } -} diff --git a/SocketHttpListener/Net/HttpKnownHeaderNames.cs b/SocketHttpListener/Net/HttpKnownHeaderNames.cs deleted file mode 100644 index dc6f2ce415..0000000000 --- a/SocketHttpListener/Net/HttpKnownHeaderNames.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal static partial class HttpKnownHeaderNames - { - // When adding a new constant, add it to HttpKnownHeaderNames.TryGetHeaderName.cs as well. - - public const string Accept = "Accept"; - public const string AcceptCharset = "Accept-Charset"; - public const string AcceptEncoding = "Accept-Encoding"; - public const string AcceptLanguage = "Accept-Language"; - public const string AcceptPatch = "Accept-Patch"; - public const string AcceptRanges = "Accept-Ranges"; - public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials"; - public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers"; - public const string AccessControlAllowMethods = "Access-Control-Allow-Methods"; - public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; - public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers"; - public const string AccessControlMaxAge = "Access-Control-Max-Age"; - public const string Age = "Age"; - public const string Allow = "Allow"; - public const string AltSvc = "Alt-Svc"; - public const string Authorization = "Authorization"; - public const string CacheControl = "Cache-Control"; - public const string Connection = "Connection"; - public const string ContentDisposition = "Content-Disposition"; - public const string ContentEncoding = "Content-Encoding"; - public const string ContentLanguage = "Content-Language"; - public const string ContentLength = "Content-Length"; - public const string ContentLocation = "Content-Location"; - public const string ContentMD5 = "Content-MD5"; - public const string ContentRange = "Content-Range"; - public const string ContentSecurityPolicy = "Content-Security-Policy"; - public const string ContentType = "Content-Type"; - public const string Cookie = "Cookie"; - public const string Cookie2 = "Cookie2"; - public const string Date = "Date"; - public const string ETag = "ETag"; - public const string Expect = "Expect"; - public const string Expires = "Expires"; - public const string From = "From"; - public const string Host = "Host"; - public const string IfMatch = "If-Match"; - public const string IfModifiedSince = "If-Modified-Since"; - public const string IfNoneMatch = "If-None-Match"; - public const string IfRange = "If-Range"; - public const string IfUnmodifiedSince = "If-Unmodified-Since"; - public const string KeepAlive = "Keep-Alive"; - public const string LastModified = "Last-Modified"; - public const string Link = "Link"; - public const string Location = "Location"; - public const string MaxForwards = "Max-Forwards"; - public const string Origin = "Origin"; - public const string P3P = "P3P"; - public const string Pragma = "Pragma"; - public const string ProxyAuthenticate = "Proxy-Authenticate"; - public const string ProxyAuthorization = "Proxy-Authorization"; - public const string ProxyConnection = "Proxy-Connection"; - public const string PublicKeyPins = "Public-Key-Pins"; - public const string Range = "Range"; - public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched. - public const string RetryAfter = "Retry-After"; - public const string SecWebSocketAccept = "Sec-WebSocket-Accept"; - public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions"; - public const string SecWebSocketKey = "Sec-WebSocket-Key"; - public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol"; - public const string SecWebSocketVersion = "Sec-WebSocket-Version"; - public const string Server = "Server"; - public const string SetCookie = "Set-Cookie"; - public const string SetCookie2 = "Set-Cookie2"; - public const string StrictTransportSecurity = "Strict-Transport-Security"; - public const string TE = "TE"; - public const string TSV = "TSV"; - public const string Trailer = "Trailer"; - public const string TransferEncoding = "Transfer-Encoding"; - public const string Upgrade = "Upgrade"; - public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests"; - public const string UserAgent = "User-Agent"; - public const string Vary = "Vary"; - public const string Via = "Via"; - public const string WWWAuthenticate = "WWW-Authenticate"; - public const string Warning = "Warning"; - public const string XAspNetVersion = "X-AspNet-Version"; - public const string XContentDuration = "X-Content-Duration"; - public const string XContentTypeOptions = "X-Content-Type-Options"; - public const string XFrameOptions = "X-Frame-Options"; - public const string XMSEdgeRef = "X-MSEdge-Ref"; - public const string XPoweredBy = "X-Powered-By"; - public const string XRequestID = "X-Request-ID"; - public const string XUACompatible = "X-UA-Compatible"; - } -} diff --git a/SocketHttpListener/Net/HttpListener.cs b/SocketHttpListener/Net/HttpListener.cs deleted file mode 100644 index f17036a21a..0000000000 --- a/SocketHttpListener/Net/HttpListener.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Net; -using System.Security.Cryptography.X509Certificates; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - public sealed class HttpListener : IDisposable - { - internal ICryptoProvider CryptoProvider { get; private set; } - internal ISocketFactory SocketFactory { get; private set; } - internal IFileSystem FileSystem { get; private set; } - internal IStreamHelper StreamHelper { get; private set; } - internal IEnvironmentInfo EnvironmentInfo { get; private set; } - - public bool EnableDualMode { get; set; } - - private AuthenticationSchemes auth_schemes; - private HttpListenerPrefixCollection prefixes; - private AuthenticationSchemeSelector auth_selector; - private string realm; - private bool unsafe_ntlm_auth; - private bool listening; - private bool disposed; - - private Dictionary registry; - private Dictionary connections; - private ILogger _logger; - private X509Certificate _certificate; - - public Action OnContext { get; set; } - - public HttpListener( - ILogger logger, - ICryptoProvider cryptoProvider, - ISocketFactory socketFactory, - IStreamHelper streamHelper, - IFileSystem fileSystem, - IEnvironmentInfo environmentInfo) - { - _logger = logger; - CryptoProvider = cryptoProvider; - SocketFactory = socketFactory; - StreamHelper = streamHelper; - FileSystem = fileSystem; - EnvironmentInfo = environmentInfo; - - prefixes = new HttpListenerPrefixCollection(logger, this); - registry = new Dictionary(); - connections = new Dictionary(); - auth_schemes = AuthenticationSchemes.Anonymous; - } - - public HttpListener( - ILogger logger, - X509Certificate certificate, - ICryptoProvider cryptoProvider, - ISocketFactory socketFactory, - IStreamHelper streamHelper, - IFileSystem fileSystem, - IEnvironmentInfo environmentInfo) - : this(logger, cryptoProvider, socketFactory, streamHelper, fileSystem, environmentInfo) - { - _certificate = certificate; - } - - public void LoadCert(X509Certificate cert) - { - _certificate = cert; - } - - // TODO: Digest, NTLM and Negotiate require ControlPrincipal - public AuthenticationSchemes AuthenticationSchemes - { - get => auth_schemes; - set - { - CheckDisposed(); - auth_schemes = value; - } - } - - public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate - { - get => auth_selector; - set - { - CheckDisposed(); - auth_selector = value; - } - } - - public bool IsListening => listening; - - public static bool IsSupported => true; - - public HttpListenerPrefixCollection Prefixes - { - get - { - CheckDisposed(); - return prefixes; - } - } - - // TODO: use this - public string Realm - { - get => realm; - set - { - CheckDisposed(); - realm = value; - } - } - - public bool UnsafeConnectionNtlmAuthentication - { - get => unsafe_ntlm_auth; - set - { - CheckDisposed(); - unsafe_ntlm_auth = value; - } - } - - //internal IMonoSslStream CreateSslStream(Stream innerStream, bool ownsStream, MSI.MonoRemoteCertificateValidationCallback callback) - //{ - // lock (registry) - // { - // if (tlsProvider == null) - // tlsProvider = MonoTlsProviderFactory.GetProviderInternal(); - // if (tlsSettings == null) - // tlsSettings = MSI.MonoTlsSettings.CopyDefaultSettings(); - // if (tlsSettings.RemoteCertificateValidationCallback == null) - // tlsSettings.RemoteCertificateValidationCallback = callback; - // return tlsProvider.CreateSslStream(innerStream, ownsStream, tlsSettings); - // } - //} - - internal X509Certificate Certificate => _certificate; - - public void Abort() - { - if (disposed) - return; - - if (!listening) - { - return; - } - - Close(true); - } - - public void Close() - { - if (disposed) - return; - - if (!listening) - { - disposed = true; - return; - } - - Close(true); - disposed = true; - } - - void Close(bool force) - { - CheckDisposed(); - HttpEndPointManager.RemoveListener(_logger, this); - Cleanup(force); - } - - void Cleanup(bool close_existing) - { - lock (registry) - { - if (close_existing) - { - // Need to copy this since closing will call UnregisterContext - ICollection keys = registry.Keys; - var all = new HttpListenerContext[keys.Count]; - keys.CopyTo(all, 0); - registry.Clear(); - for (int i = all.Length - 1; i >= 0; i--) - all[i].Connection.Close(true); - } - - lock (connections) - { - ICollection keys = connections.Keys; - var conns = new HttpConnection[keys.Count]; - keys.CopyTo(conns, 0); - connections.Clear(); - for (int i = conns.Length - 1; i >= 0; i--) - conns[i].Close(true); - } - } - } - - internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerContext context) - { - if (AuthenticationSchemeSelectorDelegate != null) - return AuthenticationSchemeSelectorDelegate(context.Request); - else - return auth_schemes; - } - - public void Start() - { - CheckDisposed(); - if (listening) - return; - - HttpEndPointManager.AddListener(_logger, this); - listening = true; - } - - public void Stop() - { - CheckDisposed(); - listening = false; - Close(false); - } - - void IDisposable.Dispose() - { - if (disposed) - return; - - Close(true); //TODO: Should we force here or not? - disposed = true; - } - - internal void CheckDisposed() - { - if (disposed) - throw new ObjectDisposedException(GetType().Name); - } - - internal void RegisterContext(HttpListenerContext context) - { - if (OnContext != null && IsListening) - { - OnContext(context); - } - - lock (registry) - registry[context] = context; - } - - internal void UnregisterContext(HttpListenerContext context) - { - lock (registry) - registry.Remove(context); - } - - internal void AddConnection(HttpConnection cnc) - { - lock (connections) - { - connections[cnc] = cnc; - } - } - - internal void RemoveConnection(HttpConnection cnc) - { - lock (connections) - { - connections.Remove(cnc); - } - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerBasicIdentity.cs b/SocketHttpListener/Net/HttpListenerBasicIdentity.cs deleted file mode 100644 index 5f6ec44b96..0000000000 --- a/SocketHttpListener/Net/HttpListenerBasicIdentity.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Security.Principal; - -namespace SocketHttpListener.Net -{ - public class HttpListenerBasicIdentity : GenericIdentity - { - string password; - - public HttpListenerBasicIdentity(string username, string password) - : base(username, "Basic") - { - this.password = password; - } - - public virtual string Password => password; - } - - public class GenericIdentity : IIdentity - { - private string m_name; - private string m_type; - - public GenericIdentity(string name) - { - if (name == null) - throw new System.ArgumentNullException(nameof(name)); - - m_name = name; - m_type = ""; - } - - public GenericIdentity(string name, string type) - { - if (name == null) - throw new System.ArgumentNullException(nameof(name)); - if (type == null) - throw new System.ArgumentNullException(nameof(type)); - - m_name = name; - m_type = type; - } - - public virtual string Name => m_name; - - public virtual string AuthenticationType => m_type; - - public virtual bool IsAuthenticated => !m_name.Equals(""); - } -} diff --git a/SocketHttpListener/Net/HttpListenerContext.Managed.cs b/SocketHttpListener/Net/HttpListenerContext.Managed.cs deleted file mode 100644 index 79742bf37f..0000000000 --- a/SocketHttpListener/Net/HttpListenerContext.Managed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.ComponentModel; -using System.Security.Principal; -using System.Text; -using System.Threading.Tasks; -using SocketHttpListener.Net.WebSockets; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerContext - { - private HttpConnection _connection; - - internal HttpListenerContext(HttpConnection connection) - { - _connection = connection; - _response = new HttpListenerResponse(this); - Request = new HttpListenerRequest(this); - ErrorStatus = 400; - } - - internal int ErrorStatus { get; set; } - - internal string ErrorMessage { get; set; } - - internal bool HaveError => ErrorMessage != null; - - internal HttpConnection Connection => _connection; - - internal void ParseAuthentication(System.Net.AuthenticationSchemes expectedSchemes) - { - if (expectedSchemes == System.Net.AuthenticationSchemes.Anonymous) - return; - - string header = Request.Headers["Authorization"]; - if (string.IsNullOrEmpty(header)) - return; - - if (IsBasicHeader(header)) - { - _user = ParseBasicAuthentication(header.Substring(AuthenticationTypes.Basic.Length + 1)); - } - } - - internal IPrincipal ParseBasicAuthentication(string authData) => - TryParseBasicAuth(authData, out HttpStatusCode errorCode, out string username, out string password) ? - new GenericPrincipal(new HttpListenerBasicIdentity(username, password), Array.Empty()) : - null; - - internal static bool IsBasicHeader(string header) => - header.Length >= 6 && - header[5] == ' ' && - string.Compare(header, 0, AuthenticationTypes.Basic, 0, 5, StringComparison.OrdinalIgnoreCase) == 0; - - internal static bool TryParseBasicAuth(string headerValue, out HttpStatusCode errorCode, out string username, out string password) - { - errorCode = HttpStatusCode.OK; - username = password = null; - try - { - if (string.IsNullOrWhiteSpace(headerValue)) - { - return false; - } - - string authString = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue)); - int colonPos = authString.IndexOf(':'); - if (colonPos < 0) - { - // username must be at least 1 char - errorCode = HttpStatusCode.BadRequest; - return false; - } - - username = authString.Substring(0, colonPos); - password = authString.Substring(colonPos + 1); - return true; - } - catch - { - errorCode = HttpStatusCode.InternalServerError; - return false; - } - } - - public Task AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval) - { - return HttpWebSocket.AcceptWebSocketAsyncCore(this, subProtocol, receiveBufferSize, keepAliveInterval); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public Task AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval, ArraySegment internalBuffer) - { - WebSocketValidate.ValidateArraySegment(internalBuffer, nameof(internalBuffer)); - HttpWebSocket.ValidateOptions(subProtocol, receiveBufferSize, HttpWebSocket.MinSendBufferSize, keepAliveInterval); - return HttpWebSocket.AcceptWebSocketAsyncCore(this, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer); - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerContext.cs b/SocketHttpListener/Net/HttpListenerContext.cs deleted file mode 100644 index d84e2d1aa0..0000000000 --- a/SocketHttpListener/Net/HttpListenerContext.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Net; -using System.Security.Principal; -using System.Threading.Tasks; -using SocketHttpListener.Net.WebSockets; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerContext - { - private HttpListenerResponse _response; - private IPrincipal _user; - - public HttpListenerRequest Request { get; } - - public IPrincipal User => _user; - - // This can be used to cache the results of HttpListener.AuthenticationSchemeSelectorDelegate. - internal AuthenticationSchemes AuthenticationSchemes { get; set; } - - public HttpListenerResponse Response => _response; - - public Task AcceptWebSocketAsync(string subProtocol) - { - return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, WebSocket.DefaultKeepAliveInterval); - } - - public Task AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval) - { - return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, keepAliveInterval); - } - } - - public class GenericPrincipal : IPrincipal - { - private IIdentity m_identity; - private string[] m_roles; - - public GenericPrincipal(IIdentity identity, string[] roles) - { - if (identity == null) - throw new ArgumentNullException(nameof(identity)); - - m_identity = identity; - if (roles != null) - { - m_roles = new string[roles.Length]; - for (int i = 0; i < roles.Length; ++i) - { - m_roles[i] = roles[i]; - } - } - else - { - m_roles = null; - } - } - - public virtual IIdentity Identity => m_identity; - - public virtual bool IsInRole(string role) - { - if (role == null || m_roles == null) - return false; - - for (int i = 0; i < m_roles.Length; ++i) - { - if (m_roles[i] != null && string.Compare(m_roles[i], role, StringComparison.OrdinalIgnoreCase) == 0) - return true; - } - return false; - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs b/SocketHttpListener/Net/HttpListenerPrefixCollection.cs deleted file mode 100644 index 400a1adb61..0000000000 --- a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - public class HttpListenerPrefixCollection : ICollection, IEnumerable, IEnumerable - { - private List _prefixes = new List(); - private HttpListener _listener; - - private ILogger _logger; - - internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener) - { - _logger = logger; - _listener = listener; - } - - public int Count => _prefixes.Count; - - public bool IsReadOnly => false; - - public bool IsSynchronized => false; - - public void Add(string uriPrefix) - { - _listener.CheckDisposed(); - //ListenerPrefix.CheckUri(uriPrefix); - if (_prefixes.Contains(uriPrefix)) - { - return; - } - - _prefixes.Add(uriPrefix); - if (_listener.IsListening) - { - HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener); - } - } - - public void AddRange(IEnumerable uriPrefixes) - { - _listener.CheckDisposed(); - - foreach (var uriPrefix in uriPrefixes) - { - if (_prefixes.Contains(uriPrefix)) - { - continue; - } - - _prefixes.Add(uriPrefix); - if (_listener.IsListening) - { - HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener); - } - } - } - - public void Clear() - { - _listener.CheckDisposed(); - _prefixes.Clear(); - if (_listener.IsListening) - { - HttpEndPointManager.RemoveListener(_logger, _listener); - } - } - - public bool Contains(string uriPrefix) - { - _listener.CheckDisposed(); - return _prefixes.Contains(uriPrefix); - } - - public void CopyTo(string[] array, int offset) - { - _listener.CheckDisposed(); - _prefixes.CopyTo(array, offset); - } - - public void CopyTo(Array array, int offset) - { - _listener.CheckDisposed(); - ((ICollection)_prefixes).CopyTo(array, offset); - } - - public IEnumerator GetEnumerator() - { - return _prefixes.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _prefixes.GetEnumerator(); - } - - public bool Remove(string uriPrefix) - { - _listener.CheckDisposed(); - if (uriPrefix == null) - { - throw new ArgumentNullException(nameof(uriPrefix)); - } - - bool result = _prefixes.Remove(uriPrefix); - if (result && _listener.IsListening) - { - HttpEndPointManager.RemovePrefix(_logger, uriPrefix, _listener); - } - - return result; - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerRequest.Managed.cs b/SocketHttpListener/Net/HttpListenerRequest.Managed.cs deleted file mode 100644 index 3f9e32f089..0000000000 --- a/SocketHttpListener/Net/HttpListenerRequest.Managed.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System; -using System.IO; -using System.Text; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerRequest - { - private long _contentLength; - private bool _clSet; - private WebHeaderCollection _headers; - private string _method; - private Stream _inputStream; - private HttpListenerContext _context; - private bool _isChunked; - - private static byte[] s_100continue = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"); - - internal HttpListenerRequest(HttpListenerContext context) - { - _context = context; - _headers = new WebHeaderCollection(); - _version = HttpVersion.Version10; - } - - private static readonly char[] s_separators = new char[] { ' ' }; - - internal void SetRequestLine(string req) - { - string[] parts = req.Split(s_separators, 3); - if (parts.Length != 3) - { - _context.ErrorMessage = "Invalid request line (parts)."; - return; - } - - _method = parts[0]; - foreach (char c in _method) - { - int ic = (int)c; - - if ((ic >= 'A' && ic <= 'Z') || - (ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' && - c != '<' && c != '>' && c != '@' && c != ',' && c != ';' && - c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' && - c != ']' && c != '?' && c != '=' && c != '{' && c != '}')) - continue; - - _context.ErrorMessage = "(Invalid verb)"; - return; - } - - _rawUrl = parts[1]; - if (parts[2].Length != 8 || !parts[2].StartsWith("HTTP/")) - { - _context.ErrorMessage = "Invalid request line (version)."; - return; - } - - try - { - _version = new Version(parts[2].Substring(5)); - } - catch - { - _context.ErrorMessage = "Invalid request line (version)."; - return; - } - - if (_version.Major < 1) - { - _context.ErrorMessage = "Invalid request line (version)."; - return; - } - if (_version.Major > 1) - { - _context.ErrorStatus = (int)HttpStatusCode.HttpVersionNotSupported; - _context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.HttpVersionNotSupported); - return; - } - } - - private static bool MaybeUri(string s) - { - int p = s.IndexOf(':'); - if (p == -1) - return false; - - if (p >= 10) - return false; - - return IsPredefinedScheme(s.Substring(0, p)); - } - - private static bool IsPredefinedScheme(string scheme) - { - if (scheme == null || scheme.Length < 3) - return false; - - char c = scheme[0]; - if (c == 'h') - return (scheme == UriScheme.Http || scheme == UriScheme.Https); - if (c == 'f') - return (scheme == UriScheme.File || scheme == UriScheme.Ftp); - - if (c == 'n') - { - c = scheme[1]; - if (c == 'e') - return (scheme == UriScheme.News || scheme == UriScheme.NetPipe || scheme == UriScheme.NetTcp); - if (scheme == UriScheme.Nntp) - return true; - return false; - } - if ((c == 'g' && scheme == UriScheme.Gopher) || (c == 'm' && scheme == UriScheme.Mailto)) - return true; - - return false; - } - - internal void FinishInitialization() - { - string host = UserHostName; - if (_version > HttpVersion.Version10 && (host == null || host.Length == 0)) - { - _context.ErrorMessage = "Invalid host name"; - return; - } - - string path; - Uri raw_uri = null; - if (MaybeUri(_rawUrl.ToLowerInvariant()) && Uri.TryCreate(_rawUrl, UriKind.Absolute, out raw_uri)) - path = raw_uri.PathAndQuery; - else - path = _rawUrl; - - if ((host == null || host.Length == 0)) - host = UserHostAddress; - - if (raw_uri != null) - host = raw_uri.Host; - - int colon = host.IndexOf(']') == -1 ? host.IndexOf(':') : host.LastIndexOf(':'); - if (colon >= 0) - host = host.Substring(0, colon); - - string base_uri = string.Format("{0}://{1}:{2}", RequestScheme, host, LocalEndPoint.Port); - - if (!Uri.TryCreate(base_uri + path, UriKind.Absolute, out _requestUri)) - { - _context.ErrorMessage = System.Net.WebUtility.HtmlEncode("Invalid url: " + base_uri + path); - return; - } - - _requestUri = HttpListenerRequestUriBuilder.GetRequestUri(_rawUrl, _requestUri.Scheme, - _requestUri.Authority, _requestUri.LocalPath, _requestUri.Query); - - if (_version >= HttpVersion.Version11) - { - string t_encoding = Headers[HttpKnownHeaderNames.TransferEncoding]; - _isChunked = (t_encoding != null && string.Equals(t_encoding, "chunked", StringComparison.OrdinalIgnoreCase)); - // 'identity' is not valid! - if (t_encoding != null && !_isChunked) - { - _context.Connection.SendError(null, 501); - return; - } - } - - if (!_isChunked && !_clSet) - { - if (string.Equals(_method, "POST", StringComparison.OrdinalIgnoreCase) || - string.Equals(_method, "PUT", StringComparison.OrdinalIgnoreCase)) - { - _context.Connection.SendError(null, 411); - return; - } - } - - if (string.Compare(Headers[HttpKnownHeaderNames.Expect], "100-continue", StringComparison.OrdinalIgnoreCase) == 0) - { - HttpResponseStream output = _context.Connection.GetResponseStream(); - output.InternalWrite(s_100continue, 0, s_100continue.Length); - } - } - - internal static string Unquote(string str) - { - int start = str.IndexOf('\"'); - int end = str.LastIndexOf('\"'); - if (start >= 0 && end >= 0) - str = str.Substring(start + 1, end - 1); - return str.Trim(); - } - - internal void AddHeader(string header) - { - int colon = header.IndexOf(':'); - if (colon == -1 || colon == 0) - { - _context.ErrorMessage = HttpStatusDescription.Get(400); - _context.ErrorStatus = 400; - return; - } - - string name = header.Substring(0, colon).Trim(); - string val = header.Substring(colon + 1).Trim(); - if (name.Equals("content-length", StringComparison.OrdinalIgnoreCase)) - { - // To match Windows behavior: - // Content lengths >= 0 and <= long.MaxValue are accepted as is. - // Content lengths > long.MaxValue and <= ulong.MaxValue are treated as 0. - // Content lengths < 0 cause the requests to fail. - // Other input is a failure, too. - long parsedContentLength = - ulong.TryParse(val, out ulong parsedUlongContentLength) ? (parsedUlongContentLength <= long.MaxValue ? (long)parsedUlongContentLength : 0) : - long.Parse(val); - if (parsedContentLength < 0 || (_clSet && parsedContentLength != _contentLength)) - { - _context.ErrorMessage = "Invalid Content-Length."; - } - else - { - _contentLength = parsedContentLength; - _clSet = true; - } - } - else if (name.Equals("transfer-encoding", StringComparison.OrdinalIgnoreCase)) - { - if (Headers[HttpKnownHeaderNames.TransferEncoding] != null) - { - _context.ErrorStatus = (int)HttpStatusCode.NotImplemented; - _context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.NotImplemented); - } - } - - if (_context.ErrorMessage == null) - { - _headers.Set(name, val); - } - } - - // returns true is the stream could be reused. - internal bool FlushInput() - { - if (!HasEntityBody) - return true; - - int length = 2048; - if (_contentLength > 0) - length = (int)Math.Min(_contentLength, (long)length); - - byte[] bytes = new byte[length]; - while (true) - { - try - { - IAsyncResult ares = InputStream.BeginRead(bytes, 0, length, null, null); - if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne(1000)) - return false; - if (InputStream.EndRead(ares) <= 0) - return true; - } - catch (ObjectDisposedException) - { - _inputStream = null; - return true; - } - catch - { - return false; - } - } - } - - public long ContentLength64 - { - get - { - if (_isChunked) - _contentLength = -1; - - return _contentLength; - } - } - - public bool HasEntityBody => (_contentLength > 0 || _isChunked); - - public QueryParamCollection Headers => _headers; - - public string HttpMethod => _method; - - public Stream InputStream - { - get - { - if (_inputStream == null) - { - if (_isChunked || _contentLength > 0) - _inputStream = _context.Connection.GetRequestStream(_isChunked, _contentLength); - else - _inputStream = Stream.Null; - } - - return _inputStream; - } - } - - public bool IsAuthenticated => false; - - public bool IsSecureConnection => _context.Connection.IsSecure; - - public System.Net.IPEndPoint LocalEndPoint => _context.Connection.LocalEndPoint; - - public System.Net.IPEndPoint RemoteEndPoint => _context.Connection.RemoteEndPoint; - - public Guid RequestTraceIdentifier { get; } = Guid.NewGuid(); - - public string ServiceName => null; - - private Uri RequestUri => _requestUri; - private bool SupportsWebSockets => true; - } -} diff --git a/SocketHttpListener/Net/HttpListenerRequest.cs b/SocketHttpListener/Net/HttpListenerRequest.cs deleted file mode 100644 index 667d58ea7b..0000000000 --- a/SocketHttpListener/Net/HttpListenerRequest.cs +++ /dev/null @@ -1,537 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; -using SocketHttpListener.Net.WebSockets; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerRequest - { - private CookieCollection _cookies; - private bool? _keepAlive; - private string _rawUrl; - private Uri _requestUri; - private Version _version; - - public string[] AcceptTypes => Helpers.ParseMultivalueHeader(Headers[HttpKnownHeaderNames.Accept]); - - public string[] UserLanguages => Helpers.ParseMultivalueHeader(Headers[HttpKnownHeaderNames.AcceptLanguage]); - - private static CookieCollection ParseCookies(Uri uri, string setCookieHeader) - { - var cookies = new CookieCollection(); - return cookies; - } - - public CookieCollection Cookies - { - get - { - if (_cookies == null) - { - string cookieString = Headers[HttpKnownHeaderNames.Cookie]; - if (!string.IsNullOrEmpty(cookieString)) - { - _cookies = ParseCookies(RequestUri, cookieString); - } - if (_cookies == null) - { - _cookies = new CookieCollection(); - } - } - return _cookies; - } - } - - public Encoding ContentEncoding - { - get - { - if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP")) - { - string postDataCharset = Headers["x-up-devcap-post-charset"]; - if (postDataCharset != null && postDataCharset.Length > 0) - { - try - { - return Encoding.GetEncoding(postDataCharset); - } - catch (ArgumentException) - { - } - } - } - if (HasEntityBody) - { - if (ContentType != null) - { - string charSet = Helpers.GetCharSetValueFromHeader(ContentType); - if (charSet != null) - { - try - { - return Encoding.GetEncoding(charSet); - } - catch (ArgumentException) - { - } - } - } - } - return Encoding.UTF8; - } - } - - public string ContentType => Headers[HttpKnownHeaderNames.ContentType]; - - public bool IsLocal => LocalEndPoint.Address.Equals(RemoteEndPoint.Address); - - public bool IsWebSocketRequest - { - get - { - if (!SupportsWebSockets) - { - return false; - } - - bool foundConnectionUpgradeHeader = false; - if (string.IsNullOrEmpty(Headers[HttpKnownHeaderNames.Connection]) || string.IsNullOrEmpty(Headers[HttpKnownHeaderNames.Upgrade])) - { - return false; - } - - foreach (string connection in Headers.GetValues(HttpKnownHeaderNames.Connection)) - { - if (string.Equals(connection, HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase)) - { - foundConnectionUpgradeHeader = true; - break; - } - } - - if (!foundConnectionUpgradeHeader) - { - return false; - } - - foreach (string upgrade in Headers.GetValues(HttpKnownHeaderNames.Upgrade)) - { - if (string.Equals(upgrade, HttpWebSocket.WebSocketUpgradeToken, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - } - - public bool KeepAlive - { - get - { - if (!_keepAlive.HasValue) - { - string header = Headers[HttpKnownHeaderNames.ProxyConnection]; - if (string.IsNullOrEmpty(header)) - { - header = Headers[HttpKnownHeaderNames.Connection]; - } - if (string.IsNullOrEmpty(header)) - { - if (ProtocolVersion >= HttpVersion.Version11) - { - _keepAlive = true; - } - else - { - header = Headers[HttpKnownHeaderNames.KeepAlive]; - _keepAlive = !string.IsNullOrEmpty(header); - } - } - else - { - header = header.ToLowerInvariant(); - _keepAlive = - header.IndexOf("close", StringComparison.OrdinalIgnoreCase) < 0 || - header.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) >= 0; - } - } - - return _keepAlive.Value; - } - } - - public QueryParamCollection QueryString - { - get - { - var queryString = new QueryParamCollection(); - Helpers.FillFromString(queryString, Url.Query, true, ContentEncoding); - return queryString; - } - } - - public string RawUrl => _rawUrl; - - private string RequestScheme => IsSecureConnection ? UriScheme.Https : UriScheme.Http; - - public string UserAgent => Headers[HttpKnownHeaderNames.UserAgent]; - - public string UserHostAddress => LocalEndPoint.ToString(); - - public string UserHostName => Headers[HttpKnownHeaderNames.Host]; - - public Uri UrlReferrer - { - get - { - string referrer = Headers[HttpKnownHeaderNames.Referer]; - if (referrer == null) - { - return null; - } - - bool success = Uri.TryCreate(referrer, UriKind.RelativeOrAbsolute, out var urlReferrer); - return success ? urlReferrer : null; - } - } - - public Uri Url => RequestUri; - - public Version ProtocolVersion => _version; - - private static class Helpers - { - // - // Get attribute off header value - // - internal static string GetCharSetValueFromHeader(string headerValue) - { - const string AttrName = "charset"; - - if (headerValue == null) - return null; - - int l = headerValue.Length; - int k = AttrName.Length; - - // find properly separated attribute name - int i = 1; // start searching from 1 - - while (i < l) - { - i = CultureInfo.InvariantCulture.CompareInfo.IndexOf(headerValue, AttrName, i, CompareOptions.IgnoreCase); - if (i < 0) - break; - if (i + k >= l) - break; - - char chPrev = headerValue[i - 1]; - char chNext = headerValue[i + k]; - if ((chPrev == ';' || chPrev == ',' || char.IsWhiteSpace(chPrev)) && (chNext == '=' || char.IsWhiteSpace(chNext))) - break; - - i += k; - } - - if (i < 0 || i >= l) - return null; - - // skip to '=' and the following whitespace - i += k; - while (i < l && char.IsWhiteSpace(headerValue[i])) - i++; - if (i >= l || headerValue[i] != '=') - return null; - i++; - while (i < l && char.IsWhiteSpace(headerValue[i])) - i++; - if (i >= l) - return null; - - // parse the value - string attrValue = null; - - int j; - - if (i < l && headerValue[i] == '"') - { - if (i == l - 1) - return null; - j = headerValue.IndexOf('"', i + 1); - if (j < 0 || j == i + 1) - return null; - - attrValue = headerValue.Substring(i + 1, j - i - 1).Trim(); - } - else - { - for (j = i; j < l; j++) - { - if (headerValue[j] == ';') - break; - } - - if (j == i) - return null; - - attrValue = headerValue.Substring(i, j - i).Trim(); - } - - return attrValue; - } - - internal static string[] ParseMultivalueHeader(string s) - { - if (s == null) - return null; - - int l = s.Length; - - // collect comma-separated values into list - - var values = new List(); - int i = 0; - - while (i < l) - { - // find next , - int ci = s.IndexOf(',', i); - if (ci < 0) - ci = l; - - // append corresponding server value - values.Add(s.Substring(i, ci - i)); - - // move to next - i = ci + 1; - - // skip leading space - if (i < l && s[i] == ' ') - i++; - } - - // return list as array of strings - - int n = values.Count; - string[] strings; - - // if n is 0 that means s was empty string - - if (n == 0) - { - strings = new string[1]; - strings[0] = string.Empty; - } - else - { - strings = new string[n]; - values.CopyTo(0, strings, 0, n); - } - return strings; - } - - - private static string UrlDecodeStringFromStringInternal(string s, Encoding e) - { - int count = s.Length; - var helper = new UrlDecoder(count, e); - - // go through the string's chars collapsing %XX and %uXXXX and - // appending each char as char, with exception of %XX constructs - // that are appended as bytes - - for (int pos = 0; pos < count; pos++) - { - char ch = s[pos]; - - if (ch == '+') - { - ch = ' '; - } - else if (ch == '%' && pos < count - 2) - { - if (s[pos + 1] == 'u' && pos < count - 5) - { - int h1 = HexToInt(s[pos + 2]); - int h2 = HexToInt(s[pos + 3]); - int h3 = HexToInt(s[pos + 4]); - int h4 = HexToInt(s[pos + 5]); - - if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) - { // valid 4 hex chars - ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); - pos += 5; - - // only add as char - helper.AddChar(ch); - continue; - } - } - else - { - int h1 = HexToInt(s[pos + 1]); - int h2 = HexToInt(s[pos + 2]); - - if (h1 >= 0 && h2 >= 0) - { // valid 2 hex chars - byte b = (byte)((h1 << 4) | h2); - pos += 2; - - // don't add as char - helper.AddByte(b); - continue; - } - } - } - - if ((ch & 0xFF80) == 0) - helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode - else - helper.AddChar(ch); - } - - return helper.GetString(); - } - - private static int HexToInt(char h) - { - return (h >= '0' && h <= '9') ? h - '0' : - (h >= 'a' && h <= 'f') ? h - 'a' + 10 : - (h >= 'A' && h <= 'F') ? h - 'A' + 10 : - -1; - } - - private class UrlDecoder - { - private int _bufferSize; - - // Accumulate characters in a special array - private int _numChars; - private char[] _charBuffer; - - // Accumulate bytes for decoding into characters in a special array - private int _numBytes; - private byte[] _byteBuffer; - - // Encoding to convert chars to bytes - private Encoding _encoding; - - private void FlushBytes() - { - if (_numBytes > 0) - { - _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars); - _numBytes = 0; - } - } - - internal UrlDecoder(int bufferSize, Encoding encoding) - { - _bufferSize = bufferSize; - _encoding = encoding; - - _charBuffer = new char[bufferSize]; - // byte buffer created on demand - } - - internal void AddChar(char ch) - { - if (_numBytes > 0) - FlushBytes(); - - _charBuffer[_numChars++] = ch; - } - - internal void AddByte(byte b) - { - { - if (_byteBuffer == null) - _byteBuffer = new byte[_bufferSize]; - - _byteBuffer[_numBytes++] = b; - } - } - - internal string GetString() - { - if (_numBytes > 0) - FlushBytes(); - - if (_numChars > 0) - return new string(_charBuffer, 0, _numChars); - else - return string.Empty; - } - } - - - internal static void FillFromString(QueryParamCollection nvc, string s, bool urlencoded, Encoding encoding) - { - int l = (s != null) ? s.Length : 0; - int i = (s.Length > 0 && s[0] == '?') ? 1 : 0; - - while (i < l) - { - // find next & while noting first = on the way (and if there are more) - - int si = i; - int ti = -1; - - while (i < l) - { - char ch = s[i]; - - if (ch == '=') - { - if (ti < 0) - ti = i; - } - else if (ch == '&') - { - break; - } - - i++; - } - - // extract the name / value pair - - string name = null; - string value = null; - - if (ti >= 0) - { - name = s.Substring(si, ti - si); - value = s.Substring(ti + 1, i - ti - 1); - } - else - { - value = s.Substring(si, i - si); - } - - // add name / value pair to the collection - - if (urlencoded) - nvc.Add( - name == null ? null : UrlDecodeStringFromStringInternal(name, encoding), - UrlDecodeStringFromStringInternal(value, encoding)); - else - nvc.Add(name, value); - - // trailing '&' - - if (i == l - 1 && s[i] == '&') - nvc.Add(null, ""); - - i++; - } - } - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs b/SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs deleted file mode 100644 index 7b4b619e68..0000000000 --- a/SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs +++ /dev/null @@ -1,443 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Text; - -namespace SocketHttpListener.Net -{ - // We don't use the cooked URL because http.sys unescapes all percent-encoded values. However, - // we also can't just use the raw Uri, since http.sys supports not only Utf-8, but also ANSI/DBCS and - // Unicode code points. System.Uri only supports Utf-8. - // The purpose of this class is to convert all ANSI, DBCS, and Unicode code points into percent encoded - // Utf-8 characters. - internal sealed class HttpListenerRequestUriBuilder - { - private static readonly Encoding s_utf8Encoding = new UTF8Encoding(false, true); - private static readonly Encoding s_ansiEncoding = Encoding.GetEncoding(0, new EncoderExceptionFallback(), new DecoderExceptionFallback()); - - private readonly string _rawUri; - private readonly string _cookedUriScheme; - private readonly string _cookedUriHost; - private readonly string _cookedUriPath; - private readonly string _cookedUriQuery; - - // This field is used to build the final request Uri string from the Uri parts passed to the ctor. - private StringBuilder _requestUriString; - - // The raw path is parsed by looping through all characters from left to right. 'rawOctets' - // is used to store consecutive percent encoded octets as actual byte values: e.g. for path /pa%C3%84th%2F/ - // rawOctets will be set to { 0xC3, 0x84 } when we reach character 't' and it will be { 0x2F } when - // we reach the final '/'. I.e. after a sequence of percent encoded octets ends, we use rawOctets as - // input to the encoding and percent encode the resulting string into UTF-8 octets. - // - // When parsing ANSI (Latin 1) encoded path '/pa%C4th/', %C4 will be added to rawOctets and when - // we reach 't', the content of rawOctets { 0xC4 } will be fed into the ANSI encoding. The resulting - // string 'Ä' will be percent encoded into UTF-8 octets and appended to requestUriString. The final - // path will be '/pa%C3%84th/', where '%C3%84' is the UTF-8 percent encoded character 'Ä'. - private List _rawOctets; - private string _rawPath; - - // Holds the final request Uri. - private Uri _requestUri; - - private HttpListenerRequestUriBuilder(string rawUri, string cookedUriScheme, string cookedUriHost, - string cookedUriPath, string cookedUriQuery) - { - _rawUri = rawUri; - _cookedUriScheme = cookedUriScheme; - _cookedUriHost = cookedUriHost; - _cookedUriPath = AddSlashToAsteriskOnlyPath(cookedUriPath); - _cookedUriQuery = cookedUriQuery ?? string.Empty; - } - - public static Uri GetRequestUri(string rawUri, string cookedUriScheme, string cookedUriHost, - string cookedUriPath, string cookedUriQuery) - { - var builder = new HttpListenerRequestUriBuilder(rawUri, - cookedUriScheme, cookedUriHost, cookedUriPath, cookedUriQuery); - - return builder.Build(); - } - - private Uri Build() - { - BuildRequestUriUsingRawPath(); - - if (_requestUri == null) - { - BuildRequestUriUsingCookedPath(); - } - - return _requestUri; - } - - private void BuildRequestUriUsingCookedPath() - { - bool isValid = Uri.TryCreate(_cookedUriScheme + Uri.SchemeDelimiter + _cookedUriHost + _cookedUriPath + - _cookedUriQuery, UriKind.Absolute, out _requestUri); - - // Creating a Uri from the cooked Uri should really always work: If not, we log at least. - if (!isValid) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_create_uri, _cookedUriScheme, _cookedUriHost, _cookedUriPath, _cookedUriQuery)); - } - } - - private void BuildRequestUriUsingRawPath() - { - bool isValid = false; - - // Initialize 'rawPath' only if really needed; i.e. if we build the request Uri from the raw Uri. - _rawPath = GetPath(_rawUri); - - // Try to check the raw path using first the primary encoding (according to http.sys settings); - // if it fails try the secondary encoding. - ParsingResult result = BuildRequestUriUsingRawPath(GetEncoding(EncodingType.Primary)); - if (result == ParsingResult.EncodingError) - { - Encoding secondaryEncoding = GetEncoding(EncodingType.Secondary); - result = BuildRequestUriUsingRawPath(secondaryEncoding); - } - isValid = (result == ParsingResult.Success) ? true : false; - - // Log that we weren't able to create a Uri from the raw string. - if (!isValid) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_create_uri, _cookedUriScheme, _cookedUriHost, _rawPath, _cookedUriQuery)); - } - } - - private static Encoding GetEncoding(EncodingType type) - { - Debug.Assert((type == EncodingType.Primary) || (type == EncodingType.Secondary), - "Unknown 'EncodingType' value: " + type.ToString()); - - if (type == EncodingType.Secondary) - { - return s_ansiEncoding; - } - else - { - return s_utf8Encoding; - } - } - - private ParsingResult BuildRequestUriUsingRawPath(Encoding encoding) - { - Debug.Assert(encoding != null, "'encoding' must be assigned."); - Debug.Assert(!string.IsNullOrEmpty(_rawPath), "'rawPath' must have at least one character."); - - _rawOctets = new List(); - _requestUriString = new StringBuilder(); - _requestUriString.Append(_cookedUriScheme); - _requestUriString.Append(Uri.SchemeDelimiter); - _requestUriString.Append(_cookedUriHost); - - ParsingResult result = ParseRawPath(encoding); - if (result == ParsingResult.Success) - { - _requestUriString.Append(_cookedUriQuery); - - Debug.Assert(_rawOctets.Count == 0, - "Still raw octets left. They must be added to the result path."); - - if (!Uri.TryCreate(_requestUriString.ToString(), UriKind.Absolute, out _requestUri)) - { - // If we can't create a Uri from the string, this is an invalid string and it doesn't make - // sense to try another encoding. - result = ParsingResult.InvalidString; - } - } - - if (result != ParsingResult.Success) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_raw_path, _rawPath, encoding.EncodingName)); - } - - return result; - } - - private ParsingResult ParseRawPath(Encoding encoding) - { - Debug.Assert(encoding != null, "'encoding' must be assigned."); - - int index = 0; - char current = '\0'; - while (index < _rawPath.Length) - { - current = _rawPath[index]; - if (current == '%') - { - // Assert is enough, since http.sys accepted the request string already. This should never happen. - Debug.Assert(index + 2 < _rawPath.Length, "Expected >=2 characters after '%' (e.g. %2F)"); - - index++; - current = _rawPath[index]; - if (current == 'u' || current == 'U') - { - // We found "%u" which means, we have a Unicode code point of the form "%uXXXX". - Debug.Assert(index + 4 < _rawPath.Length, "Expected >=4 characters after '%u' (e.g. %u0062)"); - - // Decode the content of rawOctets into percent encoded UTF-8 characters and append them - // to requestUriString. - if (!EmptyDecodeAndAppendRawOctetsList(encoding)) - { - return ParsingResult.EncodingError; - } - if (!AppendUnicodeCodePointValuePercentEncoded(_rawPath.Substring(index + 1, 4))) - { - return ParsingResult.InvalidString; - } - index += 5; - } - else - { - // We found '%', but not followed by 'u', i.e. we have a percent encoded octed: %XX - if (!AddPercentEncodedOctetToRawOctetsList(encoding, _rawPath.Substring(index, 2))) - { - return ParsingResult.InvalidString; - } - index += 2; - } - } - else - { - // We found a non-'%' character: decode the content of rawOctets into percent encoded - // UTF-8 characters and append it to the result. - if (!EmptyDecodeAndAppendRawOctetsList(encoding)) - { - return ParsingResult.EncodingError; - } - // Append the current character to the result. - _requestUriString.Append(current); - index++; - } - } - - // if the raw path ends with a sequence of percent encoded octets, make sure those get added to the - // result (requestUriString). - if (!EmptyDecodeAndAppendRawOctetsList(encoding)) - { - return ParsingResult.EncodingError; - } - - return ParsingResult.Success; - } - - private bool AppendUnicodeCodePointValuePercentEncoded(string codePoint) - { - // http.sys only supports %uXXXX (4 hex-digits), even though unicode code points could have up to - // 6 hex digits. Therefore we parse always 4 characters after %u and convert them to an int. - if (!int.TryParse(codePoint, NumberStyles.HexNumber, null, out var codePointValue)) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, codePoint)); - return false; - } - - string unicodeString = null; - try - { - unicodeString = char.ConvertFromUtf32(codePointValue); - AppendOctetsPercentEncoded(_requestUriString, s_utf8Encoding.GetBytes(unicodeString)); - - return true; - } - catch (ArgumentOutOfRangeException) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, codePoint)); - } - catch (EncoderFallbackException) - { - // If utf8Encoding.GetBytes() fails - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_to_utf8, unicodeString, e.Message)); - } - - return false; - } - - private bool AddPercentEncodedOctetToRawOctetsList(Encoding encoding, string escapedCharacter) - { - if (!byte.TryParse(escapedCharacter, NumberStyles.HexNumber, null, out byte encodedValue)) - { - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, escapedCharacter)); - return false; - } - - _rawOctets.Add(encodedValue); - - return true; - } - - private bool EmptyDecodeAndAppendRawOctetsList(Encoding encoding) - { - if (_rawOctets.Count == 0) - { - return true; - } - - string decodedString = null; - try - { - // If the encoding can get a string out of the byte array, this is a valid string in the - // 'encoding' encoding. - decodedString = encoding.GetString(_rawOctets.ToArray()); - - if (encoding == s_utf8Encoding) - { - AppendOctetsPercentEncoded(_requestUriString, _rawOctets.ToArray()); - } - else - { - AppendOctetsPercentEncoded(_requestUriString, s_utf8Encoding.GetBytes(decodedString)); - } - - _rawOctets.Clear(); - - return true; - } - catch (DecoderFallbackException) - { - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_bytes, GetOctetsAsString(_rawOctets), e.Message)); - } - catch (EncoderFallbackException) - { - // If utf8Encoding.GetBytes() fails - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_to_utf8, decodedString, e.Message)); - } - - return false; - } - - private static void AppendOctetsPercentEncoded(StringBuilder target, IEnumerable octets) - { - foreach (byte octet in octets) - { - target.Append('%'); - target.Append(octet.ToString("X2", CultureInfo.InvariantCulture)); - } - } - - private static string GetOctetsAsString(IEnumerable octets) - { - var octetString = new StringBuilder(); - - bool first = true; - foreach (byte octet in octets) - { - if (first) - { - first = false; - } - else - { - octetString.Append(' '); - } - octetString.Append(octet.ToString("X2", CultureInfo.InvariantCulture)); - } - - return octetString.ToString(); - } - - private static string GetPath(string uriString) - { - Debug.Assert(uriString != null, "uriString must not be null"); - Debug.Assert(uriString.Length > 0, "uriString must not be empty"); - - int pathStartIndex = 0; - - // Perf. improvement: nearly all strings are relative Uris. So just look if the - // string starts with '/'. If so, we have a relative Uri and the path starts at position 0. - // (http.sys already trimmed leading whitespaces) - if (uriString[0] != '/') - { - // We can't check against cookedUriScheme, since http.sys allows for request http://myserver/ to - // use a request line 'GET https://myserver/' (note http vs. https). Therefore check if the - // Uri starts with either http:// or https://. - int authorityStartIndex = 0; - if (uriString.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) - { - authorityStartIndex = 7; - } - else if (uriString.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) - { - authorityStartIndex = 8; - } - - if (authorityStartIndex > 0) - { - // we have an absolute Uri. Find out where the authority ends and the path begins. - // Note that Uris like "http://server?query=value/1/2" are invalid according to RFC2616 - // and http.sys behavior: If the Uri contains a query, there must be at least one '/' - // between the authority and the '?' character: It's safe to just look for the first - // '/' after the authority to determine the beginning of the path. - pathStartIndex = uriString.IndexOf('/', authorityStartIndex); - if (pathStartIndex == -1) - { - // e.g. for request lines like: 'GET http://myserver' (no final '/') - pathStartIndex = uriString.Length; - } - } - else - { - // RFC2616: Request-URI = "*" | absoluteURI | abs_path | authority - // 'authority' can only be used with CONNECT which is never received by HttpListener. - // I.e. if we don't have an absolute path (must start with '/') and we don't have - // an absolute Uri (must start with http:// or https://), then 'uriString' must be '*'. - Debug.Assert((uriString.Length == 1) && (uriString[0] == '*'), "Unknown request Uri string format", - "Request Uri string is not an absolute Uri, absolute path, or '*': {0}", uriString); - - // Should we ever get here, be consistent with 2.0/3.5 behavior: just add an initial - // slash to the string and treat it as a path: - uriString = "/" + uriString; - } - } - - // Find end of path: The path is terminated by - // - the first '?' character - // - the first '#' character: This is never the case here, since http.sys won't accept - // Uris containing fragments. Also, RFC2616 doesn't allow fragments in request Uris. - // - end of Uri string - int queryIndex = uriString.IndexOf('?'); - if (queryIndex == -1) - { - queryIndex = uriString.Length; - } - - // will always return a != null string. - return AddSlashToAsteriskOnlyPath(uriString.Substring(pathStartIndex, queryIndex - pathStartIndex)); - } - - private static string AddSlashToAsteriskOnlyPath(string path) - { - Debug.Assert(path != null, "'path' must not be null"); - - // If a request like "OPTIONS * HTTP/1.1" is sent to the listener, then the request Uri - // should be "http[s]://server[:port]/*" to be compatible with pre-4.0 behavior. - if ((path.Length == 1) && (path[0] == '*')) - { - return "/*"; - } - - return path; - } - - private enum ParsingResult - { - Success, - InvalidString, - EncodingError - } - - private enum EncodingType - { - Primary, - Secondary - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerResponse.Managed.cs b/SocketHttpListener/Net/HttpListenerResponse.Managed.cs deleted file mode 100644 index f595fce7cc..0000000000 --- a/SocketHttpListener/Net/HttpListenerResponse.Managed.cs +++ /dev/null @@ -1,333 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerResponse : IDisposable - { - private long _contentLength; - private Version _version = HttpVersion.Version11; - private int _statusCode = 200; - internal object _headersLock = new object(); - private bool _forceCloseChunked; - - internal HttpListenerResponse(HttpListenerContext context) - { - _httpContext = context; - } - - internal bool ForceCloseChunked => _forceCloseChunked; - - private void EnsureResponseStream() - { - if (_responseStream == null) - { - _responseStream = _httpContext.Connection.GetResponseStream(); - } - } - - public Version ProtocolVersion - { - get => _version; - set - { - CheckDisposed(); - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1)) - { - throw new ArgumentException("Wrong version"); - } - - _version = new Version(value.Major, value.Minor); // match Windows behavior, trimming to just Major.Minor - } - } - - public int StatusCode - { - get => _statusCode; - set - { - CheckDisposed(); - - if (value < 100 || value > 999) - throw new ProtocolViolationException("Invalid status"); - - _statusCode = value; - } - } - - private void Dispose() - { - Close(true); - } - - public void Close() - { - if (Disposed) - return; - - Close(false); - } - - public void Abort() - { - if (Disposed) - return; - - Close(true); - } - - private void Close(bool force) - { - Disposed = true; - _httpContext.Connection.Close(force); - } - - public void Close(byte[] responseEntity, bool willBlock) - { - CheckDisposed(); - - if (responseEntity == null) - { - throw new ArgumentNullException(nameof(responseEntity)); - } - - if (!SentHeaders && _boundaryType != BoundaryType.Chunked) - { - ContentLength64 = responseEntity.Length; - } - - if (willBlock) - { - try - { - OutputStream.Write(responseEntity, 0, responseEntity.Length); - } - finally - { - Close(false); - } - } - else - { - OutputStream.BeginWrite(responseEntity, 0, responseEntity.Length, iar => - { - var thisRef = (HttpListenerResponse)iar.AsyncState; - try - { - try - { - thisRef.OutputStream.EndWrite(iar); - } - finally - { - thisRef.Close(false); - } - } - catch (Exception) - { - // In case response was disposed during this time - } - }, this); - } - } - - public void CopyFrom(HttpListenerResponse templateResponse) - { - _webHeaders.Clear(); - //_webHeaders.Add(templateResponse._webHeaders); - _contentLength = templateResponse._contentLength; - _statusCode = templateResponse._statusCode; - _statusDescription = templateResponse._statusDescription; - _keepAlive = templateResponse._keepAlive; - _version = templateResponse._version; - } - - internal void SendHeaders(bool closing, MemoryStream ms, bool isWebSocketHandshake = false) - { - if (!isWebSocketHandshake) - { - if (_webHeaders["Server"] == null) - { - _webHeaders.Set("Server", "Microsoft-NetCore/2.0"); - } - - if (_webHeaders["Date"] == null) - { - _webHeaders.Set("Date", DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture)); - } - - if (_boundaryType == BoundaryType.None) - { - if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10) - { - _keepAlive = false; - } - else - { - _boundaryType = BoundaryType.Chunked; - } - - if (CanSendResponseBody(_httpContext.Response.StatusCode)) - { - _contentLength = -1; - } - else - { - _boundaryType = BoundaryType.ContentLength; - _contentLength = 0; - } - } - - if (_boundaryType != BoundaryType.Chunked) - { - if (_boundaryType != BoundaryType.ContentLength && closing) - { - _contentLength = CanSendResponseBody(_httpContext.Response.StatusCode) ? -1 : 0; - } - - if (_boundaryType == BoundaryType.ContentLength) - { - _webHeaders.Set("Content-Length", _contentLength.ToString("D", CultureInfo.InvariantCulture)); - } - } - - /* Apache forces closing the connection for these status codes: - * HttpStatusCode.BadRequest 400 - * HttpStatusCode.RequestTimeout 408 - * HttpStatusCode.LengthRequired 411 - * HttpStatusCode.RequestEntityTooLarge 413 - * HttpStatusCode.RequestUriTooLong 414 - * HttpStatusCode.InternalServerError 500 - * HttpStatusCode.ServiceUnavailable 503 - */ - bool conn_close = (_statusCode == (int)HttpStatusCode.BadRequest || _statusCode == (int)HttpStatusCode.RequestTimeout - || _statusCode == (int)HttpStatusCode.LengthRequired || _statusCode == (int)HttpStatusCode.RequestEntityTooLarge - || _statusCode == (int)HttpStatusCode.RequestUriTooLong || _statusCode == (int)HttpStatusCode.InternalServerError - || _statusCode == (int)HttpStatusCode.ServiceUnavailable); - - if (!conn_close) - { - conn_close = !_httpContext.Request.KeepAlive; - } - - // They sent both KeepAlive: true and Connection: close - if (!_keepAlive || conn_close) - { - _webHeaders.Set("Connection", "Close"); - conn_close = true; - } - - if (SendChunked) - { - _webHeaders.Set("Transfer-Encoding", "Chunked"); - } - - int reuses = _httpContext.Connection.Reuses; - if (reuses >= 100) - { - _forceCloseChunked = true; - if (!conn_close) - { - _webHeaders.Set("Connection", "Close"); - conn_close = true; - } - } - - if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10) - { - if (_keepAlive) - { - Headers["Keep-Alive"] = "true"; - } - - if (!conn_close) - { - _webHeaders.Set("Connection", "Keep-Alive"); - } - } - - ComputeCookies(); - } - - var encoding = Encoding.UTF8; - var writer = new StreamWriter(ms, encoding, 256); - writer.Write("HTTP/1.1 {0} ", _statusCode); // "1.1" matches Windows implementation, which ignores the response version - writer.Flush(); - byte[] statusDescriptionBytes = WebHeaderEncoding.GetBytes(StatusDescription); - ms.Write(statusDescriptionBytes, 0, statusDescriptionBytes.Length); - writer.Write("\r\n"); - - writer.Write(FormatHeaders(_webHeaders)); - writer.Flush(); - int preamble = encoding.GetPreamble().Length; - EnsureResponseStream(); - - /* Assumes that the ms was at position 0 */ - ms.Position = preamble; - SentHeaders = !isWebSocketHandshake; - } - - private static bool HeaderCanHaveEmptyValue(string name) => - !string.Equals(name, "Location", StringComparison.OrdinalIgnoreCase); - - private static string FormatHeaders(WebHeaderCollection headers) - { - var sb = new StringBuilder(); - - for (int i = 0; i < headers.Count; i++) - { - string key = headers.GetKey(i); - string[] values = headers.GetValues(i); - - int startingLength = sb.Length; - - sb.Append(key).Append(": "); - bool anyValues = false; - for (int j = 0; j < values.Length; j++) - { - string value = values[j]; - if (!string.IsNullOrWhiteSpace(value)) - { - if (anyValues) - { - sb.Append(", "); - } - sb.Append(value); - anyValues = true; - } - } - - if (anyValues || HeaderCanHaveEmptyValue(key)) - { - // Complete the header - sb.Append("\r\n"); - } - else - { - // Empty header; remove it. - sb.Length = startingLength; - } - } - - return sb.Append("\r\n").ToString(); - } - - private bool Disposed { get; set; } - internal bool SentHeaders { get; set; } - - public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) - { - return ((HttpResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken); - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerResponse.cs b/SocketHttpListener/Net/HttpListenerResponse.cs deleted file mode 100644 index 66ef885643..0000000000 --- a/SocketHttpListener/Net/HttpListenerResponse.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Text; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerResponse : IDisposable - { - private BoundaryType _boundaryType = BoundaryType.None; - private CookieCollection _cookies; - private HttpListenerContext _httpContext; - private bool _keepAlive = true; - private HttpResponseStream _responseStream; - private string _statusDescription; - private WebHeaderCollection _webHeaders = new WebHeaderCollection(); - - public WebHeaderCollection Headers => _webHeaders; - - public Encoding ContentEncoding { get; set; } - - public string ContentType - { - get => Headers["Content-Type"]; - set - { - CheckDisposed(); - if (string.IsNullOrEmpty(value)) - { - Headers.Remove("Content-Type"); - } - else - { - Headers.Set("Content-Type", value); - } - } - } - - private HttpListenerContext HttpListenerContext => _httpContext; - - private HttpListenerRequest HttpListenerRequest => HttpListenerContext.Request; - - internal EntitySendFormat EntitySendFormat - { - get => (EntitySendFormat)_boundaryType; - set - { - CheckDisposed(); - CheckSentHeaders(); - if (value == EntitySendFormat.Chunked && HttpListenerRequest.ProtocolVersion.Minor == 0) - { - throw new ProtocolViolationException("net_nochunkuploadonhttp10"); - } - _boundaryType = (BoundaryType)value; - if (value != EntitySendFormat.ContentLength) - { - _contentLength = -1; - } - } - } - - public bool SendChunked - { - get => EntitySendFormat == EntitySendFormat.Chunked; - set => EntitySendFormat = value ? EntitySendFormat.Chunked : EntitySendFormat.ContentLength; - } - - // We MUST NOT send message-body when we send responses with these Status codes - private static readonly int[] s_noResponseBody = { 100, 101, 204, 205, 304 }; - - private static bool CanSendResponseBody(int responseCode) - { - for (int i = 0; i < s_noResponseBody.Length; i++) - { - if (responseCode == s_noResponseBody[i]) - { - return false; - } - } - return true; - } - - public long ContentLength64 - { - get => _contentLength; - set - { - CheckDisposed(); - CheckSentHeaders(); - if (value >= 0) - { - _contentLength = value; - _boundaryType = BoundaryType.ContentLength; - } - else - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - } - } - - public CookieCollection Cookies - { - get => _cookies ?? (_cookies = new CookieCollection()); - set => _cookies = value; - } - - public bool KeepAlive - { - get => _keepAlive; - set - { - CheckDisposed(); - _keepAlive = value; - } - } - - public Stream OutputStream - { - get - { - CheckDisposed(); - EnsureResponseStream(); - return _responseStream; - } - } - - public string RedirectLocation - { - get => Headers["Location"]; - set - { - // note that this doesn't set the status code to a redirect one - CheckDisposed(); - if (string.IsNullOrEmpty(value)) - { - Headers.Remove("Location"); - } - else - { - Headers.Set("Location", value); - } - } - } - - public string StatusDescription - { - get - { - if (_statusDescription == null) - { - // if the user hasn't set this, generated on the fly, if possible. - // We know this one is safe, no need to verify it as in the setter. - _statusDescription = HttpStatusDescription.Get(StatusCode); - } - if (_statusDescription == null) - { - _statusDescription = string.Empty; - } - return _statusDescription; - } - set - { - CheckDisposed(); - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - // Need to verify the status description doesn't contain any control characters except HT. We mask off the high - // byte since that's how it's encoded. - for (int i = 0; i < value.Length; i++) - { - char c = (char)(0x000000ff & (uint)value[i]); - if ((c <= 31 && c != (byte)'\t') || c == 127) - { - throw new ArgumentException("net_WebHeaderInvalidControlChars"); - } - } - - _statusDescription = value; - } - } - - public void AddHeader(string name, string value) - { - Headers.Set(name, value); - } - - public void AppendHeader(string name, string value) - { - Headers.Add(name, value); - } - - public void AppendCookie(Cookie cookie) - { - if (cookie == null) - { - throw new ArgumentNullException(nameof(cookie)); - } - Cookies.Add(cookie); - } - - private void ComputeCookies() - { - if (_cookies != null) - { - // now go through the collection, and concatenate all the cookies in per-variant strings - //string setCookie2 = null, setCookie = null; - //for (int index = 0; index < _cookies.Count; index++) - //{ - // Cookie cookie = _cookies[index]; - // string cookieString = cookie.ToServerString(); - // if (cookieString == null || cookieString.Length == 0) - // { - // continue; - // } - - // if (cookie.IsRfc2965Variant()) - // { - // setCookie2 = setCookie2 == null ? cookieString : setCookie2 + ", " + cookieString; - // } - // else - // { - // setCookie = setCookie == null ? cookieString : setCookie + ", " + cookieString; - // } - //} - - //if (!string.IsNullOrEmpty(setCookie)) - //{ - // Headers.Set(HttpKnownHeaderNames.SetCookie, setCookie); - // if (string.IsNullOrEmpty(setCookie2)) - // { - // Headers.Remove(HttpKnownHeaderNames.SetCookie2); - // } - //} - - //if (!string.IsNullOrEmpty(setCookie2)) - //{ - // Headers.Set(HttpKnownHeaderNames.SetCookie2, setCookie2); - // if (string.IsNullOrEmpty(setCookie)) - // { - // Headers.Remove(HttpKnownHeaderNames.SetCookie); - // } - //} - } - } - - public void Redirect(string url) - { - Headers["Location"] = url; - StatusCode = (int)HttpStatusCode.Redirect; - StatusDescription = "Found"; - } - - public void SetCookie(Cookie cookie) - { - if (cookie == null) - { - throw new ArgumentNullException(nameof(cookie)); - } - - //Cookie newCookie = cookie.Clone(); - //int added = Cookies.InternalAdd(newCookie, true); - - //if (added != 1) - //{ - // // The Cookie already existed and couldn't be replaced. - // throw new ArgumentException("Cookie exists"); - //} - } - - void IDisposable.Dispose() - { - Dispose(); - } - - private void CheckDisposed() - { - if (Disposed) - { - throw new ObjectDisposedException(GetType().FullName); - } - } - - private void CheckSentHeaders() - { - if (SentHeaders) - { - throw new InvalidOperationException(); - } - } - } -} diff --git a/SocketHttpListener/Net/HttpRequestStream.Managed.cs b/SocketHttpListener/Net/HttpRequestStream.Managed.cs deleted file mode 100644 index 42fc4d97c2..0000000000 --- a/SocketHttpListener/Net/HttpRequestStream.Managed.cs +++ /dev/null @@ -1,210 +0,0 @@ -using System; -using System.IO; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal partial class HttpRequestStream : Stream - { - private byte[] _buffer; - private int _offset; - private int _length; - private long _remainingBody; - protected bool _closed; - private Stream _stream; - - internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length) - : this(stream, buffer, offset, length, -1) - { - } - - internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length, long contentlength) - { - _stream = stream; - _buffer = buffer; - _offset = offset; - _length = length; - _remainingBody = contentlength; - } - - // Returns 0 if we can keep reading from the base stream, - // > 0 if we read something from the buffer. - // -1 if we had a content length set and we finished reading that many bytes. - private int FillFromBuffer(byte[] buffer, int offset, int count) - { - if (_remainingBody == 0) - return -1; - - if (_length == 0) - return 0; - - int size = Math.Min(_length, count); - if (_remainingBody > 0) - size = (int)Math.Min(size, _remainingBody); - - if (_offset > _buffer.Length - size) - { - size = Math.Min(size, _buffer.Length - _offset); - } - if (size == 0) - return 0; - - Buffer.BlockCopy(_buffer, _offset, buffer, offset, size); - _offset += size; - _length -= size; - if (_remainingBody > 0) - _remainingBody -= size; - return size; - } - - protected virtual int ReadCore(byte[] buffer, int offset, int size) - { - // Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0 - int nread = FillFromBuffer(buffer, offset, size); - if (nread == -1) - { // No more bytes available (Content-Length) - return 0; - } - else if (nread > 0) - { - return nread; - } - - if (_remainingBody > 0) - { - size = (int)Math.Min(_remainingBody, (long)size); - } - - nread = _stream.Read(buffer, offset, size); - - if (_remainingBody > 0) - { - if (nread == 0) - { - throw new Exception("Bad request"); - } - - //Debug.Assert(nread <= _remainingBody); - _remainingBody -= nread; - } - - return nread; - } - - protected virtual IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state) - { - if (size == 0 || _closed) - { - var ares = new HttpStreamAsyncResult(this); - ares._callback = cback; - ares._state = state; - ares.Complete(); - return ares; - } - - int nread = FillFromBuffer(buffer, offset, size); - if (nread > 0 || nread == -1) - { - var ares = new HttpStreamAsyncResult(this); - ares._buffer = buffer; - ares._offset = offset; - ares._count = size; - ares._callback = cback; - ares._state = state; - ares._synchRead = Math.Max(0, nread); - ares.Complete(); - return ares; - } - - // Avoid reading past the end of the request to allow - // for HTTP pipelining - if (_remainingBody >= 0 && size > _remainingBody) - { - size = (int)Math.Min(_remainingBody, (long)size); - } - - return _stream.BeginRead(buffer, offset, size, cback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - var r = asyncResult as HttpStreamAsyncResult; - if (r != null) - { - if (!ReferenceEquals(this, r._parent)) - { - throw new ArgumentException("Invalid async result"); - } - if (r._endCalled) - { - throw new InvalidOperationException("invalid end call"); - } - r._endCalled = true; - - if (!asyncResult.IsCompleted) - { - asyncResult.AsyncWaitHandle.WaitOne(); - } - - return r._synchRead; - } - - if (_closed) - return 0; - - int nread = 0; - try - { - nread = _stream.EndRead(asyncResult); - } - catch (IOException e) when (e.InnerException is ArgumentException || e.InnerException is InvalidOperationException) - { - throw e.InnerException; - } - - if (_remainingBody > 0) - { - if (nread == 0) - { - throw new Exception("Bad request"); - } - - _remainingBody -= nread; - } - - return nread; - } - } -} diff --git a/SocketHttpListener/Net/HttpRequestStream.cs b/SocketHttpListener/Net/HttpRequestStream.cs deleted file mode 100644 index 1c554df20b..0000000000 --- a/SocketHttpListener/Net/HttpRequestStream.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal partial class HttpRequestStream : Stream - { - public override bool CanSeek => false; - public override bool CanWrite => false; - public override bool CanRead => true; - - public override int Read(byte[] buffer, int offset, int size) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - if (size == 0 || _closed) - { - return 0; - } - - return ReadCore(buffer, offset, size); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - - return BeginReadCore(buffer, offset, size, callback, state); - } - - public override void Flush() { } - public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - - set => throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return base.BeginWrite(buffer, offset, count, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - base.EndWrite(asyncResult); - } - - internal bool Closed => _closed; - - protected override void Dispose(bool disposing) - { - _closed = true; - base.Dispose(disposing); - } - } -} diff --git a/SocketHttpListener/Net/HttpResponseStream.Managed.cs b/SocketHttpListener/Net/HttpResponseStream.Managed.cs deleted file mode 100644 index 5d02a9c956..0000000000 --- a/SocketHttpListener/Net/HttpResponseStream.Managed.cs +++ /dev/null @@ -1,329 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal partial class HttpResponseStream : Stream - { - private HttpListenerResponse _response; - private bool _ignore_errors; - private bool _trailer_sent; - private Stream _stream; - private readonly IStreamHelper _streamHelper; - private readonly Socket _socket; - private readonly bool _supportsDirectSocketAccess; - private readonly IEnvironmentInfo _environment; - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; - - internal HttpResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors, IStreamHelper streamHelper, Socket socket, bool supportsDirectSocketAccess, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger) - { - _response = response; - _ignore_errors = ignore_errors; - _streamHelper = streamHelper; - _socket = socket; - _supportsDirectSocketAccess = supportsDirectSocketAccess; - _environment = environment; - _fileSystem = fileSystem; - _logger = logger; - _stream = stream; - } - - private void DisposeCore() - { - byte[] bytes = null; - MemoryStream ms = GetHeaders(true); - bool chunked = _response.SendChunked; - if (_stream.CanWrite) - { - try - { - if (ms != null) - { - long start = ms.Position; - if (chunked && !_trailer_sent) - { - bytes = GetChunkSizeBytes(0, true); - ms.Position = ms.Length; - ms.Write(bytes, 0, bytes.Length); - } - InternalWrite(ms.GetBuffer(), (int)start, (int)(ms.Length - start)); - _trailer_sent = true; - } - else if (chunked && !_trailer_sent) - { - bytes = GetChunkSizeBytes(0, true); - InternalWrite(bytes, 0, bytes.Length); - _trailer_sent = true; - } - } - catch (HttpListenerException) - { - // Ignore error due to connection reset by peer - } - } - _response.Close(); - } - - internal async Task WriteWebSocketHandshakeHeadersAsync() - { - if (_closed) - throw new ObjectDisposedException(GetType().ToString()); - - if (_stream.CanWrite) - { - MemoryStream ms = GetHeaders(closing: false, isWebSocketHandshake: true); - bool chunked = _response.SendChunked; - - long start = ms.Position; - if (chunked) - { - byte[] bytes = GetChunkSizeBytes(0, true); - ms.Position = ms.Length; - ms.Write(bytes, 0, bytes.Length); - } - - await InternalWriteAsync(ms.GetBuffer(), (int)start, (int)(ms.Length - start)).ConfigureAwait(false); - await _stream.FlushAsync().ConfigureAwait(false); - } - } - - private MemoryStream GetHeaders(bool closing, bool isWebSocketHandshake = false) - { - //// SendHeaders works on shared headers - //lock (_response.headers_lock) - //{ - // if (_response.HeadersSent) - // return null; - // var ms = CreateNew(); - // _response.SendHeaders(closing, ms); - // return ms; - //} - - // SendHeaders works on shared headers - lock (_response._headersLock) - { - if (_response.SentHeaders) - { - return null; - } - - MemoryStream ms = new MemoryStream(); - _response.SendHeaders(closing, ms, isWebSocketHandshake); - return ms; - } - } - - private static byte[] s_crlf = new byte[] { 13, 10 }; - private static byte[] GetChunkSizeBytes(int size, bool final) - { - string str = string.Format("{0:x}\r\n{1}", size, final ? "\r\n" : ""); - return Encoding.ASCII.GetBytes(str); - } - - internal void InternalWrite(byte[] buffer, int offset, int count) - { - if (_ignore_errors) - { - try - { - _stream.Write(buffer, offset, count); - } - catch { } - } - else - { - _stream.Write(buffer, offset, count); - } - } - - internal Task InternalWriteAsync(byte[] buffer, int offset, int count) => - _ignore_errors ? InternalWriteIgnoreErrorsAsync(buffer, offset, count) : _stream.WriteAsync(buffer, offset, count); - - private async Task InternalWriteIgnoreErrorsAsync(byte[] buffer, int offset, int count) - { - try { await _stream.WriteAsync(buffer, offset, count).ConfigureAwait(false); } - catch { } - } - - private void WriteCore(byte[] buffer, int offset, int size) - { - if (size == 0) - return; - - byte[] bytes = null; - MemoryStream ms = GetHeaders(false); - bool chunked = _response.SendChunked; - if (ms != null) - { - long start = ms.Position; // After the possible preamble for the encoding - ms.Position = ms.Length; - if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - ms.Write(bytes, 0, bytes.Length); - } - - int new_count = Math.Min(size, 16384 - (int)ms.Position + (int)start); - ms.Write(buffer, offset, new_count); - size -= new_count; - offset += new_count; - InternalWrite(ms.GetBuffer(), (int)start, (int)(ms.Length - start)); - ms.SetLength(0); - ms.Capacity = 0; // 'dispose' the buffer in ms. - } - else if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - InternalWrite(bytes, 0, bytes.Length); - } - - if (size > 0) - InternalWrite(buffer, offset, size); - if (chunked) - InternalWrite(s_crlf, 0, 2); - } - - private IAsyncResult BeginWriteCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state) - { - if (_closed) - { - var ares = new HttpStreamAsyncResult(this); - ares._callback = cback; - ares._state = state; - ares.Complete(); - return ares; - } - - byte[] bytes = null; - MemoryStream ms = GetHeaders(false); - bool chunked = _response.SendChunked; - if (ms != null) - { - long start = ms.Position; - ms.Position = ms.Length; - if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - ms.Write(bytes, 0, bytes.Length); - } - ms.Write(buffer, offset, size); - buffer = ms.GetBuffer(); - offset = (int)start; - size = (int)(ms.Position - start); - } - else if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - InternalWrite(bytes, 0, bytes.Length); - } - - return _stream.BeginWrite(buffer, offset, size, cback, state); - } - - private void EndWriteCore(IAsyncResult asyncResult) - { - if (_closed) - return; - - if (_ignore_errors) - { - try - { - _stream.EndWrite(asyncResult); - if (_response.SendChunked) - _stream.Write(s_crlf, 0, 2); - } - catch { } - } - else - { - _stream.EndWrite(asyncResult); - if (_response.SendChunked) - _stream.Write(s_crlf, 0, 2); - } - } - - public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) - { - return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken); - } - - const int StreamCopyToBufferSize = 81920; - private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) - { - var allowAsync = _environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; - - //if (count <= 0) - //{ - // allowAsync = true; - //} - - var fileOpenOptions = FileOpenOptions.SequentialScan; - - if (allowAsync) - { - fileOpenOptions |= FileOpenOptions.Asynchronous; - } - - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - - using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions)) - { - if (offset > 0) - { - fs.Position = offset; - } - - var targetStream = this; - - if (count > 0) - { - await _streamHelper.CopyToAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false); - } - else - { - await fs.CopyToAsync(targetStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } - } - } - } -} diff --git a/SocketHttpListener/Net/HttpResponseStream.cs b/SocketHttpListener/Net/HttpResponseStream.cs deleted file mode 100644 index 085c2ad0cb..0000000000 --- a/SocketHttpListener/Net/HttpResponseStream.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net -{ - internal sealed partial class HttpResponseStream : Stream - { - private bool _closed; - internal bool Closed => _closed; - - public override bool CanRead => false; - public override bool CanSeek => false; - public override bool CanWrite => true; - - public override void Flush() { } - public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - - set => throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return base.BeginRead(buffer, offset, count, callback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - return base.EndRead(asyncResult); - } - - public override void Write(byte[] buffer, int offset, int size) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - if (_closed) - { - return; - } - - WriteCore(buffer, offset, size); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - - return BeginWriteCore(buffer, offset, size, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - if (asyncResult == null) - { - throw new ArgumentNullException(nameof(asyncResult)); - } - - EndWriteCore(asyncResult); - } - - protected override void Dispose(bool disposing) - { - try - { - if (disposing) - { - if (_closed) - { - return; - } - _closed = true; - DisposeCore(); - } - } - finally - { - base.Dispose(disposing); - } - } - } -} diff --git a/SocketHttpListener/Net/HttpStatusCode.cs b/SocketHttpListener/Net/HttpStatusCode.cs deleted file mode 100644 index d4bb61b8a7..0000000000 --- a/SocketHttpListener/Net/HttpStatusCode.cs +++ /dev/null @@ -1,321 +0,0 @@ -namespace SocketHttpListener.Net -{ - /// - /// Contains the values of the HTTP status codes. - /// - /// - /// The HttpStatusCode enumeration contains the values of the HTTP status codes defined in - /// RFC 2616 for HTTP 1.1. - /// - public enum HttpStatusCode - { - /// - /// Equivalent to status code 100. - /// Indicates that the client should continue with its request. - /// - Continue = 100, - /// - /// Equivalent to status code 101. - /// Indicates that the server is switching the HTTP version or protocol on the connection. - /// - SwitchingProtocols = 101, - /// - /// Equivalent to status code 200. - /// Indicates that the client's request has succeeded. - /// - OK = 200, - /// - /// Equivalent to status code 201. - /// Indicates that the client's request has been fulfilled and resulted in a new resource being - /// created. - /// - Created = 201, - /// - /// Equivalent to status code 202. - /// Indicates that the client's request has been accepted for processing, but the processing - /// hasn't been completed. - /// - Accepted = 202, - /// - /// Equivalent to status code 203. - /// Indicates that the returned metainformation is from a local or a third-party copy instead of - /// the origin server. - /// - NonAuthoritativeInformation = 203, - /// - /// Equivalent to status code 204. - /// Indicates that the server has fulfilled the client's request but doesn't need to return - /// an entity-body. - /// - NoContent = 204, - /// - /// Equivalent to status code 205. - /// Indicates that the server has fulfilled the client's request, and the user agent should - /// reset the document view which caused the request to be sent. - /// - ResetContent = 205, - /// - /// Equivalent to status code 206. - /// Indicates that the server has fulfilled the partial GET request for the resource. - /// - PartialContent = 206, - /// - /// - /// Equivalent to status code 300. - /// Indicates that the requested resource corresponds to any of multiple representations. - /// - /// - /// MultipleChoices is a synonym for Ambiguous. - /// - /// - MultipleChoices = 300, - /// - /// - /// Equivalent to status code 300. - /// Indicates that the requested resource corresponds to any of multiple representations. - /// - /// - /// Ambiguous is a synonym for MultipleChoices. - /// - /// - Ambiguous = 300, - /// - /// - /// Equivalent to status code 301. - /// Indicates that the requested resource has been assigned a new permanent URI and - /// any future references to this resource should use one of the returned URIs. - /// - /// - /// MovedPermanently is a synonym for Moved. - /// - /// - MovedPermanently = 301, - /// - /// - /// Equivalent to status code 301. - /// Indicates that the requested resource has been assigned a new permanent URI and - /// any future references to this resource should use one of the returned URIs. - /// - /// - /// Moved is a synonym for MovedPermanently. - /// - /// - Moved = 301, - /// - /// - /// Equivalent to status code 302. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// Found is a synonym for Redirect. - /// - /// - Found = 302, - /// - /// - /// Equivalent to status code 302. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// Redirect is a synonym for Found. - /// - /// - Redirect = 302, - /// - /// - /// Equivalent to status code 303. - /// Indicates that the response to the request can be found under a different URI and - /// should be retrieved using a GET method on that resource. - /// - /// - /// SeeOther is a synonym for RedirectMethod. - /// - /// - SeeOther = 303, - /// - /// - /// Equivalent to status code 303. - /// Indicates that the response to the request can be found under a different URI and - /// should be retrieved using a GET method on that resource. - /// - /// - /// RedirectMethod is a synonym for SeeOther. - /// - /// - RedirectMethod = 303, - /// - /// Equivalent to status code 304. - /// Indicates that the client has performed a conditional GET request and access is allowed, - /// but the document hasn't been modified. - /// - NotModified = 304, - /// - /// Equivalent to status code 305. - /// Indicates that the requested resource must be accessed through the proxy given by - /// the Location field. - /// - UseProxy = 305, - /// - /// Equivalent to status code 306. - /// This status code was used in a previous version of the specification, is no longer used, - /// and is reserved for future use. - /// - Unused = 306, - /// - /// - /// Equivalent to status code 307. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// TemporaryRedirect is a synonym for RedirectKeepVerb. - /// - /// - TemporaryRedirect = 307, - /// - /// - /// Equivalent to status code 307. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// RedirectKeepVerb is a synonym for TemporaryRedirect. - /// - /// - RedirectKeepVerb = 307, - /// - /// Equivalent to status code 400. - /// Indicates that the client's request couldn't be understood by the server due to - /// malformed syntax. - /// - BadRequest = 400, - /// - /// Equivalent to status code 401. - /// Indicates that the client's request requires user authentication. - /// - Unauthorized = 401, - /// - /// Equivalent to status code 402. - /// This status code is reserved for future use. - /// - PaymentRequired = 402, - /// - /// Equivalent to status code 403. - /// Indicates that the server understood the client's request but is refusing to fulfill it. - /// - Forbidden = 403, - /// - /// Equivalent to status code 404. - /// Indicates that the server hasn't found anything matching the request URI. - /// - NotFound = 404, - /// - /// Equivalent to status code 405. - /// Indicates that the method specified in the request line isn't allowed for the resource - /// identified by the request URI. - /// - MethodNotAllowed = 405, - /// - /// Equivalent to status code 406. - /// Indicates that the server doesn't have the appropriate resource to respond to the Accept - /// headers in the client's request. - /// - NotAcceptable = 406, - /// - /// Equivalent to status code 407. - /// Indicates that the client must first authenticate itself with the proxy. - /// - ProxyAuthenticationRequired = 407, - /// - /// Equivalent to status code 408. - /// Indicates that the client didn't produce a request within the time that the server was - /// prepared to wait. - /// - RequestTimeout = 408, - /// - /// Equivalent to status code 409. - /// Indicates that the client's request couldn't be completed due to a conflict on the server. - /// - Conflict = 409, - /// - /// Equivalent to status code 410. - /// Indicates that the requested resource is no longer available at the server and - /// no forwarding address is known. - /// - Gone = 410, - /// - /// Equivalent to status code 411. - /// Indicates that the server refuses to accept the client's request without a defined - /// Content-Length. - /// - LengthRequired = 411, - /// - /// Equivalent to status code 412. - /// Indicates that the precondition given in one or more of the request headers evaluated to - /// false when it was tested on the server. - /// - PreconditionFailed = 412, - /// - /// Equivalent to status code 413. - /// Indicates that the entity of the client's request is larger than the server is willing or - /// able to process. - /// - RequestEntityTooLarge = 413, - /// - /// Equivalent to status code 414. - /// Indicates that the request URI is longer than the server is willing to interpret. - /// - RequestUriTooLong = 414, - /// - /// Equivalent to status code 415. - /// Indicates that the entity of the client's request is in a format not supported by - /// the requested resource for the requested method. - /// - UnsupportedMediaType = 415, - /// - /// Equivalent to status code 416. - /// Indicates that none of the range specifier values in a Range request header overlap - /// the current extent of the selected resource. - /// - RequestedRangeNotSatisfiable = 416, - /// - /// Equivalent to status code 417. - /// Indicates that the expectation given in an Expect request header couldn't be met by - /// the server. - /// - ExpectationFailed = 417, - /// - /// Equivalent to status code 500. - /// Indicates that the server encountered an unexpected condition which prevented it from - /// fulfilling the client's request. - /// - InternalServerError = 500, - /// - /// Equivalent to status code 501. - /// Indicates that the server doesn't support the functionality required to fulfill the client's - /// request. - /// - NotImplemented = 501, - /// - /// Equivalent to status code 502. - /// Indicates that a gateway or proxy server received an invalid response from the upstream - /// server. - /// - BadGateway = 502, - /// - /// Equivalent to status code 503. - /// Indicates that the server is currently unable to handle the client's request due to - /// a temporary overloading or maintenance of the server. - /// - ServiceUnavailable = 503, - /// - /// Equivalent to status code 504. - /// Indicates that a gateway or proxy server didn't receive a timely response from the upstream - /// server or some other auxiliary server. - /// - GatewayTimeout = 504, - /// - /// Equivalent to status code 505. - /// Indicates that the server doesn't support the HTTP version used in the client's request. - /// - HttpVersionNotSupported = 505, - } -} diff --git a/SocketHttpListener/Net/HttpStatusDescription.cs b/SocketHttpListener/Net/HttpStatusDescription.cs deleted file mode 100644 index a4e42560b9..0000000000 --- a/SocketHttpListener/Net/HttpStatusDescription.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal static class HttpStatusDescription - { - internal static string Get(HttpStatusCode code) - { - return Get((int)code); - } - - internal static string Get(int code) - { - switch (code) - { - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 102: return "Processing"; - - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 207: return "Multi-Status"; - - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 307: return "Temporary Redirect"; - - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-Uri Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 422: return "Unprocessable Entity"; - case 423: return "Locked"; - case 424: return "Failed Dependency"; - case 426: return "Upgrade Required"; // RFC 2817 - - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "Http Version Not Supported"; - case 507: return "Insufficient Storage"; - } - return null; - } - } -} diff --git a/SocketHttpListener/Net/HttpStreamAsyncResult.cs b/SocketHttpListener/Net/HttpStreamAsyncResult.cs deleted file mode 100644 index 46944c624a..0000000000 --- a/SocketHttpListener/Net/HttpStreamAsyncResult.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net -{ - internal class HttpStreamAsyncResult : IAsyncResult - { - private object _locker = new object(); - private ManualResetEvent _handle; - private bool _completed; - - internal readonly object _parent; - internal byte[] _buffer; - internal int _offset; - internal int _count; - internal AsyncCallback _callback; - internal object _state; - internal int _synchRead; - internal Exception _error; - internal bool _endCalled; - - internal HttpStreamAsyncResult(object parent) - { - _parent = parent; - } - - public void Complete(Exception e) - { - _error = e; - Complete(); - } - - public void Complete() - { - lock (_locker) - { - if (_completed) - return; - - _completed = true; - if (_handle != null) - _handle.Set(); - - if (_callback != null) - Task.Run(() => _callback(this)); - } - } - - public object AsyncState => _state; - - public WaitHandle AsyncWaitHandle - { - get - { - lock (_locker) - { - if (_handle == null) - _handle = new ManualResetEvent(_completed); - } - - return _handle; - } - } - - public bool CompletedSynchronously => false; - - public bool IsCompleted - { - get - { - lock (_locker) - { - return _completed; - } - } - } - } -} diff --git a/SocketHttpListener/Net/HttpVersion.cs b/SocketHttpListener/Net/HttpVersion.cs deleted file mode 100644 index c0839b46d5..0000000000 --- a/SocketHttpListener/Net/HttpVersion.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace SocketHttpListener.Net -{ - // - // - public class HttpVersion - { - - public static readonly Version Version10 = new Version(1, 0); - public static readonly Version Version11 = new Version(1, 1); - - // pretty useless.. - public HttpVersion() { } - } -} diff --git a/SocketHttpListener/Net/ListenerPrefix.cs b/SocketHttpListener/Net/ListenerPrefix.cs deleted file mode 100644 index edfcb89043..0000000000 --- a/SocketHttpListener/Net/ListenerPrefix.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Net; - -namespace SocketHttpListener.Net -{ - internal sealed class ListenerPrefix - { - private string _original; - private string _host; - private ushort _port; - private string _path; - private bool _secure; - private IPAddress[] _addresses; - internal HttpListener _listener; - - public ListenerPrefix(string prefix) - { - _original = prefix; - Parse(prefix); - } - - public override string ToString() - { - return _original; - } - - public IPAddress[] Addresses - { - get => _addresses; - set => _addresses = value; - } - public bool Secure => _secure; - - public string Host => _host; - - public int Port => _port; - - public string Path => _path; - - // Equals and GetHashCode are required to detect duplicates in HttpListenerPrefixCollection. - public override bool Equals(object o) - { - var other = o as ListenerPrefix; - if (other == null) - return false; - - return (_original == other._original); - } - - public override int GetHashCode() - { - return _original.GetHashCode(); - } - - private void Parse(string uri) - { - ushort default_port = 80; - if (uri.StartsWith("https://")) - { - default_port = 443; - _secure = true; - } - - int length = uri.Length; - int start_host = uri.IndexOf(':') + 3; - if (start_host >= length) - throw new ArgumentException("net_listener_host"); - - int colon = uri.IndexOf(':', start_host, length - start_host); - int root; - if (colon > 0) - { - _host = uri.Substring(start_host, colon - start_host); - root = uri.IndexOf('/', colon, length - colon); - _port = (ushort)int.Parse(uri.Substring(colon + 1, root - colon - 1)); - _path = uri.Substring(root); - } - else - { - root = uri.IndexOf('/', start_host, length - start_host); - _host = uri.Substring(start_host, root - start_host); - _port = default_port; - _path = uri.Substring(root); - } - if (_path.Length != 1) - _path = _path.Substring(0, _path.Length - 1); - } - } -} diff --git a/SocketHttpListener/Net/UriScheme.cs b/SocketHttpListener/Net/UriScheme.cs deleted file mode 100644 index 33d1f09db6..0000000000 --- a/SocketHttpListener/Net/UriScheme.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal static class UriScheme - { - public const string File = "file"; - public const string Ftp = "ftp"; - public const string Gopher = "gopher"; - public const string Http = "http"; - public const string Https = "https"; - public const string News = "news"; - public const string NetPipe = "net.pipe"; - public const string NetTcp = "net.tcp"; - public const string Nntp = "nntp"; - public const string Mailto = "mailto"; - public const string Ws = "ws"; - public const string Wss = "wss"; - - public const string SchemeDelimiter = "://"; - } -} diff --git a/SocketHttpListener/Net/WebHeaderCollection.cs b/SocketHttpListener/Net/WebHeaderCollection.cs deleted file mode 100644 index 34fca808be..0000000000 --- a/SocketHttpListener/Net/WebHeaderCollection.cs +++ /dev/null @@ -1,360 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Text; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net -{ - [ComVisible(true)] - public class WebHeaderCollection : QueryParamCollection - { - [Flags] - internal enum HeaderInfo - { - Request = 1, - Response = 1 << 1, - MultiValue = 1 << 10 - } - - static readonly bool[] allowed_chars = { - false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, true, false, true, true, true, true, false, false, false, true, - true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false, - false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, - false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, - false, true, false - }; - - static readonly Dictionary headers; - - static WebHeaderCollection() - { - headers = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { "Allow", HeaderInfo.MultiValue }, - { "Accept", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Accept-Charset", HeaderInfo.MultiValue }, - { "Accept-Encoding", HeaderInfo.MultiValue }, - { "Accept-Language", HeaderInfo.MultiValue }, - { "Accept-Ranges", HeaderInfo.MultiValue }, - { "Age", HeaderInfo.Response }, - { "Authorization", HeaderInfo.MultiValue }, - { "Cache-Control", HeaderInfo.MultiValue }, - { "Cookie", HeaderInfo.MultiValue }, - { "Connection", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Content-Encoding", HeaderInfo.MultiValue }, - { "Content-Length", HeaderInfo.Request | HeaderInfo.Response }, - { "Content-Type", HeaderInfo.Request }, - { "Content-Language", HeaderInfo.MultiValue }, - { "Date", HeaderInfo.Request }, - { "Expect", HeaderInfo.Request | HeaderInfo.MultiValue}, - { "Host", HeaderInfo.Request }, - { "If-Match", HeaderInfo.MultiValue }, - { "If-Modified-Since", HeaderInfo.Request }, - { "If-None-Match", HeaderInfo.MultiValue }, - { "Keep-Alive", HeaderInfo.Response }, - { "Pragma", HeaderInfo.MultiValue }, - { "Proxy-Authenticate", HeaderInfo.MultiValue }, - { "Proxy-Authorization", HeaderInfo.MultiValue }, - { "Proxy-Connection", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Range", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Referer", HeaderInfo.Request }, - { "Set-Cookie", HeaderInfo.MultiValue }, - { "Set-Cookie2", HeaderInfo.MultiValue }, - { "Server", HeaderInfo.Response }, - { "TE", HeaderInfo.MultiValue }, - { "Trailer", HeaderInfo.MultiValue }, - { "Transfer-Encoding", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo.MultiValue }, - { "Translate", HeaderInfo.Request | HeaderInfo.Response }, - { "Upgrade", HeaderInfo.MultiValue }, - { "User-Agent", HeaderInfo.Request }, - { "Vary", HeaderInfo.MultiValue }, - { "Via", HeaderInfo.MultiValue }, - { "Warning", HeaderInfo.MultiValue }, - { "WWW-Authenticate", HeaderInfo.Response | HeaderInfo. MultiValue }, - { "SecWebSocketAccept", HeaderInfo.Response }, - { "SecWebSocketExtensions", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue }, - { "SecWebSocketKey", HeaderInfo.Request }, - { "Sec-WebSocket-Protocol", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue }, - { "SecWebSocketVersion", HeaderInfo.Response | HeaderInfo. MultiValue } - }; - } - - // Methods - - public void Add(string header) - { - if (header == null) - throw new ArgumentNullException(nameof(header)); - int pos = header.IndexOf(':'); - if (pos == -1) - throw new ArgumentException("no colon found", nameof(header)); - - this.Add(header.Substring(0, pos), header.Substring(pos + 1)); - } - - public override void Add(string name, string value) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - this.AddWithoutValidate(name, value); - } - - protected void AddWithoutValidate(string headerName, string headerValue) - { - if (!IsHeaderName(headerName)) - throw new ArgumentException("invalid header name: " + headerName, nameof(headerName)); - if (headerValue == null) - headerValue = string.Empty; - else - headerValue = headerValue.Trim(); - if (!IsHeaderValue(headerValue)) - throw new ArgumentException("invalid header value: " + headerValue, nameof(headerValue)); - - AddValue(headerName, headerValue); - } - - internal void AddValue(string headerName, string headerValue) - { - base.Add(headerName, headerValue); - } - - internal List GetValues_internal(string header, bool split) - { - if (header == null) - throw new ArgumentNullException(nameof(header)); - - var values = base.GetValues(header); - if (values == null || values.Count == 0) - return null; - - if (split && IsMultiValue(header)) - { - List separated = null; - foreach (var value in values) - { - if (value.IndexOf(',') < 0) - { - if (separated != null) - separated.Add(value); - - continue; - } - - if (separated == null) - { - separated = new List(values.Count + 1); - foreach (var v in values) - { - if (v == value) - break; - - separated.Add(v); - } - } - - var slices = value.Split(','); - var slices_length = slices.Length; - if (value[value.Length - 1] == ',') - --slices_length; - - for (int i = 0; i < slices_length; ++i) - { - separated.Add(slices[i].Trim()); - } - } - - if (separated != null) - return separated; - } - - return values; - } - - public override List GetValues(string header) - { - return GetValues_internal(header, true); - } - - public override string[] GetValues(int index) - { - string[] values = base.GetValues(index); - - if (values == null || values.Length == 0) - { - return null; - } - - return values; - } - - public static bool IsRestricted(string headerName) - { - return IsRestricted(headerName, false); - } - - public static bool IsRestricted(string headerName, bool response) - { - if (headerName == null) - throw new ArgumentNullException(nameof(headerName)); - - if (headerName.Length == 0) - throw new ArgumentException("empty string", nameof(headerName)); - - if (!IsHeaderName(headerName)) - throw new ArgumentException("Invalid character in header"); - - if (!headers.TryGetValue(headerName, out HeaderInfo info)) - return false; - - var flag = response ? HeaderInfo.Response : HeaderInfo.Request; - return (info & flag) != 0; - } - - public override void Set(string name, string value) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (!IsHeaderName(name)) - throw new ArgumentException("invalid header name"); - if (value == null) - value = string.Empty; - else - value = value.Trim(); - if (!IsHeaderValue(value)) - throw new ArgumentException("invalid header value"); - - base.Set(name, value); - } - - internal string ToStringMultiValue() - { - var sb = new StringBuilder(); - - int count = base.Count; - for (int i = 0; i < count; i++) - { - string key = GetKey(i); - if (IsMultiValue(key)) - { - foreach (string v in GetValues(i)) - { - sb.Append(key) - .Append(": ") - .Append(v) - .Append("\r\n"); - } - } - else - { - sb.Append(key) - .Append(": ") - .Append(Get(i)) - .Append("\r\n"); - } - } - return sb.Append("\r\n").ToString(); - } - - public override string ToString() - { - var sb = new StringBuilder(); - - int count = base.Count; - for (int i = 0; i < count; i++) - sb.Append(GetKey(i)) - .Append(": ") - .Append(Get(i)) - .Append("\r\n"); - - return sb.Append("\r\n").ToString(); - } - - - // Internal Methods - - // With this we don't check for invalid characters in header. See bug #55994. - internal void SetInternal(string header) - { - int pos = header.IndexOf(':'); - if (pos == -1) - throw new ArgumentException("no colon found", nameof(header)); - - SetInternal(header.Substring(0, pos), header.Substring(pos + 1)); - } - - internal void SetInternal(string name, string value) - { - if (value == null) - value = string.Empty; - else - value = value.Trim(); - if (!IsHeaderValue(value)) - throw new ArgumentException("invalid header value"); - - if (IsMultiValue(name)) - { - base.Add(name, value); - } - else - { - base.Remove(name); - base.Set(name, value); - } - } - - internal static bool IsMultiValue(string headerName) - { - if (headerName == null) - return false; - - return headers.TryGetValue(headerName, out HeaderInfo info) && (info & HeaderInfo.MultiValue) != 0; - } - - internal static bool IsHeaderValue(string value) - { - // TEXT any 8 bit value except CTL's (0-31 and 127) - // but including \r\n space and \t - // after a newline at least one space or \t must follow - // certain header fields allow comments () - - int len = value.Length; - for (int i = 0; i < len; i++) - { - char c = value[i]; - if (c == 127) - return false; - if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t')) - return false; - if (c == '\n' && ++i < len) - { - c = value[i]; - if (c != ' ' && c != '\t') - return false; - } - } - - return true; - } - - internal static bool IsHeaderName(string name) - { - if (name == null || name.Length == 0) - return false; - - int len = name.Length; - for (int i = 0; i < len; i++) - { - char c = name[i]; - if (c > 126 || !allowed_chars[c]) - return false; - } - - return true; - } - } -} diff --git a/SocketHttpListener/Net/WebHeaderEncoding.cs b/SocketHttpListener/Net/WebHeaderEncoding.cs deleted file mode 100644 index 96e0cc85d8..0000000000 --- a/SocketHttpListener/Net/WebHeaderEncoding.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Text; - -namespace SocketHttpListener.Net -{ - // we use this static class as a helper class to encode/decode HTTP headers. - // what we need is a 1-1 correspondence between a char in the range U+0000-U+00FF - // and a byte in the range 0x00-0xFF (which is the range that can hit the network). - // The Latin-1 encoding (ISO-88591-1) (GetEncoding(28591)) works for byte[] to string, but is a little slow. - // It doesn't work for string -> byte[] because of best-fit-mapping problems. - internal static class WebHeaderEncoding - { - // We don't want '?' replacement characters, just fail. - private static readonly Encoding s_utf8Decoder = Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); - - internal static unsafe string GetString(byte[] bytes, int byteIndex, int byteCount) - { - fixed (byte* pBytes = bytes) - return GetString(pBytes + byteIndex, byteCount); - } - - internal static unsafe string GetString(byte* pBytes, int byteCount) - { - if (byteCount < 1) - return ""; - - string s = new string('\0', byteCount); - - fixed (char* pStr = s) - { - char* pString = pStr; - while (byteCount >= 8) - { - pString[0] = (char)pBytes[0]; - pString[1] = (char)pBytes[1]; - pString[2] = (char)pBytes[2]; - pString[3] = (char)pBytes[3]; - pString[4] = (char)pBytes[4]; - pString[5] = (char)pBytes[5]; - pString[6] = (char)pBytes[6]; - pString[7] = (char)pBytes[7]; - pString += 8; - pBytes += 8; - byteCount -= 8; - } - for (int i = 0; i < byteCount; i++) - { - pString[i] = (char)pBytes[i]; - } - } - - return s; - } - - internal static int GetByteCount(string myString) - { - return myString.Length; - } - internal static unsafe void GetBytes(string myString, int charIndex, int charCount, byte[] bytes, int byteIndex) - { - if (myString.Length == 0) - { - return; - } - fixed (byte* bufferPointer = bytes) - { - byte* newBufferPointer = bufferPointer + byteIndex; - int finalIndex = charIndex + charCount; - while (charIndex < finalIndex) - { - *newBufferPointer++ = (byte)myString[charIndex++]; - } - } - } - internal static byte[] GetBytes(string myString) - { - byte[] bytes = new byte[myString.Length]; - if (myString.Length != 0) - { - GetBytes(myString, 0, myString.Length, bytes, 0); - } - return bytes; - } - } -} diff --git a/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs b/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs deleted file mode 100644 index 5ed49ec47d..0000000000 --- a/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Security.Principal; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net.WebSockets -{ - public class HttpListenerWebSocketContext : WebSocketContext - { - private readonly Uri _requestUri; - private readonly QueryParamCollection _headers; - private readonly CookieCollection _cookieCollection; - private readonly IPrincipal _user; - private readonly bool _isAuthenticated; - private readonly bool _isLocal; - private readonly bool _isSecureConnection; - - private readonly string _origin; - private readonly IEnumerable _secWebSocketProtocols; - private readonly string _secWebSocketVersion; - private readonly string _secWebSocketKey; - - private readonly WebSocket _webSocket; - - internal HttpListenerWebSocketContext( - Uri requestUri, - QueryParamCollection headers, - CookieCollection cookieCollection, - IPrincipal user, - bool isAuthenticated, - bool isLocal, - bool isSecureConnection, - string origin, - IEnumerable secWebSocketProtocols, - string secWebSocketVersion, - string secWebSocketKey, - WebSocket webSocket) - { - _cookieCollection = new CookieCollection(); - _cookieCollection.Add(cookieCollection); - - //_headers = new NameValueCollection(headers); - _headers = headers; - _user = CopyPrincipal(user); - - _requestUri = requestUri; - _isAuthenticated = isAuthenticated; - _isLocal = isLocal; - _isSecureConnection = isSecureConnection; - _origin = origin; - _secWebSocketProtocols = secWebSocketProtocols; - _secWebSocketVersion = secWebSocketVersion; - _secWebSocketKey = secWebSocketKey; - _webSocket = webSocket; - } - - public override Uri RequestUri => _requestUri; - - public override QueryParamCollection Headers => _headers; - - public override string Origin => _origin; - - public override IEnumerable SecWebSocketProtocols => _secWebSocketProtocols; - - public override string SecWebSocketVersion => _secWebSocketVersion; - - public override string SecWebSocketKey => _secWebSocketKey; - - public override CookieCollection CookieCollection => _cookieCollection; - - public override IPrincipal User => _user; - - public override bool IsAuthenticated => _isAuthenticated; - - public override bool IsLocal => _isLocal; - - public override bool IsSecureConnection => _isSecureConnection; - - public override WebSocket WebSocket => _webSocket; - - private static IPrincipal CopyPrincipal(IPrincipal user) - { - if (user != null) - { - throw new NotImplementedException(); - } - - return null; - } - } -} diff --git a/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs b/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs deleted file mode 100644 index 1cfd2dc902..0000000000 --- a/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net.WebSockets -{ - internal static partial class HttpWebSocket - { - private const string SupportedVersion = "13"; - - internal static async Task AcceptWebSocketAsyncCore(HttpListenerContext context, - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment? internalBuffer = null) - { - ValidateOptions(subProtocol, receiveBufferSize, MinSendBufferSize, keepAliveInterval); - - // get property will create a new response if one doesn't exist. - HttpListenerResponse response = context.Response; - HttpListenerRequest request = context.Request; - ValidateWebSocketHeaders(context); - - string secWebSocketVersion = request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - - // Optional for non-browser client - string origin = request.Headers[HttpKnownHeaderNames.Origin]; - - string[] secWebSocketProtocols = null; - bool shouldSendSecWebSocketProtocolHeader = - ProcessWebSocketProtocolHeader( - request.Headers[HttpKnownHeaderNames.SecWebSocketProtocol], - subProtocol, - out var outgoingSecWebSocketProtocolString); - - if (shouldSendSecWebSocketProtocolHeader) - { - secWebSocketProtocols = new string[] { outgoingSecWebSocketProtocolString }; - response.Headers.Add(HttpKnownHeaderNames.SecWebSocketProtocol, outgoingSecWebSocketProtocolString); - } - - // negotiate the websocket key return value - string secWebSocketKey = request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - string secWebSocketAccept = HttpWebSocket.GetSecWebSocketAcceptString(secWebSocketKey); - - response.Headers.Add(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade); - response.Headers.Add(HttpKnownHeaderNames.Upgrade, WebSocketUpgradeToken); - response.Headers.Add(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept); - - response.StatusCode = (int)HttpStatusCode.SwitchingProtocols; // HTTP 101 - response.StatusDescription = HttpStatusDescription.Get(HttpStatusCode.SwitchingProtocols); - - var responseStream = response.OutputStream as HttpResponseStream; - - // Send websocket handshake headers - await responseStream.WriteWebSocketHandshakeHeadersAsync().ConfigureAwait(false); - - //WebSocket webSocket = WebSocket.CreateFromStream(context.Connection.ConnectedStream, isServer: true, subProtocol, keepAliveInterval); - var webSocket = new WebSocket(subProtocol); - - var webSocketContext = new HttpListenerWebSocketContext( - request.Url, - request.Headers, - request.Cookies, - context.User, - request.IsAuthenticated, - request.IsLocal, - request.IsSecureConnection, - origin, - secWebSocketProtocols != null ? secWebSocketProtocols : Array.Empty(), - secWebSocketVersion, - secWebSocketKey, - webSocket); - - webSocket.SetContext(webSocketContext, context.Connection.Close, context.Connection.Stream); - - return webSocketContext; - } - - private const bool WebSocketsSupported = true; - } -} diff --git a/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs b/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs deleted file mode 100644 index b346cc98ec..0000000000 --- a/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography; -using System.Text; -using System.Threading; - -namespace SocketHttpListener.Net.WebSockets -{ - internal static partial class HttpWebSocket - { - internal const string SecWebSocketKeyGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - internal const string WebSocketUpgradeToken = "websocket"; - internal const int DefaultReceiveBufferSize = 16 * 1024; - internal const int DefaultClientSendBufferSize = 16 * 1024; - - [SuppressMessage("Microsoft.Security", "CA5350", Justification = "SHA1 used only for hashing purposes, not for crypto.")] - internal static string GetSecWebSocketAcceptString(string secWebSocketKey) - { - string retVal; - - // SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat. - using (var sha1 = SHA1.Create()) - { - string acceptString = string.Concat(secWebSocketKey, HttpWebSocket.SecWebSocketKeyGuid); - byte[] toHash = Encoding.UTF8.GetBytes(acceptString); - retVal = Convert.ToBase64String(sha1.ComputeHash(toHash)); - } - - return retVal; - } - - // return value here signifies if a Sec-WebSocket-Protocol header should be returned by the server. - internal static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol, - string subProtocol, - out string acceptProtocol) - { - acceptProtocol = string.Empty; - if (string.IsNullOrEmpty(clientSecWebSocketProtocol)) - { - // client hasn't specified any Sec-WebSocket-Protocol header - if (subProtocol != null) - { - // If the server specified _anything_ this isn't valid. - throw new WebSocketException("UnsupportedProtocol"); - } - // Treat empty and null from the server as the same thing here, server should not send headers. - return false; - } - - // here, we know the client specified something and it's non-empty. - - if (subProtocol == null) - { - // client specified some protocols, server specified 'null'. So server should send headers. - return true; - } - - // here, we know that the client has specified something, it's not empty - // and the server has specified exactly one protocol - - string[] requestProtocols = clientSecWebSocketProtocol.Split(new char[] { ',' }, - StringSplitOptions.RemoveEmptyEntries); - acceptProtocol = subProtocol; - - // client specified protocols, serverOptions has exactly 1 non-empty entry. Check that - // this exists in the list the client specified. - for (int i = 0; i < requestProtocols.Length; i++) - { - string currentRequestProtocol = requestProtocols[i].Trim(); - if (string.Equals(acceptProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - throw new WebSocketException("net_WebSockets_AcceptUnsupportedProtocol"); - } - - internal static void ValidateOptions(string subProtocol, int receiveBufferSize, int sendBufferSize, TimeSpan keepAliveInterval) - { - if (subProtocol != null) - { - WebSocketValidate.ValidateSubprotocol(subProtocol); - } - - if (receiveBufferSize < MinReceiveBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(receiveBufferSize), "The receiveBufferSize was too small."); - } - - if (sendBufferSize < MinSendBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(sendBufferSize), "The sendBufferSize was too small."); - } - - if (receiveBufferSize > MaxBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(receiveBufferSize), "The receiveBufferSize was too large."); - } - - if (sendBufferSize > MaxBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(sendBufferSize), "The sendBufferSize was too large."); - } - - if (keepAliveInterval < Timeout.InfiniteTimeSpan) // -1 millisecond - { - throw new ArgumentOutOfRangeException(nameof(keepAliveInterval), "The keepAliveInterval was too small."); - } - } - - internal const int MinSendBufferSize = 16; - internal const int MinReceiveBufferSize = 256; - internal const int MaxBufferSize = 64 * 1024; - - private static void ValidateWebSocketHeaders(HttpListenerContext context) - { - if (!WebSocketsSupported) - { - throw new PlatformNotSupportedException("net_WebSockets_UnsupportedPlatform"); - } - - if (!context.Request.IsWebSocketRequest) - { - throw new WebSocketException("net_WebSockets_AcceptNotAWebSocket"); - } - - string secWebSocketVersion = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - if (string.IsNullOrEmpty(secWebSocketVersion)) - { - throw new WebSocketException("net_WebSockets_AcceptHeaderNotFound"); - } - - if (!string.Equals(secWebSocketVersion, SupportedVersion, StringComparison.OrdinalIgnoreCase)) - { - throw new WebSocketException("net_WebSockets_AcceptUnsupportedWebSocketVersion"); - } - - string secWebSocketKey = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - bool isSecWebSocketKeyInvalid = string.IsNullOrWhiteSpace(secWebSocketKey); - if (!isSecWebSocketKeyInvalid) - { - try - { - // key must be 16 bytes then base64-encoded - isSecWebSocketKeyInvalid = Convert.FromBase64String(secWebSocketKey).Length != 16; - } - catch - { - isSecWebSocketKeyInvalid = true; - } - } - if (isSecWebSocketKeyInvalid) - { - throw new WebSocketException("net_WebSockets_AcceptHeaderNotFound"); - } - } - } -} diff --git a/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs b/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs deleted file mode 100644 index 5ac89cf480..0000000000 --- a/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace SocketHttpListener.Net.WebSockets -{ - public enum WebSocketCloseStatus - { - NormalClosure = 1000, - EndpointUnavailable = 1001, - ProtocolError = 1002, - InvalidMessageType = 1003, - Empty = 1005, - // AbnormalClosure = 1006, // 1006 is reserved and should never be used by user - InvalidPayloadData = 1007, - PolicyViolation = 1008, - MessageTooBig = 1009, - MandatoryExtension = 1010, - InternalServerError = 1011 - // TLSHandshakeFailed = 1015, // 1015 is reserved and should never be used by user - - // 0 - 999 Status codes in the range 0-999 are not used. - // 1000 - 1999 Status codes in the range 1000-1999 are reserved for definition by this protocol. - // 2000 - 2999 Status codes in the range 2000-2999 are reserved for use by extensions. - // 3000 - 3999 Status codes in the range 3000-3999 MAY be used by libraries and frameworks. The - // interpretation of these codes is undefined by this protocol. End applications MUST - // NOT use status codes in this range. - // 4000 - 4999 Status codes in the range 4000-4999 MAY be used by application code. The interpretation - // of these codes is undefined by this protocol. - } -} diff --git a/SocketHttpListener/Net/WebSockets/WebSocketContext.cs b/SocketHttpListener/Net/WebSockets/WebSocketContext.cs deleted file mode 100644 index 10ad864398..0000000000 --- a/SocketHttpListener/Net/WebSockets/WebSocketContext.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Security.Principal; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net.WebSockets -{ - public abstract class WebSocketContext - { - public abstract Uri RequestUri { get; } - public abstract QueryParamCollection Headers { get; } - public abstract string Origin { get; } - public abstract IEnumerable SecWebSocketProtocols { get; } - public abstract string SecWebSocketVersion { get; } - public abstract string SecWebSocketKey { get; } - public abstract CookieCollection CookieCollection { get; } - public abstract IPrincipal User { get; } - public abstract bool IsAuthenticated { get; } - public abstract bool IsLocal { get; } - public abstract bool IsSecureConnection { get; } - public abstract WebSocket WebSocket { get; } - } -} diff --git a/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs b/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs deleted file mode 100644 index 0469e3b6c5..0000000000 --- a/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Globalization; -using System.Text; -using WebSocketState = System.Net.WebSockets.WebSocketState; - -namespace SocketHttpListener.Net.WebSockets -{ - internal static partial class WebSocketValidate - { - internal const int MaxControlFramePayloadLength = 123; - private const int CloseStatusCodeAbort = 1006; - private const int CloseStatusCodeFailedTLSHandshake = 1015; - private const int InvalidCloseStatusCodesFrom = 0; - private const int InvalidCloseStatusCodesTo = 999; - private const string Separators = "()<>@,;:\\\"/[]?={} "; - - internal static void ThrowIfInvalidState(WebSocketState currentState, bool isDisposed, WebSocketState[] validStates) - { - string validStatesText = string.Empty; - - if (validStates != null && validStates.Length > 0) - { - foreach (WebSocketState validState in validStates) - { - if (currentState == validState) - { - // Ordering is important to maintain .NET 4.5 WebSocket implementation exception behavior. - if (isDisposed) - { - throw new ObjectDisposedException(nameof(WebSocket)); - } - - return; - } - } - - validStatesText = string.Join(", ", validStates); - } - - throw new WebSocketException("net_WebSockets_InvalidState"); - } - - internal static void ValidateSubprotocol(string subProtocol) - { - if (string.IsNullOrWhiteSpace(subProtocol)) - { - throw new ArgumentException("net_WebSockets_InvalidEmptySubProtocol"); - } - - string invalidChar = null; - int i = 0; - while (i < subProtocol.Length) - { - char ch = subProtocol[i]; - if (ch < 0x21 || ch > 0x7e) - { - invalidChar = string.Format(CultureInfo.InvariantCulture, "[{0}]", (int)ch); - break; - } - - if (!char.IsLetterOrDigit(ch) && - Separators.IndexOf(ch) >= 0) - { - invalidChar = ch.ToString(); - break; - } - - i++; - } - - if (invalidChar != null) - { - throw new ArgumentException("net_WebSockets_InvalidCharInProtocolString"); - } - } - - internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, string statusDescription) - { - if (closeStatus == WebSocketCloseStatus.Empty && !string.IsNullOrEmpty(statusDescription)) - { - throw new ArgumentException("net_WebSockets_ReasonNotNull"); - } - - int closeStatusCode = (int)closeStatus; - - if ((closeStatusCode >= InvalidCloseStatusCodesFrom && - closeStatusCode <= InvalidCloseStatusCodesTo) || - closeStatusCode == CloseStatusCodeAbort || - closeStatusCode == CloseStatusCodeFailedTLSHandshake) - { - // CloseStatus 1006 means Aborted - this will never appear on the wire and is reflected by calling WebSocket.Abort - throw new ArgumentException("net_WebSockets_InvalidCloseStatusCode"); - } - - int length = 0; - if (!string.IsNullOrEmpty(statusDescription)) - { - length = Encoding.UTF8.GetByteCount(statusDescription); - } - - if (length > MaxControlFramePayloadLength) - { - throw new ArgumentException("net_WebSockets_InvalidCloseStatusDescription"); - } - } - - internal static void ValidateArraySegment(ArraySegment arraySegment, string parameterName) - { - if (arraySegment.Array == null) - { - throw new ArgumentNullException(parameterName + "." + nameof(arraySegment.Array)); - } - if (arraySegment.Offset < 0 || arraySegment.Offset > arraySegment.Array.Length) - { - throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Offset)); - } - if (arraySegment.Count < 0 || arraySegment.Count > (arraySegment.Array.Length - arraySegment.Offset)) - { - throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Count)); - } - } - - internal static void ValidateBuffer(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (count < 0 || count > (buffer.Length - offset)) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - } - } -} diff --git a/SocketHttpListener/WebSocket.cs b/SocketHttpListener/WebSocket.cs index 0dcb6a64bc..afce871fd6 100644 --- a/SocketHttpListener/WebSocket.cs +++ b/SocketHttpListener/WebSocket.cs @@ -4,11 +4,10 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; +using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; -using SocketHttpListener.Net.WebSockets; -using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode; using WebSocketState = System.Net.WebSockets.WebSocketState; namespace SocketHttpListener -- cgit v1.2.3 From 852460b99155e015ed5f1d7ad2fab0281bfdfbec Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Feb 2019 23:34:32 +0100 Subject: kestrel init --- Emby.Server.Implementations/ApplicationHost.cs | 119 +++- .../Emby.Server.Implementations.csproj | 10 + .../HttpServer/HttpListenerHost.cs | 2 +- .../SocketSharp/HttpFile.cs | 18 + .../SocketSharp/HttpPostedFile.cs | 204 ++++++ .../SocketSharp/RequestMono.cs | 681 +++++++++++++++++++++ .../SocketSharp/SharpWebSocket.cs | 149 +++++ .../SocketSharp/WebSocketSharpListener.cs | 261 ++++++++ .../SocketSharp/WebSocketSharpRequest.cs | 539 ++++++++++++++++ .../SocketSharp/WebSocketSharpResponse.cs | 206 +++++++ Emby.Server.Implementations/Startup.cs | 34 + Jellyfin.Server/Program.cs | 3 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 4 +- .../Services/QueryParamCollection.cs | 17 + 14 files changed, 2242 insertions(+), 5 deletions(-) create mode 100644 Emby.Server.Implementations/SocketSharp/HttpFile.cs create mode 100644 Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs create mode 100644 Emby.Server.Implementations/SocketSharp/RequestMono.cs create mode 100644 Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs create mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs create mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs create mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs create mode 100644 Emby.Server.Implementations/Startup.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 45a819f260..5e8611d531 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -42,6 +42,7 @@ using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; +using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using Emby.Server.Implementations.Xml; @@ -105,10 +106,15 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.Providers.TV.TheTVDB; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using ServiceStack; +using HttpResponse = MediaBrowser.Model.Net.HttpResponse; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; namespace Emby.Server.Implementations @@ -123,7 +129,7 @@ namespace Emby.Server.Implementations ///
/// true if this instance can self restart; otherwise, false. public abstract bool CanSelfRestart { get; } - + public IWebHost Host { get; set; } public virtual bool CanLaunchWebBrowser { get @@ -612,6 +618,115 @@ namespace Emby.Server.Implementations await RegisterResources(serviceCollection); FindParts(); + + Host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot("/Users/clausvium/RiderProjects/jellyfin/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin-web/src") + .UseStartup() +// .ConfigureServices(async services => +// { +// services.AddSingleton(startUp); +// RegisterResources(services); +// FindParts(); +// try +// { +// ImageProcessor.ImageEncoder = +// new NullImageEncoder(); //SkiaEncoder(_loggerFactory, appPaths, fileSystem, localizationManager); +// } +// catch (Exception ex) +// { +// Logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder. {0}"); +// ImageProcessor.ImageEncoder = new NullImageEncoder(); +// } +// await RunStartupTasks().ConfigureAwait(false); +// }) + .UseUrls("http://localhost:8096") + .ConfigureServices(s => s.AddRouting()) + .Configure( app => + { + app.UseWebSockets(new WebSocketOptions { + KeepAliveInterval = TimeSpan.FromMilliseconds(1000000000), + ReceiveBufferSize = 0x10000 + }); + + app.UseRouter(r => + { + // TODO all the verbs, but really MVC... + r.MapGet("/{*localpath}", ExecuteHandler); + r.MapPut("/{*localpath}", ExecuteHandler); + r.MapPost("/{*localpath}", ExecuteHandler); + r.MapDelete("/{*localpath}", ExecuteHandler); + r.MapVerb("HEAD", "/{*localpath}", ExecuteHandler); + }); + }) + .Build(); + } + + public async Task ExecuteHandler(HttpRequest request, Microsoft.AspNetCore.Http.HttpResponse response, RouteData data) + { + var ctx = request.HttpContext; + if (ctx.WebSockets.IsWebSocketRequest) + { + try + { + var endpoint = ctx.Request.Path.ToString(); + var url = ctx.Request.Path.ToString(); + + var queryString = new QueryParamCollection(request.Query); + + var connectingArgs = new WebSocketConnectingEventArgs + { + Url = url, + QueryString = queryString, + Endpoint = endpoint + }; + + if (connectingArgs.AllowConnection) + { + Logger.LogDebug("Web socket connection allowed"); + + var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; + + //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); + //socket.ConnectAsServerAsync().ConfigureAwait(false); + +// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) +// { +// OnReceive = ProcessWebSocketMessageReceived, +// Url = e.Url, +// QueryString = e.QueryString ?? new QueryParamCollection() +// }; +// +// connection.Closed += Connection_Closed; +// +// lock (_webSocketConnections) +// { +// _webSocketConnections.Add(connection); +// } +// +// WebSocketConnected(new WebSocketConnectEventArgs +// { +// Url = url, +// QueryString = queryString, +// WebSocket = socket, +// Endpoint = endpoint +// }); + await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); + } + else + { + Logger.LogWarning("Web socket connection not allowed"); + ctx.Response.StatusCode = 401; + } + } + catch (Exception ex) + { + ctx.Response.StatusCode = 500; + } + } + + var req = new WebSocketSharpRequest(request, response, request.Path, Logger); + await ((HttpListenerHost)HttpServer).RequestHandler(req,request.Path.ToString(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() @@ -1067,7 +1182,7 @@ namespace Emby.Server.Implementations HttpServer.Init(GetExports(false), GetExports()); - StartServer(); + //StartServer(); LibraryManager.AddParts(GetExports(), GetExports(), diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index bbf165d627..acbc60c39e 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -22,6 +22,16 @@ + + + + + + + + + + diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index fb42460f1b..1bd084259d 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// Overridable method that can be used to implement a custom hnandler /// - protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) + public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) { var stopWatch = new Stopwatch(); stopWatch.Start(); diff --git a/Emby.Server.Implementations/SocketSharp/HttpFile.cs b/Emby.Server.Implementations/SocketSharp/HttpFile.cs new file mode 100644 index 0000000000..120ac50d9c --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/HttpFile.cs @@ -0,0 +1,18 @@ +using System.IO; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.SocketSharp +{ + public class HttpFile : IHttpFile + { + public string Name { get; set; } + + public string FileName { get; set; } + + public long ContentLength { get; set; } + + public string ContentType { get; set; } + + public Stream InputStream { get; set; } + } +} diff --git a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs new file mode 100644 index 0000000000..f38ed848ee --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +public sealed class HttpPostedFile : IDisposable +{ + private string _name; + private string _contentType; + private Stream _stream; + private bool _disposed = false; + + internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length) + { + _name = name; + _contentType = content_type; + _stream = new ReadSubStream(base_stream, offset, length); + } + + public string ContentType => _contentType; + + public int ContentLength => (int)_stream.Length; + + public string FileName => _name; + + public Stream InputStream => _stream; + + /// + /// Releases the unmanaged resources and disposes of the managed resources used. + /// + public void Dispose() + { + if (_disposed) + { + return; + } + + _stream.Dispose(); + _stream = null; + + _name = null; + _contentType = null; + + _disposed = true; + } + + private class ReadSubStream : Stream + { + private Stream _stream; + private long _offset; + private long _end; + private long _position; + + public ReadSubStream(Stream s, long offset, long length) + { + _stream = s; + _offset = offset; + _end = offset + length; + _position = offset; + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int dest_offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (dest_offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "< 0"); + } + + int len = buffer.Length; + if (dest_offset > len) + { + throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset)); + } + + // reordered to avoid possible integer overflow + if (dest_offset > len - count) + { + throw new ArgumentException("Reading would overrun buffer", nameof(count)); + } + + if (count > _end - _position) + { + count = (int)(_end - _position); + } + + if (count <= 0) + { + return 0; + } + + _stream.Position = _position; + int result = _stream.Read(buffer, dest_offset, count); + if (result > 0) + { + _position += result; + } + else + { + _position = _end; + } + + return result; + } + + public override int ReadByte() + { + if (_position >= _end) + { + return -1; + } + + _stream.Position = _position; + int result = _stream.ReadByte(); + if (result < 0) + { + _position = _end; + } + else + { + _position++; + } + + return result; + } + + public override long Seek(long d, SeekOrigin origin) + { + long real; + switch (origin) + { + case SeekOrigin.Begin: + real = _offset + d; + break; + case SeekOrigin.End: + real = _end + d; + break; + case SeekOrigin.Current: + real = _position + d; + break; + default: + throw new ArgumentException("Unknown SeekOrigin value", nameof(origin)); + } + + long virt = real - _offset; + if (virt < 0 || virt > Length) + { + throw new ArgumentException("Invalid position", nameof(d)); + } + + _position = _stream.Seek(real, SeekOrigin.Begin); + return _position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => _end - _offset; + + public override long Position + { + get => _position - _offset; + set + { + if (value > Length) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _position = Seek(value, SeekOrigin.Begin); + } + } + } +} diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs new file mode 100644 index 0000000000..a8142aef68 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -0,0 +1,681 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; +using Microsoft.Extensions.Primitives; + +namespace Emby.Server.Implementations.SocketSharp +{ + public partial class WebSocketSharpRequest : IHttpRequest + { + internal static string GetParameter(string header, string attr) + { + int ap = header.IndexOf(attr, StringComparison.Ordinal); + if (ap == -1) + { + return null; + } + + ap += attr.Length; + if (ap >= header.Length) + { + return null; + } + + char ending = header[ap]; + if (ending != '"') + { + ending = ' '; + } + + int end = header.IndexOf(ending, ap + 1); + if (end == -1) + { + return ending == '"' ? null : header.Substring(ap); + } + + return header.Substring(ap + 1, end - ap - 1); + } + + private async Task LoadMultiPart(WebROCollection form) + { + string boundary = GetParameter(ContentType, "; boundary="); + if (boundary == null) + { + return; + } + + using (var requestStream = InputStream) + { + // DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request + // Not ending with \r\n? + var ms = new MemoryStream(32 * 1024); + await requestStream.CopyToAsync(ms).ConfigureAwait(false); + + var input = ms; + ms.WriteByte((byte)'\r'); + ms.WriteByte((byte)'\n'); + + input.Position = 0; + + // Uncomment to debug + // var content = new StreamReader(ms).ReadToEnd(); + // Console.WriteLine(boundary + "::" + content); + // input.Position = 0; + + var multi_part = new HttpMultipart(input, boundary, ContentEncoding); + + HttpMultipart.Element e; + while ((e = multi_part.ReadNextElement()) != null) + { + if (e.Filename == null) + { + byte[] copy = new byte[e.Length]; + + input.Position = e.Start; + input.Read(copy, 0, (int)e.Length); + + form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length)); + } + else + { + // We use a substream, as in 2.x we will support large uploads streamed to disk, + var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length); + files[e.Name] = sub; + } + } + } + } + + public async Task GetFormData() + { + var form = new WebROCollection(); + files = new Dictionary(); + + if (IsContentType("multipart/form-data", true)) + { + await LoadMultiPart(form).ConfigureAwait(false); + } + else if (IsContentType("application/x-www-form-urlencoded", true)) + { + await LoadWwwForm(form).ConfigureAwait(false); + } + +#if NET_4_0 + if (validateRequestNewMode && !checked_form) { + // Setting this before calling the validator prevents + // possible endless recursion + checked_form = true; + ValidateNameValueCollection("Form", query_string_nvc, RequestValidationSource.Form); + } else +#endif + if (validate_form && !checked_form) + { + checked_form = true; + ValidateNameValueCollection("Form", form); + } + + return form; + } + + public string Accept => StringValues.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"].ToString(); + + public string Authorization => StringValues.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"].ToString(); + + protected bool validate_cookies { get; set; } + protected bool validate_query_string { get; set; } + protected bool validate_form { get; set; } + protected bool checked_cookies { get; set; } + protected bool checked_query_string { get; set; } + protected bool checked_form { get; set; } + + private static void ThrowValidationException(string name, string key, string value) + { + string v = "\"" + value + "\""; + if (v.Length > 20) + { + v = v.Substring(0, 16) + "...\""; + } + + string msg = string.Format( + CultureInfo.InvariantCulture, + "A potentially dangerous Request.{0} value was detected from the client ({1}={2}).", + name, + key, + v); + + throw new Exception(msg); + } + + private static void ValidateNameValueCollection(string name, QueryParamCollection coll) + { + if (coll == null) + { + return; + } + + foreach (var pair in coll) + { + var key = pair.Name; + var val = pair.Value; + if (val != null && val.Length > 0 && IsInvalidString(val)) + { + ThrowValidationException(name, key, val); + } + } + } + + internal static bool IsInvalidString(string val) + => IsInvalidString(val, out var validationFailureIndex); + + internal static bool IsInvalidString(string val, out int validationFailureIndex) + { + validationFailureIndex = 0; + + int len = val.Length; + if (len < 2) + { + return false; + } + + char current = val[0]; + for (int idx = 1; idx < len; idx++) + { + char next = val[idx]; + + // See http://secunia.com/advisories/14325 + if (current == '<' || current == '\xff1c') + { + if (next == '!' || next < ' ' + || (next >= 'a' && next <= 'z') + || (next >= 'A' && next <= 'Z')) + { + validationFailureIndex = idx - 1; + return true; + } + } + else if (current == '&' && next == '#') + { + validationFailureIndex = idx - 1; + return true; + } + + current = next; + } + + return false; + } + + public void ValidateInput() + { + validate_cookies = true; + validate_query_string = true; + validate_form = true; + } + + private bool IsContentType(string ct, bool starts_with) + { + if (ct == null || ContentType == null) + { + return false; + } + + if (starts_with) + { + return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase); + } + + return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase); + } + + private async Task LoadWwwForm(WebROCollection form) + { + using (var input = InputStream) + { + using (var ms = new MemoryStream()) + { + await input.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + + using (var s = new StreamReader(ms, ContentEncoding)) + { + var key = new StringBuilder(); + var value = new StringBuilder(); + int c; + + while ((c = s.Read()) != -1) + { + if (c == '=') + { + value.Length = 0; + while ((c = s.Read()) != -1) + { + if (c == '&') + { + AddRawKeyValue(form, key, value); + break; + } + else + { + value.Append((char)c); + } + } + + if (c == -1) + { + AddRawKeyValue(form, key, value); + return; + } + } + else if (c == '&') + { + AddRawKeyValue(form, key, value); + } + else + { + key.Append((char)c); + } + } + + if (c == -1) + { + AddRawKeyValue(form, key, value); + } + } + } + } + } + + private static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value) + { + form.Add(WebUtility.UrlDecode(key.ToString()), WebUtility.UrlDecode(value.ToString())); + + key.Length = 0; + value.Length = 0; + } + + private Dictionary files; + + private class WebROCollection : QueryParamCollection + { + public override string ToString() + { + var result = new StringBuilder(); + foreach (var pair in this) + { + if (result.Length > 0) + { + result.Append('&'); + } + + var key = pair.Name; + if (key != null && key.Length > 0) + { + result.Append(key); + result.Append('='); + } + + result.Append(pair.Value); + } + + return result.ToString(); + } + } + private class HttpMultipart + { + + public class Element + { + public string ContentType { get; set; } + + public string Name { get; set; } + + public string Filename { get; set; } + + public Encoding Encoding { get; set; } + + public long Start { get; set; } + + public long Length { get; set; } + + public override string ToString() + { + return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " + + Start.ToString(CultureInfo.CurrentCulture) + ", Length " + Length.ToString(CultureInfo.CurrentCulture); + } + } + + private const byte LF = (byte)'\n'; + + private const byte CR = (byte)'\r'; + + private Stream data; + + private string boundary; + + private byte[] boundaryBytes; + + private byte[] buffer; + + private bool atEof; + + private Encoding encoding; + + private StringBuilder sb; + + // See RFC 2046 + // In the case of multipart entities, in which one or more different + // sets of data are combined in a single body, a "multipart" media type + // field must appear in the entity's header. The body must then contain + // one or more body parts, each preceded by a boundary delimiter line, + // and the last one followed by a closing boundary delimiter line. + // After its boundary delimiter line, each body part then consists of a + // header area, a blank line, and a body area. Thus a body part is + // similar to an RFC 822 message in syntax, but different in meaning. + + public HttpMultipart(Stream data, string b, Encoding encoding) + { + this.data = data; + boundary = b; + boundaryBytes = encoding.GetBytes(b); + buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--' + this.encoding = encoding; + sb = new StringBuilder(); + } + + public Element ReadNextElement() + { + if (atEof || ReadBoundary()) + { + return null; + } + + var elem = new Element(); + string header; + while ((header = ReadHeaders()) != null) + { + if (header.StartsWith("Content-Disposition:", StringComparison.OrdinalIgnoreCase)) + { + elem.Name = GetContentDispositionAttribute(header, "name"); + elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); + } + else if (header.StartsWith("Content-Type:", StringComparison.OrdinalIgnoreCase)) + { + elem.ContentType = header.Substring("Content-Type:".Length).Trim(); + elem.Encoding = GetEncoding(elem.ContentType); + } + } + + long start = data.Position; + elem.Start = start; + long pos = MoveToNextBoundary(); + if (pos == -1) + { + return null; + } + + elem.Length = pos - start; + return elem; + } + + private string ReadLine() + { + // CRLF or LF are ok as line endings. + bool got_cr = false; + int b = 0; + sb.Length = 0; + while (true) + { + b = data.ReadByte(); + if (b == -1) + { + return null; + } + + if (b == LF) + { + break; + } + + got_cr = b == CR; + sb.Append((char)b); + } + + if (got_cr) + { + sb.Length--; + } + + return sb.ToString(); + } + + private static string GetContentDispositionAttribute(string l, string name) + { + int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); + if (idx < 0) + { + return null; + } + + int begin = idx + name.Length + "=\"".Length; + int end = l.IndexOf('"', begin); + if (end < 0) + { + return null; + } + + if (begin == end) + { + return string.Empty; + } + + return l.Substring(begin, end - begin); + } + + private string GetContentDispositionAttributeWithEncoding(string l, string name) + { + int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); + if (idx < 0) + { + return null; + } + + int begin = idx + name.Length + "=\"".Length; + int end = l.IndexOf('"', begin); + if (end < 0) + { + return null; + } + + if (begin == end) + { + return string.Empty; + } + + string temp = l.Substring(begin, end - begin); + byte[] source = new byte[temp.Length]; + for (int i = temp.Length - 1; i >= 0; i--) + { + source[i] = (byte)temp[i]; + } + + return encoding.GetString(source, 0, source.Length); + } + + private bool ReadBoundary() + { + try + { + string line; + do + { + line = ReadLine(); + } + while (line.Length == 0); + + if (line[0] != '-' || line[1] != '-') + { + return false; + } + + if (!line.EndsWith(boundary, StringComparison.Ordinal)) + { + return true; + } + } + catch + { + + } + + return false; + } + + private string ReadHeaders() + { + string s = ReadLine(); + if (s.Length == 0) + { + return null; + } + + return s; + } + + private static bool CompareBytes(byte[] orig, byte[] other) + { + for (int i = orig.Length - 1; i >= 0; i--) + { + if (orig[i] != other[i]) + { + return false; + } + } + + return true; + } + + private long MoveToNextBoundary() + { + long retval = 0; + bool got_cr = false; + + int state = 0; + int c = data.ReadByte(); + while (true) + { + if (c == -1) + { + return -1; + } + + if (state == 0 && c == LF) + { + retval = data.Position - 1; + if (got_cr) + { + retval--; + } + + state = 1; + c = data.ReadByte(); + } + else if (state == 0) + { + got_cr = c == CR; + c = data.ReadByte(); + } + else if (state == 1 && c == '-') + { + c = data.ReadByte(); + if (c == -1) + { + return -1; + } + + if (c != '-') + { + state = 0; + got_cr = false; + continue; // no ReadByte() here + } + + int nread = data.Read(buffer, 0, buffer.Length); + int bl = buffer.Length; + if (nread != bl) + { + return -1; + } + + if (!CompareBytes(boundaryBytes, buffer)) + { + state = 0; + data.Position = retval + 2; + if (got_cr) + { + data.Position++; + got_cr = false; + } + + c = data.ReadByte(); + continue; + } + + if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-') + { + atEof = true; + } + else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF) + { + state = 0; + data.Position = retval + 2; + if (got_cr) + { + data.Position++; + got_cr = false; + } + + c = data.ReadByte(); + continue; + } + + data.Position = retval + 2; + if (got_cr) + { + data.Position++; + } + + break; + } + else + { + // state == 1 + state = 0; // no ReadByte() here + } + } + + return retval; + } + + private static string StripPath(string path) + { + if (path == null || path.Length == 0) + { + return path; + } + + if (path.IndexOf(":\\", StringComparison.Ordinal) != 1 + && !path.StartsWith("\\\\", StringComparison.Ordinal)) + { + return path; + } + + return path.Substring(path.LastIndexOf('\\') + 1); + } + } + } +} diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs new file mode 100644 index 0000000000..80ef451dc7 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -0,0 +1,149 @@ +using System; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Net; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.SocketSharp +{ + public class SharpWebSocket : IWebSocket + { + /// + /// The logger + /// + private readonly ILogger _logger; + + public event EventHandler Closed; + + /// + /// Gets or sets the web socket. + /// + /// The web socket. + private SocketHttpListener.WebSocket WebSocket { get; set; } + + private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private bool _disposed = false; + + public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) + { + if (socket == null) + { + throw new ArgumentNullException(nameof(socket)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + _logger = logger; + WebSocket = socket; + + socket.OnMessage += OnSocketMessage; + socket.OnClose += OnSocketClose; + socket.OnError += OnSocketError; + } + + public Task ConnectAsServerAsync() + => WebSocket.ConnectAsServer(); + + public Task StartReceive() + { + return _taskCompletionSource.Task; + } + + private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e) + { + _logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty); + + // Closed?.Invoke(this, EventArgs.Empty); + } + + private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e) + { + _taskCompletionSource.TrySetResult(true); + + Closed?.Invoke(this, EventArgs.Empty); + } + + private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e) + { + if (OnReceiveBytes != null) + { + OnReceiveBytes(e.RawData); + } + } + + /// + /// Gets or sets the state. + /// + /// The state. + public WebSocketState State => WebSocket.ReadyState; + + /// + /// Sends the async. + /// + /// The bytes. + /// if set to true [end of message]. + /// The cancellation token. + /// Task. + public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) + { + return WebSocket.SendAsync(bytes); + } + + /// + /// Sends the asynchronous. + /// + /// The text. + /// if set to true [end of message]. + /// The cancellation token. + /// Task. + public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) + { + return WebSocket.SendAsync(text); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (_disposed) + { + return; + } + + if (dispose) + { + WebSocket.OnMessage -= OnSocketMessage; + WebSocket.OnClose -= OnSocketClose; + WebSocket.OnError -= OnSocketError; + + _cancellationTokenSource.Cancel(); + + WebSocket.CloseAsync().GetAwaiter().GetResult(); + } + + _disposed = true; + } + + /// + /// Gets or sets the receive action. + /// + /// The receive action. + public Action OnReceiveBytes { get; set; } + } +} diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs new file mode 100644 index 0000000000..ab7ddeca20 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; + using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.HttpServer; +using Emby.Server.Implementations.Net; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + + namespace Emby.Server.Implementations.SocketSharp +{ + public class WebSocketSharpListener : IHttpListener + { + private HttpListener _listener; + + private readonly ILogger _logger; + + private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); + private CancellationToken _disposeCancellationToken; + + public WebSocketSharpListener( + ILogger logger) + { + _logger = logger; + + _disposeCancellationToken = _disposeCancellationTokenSource.Token; + } + + public Func ErrorHandler { get; set; } + public Func RequestHandler { get; set; } + + public Action WebSocketConnecting { get; set; } + + public Action WebSocketConnected { get; set; } + +// public void Start(IEnumerable urlPrefixes) +// { +// // TODO +// //if (_listener == null) +// //{ +// // _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); +// //} +// +// //_listener.EnableDualMode = _enableDualMode; +// +// //if (_certificate != null) +// //{ +// // _listener.LoadCert(_certificate); +// //} +// +// //_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); +// //_listener.Prefixes.AddRange(urlPrefixes); +// +// //_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); +// +// //_listener.Start(); +// +// if (_listener == null) +// { +// _listener = new HttpListener(); +// } +// +// _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); +// +// //foreach (var urlPrefix in urlPrefixes) +// //{ +// // _listener.Prefixes.Add(urlPrefix); +// //} +// _listener.Prefixes.Add("http://localhost:8096/"); +// +// _listener.Start(); +// +// // TODO how to do this in netcore? +// _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), +// null); +// } + + private static void LogRequest(ILogger logger, HttpListenerRequest request) + { + var url = request.Url.ToString(); + + logger.LogInformation( + "{0} {1}. UserAgent: {2}", + request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, + url, + request.UserAgent ?? string.Empty); + } +// +// private Task InitTask(IAsyncResult asyncResult, CancellationToken cancellationToken) +// { +// var context = _listener.EndGetContext(asyncResult); +// _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), null); +// IHttpRequest httpReq = null; +// var request = context.Request; +// +// try +// { +// if (request.IsWebSocketRequest) +// { +// LogRequest(_logger, request); +// +// return ProcessWebSocketRequest(context); +// } +// +// httpReq = GetRequest(context); +// } +// catch (Exception ex) +// { +// _logger.LogError(ex, "Error processing request"); +// +// httpReq = httpReq ?? GetRequest(context); +// return ErrorHandler(ex, httpReq, true, true); +// } +// +// var uri = request.Url; +// +// return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); +// } + + private async Task ProcessWebSocketRequest(HttpListenerContext ctx) + { + try + { + var endpoint = ctx.Request.RemoteEndPoint.ToString(); + var url = ctx.Request.RawUrl; + + var queryString = new QueryParamCollection(ctx.Request.QueryString); + + var connectingArgs = new WebSocketConnectingEventArgs + { + Url = url, + QueryString = queryString, + Endpoint = endpoint + }; + + WebSocketConnecting?.Invoke(connectingArgs); + + if (connectingArgs.AllowConnection) + { + _logger.LogDebug("Web socket connection allowed"); + + var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); + + if (WebSocketConnected != null) + { + SharpWebSocket socket = null; //new SharpWebSocket(webSocketContext.WebSocket, _logger); + await socket.ConnectAsServerAsync().ConfigureAwait(false); + + WebSocketConnected(new WebSocketConnectEventArgs + { + Url = url, + QueryString = queryString, + WebSocket = socket, + Endpoint = endpoint + }); + + await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + } + } + else + { + _logger.LogWarning("Web socket connection not allowed"); + ctx.Response.StatusCode = 401; + ctx.Response.Close(); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "AcceptWebSocketAsync error"); + ctx.Response.StatusCode = 500; + ctx.Response.Close(); + } + } + + private async Task ReceiveWebSocketAsync(HttpListenerContext ctx, SharpWebSocket socket) + { + try + { + await socket.StartReceive().ConfigureAwait(false); + } + finally + { + TryClose(ctx, 200); + } + } + + private void TryClose(HttpListenerContext ctx, int statusCode) + { + try + { + ctx.Response.StatusCode = statusCode; + ctx.Response.Close(); + } + catch (ObjectDisposedException) + { + // TODO: Investigate and properly fix. + } + catch (Exception ex) + { + _logger.LogError(ex, "Error closing web socket response"); + } + } + + private IHttpRequest GetRequest(HttpRequest httpContext) + { + var urlSegments = httpContext.Path; + + var operationName = urlSegments; + + var req = new WebSocketSharpRequest(httpContext, httpContext.HttpContext.Response, operationName, _logger); + + return req; + } + + public void Start(IEnumerable urlPrefixes) + { + throw new NotImplementedException(); + } + + public Task Stop() + { + _disposeCancellationTokenSource.Cancel(); + _listener?.Close(); + + return Task.CompletedTask; + } + + /// + /// Releases the unmanaged resources and disposes of the managed resources used. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool _disposed; + + /// + /// Releases the unmanaged resources and disposes of the managed resources used. + /// + /// Whether or not the managed resources should be disposed + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + Stop().GetAwaiter().GetResult(); + } + + _disposed = true; + } + } +} diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs new file mode 100644 index 0000000000..facc544461 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -0,0 +1,539 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using Emby.Server.Implementations.HttpServer; +using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using SocketHttpListener.Net; +using IHttpFile = MediaBrowser.Model.Services.IHttpFile; +using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; +using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; +using IResponse = MediaBrowser.Model.Services.IResponse; + +namespace Emby.Server.Implementations.SocketSharp +{ + public partial class WebSocketSharpRequest : IHttpRequest + { + private readonly HttpRequest request; + private readonly IHttpResponse response; + + public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger) + { + this.OperationName = operationName; + this.request = httpContext; + this.response = new WebSocketSharpResponse(logger, response, this); + + // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); + } + + public HttpRequest HttpRequest => request; + + public object OriginalRequest => request; + + public IResponse Response => response; + + public IHttpResponse HttpResponse => response; + + public string OperationName { get; set; } + + public object Dto { get; set; } + + public string RawUrl => request.Path.ToUriComponent(); + + public string AbsoluteUri => request.Path.ToUriComponent().TrimEnd('/'); + + public string UserHostAddress => ""; + + public string XForwardedFor + => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString(); + + public int? XForwardedPort + => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture); + + public string XForwardedProtocol => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"].ToString(); + + public string XRealIp => StringValues.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"].ToString(); + + private string remoteIp; + + public string RemoteIp => + remoteIp ?? + (remoteIp = CheckBadChars(XForwardedFor) ?? + NormalizeIp(CheckBadChars(XRealIp) ?? + (string.IsNullOrEmpty(request.Host.Host) ? null : NormalizeIp(request.Host.Host)))); + + private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; + + // CheckBadChars - throws on invalid chars to be not found in header name/value + internal static string CheckBadChars(string name) + { + if (name == null || name.Length == 0) + { + return name; + } + + // VALUE check + // Trim spaces from both ends + name = name.Trim(HttpTrimCharacters); + + // First, check for correctly formed multi-line value + // Second, check for absence of CTL characters + int crlf = 0; + for (int i = 0; i < name.Length; ++i) + { + char c = (char)(0x000000ff & (uint)name[i]); + switch (crlf) + { + case 0: + { + if (c == '\r') + { + crlf = 1; + } + else if (c == '\n') + { + // Technically this is bad HTTP. But it would be a breaking change to throw here. + // Is there an exploit? + crlf = 2; + } + else if (c == 127 || (c < ' ' && c != '\t')) + { + throw new ArgumentException("net_WebHeaderInvalidControlChars"); + } + + break; + } + + case 1: + { + if (c == '\n') + { + crlf = 2; + break; + } + + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } + + case 2: + { + if (c == ' ' || c == '\t') + { + crlf = 0; + break; + } + + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } + } + } + + if (crlf != 0) + { + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } + + return name; + } + + internal static bool ContainsNonAsciiChars(string token) + { + for (int i = 0; i < token.Length; ++i) + { + if ((token[i] < 0x20) || (token[i] > 0x7e)) + { + return true; + } + } + + return false; + } + + private string NormalizeIp(string ip) + { + if (!string.IsNullOrWhiteSpace(ip)) + { + // Handle ipv4 mapped to ipv6 + const string srch = "::ffff:"; + var index = ip.IndexOf(srch, StringComparison.OrdinalIgnoreCase); + if (index == 0) + { + ip = ip.Substring(srch.Length); + } + } + + return ip; + } + + public bool IsSecureConnection => request.IsHttps || XForwardedProtocol == "https"; + + public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); + + private Dictionary items; + public Dictionary Items => items ?? (items = new Dictionary()); + + private string responseContentType; + public string ResponseContentType + { + get => + responseContentType + ?? (responseContentType = GetResponseContentType(HttpRequest)); + set => this.responseContentType = value; + } + + public const string FormUrlEncoded = "application/x-www-form-urlencoded"; + public const string MultiPartFormData = "multipart/form-data"; + public static string GetResponseContentType(HttpRequest httpReq) + { + var specifiedContentType = GetQueryStringContentType(httpReq); + if (!string.IsNullOrEmpty(specifiedContentType)) + { + return specifiedContentType; + } + + const string serverDefaultContentType = "application/json"; + + var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept); // TODO; + string defaultContentType = null; + if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData)) + { + defaultContentType = serverDefaultContentType; + } + + var acceptsAnything = false; + var hasDefaultContentType = defaultContentType != null; + if (acceptContentTypes != null) + { + foreach (var acceptsType in acceptContentTypes) + { + // TODO: @bond move to Span when Span.Split lands + // https://github.com/dotnet/corefx/issues/26528 + var contentType = acceptsType?.Split(';')[0].Trim(); + acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase); + + if (acceptsAnything) + { + break; + } + } + + if (acceptsAnything) + { + if (hasDefaultContentType) + { + return defaultContentType; + } + else + { + return serverDefaultContentType; + } + } + } + + if (acceptContentTypes == null && httpReq.ContentType == Soap11) + { + return Soap11; + } + + // We could also send a '406 Not Acceptable', but this is allowed also + return serverDefaultContentType; + } + + public const string Soap11 = "text/xml; charset=utf-8"; + + public static bool HasAnyOfContentTypes(HttpRequest request, params string[] contentTypes) + { + if (contentTypes == null || request.ContentType == null) + { + return false; + } + + foreach (var contentType in contentTypes) + { + if (IsContentType(request, contentType)) + { + return true; + } + } + + return false; + } + + public static bool IsContentType(HttpRequest request, string contentType) + { + return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase); + } + + private static string GetQueryStringContentType(HttpRequest httpReq) + { + string format = httpReq.Query["format"]; + if (format == null) + { + const int formatMaxLength = 4; + string pi = httpReq.Path.ToString(); + if (pi == null || pi.Length <= formatMaxLength) + { + return null; + } + + if (pi[0] == '/') + { + pi = pi.Substring(1); + } + + format = LeftPart(pi, '/'); + if (format.Length > formatMaxLength) + { + return null; + } + } + + format = LeftPart(format, '.'); + if (format.ToLower().Contains("json")) + { + return "application/json"; + } + else if (format.ToLower().Contains("xml")) + { + return "application/xml"; + } + + return null; + } + + public static string LeftPart(string strVal, char needle) + { + if (strVal == null) + { + return null; + } + + var pos = strVal.IndexOf(needle.ToString(), StringComparison.Ordinal); + return pos == -1 ? strVal : strVal.Substring(0, pos); + } + + public static ReadOnlySpan LeftPart(ReadOnlySpan strVal, char needle) + { + if (strVal == null) + { + return null; + } + + var pos = strVal.IndexOf(needle.ToString()); + return pos == -1 ? strVal : strVal.Slice(0, pos); + } + + public static string HandlerFactoryPath; + + private string pathInfo; + public string PathInfo + { + get + { + if (this.pathInfo == null) + { + var mode = HandlerFactoryPath; + + var pos = request.Path.ToString().IndexOf("?", StringComparison.Ordinal); + if (pos != -1) + { + var path = request.Path.ToString().Substring(0, pos); + this.pathInfo = GetPathInfo( + path, + mode, + mode ?? string.Empty); + } + else + { + this.pathInfo = request.Path.ToString(); + } + + this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); + this.pathInfo = NormalizePathInfo(pathInfo, mode); + } + + return this.pathInfo; + } + } + + private static string GetPathInfo(string fullPath, string mode, string appPath) + { + var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode); + if (!string.IsNullOrEmpty(pathInfo)) + { + return pathInfo; + } + + // Wildcard mode relies on this to work out the handlerPath + pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath); + if (!string.IsNullOrEmpty(pathInfo)) + { + return pathInfo; + } + + return fullPath; + } + + private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot) + { + if (mappedPathRoot == null) + { + return null; + } + + var sbPathInfo = new StringBuilder(); + var fullPathParts = fullPath.Split('/'); + var mappedPathRootParts = mappedPathRoot.Split('/'); + var fullPathIndexOffset = mappedPathRootParts.Length - 1; + var pathRootFound = false; + + for (var fullPathIndex = 0; fullPathIndex < fullPathParts.Length; fullPathIndex++) + { + if (pathRootFound) + { + sbPathInfo.Append("/" + fullPathParts[fullPathIndex]); + } + else if (fullPathIndex - fullPathIndexOffset >= 0) + { + pathRootFound = true; + for (var mappedPathRootIndex = 0; mappedPathRootIndex < mappedPathRootParts.Length; mappedPathRootIndex++) + { + if (!string.Equals(fullPathParts[fullPathIndex - fullPathIndexOffset + mappedPathRootIndex], mappedPathRootParts[mappedPathRootIndex], StringComparison.OrdinalIgnoreCase)) + { + pathRootFound = false; + break; + } + } + } + } + + if (!pathRootFound) + { + return null; + } + + var path = sbPathInfo.ToString(); + return path.Length > 1 ? path.TrimEnd('/') : "/"; + } + + private Dictionary cookies; + public IDictionary Cookies + { + get + { + if (cookies == null) + { + cookies = new Dictionary(); + foreach (var cookie in this.request.Cookies) + { + var httpCookie = cookie; + cookies[httpCookie.Key] = new Cookie(httpCookie.Key, httpCookie.Value, "", ""); + } + } + + return cookies; + } + } + + public string UserAgent => request.Headers[HeaderNames.UserAgent]; + + public QueryParamCollection Headers => new QueryParamCollection(request.Headers); + + private QueryParamCollection queryString; + public QueryParamCollection QueryString => queryString ?? (queryString = new QueryParamCollection(request.Query)); + + public bool IsLocal => true; // TODO + + private string httpMethod; + public string HttpMethod => + httpMethod + ?? (httpMethod = request.Method); + + public string Verb => HttpMethod; + + public string ContentType => request.ContentType; + + private Encoding contentEncoding; + public Encoding ContentEncoding + { + get => contentEncoding ?? Encoding.GetEncoding(request.Headers[HeaderNames.ContentEncoding].ToString()); + set => contentEncoding = value; + } + + public Uri UrlReferrer => request.GetTypedHeaders().Referer; + + public static Encoding GetEncoding(string contentTypeHeader) + { + var param = GetParameter(contentTypeHeader, "charset="); + if (param == null) + { + return null; + } + + try + { + return Encoding.GetEncoding(param); + } + catch (ArgumentException) + { + return null; + } + } + + public Stream InputStream => request.Body; + + public long ContentLength => request.ContentLength ?? 0; + + private IHttpFile[] httpFiles; + public IHttpFile[] Files + { + get + { + if (httpFiles == null) + { + if (files == null) + { + return httpFiles = Array.Empty(); + } + + httpFiles = new IHttpFile[files.Count]; + var i = 0; + foreach (var pair in files) + { + var reqFile = pair.Value; + httpFiles[i] = new HttpFile + { + ContentType = reqFile.ContentType, + ContentLength = reqFile.ContentLength, + FileName = reqFile.FileName, + InputStream = reqFile.InputStream, + }; + i++; + } + } + + return httpFiles; + } + } + + public static string NormalizePathInfo(string pathInfo, string handlerPath) + { + if (handlerPath != null) + { + var trimmed = pathInfo.TrimStart('/'); + if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase)) + { + return trimmed.Substring(handlerPath.Length); + } + } + + return pathInfo; + } + } +} diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs new file mode 100644 index 0000000000..c68b959846 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; +using IRequest = MediaBrowser.Model.Services.IRequest; + +namespace Emby.Server.Implementations.SocketSharp +{ + public class WebSocketSharpResponse : IHttpResponse + { + private readonly ILogger _logger; + + private readonly HttpResponse _response; + + public WebSocketSharpResponse(ILogger logger, HttpResponse response, IRequest request) + { + _logger = logger; + this._response = response; + Items = new Dictionary(); + Request = request; + } + + public IRequest Request { get; private set; } + + public Dictionary Items { get; private set; } + + public object OriginalResponse => _response; + + public int StatusCode + { + get => this._response.StatusCode; + set => this._response.StatusCode = value; + } + + public string StatusDescription { get; set; } + + public string ContentType + { + get => _response.ContentType; + set => _response.ContentType = value; + } + + public QueryParamCollection Headers => new QueryParamCollection(_response.Headers); + + private static string AsHeaderValue(Cookie cookie) + { + DateTime defaultExpires = DateTime.MinValue; + + var path = cookie.Expires == defaultExpires + ? "/" + : cookie.Path ?? "/"; + + var sb = new StringBuilder(); + + sb.Append($"{cookie.Name}={cookie.Value};path={path}"); + + if (cookie.Expires != defaultExpires) + { + sb.Append($";expires={cookie.Expires:R}"); + } + + if (!string.IsNullOrEmpty(cookie.Domain)) + { + sb.Append($";domain={cookie.Domain}"); + } + + if (cookie.Secure) + { + sb.Append(";Secure"); + } + + if (cookie.HttpOnly) + { + sb.Append(";HttpOnly"); + } + + return sb.ToString(); + } + + public void AddHeader(string name, string value) + { + if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) + { + ContentType = value; + return; + } + + _response.Headers.Add(name, value); + } + + public string GetHeader(string name) + { + return _response.Headers[name]; + } + + public void Redirect(string url) + { + _response.Redirect(url); + } + + public Stream OutputStream => _response.Body; + + public void Close() + { + if (!this.IsClosed) + { + this.IsClosed = true; + + try + { + var response = this._response; + + var outputStream = response.Body; + + // This is needed with compression + outputStream.Flush(); + outputStream.Dispose(); + } + catch (SocketException) + { + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in HttpListenerResponseWrapper"); + } + } + } + + public bool IsClosed + { + get; + private set; + } + + public void SetContentLength(long contentLength) + { + // you can happily set the Content-Length header in Asp.Net + // but HttpListener will complain if you do - you have to set ContentLength64 on the response. + // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header + //_response.ContentLength64 = contentLength; + } + + public void SetCookie(Cookie cookie) + { + var cookieStr = AsHeaderValue(cookie); + _response.Headers.Add("Set-Cookie", cookieStr); + } + + public bool SendChunked { get; set; } + + public bool KeepAlive { get; set; } + + public void ClearCookies() + { + } + const int StreamCopyToBufferSize = 81920; + public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) + { + // TODO + // return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken); + var allowAsync = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + //if (count <= 0) + //{ + // allowAsync = true; + //} + + var fileOpenOptions = FileOpenOptions.SequentialScan; + + if (allowAsync) + { + fileOpenOptions |= FileOpenOptions.Asynchronous; + } + + // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + + using (var fs = fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions)) + { + if (offset > 0) + { + fs.Position = offset; + } + + if (count > 0) + { + await streamHelper.CopyToAsync(fs, OutputStream, count, cancellationToken).ConfigureAwait(false); + } + else + { + await fs.CopyToAsync(OutputStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false); + } + } + } + } +} diff --git a/Emby.Server.Implementations/Startup.cs b/Emby.Server.Implementations/Startup.cs new file mode 100644 index 0000000000..164c7eeaa7 --- /dev/null +++ b/Emby.Server.Implementations/Startup.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using MediaBrowser.Api; +using MediaBrowser.Controller; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations +{ + public class Startup + { + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) => Configuration = configuration; + + // Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddRouting(); + } + + // Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app) + { + + } + } + +} diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 41ee73a565..bfc50468f7 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -20,6 +20,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -143,7 +144,7 @@ namespace Jellyfin.Server appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); await appHost.RunStartupTasks().ConfigureAwait(false); - + appHost.Host.Run(); // TODO: read input for a stop command try diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index f17fd7159d..2f1ea13d9b 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors @@ -13,6 +13,8 @@ + + diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 0e0ebf848b..09806ed9c2 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using System.Linq; using System.Net; using MediaBrowser.Model.Dto; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services { @@ -23,6 +24,14 @@ namespace MediaBrowser.Model.Services } } + public QueryParamCollection(Microsoft.AspNetCore.Http.IHeaderDictionary headers) + { + foreach (var pair in headers) + { + Add(pair.Key, pair.Value); + } + } + // TODO remove this shit public QueryParamCollection(WebHeaderCollection webHeaderCollection) { @@ -47,6 +56,14 @@ namespace MediaBrowser.Model.Services } } + public QueryParamCollection(IQueryCollection queryCollection) + { + foreach (var pair in queryCollection) + { + Add(pair.Key, pair.Value); + } + } + private static StringComparison GetStringComparison() { return StringComparison.OrdinalIgnoreCase; -- cgit v1.2.3 From c3fa299acc1612966ee0d778c3e86f71c21a252a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 07:22:49 +0100 Subject: Remove hardcoded path and fix url bug in Windows --- Emby.Server.Implementations/ApplicationHost.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 5e8611d531..56089320ef 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -109,6 +109,7 @@ using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -621,8 +622,8 @@ namespace Emby.Server.Implementations Host = new WebHostBuilder() .UseKestrel() - .UseContentRoot("/Users/clausvium/RiderProjects/jellyfin/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin-web/src") - .UseStartup() + .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), @"jellyfin-web\src")) + //.UseStartup() // .ConfigureServices(async services => // { // services.AddSingleton(startUp); @@ -726,7 +727,7 @@ namespace Emby.Server.Implementations } var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await ((HttpListenerHost)HttpServer).RequestHandler(req,request.Path.ToString(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); + await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() -- cgit v1.2.3 From f3e7bc0573e69df236ac8c565a354520dd094775 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 08:09:42 +0100 Subject: Replace some todos with http extensions and prepare some socket work --- Emby.Server.Implementations/ApplicationHost.cs | 122 +++++++++++---------- .../HttpServer/HttpListenerHost.cs | 7 ++ .../SocketSharp/WebSocketSharpListener.cs | 46 ++++---- .../SocketSharp/WebSocketSharpRequest.cs | 13 ++- 4 files changed, 102 insertions(+), 86 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 56089320ef..18e752e53b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -110,10 +110,12 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using ServiceStack; using HttpResponse = MediaBrowser.Model.Net.HttpResponse; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; @@ -642,7 +644,12 @@ namespace Emby.Server.Implementations // await RunStartupTasks().ConfigureAwait(false); // }) .UseUrls("http://localhost:8096") - .ConfigureServices(s => s.AddRouting()) + .ConfigureServices(services => + { + services.AddRouting(); + services.AddHttpContextAccessor(); + services.TryAddSingleton(); + }) .Configure( app => { app.UseWebSockets(new WebSocketOptions { @@ -668,62 +675,63 @@ namespace Emby.Server.Implementations var ctx = request.HttpContext; if (ctx.WebSockets.IsWebSocketRequest) { - try - { - var endpoint = ctx.Request.Path.ToString(); - var url = ctx.Request.Path.ToString(); - - var queryString = new QueryParamCollection(request.Query); - - var connectingArgs = new WebSocketConnectingEventArgs - { - Url = url, - QueryString = queryString, - Endpoint = endpoint - }; - - if (connectingArgs.AllowConnection) - { - Logger.LogDebug("Web socket connection allowed"); - - var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; - - //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); - //socket.ConnectAsServerAsync().ConfigureAwait(false); - -// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) -// { -// OnReceive = ProcessWebSocketMessageReceived, -// Url = e.Url, -// QueryString = e.QueryString ?? new QueryParamCollection() -// }; -// -// connection.Closed += Connection_Closed; -// -// lock (_webSocketConnections) -// { -// _webSocketConnections.Add(connection); -// } -// -// WebSocketConnected(new WebSocketConnectEventArgs -// { -// Url = url, -// QueryString = queryString, -// WebSocket = socket, -// Endpoint = endpoint -// }); - await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); - } - else - { - Logger.LogWarning("Web socket connection not allowed"); - ctx.Response.StatusCode = 401; - } - } - catch (Exception ex) - { - ctx.Response.StatusCode = 500; - } + await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(ctx).ConfigureAwait(false); +// try +// { +// var endpoint = ctx.Request.Path.ToString(); +// var url = ctx.Request.Path.ToString(); + + // var queryString = new QueryParamCollection(request.Query); + + // var connectingArgs = new WebSocketConnectingEventArgs + // { + // Url = url, + // QueryString = queryString, + // Endpoint = endpoint + // }; + + // if (connectingArgs.AllowConnection) + // { + // Logger.LogDebug("Web socket connection allowed"); + + // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; + + // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); + // //socket.ConnectAsServerAsync().ConfigureAwait(false); + + //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) + //// { + //// OnReceive = ProcessWebSocketMessageReceived, + //// Url = e.Url, + //// QueryString = e.QueryString ?? new QueryParamCollection() + //// }; + //// + //// connection.Closed += Connection_Closed; + //// + //// lock (_webSocketConnections) + //// { + //// _webSocketConnections.Add(connection); + //// } + //// + //// WebSocketConnected(new WebSocketConnectEventArgs + //// { + //// Url = url, + //// QueryString = queryString, + //// WebSocket = socket, + //// Endpoint = endpoint + //// }); + // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); + // } + // else + // { + // Logger.LogWarning("Web socket connection not allowed"); + // ctx.Response.StatusCode = 401; + // } + // } + // catch (Exception ex) + // { + // ctx.Response.StatusCode = 500; + // } } var req = new WebSocketSharpRequest(request, response, request.Path, Logger); diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 1bd084259d..cfe0bbe974 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Services; +using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -73,6 +74,10 @@ namespace Emby.Server.Implementations.HttpServer Instance = this; ResponseFilters = Array.Empty>(); + _websocketlistener = new WebSocketSharpListener(_logger) + { + WebSocketConnected = OnWebSocketConnected + }; } public string GlobalResponse { get; set; } @@ -796,6 +801,8 @@ namespace Emby.Server.Implementations.HttpServer private bool _disposed; private readonly object _disposeLock = new object(); + public WebSocketSharpListener _websocketlistener; + protected virtual void Dispose(bool disposing) { if (_disposed) return; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index ab7ddeca20..824c9a822d 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Threading; @@ -8,6 +8,7 @@ using Emby.Server.Implementations.Net; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.SocketSharp @@ -120,14 +121,14 @@ using Microsoft.Extensions.Logging; // return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); // } - private async Task ProcessWebSocketRequest(HttpListenerContext ctx) + public async Task ProcessWebSocketRequest(HttpContext ctx) { try { - var endpoint = ctx.Request.RemoteEndPoint.ToString(); - var url = ctx.Request.RawUrl; + var endpoint = ctx.Connection.RemoteIpAddress.ToString(); + var url = ctx.Request.GetDisplayUrl(); - var queryString = new QueryParamCollection(ctx.Request.QueryString); + var queryString = new QueryParamCollection(ctx.Request.Query); var connectingArgs = new WebSocketConnectingEventArgs { @@ -142,40 +143,40 @@ using Microsoft.Extensions.Logging; { _logger.LogDebug("Web socket connection allowed"); - var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); + var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); if (WebSocketConnected != null) { - SharpWebSocket socket = null; //new SharpWebSocket(webSocketContext.WebSocket, _logger); - await socket.ConnectAsServerAsync().ConfigureAwait(false); - - WebSocketConnected(new WebSocketConnectEventArgs - { - Url = url, - QueryString = queryString, - WebSocket = socket, - Endpoint = endpoint - }); - - await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); + //await socket.ConnectAsServerAsync().ConfigureAwait(false); + + //WebSocketConnected(new WebSocketConnectEventArgs + //{ + // Url = url, + // QueryString = queryString, + // WebSocket = socket, + // Endpoint = endpoint + //}); + + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); } } else { _logger.LogWarning("Web socket connection not allowed"); ctx.Response.StatusCode = 401; - ctx.Response.Close(); + //ctx.Response.Close(); } } catch (Exception ex) { _logger.LogError(ex, "AcceptWebSocketAsync error"); ctx.Response.StatusCode = 500; - ctx.Response.Close(); + //ctx.Response.Close(); } } - private async Task ReceiveWebSocketAsync(HttpListenerContext ctx, SharpWebSocket socket) + private async Task ReceiveWebSocketAsync(HttpContext ctx, SharpWebSocket socket) { try { @@ -187,12 +188,11 @@ using Microsoft.Extensions.Logging; } } - private void TryClose(HttpListenerContext ctx, int statusCode) + private void TryClose(HttpContext ctx, int statusCode) { try { ctx.Response.StatusCode = statusCode; - ctx.Response.Close(); } catch (ObjectDisposedException) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index facc544461..9c5b2b0834 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -7,6 +7,7 @@ using System.Text; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; @@ -44,11 +45,11 @@ namespace Emby.Server.Implementations.SocketSharp public object Dto { get; set; } - public string RawUrl => request.Path.ToUriComponent(); + public string RawUrl => request.Path.ToString(); - public string AbsoluteUri => request.Path.ToUriComponent().TrimEnd('/'); + public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); - public string UserHostAddress => ""; + public string UserHostAddress => request.HttpContext.Connection.RemoteIpAddress.ToString(); public string XForwardedFor => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString(); @@ -66,7 +67,7 @@ namespace Emby.Server.Implementations.SocketSharp remoteIp ?? (remoteIp = CheckBadChars(XForwardedFor) ?? NormalizeIp(CheckBadChars(XRealIp) ?? - (string.IsNullOrEmpty(request.Host.Host) ? null : NormalizeIp(request.Host.Host)))); + (string.IsNullOrEmpty(request.HttpContext.Connection.RemoteIpAddress.ToString()) ? null : NormalizeIp(request.HttpContext.Connection.RemoteIpAddress.ToString())))); private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; @@ -199,7 +200,7 @@ namespace Emby.Server.Implementations.SocketSharp const string serverDefaultContentType = "application/json"; - var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept); // TODO; + var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept); string defaultContentType = null; if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData)) { @@ -448,7 +449,7 @@ namespace Emby.Server.Implementations.SocketSharp private QueryParamCollection queryString; public QueryParamCollection QueryString => queryString ?? (queryString = new QueryParamCollection(request.Query)); - public bool IsLocal => true; // TODO + public bool IsLocal => string.Equals(request.HttpContext.Connection.LocalIpAddress.ToString(), request.HttpContext.Connection.RemoteIpAddress.ToString()); private string httpMethod; public string HttpMethod => -- cgit v1.2.3 From 194da8416bb93b492b86bab6dd199eb5dcc06e55 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 10:23:58 +0100 Subject: Use middlewares instead of Routing --- Emby.Server.Implementations/ApplicationHost.cs | 147 +++++++++++---------- .../SocketSharp/SharpWebSocket.cs | 14 +- 2 files changed, 79 insertions(+), 82 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 18e752e53b..ca1dceedfa 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -657,85 +657,92 @@ namespace Emby.Server.Implementations ReceiveBufferSize = 0x10000 }); - app.UseRouter(r => - { - // TODO all the verbs, but really MVC... - r.MapGet("/{*localpath}", ExecuteHandler); - r.MapPut("/{*localpath}", ExecuteHandler); - r.MapPost("/{*localpath}", ExecuteHandler); - r.MapDelete("/{*localpath}", ExecuteHandler); - r.MapVerb("HEAD", "/{*localpath}", ExecuteHandler); - }); + app.Use(ExecuteWebsocketHandlerAsync); + app.Use(ExecuteHttpHandlerAsync); }) .Build(); } - public async Task ExecuteHandler(HttpRequest request, Microsoft.AspNetCore.Http.HttpResponse response, RouteData data) + public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) { - var ctx = request.HttpContext; - if (ctx.WebSockets.IsWebSocketRequest) + if (!context.WebSockets.IsWebSocketRequest) { - await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(ctx).ConfigureAwait(false); -// try -// { -// var endpoint = ctx.Request.Path.ToString(); -// var url = ctx.Request.Path.ToString(); - - // var queryString = new QueryParamCollection(request.Query); - - // var connectingArgs = new WebSocketConnectingEventArgs - // { - // Url = url, - // QueryString = queryString, - // Endpoint = endpoint - // }; - - // if (connectingArgs.AllowConnection) - // { - // Logger.LogDebug("Web socket connection allowed"); - - // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; - - // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); - // //socket.ConnectAsServerAsync().ConfigureAwait(false); - - //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) - //// { - //// OnReceive = ProcessWebSocketMessageReceived, - //// Url = e.Url, - //// QueryString = e.QueryString ?? new QueryParamCollection() - //// }; - //// - //// connection.Closed += Connection_Closed; - //// - //// lock (_webSocketConnections) - //// { - //// _webSocketConnections.Add(connection); - //// } - //// - //// WebSocketConnected(new WebSocketConnectEventArgs - //// { - //// Url = url, - //// QueryString = queryString, - //// WebSocket = socket, - //// Endpoint = endpoint - //// }); - // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); - // } - // else - // { - // Logger.LogWarning("Web socket connection not allowed"); - // ctx.Response.StatusCode = 401; - // } - // } - // catch (Exception ex) - // { - // ctx.Response.StatusCode = 500; - // } + await next(); + return; + } + + await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(context).ConfigureAwait(false); + // try + // { + // var endpoint = ctx.Request.Path.ToString(); + // var url = ctx.Request.Path.ToString(); + + // var queryString = new QueryParamCollection(request.Query); + + // var connectingArgs = new WebSocketConnectingEventArgs + // { + // Url = url, + // QueryString = queryString, + // Endpoint = endpoint + // }; + + // if (connectingArgs.AllowConnection) + // { + // Logger.LogDebug("Web socket connection allowed"); + + // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; + + // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); + // //socket.ConnectAsServerAsync().ConfigureAwait(false); + + //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) + //// { + //// OnReceive = ProcessWebSocketMessageReceived, + //// Url = e.Url, + //// QueryString = e.QueryString ?? new QueryParamCollection() + //// }; + //// + //// connection.Closed += Connection_Closed; + //// + //// lock (_webSocketConnections) + //// { + //// _webSocketConnections.Add(connection); + //// } + //// + //// WebSocketConnected(new WebSocketConnectEventArgs + //// { + //// Url = url, + //// QueryString = queryString, + //// WebSocket = socket, + //// Endpoint = endpoint + //// }); + // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); + // } + // else + // { + // Logger.LogWarning("Web socket connection not allowed"); + // ctx.Response.StatusCode = 401; + // } + // } + // catch (Exception ex) + // { + // ctx.Response.StatusCode = 500; + // } + } + public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) + { + if (context.WebSockets.IsWebSocketRequest) + { + await next(); + return; } + var request = context.Request; + var response = context.Response; + var localPath = context.Request.Path.ToString().TrimStart('/'); + var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); + await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 80ef451dc7..854d33f3a0 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -28,18 +28,8 @@ namespace Emby.Server.Implementations.SocketSharp public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) { - if (socket == null) - { - throw new ArgumentNullException(nameof(socket)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - _logger = logger; - WebSocket = socket; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + WebSocket = socket ?? throw new ArgumentNullException(nameof(socket)); socket.OnMessage += OnSocketMessage; socket.OnClose += OnSocketClose; -- cgit v1.2.3 From 38f52a139e8017ddaaef55e1eb4cc97f1d4d8c04 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 10:30:51 +0100 Subject: Add response compression middleware --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ca1dceedfa..da9550b851 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -646,7 +646,7 @@ namespace Emby.Server.Implementations .UseUrls("http://localhost:8096") .ConfigureServices(services => { - services.AddRouting(); + services.AddResponseCompression(); services.AddHttpContextAccessor(); services.TryAddSingleton(); }) @@ -657,6 +657,7 @@ namespace Emby.Server.Implementations ReceiveBufferSize = 0x10000 }); + app.UseResponseCompression(); app.Use(ExecuteWebsocketHandlerAsync); app.Use(ExecuteHttpHandlerAsync); }) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index acbc60c39e..a4dac4a7f9 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -29,6 +29,8 @@ + + -- cgit v1.2.3 From d6c6f3c10cff68b11df884be502c58a17da0d332 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 15:13:06 +0100 Subject: Still broken --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Net/WebSocketConnectEventArgs.cs | 1 + .../SocketSharp/SharpWebSocket.cs | 31 ++++++-------- .../SocketSharp/WebSocketSharpListener.cs | 48 +++++++++++++++------- .../SocketSharp/WebSocketSharpListener.cs | 4 +- 5 files changed, 51 insertions(+), 35 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index da9550b851..65b6681c89 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -740,7 +740,7 @@ namespace Emby.Server.Implementations var request = context.Request; var response = context.Response; - var localPath = context.Request.Path.ToString().TrimStart('/'); + var localPath = context.Request.Path.ToString(); var req = new WebSocketSharpRequest(request, response, request.Path, Logger); await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs index 3ab8e854a0..666f1f6011 100644 --- a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs +++ b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs @@ -1,4 +1,5 @@ using System; +using System.Net.WebSockets; using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.Net diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 854d33f3a0..89004ba7fe 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -1,5 +1,6 @@ using System; using System.Net.WebSockets; +using System.Text; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; @@ -20,25 +21,18 @@ namespace Emby.Server.Implementations.SocketSharp /// Gets or sets the web socket. ///
/// The web socket. - private SocketHttpListener.WebSocket WebSocket { get; set; } + private WebSocket WebSocket { get; set; } private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private bool _disposed = false; - public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) + public SharpWebSocket(WebSocket socket, ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); WebSocket = socket ?? throw new ArgumentNullException(nameof(socket)); - - socket.OnMessage += OnSocketMessage; - socket.OnClose += OnSocketClose; - socket.OnError += OnSocketError; } - public Task ConnectAsServerAsync() - => WebSocket.ConnectAsServer(); - public Task StartReceive() { return _taskCompletionSource.Task; @@ -58,7 +52,7 @@ namespace Emby.Server.Implementations.SocketSharp Closed?.Invoke(this, EventArgs.Empty); } - private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e) + private void OnSocketMessage(SocketHttpListener.MessageEventArgs e) { if (OnReceiveBytes != null) { @@ -66,11 +60,15 @@ namespace Emby.Server.Implementations.SocketSharp } } + public Task ConnectAsServerAsync() + { + return Task.CompletedTask; + } /// /// Gets or sets the state. /// /// The state. - public WebSocketState State => WebSocket.ReadyState; + public WebSocketState State => WebSocket.State; /// /// Sends the async. @@ -81,7 +79,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(bytes); + return WebSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken); } /// @@ -93,7 +91,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(text); + return WebSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken); } /// @@ -118,13 +116,10 @@ namespace Emby.Server.Implementations.SocketSharp if (dispose) { - WebSocket.OnMessage -= OnSocketMessage; - WebSocket.OnClose -= OnSocketClose; - WebSocket.OnError -= OnSocketError; - _cancellationTokenSource.Cancel(); - WebSocket.CloseAsync().GetAwaiter().GetResult(); + // TODO + WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 824c9a822d..b64fc06833 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; @@ -144,22 +145,41 @@ using Microsoft.Extensions.Logging; _logger.LogDebug("Web socket connection allowed"); var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); + var socket = new SharpWebSocket(webSocketContext, _logger); + await socket.ConnectAsServerAsync().ConfigureAwait(false); - if (WebSocketConnected != null) + WebSocketConnected(new WebSocketConnectEventArgs { - //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); - //await socket.ConnectAsServerAsync().ConfigureAwait(false); - - //WebSocketConnected(new WebSocketConnectEventArgs - //{ - // Url = url, - // QueryString = queryString, - // WebSocket = socket, - // Endpoint = endpoint - //}); - - //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); - } + Url = url, + QueryString = queryString, + WebSocket = socket, + Endpoint = endpoint + }); + + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + var buffer = WebSocket.CreateClientBuffer(1024 * 4, 1024 * 4); + WebSocketReceiveResult result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); + socket.OnReceiveBytes(buffer.Array); + //while (!result.CloseStatus.HasValue) + //{ + // await webSocketContext.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); + + // result = await webSocketContext.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + //} + //WebSocketConnected?.Invoke(new WebSocketConnectEventArgs + //{ + // Url = url, + // QueryString = queryString, + // WebSocket = webSocketContext, + // Endpoint = endpoint + //}); + await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); + //await socket.ConnectAsServerAsync().ConfigureAwait(false); + + + + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); } else { diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index c458fdb78f..4dc4103cb1 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Threading; @@ -152,7 +152,7 @@ using Microsoft.Extensions.Logging; { Url = url, QueryString = queryString, - WebSocket = socket, + WebSocket = null, // socket, Endpoint = endpoint }); -- cgit v1.2.3 From 5a7cca9d1bdc5280626a6654cdbd8c0d45016af5 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 19:48:18 +0100 Subject: Fix websockets and RawUrl --- Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs | 2 +- .../SocketSharp/WebSocketSharpListener.cs | 13 +++++++++++-- .../SocketSharp/WebSocketSharpRequest.cs | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 89004ba7fe..eab903db8e 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -119,7 +119,7 @@ namespace Emby.Server.Implementations.SocketSharp _cancellationTokenSource.Cancel(); // TODO - WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); + // WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index b64fc06833..05f159b4ee 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -160,6 +160,15 @@ using Microsoft.Extensions.Logging; var buffer = WebSocket.CreateClientBuffer(1024 * 4, 1024 * 4); WebSocketReceiveResult result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); socket.OnReceiveBytes(buffer.Array); + + while (result.MessageType != WebSocketMessageType.Close) + { + result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); + socket.OnReceiveBytes(buffer.Array); + } + await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + socket.Dispose(); + //while (!result.CloseStatus.HasValue) //{ // await webSocketContext.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); @@ -173,11 +182,11 @@ using Microsoft.Extensions.Logging; // WebSocket = webSocketContext, // Endpoint = endpoint //}); - await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + //await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); //await socket.ConnectAsServerAsync().ConfigureAwait(false); - + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 9c5b2b0834..b07c4bfebb 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.SocketSharp public object Dto { get; set; } - public string RawUrl => request.Path.ToString(); + public string RawUrl => request.GetEncodedPathAndQuery(); public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); -- cgit v1.2.3 From a85488cd205532662c59ada5032dc81c0315543b Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:13:48 +0100 Subject: Fix websockets array index out of bounds and some cleanup --- Emby.Server.Implementations/ApplicationHost.cs | 78 +--------- .../HttpServer/HttpListenerHost.cs | 7 +- .../SocketSharp/WebSocketSharpListener.cs | 166 ++------------------- 3 files changed, 24 insertions(+), 227 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 65b6681c89..e9d8d8de7e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -624,25 +624,7 @@ namespace Emby.Server.Implementations Host = new WebHostBuilder() .UseKestrel() - .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), @"jellyfin-web\src")) - //.UseStartup() -// .ConfigureServices(async services => -// { -// services.AddSingleton(startUp); -// RegisterResources(services); -// FindParts(); -// try -// { -// ImageProcessor.ImageEncoder = -// new NullImageEncoder(); //SkiaEncoder(_loggerFactory, appPaths, fileSystem, localizationManager); -// } -// catch (Exception ex) -// { -// Logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder. {0}"); -// ImageProcessor.ImageEncoder = new NullImageEncoder(); -// } -// await RunStartupTasks().ConfigureAwait(false); -// }) + .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) .UseUrls("http://localhost:8096") .ConfigureServices(services => { @@ -672,63 +654,7 @@ namespace Emby.Server.Implementations return; } - await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(context).ConfigureAwait(false); - // try - // { - // var endpoint = ctx.Request.Path.ToString(); - // var url = ctx.Request.Path.ToString(); - - // var queryString = new QueryParamCollection(request.Query); - - // var connectingArgs = new WebSocketConnectingEventArgs - // { - // Url = url, - // QueryString = queryString, - // Endpoint = endpoint - // }; - - // if (connectingArgs.AllowConnection) - // { - // Logger.LogDebug("Web socket connection allowed"); - - // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; - - // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); - // //socket.ConnectAsServerAsync().ConfigureAwait(false); - - //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) - //// { - //// OnReceive = ProcessWebSocketMessageReceived, - //// Url = e.Url, - //// QueryString = e.QueryString ?? new QueryParamCollection() - //// }; - //// - //// connection.Closed += Connection_Closed; - //// - //// lock (_webSocketConnections) - //// { - //// _webSocketConnections.Add(connection); - //// } - //// - //// WebSocketConnected(new WebSocketConnectEventArgs - //// { - //// Url = url, - //// QueryString = queryString, - //// WebSocket = socket, - //// Endpoint = endpoint - //// }); - // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); - // } - // else - // { - // Logger.LogWarning("Web socket connection not allowed"); - // ctx.Response.StatusCode = 401; - // } - // } - // catch (Exception ex) - // { - // ctx.Response.StatusCode = 500; - // } + await ((HttpListenerHost)HttpServer).ProcessWebSocketRequest(context).ConfigureAwait(false); } public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index cfe0bbe974..a3eaa2fb50 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -21,6 +21,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using ServiceStack.Text.Jsv; @@ -767,6 +768,10 @@ namespace Emby.Server.Implementations.HttpServer return _jsonSerializer.DeserializeFromStreamAsync(stream, type); } + public Task ProcessWebSocketRequest(HttpContext context) + { + return _websocketlistener.ProcessWebSocketRequest(context); + } //TODO Add Jellyfin Route Path Normalizer private static string NormalizeEmbyRoutePath(string path) @@ -801,7 +806,7 @@ namespace Emby.Server.Implementations.HttpServer private bool _disposed; private readonly object _disposeLock = new object(); - public WebSocketSharpListener _websocketlistener; + private readonly WebSocketSharpListener _websocketlistener; protected virtual void Dispose(bool disposing) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 05f159b4ee..6e5190efdb 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; - using System.Net; +using System.Linq; +using System.Net; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; @@ -38,94 +39,18 @@ using Microsoft.Extensions.Logging; public Action WebSocketConnected { get; set; } -// public void Start(IEnumerable urlPrefixes) -// { -// // TODO -// //if (_listener == null) -// //{ -// // _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); -// //} -// -// //_listener.EnableDualMode = _enableDualMode; -// -// //if (_certificate != null) -// //{ -// // _listener.LoadCert(_certificate); -// //} -// -// //_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); -// //_listener.Prefixes.AddRange(urlPrefixes); -// -// //_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); -// -// //_listener.Start(); -// -// if (_listener == null) -// { -// _listener = new HttpListener(); -// } -// -// _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); -// -// //foreach (var urlPrefix in urlPrefixes) -// //{ -// // _listener.Prefixes.Add(urlPrefix); -// //} -// _listener.Prefixes.Add("http://localhost:8096/"); -// -// _listener.Start(); -// -// // TODO how to do this in netcore? -// _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), -// null); -// } - - private static void LogRequest(ILogger logger, HttpListenerRequest request) + private static void LogRequest(ILogger logger, HttpRequest request) { - var url = request.Url.ToString(); + var url = request.GetDisplayUrl(); - logger.LogInformation( - "{0} {1}. UserAgent: {2}", - request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, - url, - request.UserAgent ?? string.Empty); + logger.LogInformation("{0} {1}. UserAgent: {2}", "WS", url, request.Headers["User-Agent"].ToString()); } -// -// private Task InitTask(IAsyncResult asyncResult, CancellationToken cancellationToken) -// { -// var context = _listener.EndGetContext(asyncResult); -// _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), null); -// IHttpRequest httpReq = null; -// var request = context.Request; -// -// try -// { -// if (request.IsWebSocketRequest) -// { -// LogRequest(_logger, request); -// -// return ProcessWebSocketRequest(context); -// } -// -// httpReq = GetRequest(context); -// } -// catch (Exception ex) -// { -// _logger.LogError(ex, "Error processing request"); -// -// httpReq = httpReq ?? GetRequest(context); -// return ErrorHandler(ex, httpReq, true, true); -// } -// -// var uri = request.Url; -// -// return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); -// } public async Task ProcessWebSocketRequest(HttpContext ctx) { try { + LogRequest(_logger, ctx.Request); var endpoint = ctx.Connection.RemoteIpAddress.ToString(); var url = ctx.Request.GetDisplayUrl(); @@ -156,94 +81,35 @@ using Microsoft.Extensions.Logging; Endpoint = endpoint }); - //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); - var buffer = WebSocket.CreateClientBuffer(1024 * 4, 1024 * 4); - WebSocketReceiveResult result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); - socket.OnReceiveBytes(buffer.Array); + var buffer = WebSocket.CreateClientBuffer(4096, 4096); + WebSocketReceiveResult result; + var message = new List(); - while (result.MessageType != WebSocketMessageType.Close) + do { result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); socket.OnReceiveBytes(buffer.Array); - } - await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); - socket.Dispose(); - - //while (!result.CloseStatus.HasValue) - //{ - // await webSocketContext.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); - - // result = await webSocketContext.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - //} - //WebSocketConnected?.Invoke(new WebSocketConnectEventArgs - //{ - // Url = url, - // QueryString = queryString, - // WebSocket = webSocketContext, - // Endpoint = endpoint - //}); - //await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); - //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); - //await socket.ConnectAsServerAsync().ConfigureAwait(false); + message.AddRange(buffer.Array.Take(result.Count)); + } while (!result.EndOfMessage && result.MessageType != WebSocketMessageType.Close); - - - //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + socket.OnReceiveBytes(message.ToArray()); + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, CancellationToken.None); + socket.Dispose(); } else { _logger.LogWarning("Web socket connection not allowed"); ctx.Response.StatusCode = 401; - //ctx.Response.Close(); } } catch (Exception ex) { _logger.LogError(ex, "AcceptWebSocketAsync error"); ctx.Response.StatusCode = 500; - //ctx.Response.Close(); - } - } - - private async Task ReceiveWebSocketAsync(HttpContext ctx, SharpWebSocket socket) - { - try - { - await socket.StartReceive().ConfigureAwait(false); - } - finally - { - TryClose(ctx, 200); - } - } - - private void TryClose(HttpContext ctx, int statusCode) - { - try - { - ctx.Response.StatusCode = statusCode; - } - catch (ObjectDisposedException) - { - // TODO: Investigate and properly fix. - } - catch (Exception ex) - { - _logger.LogError(ex, "Error closing web socket response"); } } - private IHttpRequest GetRequest(HttpRequest httpContext) - { - var urlSegments = httpContext.Path; - - var operationName = urlSegments; - - var req = new WebSocketSharpRequest(httpContext, httpContext.HttpContext.Response, operationName, _logger); - - return req; - } - public void Start(IEnumerable urlPrefixes) { throw new NotImplementedException(); -- cgit v1.2.3 From 4e8de67acabbfe1aeb5fee6d1162abc760fbd540 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:22:40 +0100 Subject: Remove SocketSharp from Jellyfin.Server and some other cleanup --- Emby.Server.Implementations/ApplicationHost.cs | 45 +- .../HttpServer/HttpListenerHost.cs | 15 +- Jellyfin.Server/CoreAppHost.cs | 4 - Jellyfin.Server/Jellyfin.Server.csproj | 2 +- Jellyfin.Server/SocketSharp/HttpFile.cs | 18 - Jellyfin.Server/SocketSharp/HttpPostedFile.cs | 204 ------- Jellyfin.Server/SocketSharp/RequestMono.cs | 680 --------------------- Jellyfin.Server/SocketSharp/SharpWebSocket.cs | 149 ----- .../SocketSharp/WebSocketSharpListener.cs | 237 ------- .../SocketSharp/WebSocketSharpRequest.cs | 535 ---------------- .../SocketSharp/WebSocketSharpResponse.cs | 215 ------- 11 files changed, 11 insertions(+), 2093 deletions(-) delete mode 100644 Jellyfin.Server/SocketSharp/HttpFile.cs delete mode 100644 Jellyfin.Server/SocketSharp/HttpPostedFile.cs delete mode 100644 Jellyfin.Server/SocketSharp/RequestMono.cs delete mode 100644 Jellyfin.Server/SocketSharp/SharpWebSocket.cs delete mode 100644 Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs delete mode 100644 Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs delete mode 100644 Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index e9d8d8de7e..ad6b8e84a8 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -791,7 +791,8 @@ namespace Emby.Server.Implementations _configuration, NetworkManager, JsonSerializer, - XmlSerializer); + XmlSerializer, + CreateHttpListener()); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); serviceCollection.AddSingleton(HttpServer); @@ -1125,8 +1126,6 @@ namespace Emby.Server.Implementations HttpServer.Init(GetExports(false), GetExports()); - //StartServer(); - LibraryManager.AddParts(GetExports(), GetExports(), GetExports(), @@ -1239,45 +1238,7 @@ namespace Emby.Server.Implementations }); } - protected abstract IHttpListener CreateHttpListener(); - - /// - /// Starts the server. - /// - private void StartServer() - { - try - { - ((HttpListenerHost)HttpServer).StartServer(GetUrlPrefixes().ToArray(), CreateHttpListener()); - return; - } - catch (Exception ex) - { - var msg = string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase) - ? "The http server is unable to start due to a Socket error. This can occasionally happen when the operating system takes longer than usual to release the IP bindings from the previous session. This can take up to five minutes. Please try waiting or rebooting the system." - : "Error starting Http Server"; - - Logger.LogError(ex, msg); - - if (HttpPort == ServerConfiguration.DefaultHttpPort) - { - throw; - } - } - - HttpPort = ServerConfiguration.DefaultHttpPort; - - try - { - ((HttpListenerHost)HttpServer).StartServer(GetUrlPrefixes().ToArray(), CreateHttpListener()); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error starting http server"); - - throw; - } - } + protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(Logger); private CertificateInfo GetCertificateInfo(bool generateCertificate) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index a3eaa2fb50..12c23a8693 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -44,6 +44,7 @@ namespace Emby.Server.Implementations.HttpServer private readonly IServerApplicationHost _appHost; private readonly IJsonSerializer _jsonSerializer; private readonly IXmlSerializer _xmlSerializer; + private readonly IHttpListener _socketListener; private readonly Func> _funcParseFn; public Action[] ResponseFilters { get; set; } @@ -61,7 +62,8 @@ namespace Emby.Server.Implementations.HttpServer IConfiguration configuration, INetworkManager networkManager, IJsonSerializer jsonSerializer, - IXmlSerializer xmlSerializer) + IXmlSerializer xmlSerializer, + IHttpListener socketListener) { _appHost = applicationHost; _logger = loggerFactory.CreateLogger("HttpServer"); @@ -70,15 +72,13 @@ namespace Emby.Server.Implementations.HttpServer _networkManager = networkManager; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; + _socketListener = socketListener; + _socketListener.WebSocketConnected = OnWebSocketConnected; _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); Instance = this; ResponseFilters = Array.Empty>(); - _websocketlistener = new WebSocketSharpListener(_logger) - { - WebSocketConnected = OnWebSocketConnected - }; } public string GlobalResponse { get; set; } @@ -770,7 +770,8 @@ namespace Emby.Server.Implementations.HttpServer public Task ProcessWebSocketRequest(HttpContext context) { - return _websocketlistener.ProcessWebSocketRequest(context); + // TODO + return ((WebSocketSharpListener)_socketListener).ProcessWebSocketRequest(context); } //TODO Add Jellyfin Route Path Normalizer @@ -867,8 +868,6 @@ namespace Emby.Server.Implementations.HttpServer _listener.WebSocketConnected = OnWebSocketConnected; _listener.ErrorHandler = ErrorHandler; _listener.RequestHandler = RequestHandler; - - _listener.Start(UrlPrefixes); } } } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 68fd2453e3..9e52247900 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Reflection; using Emby.Server.Implementations; using Emby.Server.Implementations.HttpServer; -using Jellyfin.Server.SocketSharp; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Microsoft.Extensions.Configuration; @@ -45,8 +44,5 @@ namespace Jellyfin.Server } protected override void ShutdownInternal() => Program.Shutdown(); - - protected override IHttpListener CreateHttpListener() - => new WebSocketSharpListener(Logger); } } diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index bd670df527..94a7e58cb5 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -1,4 +1,4 @@ - + jellyfin diff --git a/Jellyfin.Server/SocketSharp/HttpFile.cs b/Jellyfin.Server/SocketSharp/HttpFile.cs deleted file mode 100644 index 448b666b63..0000000000 --- a/Jellyfin.Server/SocketSharp/HttpFile.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.IO; -using MediaBrowser.Model.Services; - -namespace Jellyfin.Server.SocketSharp -{ - public class HttpFile : IHttpFile - { - public string Name { get; set; } - - public string FileName { get; set; } - - public long ContentLength { get; set; } - - public string ContentType { get; set; } - - public Stream InputStream { get; set; } - } -} diff --git a/Jellyfin.Server/SocketSharp/HttpPostedFile.cs b/Jellyfin.Server/SocketSharp/HttpPostedFile.cs deleted file mode 100644 index f38ed848ee..0000000000 --- a/Jellyfin.Server/SocketSharp/HttpPostedFile.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - -public sealed class HttpPostedFile : IDisposable -{ - private string _name; - private string _contentType; - private Stream _stream; - private bool _disposed = false; - - internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length) - { - _name = name; - _contentType = content_type; - _stream = new ReadSubStream(base_stream, offset, length); - } - - public string ContentType => _contentType; - - public int ContentLength => (int)_stream.Length; - - public string FileName => _name; - - public Stream InputStream => _stream; - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - _stream.Dispose(); - _stream = null; - - _name = null; - _contentType = null; - - _disposed = true; - } - - private class ReadSubStream : Stream - { - private Stream _stream; - private long _offset; - private long _end; - private long _position; - - public ReadSubStream(Stream s, long offset, long length) - { - _stream = s; - _offset = offset; - _end = offset + length; - _position = offset; - } - - public override void Flush() - { - } - - public override int Read(byte[] buffer, int dest_offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (dest_offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "< 0"); - } - - int len = buffer.Length; - if (dest_offset > len) - { - throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset)); - } - - // reordered to avoid possible integer overflow - if (dest_offset > len - count) - { - throw new ArgumentException("Reading would overrun buffer", nameof(count)); - } - - if (count > _end - _position) - { - count = (int)(_end - _position); - } - - if (count <= 0) - { - return 0; - } - - _stream.Position = _position; - int result = _stream.Read(buffer, dest_offset, count); - if (result > 0) - { - _position += result; - } - else - { - _position = _end; - } - - return result; - } - - public override int ReadByte() - { - if (_position >= _end) - { - return -1; - } - - _stream.Position = _position; - int result = _stream.ReadByte(); - if (result < 0) - { - _position = _end; - } - else - { - _position++; - } - - return result; - } - - public override long Seek(long d, SeekOrigin origin) - { - long real; - switch (origin) - { - case SeekOrigin.Begin: - real = _offset + d; - break; - case SeekOrigin.End: - real = _end + d; - break; - case SeekOrigin.Current: - real = _position + d; - break; - default: - throw new ArgumentException("Unknown SeekOrigin value", nameof(origin)); - } - - long virt = real - _offset; - if (virt < 0 || virt > Length) - { - throw new ArgumentException("Invalid position", nameof(d)); - } - - _position = _stream.Seek(real, SeekOrigin.Begin); - return _position; - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - public override bool CanRead => true; - - public override bool CanSeek => true; - - public override bool CanWrite => false; - - public override long Length => _end - _offset; - - public override long Position - { - get => _position - _offset; - set - { - if (value > Length) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _position = Seek(value, SeekOrigin.Begin); - } - } - } -} diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs deleted file mode 100644 index f2a08c9ae2..0000000000 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ /dev/null @@ -1,680 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - -namespace Jellyfin.Server.SocketSharp -{ - public partial class WebSocketSharpRequest : IHttpRequest - { - internal static string GetParameter(string header, string attr) - { - int ap = header.IndexOf(attr, StringComparison.Ordinal); - if (ap == -1) - { - return null; - } - - ap += attr.Length; - if (ap >= header.Length) - { - return null; - } - - char ending = header[ap]; - if (ending != '"') - { - ending = ' '; - } - - int end = header.IndexOf(ending, ap + 1); - if (end == -1) - { - return ending == '"' ? null : header.Substring(ap); - } - - return header.Substring(ap + 1, end - ap - 1); - } - - private async Task LoadMultiPart(WebROCollection form) - { - string boundary = GetParameter(ContentType, "; boundary="); - if (boundary == null) - { - return; - } - - using (var requestStream = InputStream) - { - // DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request - // Not ending with \r\n? - var ms = new MemoryStream(32 * 1024); - await requestStream.CopyToAsync(ms).ConfigureAwait(false); - - var input = ms; - ms.WriteByte((byte)'\r'); - ms.WriteByte((byte)'\n'); - - input.Position = 0; - - // Uncomment to debug - // var content = new StreamReader(ms).ReadToEnd(); - // Console.WriteLine(boundary + "::" + content); - // input.Position = 0; - - var multi_part = new HttpMultipart(input, boundary, ContentEncoding); - - HttpMultipart.Element e; - while ((e = multi_part.ReadNextElement()) != null) - { - if (e.Filename == null) - { - byte[] copy = new byte[e.Length]; - - input.Position = e.Start; - input.Read(copy, 0, (int)e.Length); - - form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length)); - } - else - { - // We use a substream, as in 2.x we will support large uploads streamed to disk, - var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length); - files[e.Name] = sub; - } - } - } - } - - public async Task GetFormData() - { - var form = new WebROCollection(); - files = new Dictionary(); - - if (IsContentType("multipart/form-data", true)) - { - await LoadMultiPart(form).ConfigureAwait(false); - } - else if (IsContentType("application/x-www-form-urlencoded", true)) - { - await LoadWwwForm(form).ConfigureAwait(false); - } - -#if NET_4_0 - if (validateRequestNewMode && !checked_form) { - // Setting this before calling the validator prevents - // possible endless recursion - checked_form = true; - ValidateNameValueCollection("Form", query_string_nvc, RequestValidationSource.Form); - } else -#endif - if (validate_form && !checked_form) - { - checked_form = true; - ValidateNameValueCollection("Form", form); - } - - return form; - } - - public string Accept => string.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"]; - - public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"]; - - protected bool validate_cookies { get; set; } - protected bool validate_query_string { get; set; } - protected bool validate_form { get; set; } - protected bool checked_cookies { get; set; } - protected bool checked_query_string { get; set; } - protected bool checked_form { get; set; } - - private static void ThrowValidationException(string name, string key, string value) - { - string v = "\"" + value + "\""; - if (v.Length > 20) - { - v = v.Substring(0, 16) + "...\""; - } - - string msg = string.Format( - CultureInfo.InvariantCulture, - "A potentially dangerous Request.{0} value was detected from the client ({1}={2}).", - name, - key, - v); - - throw new Exception(msg); - } - - private static void ValidateNameValueCollection(string name, QueryParamCollection coll) - { - if (coll == null) - { - return; - } - - foreach (var pair in coll) - { - var key = pair.Name; - var val = pair.Value; - if (val != null && val.Length > 0 && IsInvalidString(val)) - { - ThrowValidationException(name, key, val); - } - } - } - - internal static bool IsInvalidString(string val) - => IsInvalidString(val, out var validationFailureIndex); - - internal static bool IsInvalidString(string val, out int validationFailureIndex) - { - validationFailureIndex = 0; - - int len = val.Length; - if (len < 2) - { - return false; - } - - char current = val[0]; - for (int idx = 1; idx < len; idx++) - { - char next = val[idx]; - - // See http://secunia.com/advisories/14325 - if (current == '<' || current == '\xff1c') - { - if (next == '!' || next < ' ' - || (next >= 'a' && next <= 'z') - || (next >= 'A' && next <= 'Z')) - { - validationFailureIndex = idx - 1; - return true; - } - } - else if (current == '&' && next == '#') - { - validationFailureIndex = idx - 1; - return true; - } - - current = next; - } - - return false; - } - - public void ValidateInput() - { - validate_cookies = true; - validate_query_string = true; - validate_form = true; - } - - private bool IsContentType(string ct, bool starts_with) - { - if (ct == null || ContentType == null) - { - return false; - } - - if (starts_with) - { - return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase); - } - - return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase); - } - - private async Task LoadWwwForm(WebROCollection form) - { - using (var input = InputStream) - { - using (var ms = new MemoryStream()) - { - await input.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - - using (var s = new StreamReader(ms, ContentEncoding)) - { - var key = new StringBuilder(); - var value = new StringBuilder(); - int c; - - while ((c = s.Read()) != -1) - { - if (c == '=') - { - value.Length = 0; - while ((c = s.Read()) != -1) - { - if (c == '&') - { - AddRawKeyValue(form, key, value); - break; - } - else - { - value.Append((char)c); - } - } - - if (c == -1) - { - AddRawKeyValue(form, key, value); - return; - } - } - else if (c == '&') - { - AddRawKeyValue(form, key, value); - } - else - { - key.Append((char)c); - } - } - - if (c == -1) - { - AddRawKeyValue(form, key, value); - } - } - } - } - } - - private static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value) - { - form.Add(WebUtility.UrlDecode(key.ToString()), WebUtility.UrlDecode(value.ToString())); - - key.Length = 0; - value.Length = 0; - } - - private Dictionary files; - - private class WebROCollection : QueryParamCollection - { - public override string ToString() - { - var result = new StringBuilder(); - foreach (var pair in this) - { - if (result.Length > 0) - { - result.Append('&'); - } - - var key = pair.Name; - if (key != null && key.Length > 0) - { - result.Append(key); - result.Append('='); - } - - result.Append(pair.Value); - } - - return result.ToString(); - } - } - private class HttpMultipart - { - - public class Element - { - public string ContentType { get; set; } - - public string Name { get; set; } - - public string Filename { get; set; } - - public Encoding Encoding { get; set; } - - public long Start { get; set; } - - public long Length { get; set; } - - public override string ToString() - { - return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " + - Start.ToString(CultureInfo.CurrentCulture) + ", Length " + Length.ToString(CultureInfo.CurrentCulture); - } - } - - private const byte LF = (byte)'\n'; - - private const byte CR = (byte)'\r'; - - private Stream data; - - private string boundary; - - private byte[] boundaryBytes; - - private byte[] buffer; - - private bool atEof; - - private Encoding encoding; - - private StringBuilder sb; - - // See RFC 2046 - // In the case of multipart entities, in which one or more different - // sets of data are combined in a single body, a "multipart" media type - // field must appear in the entity's header. The body must then contain - // one or more body parts, each preceded by a boundary delimiter line, - // and the last one followed by a closing boundary delimiter line. - // After its boundary delimiter line, each body part then consists of a - // header area, a blank line, and a body area. Thus a body part is - // similar to an RFC 822 message in syntax, but different in meaning. - - public HttpMultipart(Stream data, string b, Encoding encoding) - { - this.data = data; - boundary = b; - boundaryBytes = encoding.GetBytes(b); - buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--' - this.encoding = encoding; - sb = new StringBuilder(); - } - - public Element ReadNextElement() - { - if (atEof || ReadBoundary()) - { - return null; - } - - var elem = new Element(); - string header; - while ((header = ReadHeaders()) != null) - { - if (header.StartsWith("Content-Disposition:", StringComparison.OrdinalIgnoreCase)) - { - elem.Name = GetContentDispositionAttribute(header, "name"); - elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); - } - else if (header.StartsWith("Content-Type:", StringComparison.OrdinalIgnoreCase)) - { - elem.ContentType = header.Substring("Content-Type:".Length).Trim(); - elem.Encoding = GetEncoding(elem.ContentType); - } - } - - long start = data.Position; - elem.Start = start; - long pos = MoveToNextBoundary(); - if (pos == -1) - { - return null; - } - - elem.Length = pos - start; - return elem; - } - - private string ReadLine() - { - // CRLF or LF are ok as line endings. - bool got_cr = false; - int b = 0; - sb.Length = 0; - while (true) - { - b = data.ReadByte(); - if (b == -1) - { - return null; - } - - if (b == LF) - { - break; - } - - got_cr = b == CR; - sb.Append((char)b); - } - - if (got_cr) - { - sb.Length--; - } - - return sb.ToString(); - } - - private static string GetContentDispositionAttribute(string l, string name) - { - int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); - if (idx < 0) - { - return null; - } - - int begin = idx + name.Length + "=\"".Length; - int end = l.IndexOf('"', begin); - if (end < 0) - { - return null; - } - - if (begin == end) - { - return string.Empty; - } - - return l.Substring(begin, end - begin); - } - - private string GetContentDispositionAttributeWithEncoding(string l, string name) - { - int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); - if (idx < 0) - { - return null; - } - - int begin = idx + name.Length + "=\"".Length; - int end = l.IndexOf('"', begin); - if (end < 0) - { - return null; - } - - if (begin == end) - { - return string.Empty; - } - - string temp = l.Substring(begin, end - begin); - byte[] source = new byte[temp.Length]; - for (int i = temp.Length - 1; i >= 0; i--) - { - source[i] = (byte)temp[i]; - } - - return encoding.GetString(source, 0, source.Length); - } - - private bool ReadBoundary() - { - try - { - string line; - do - { - line = ReadLine(); - } - while (line.Length == 0); - - if (line[0] != '-' || line[1] != '-') - { - return false; - } - - if (!line.EndsWith(boundary, StringComparison.Ordinal)) - { - return true; - } - } - catch - { - - } - - return false; - } - - private string ReadHeaders() - { - string s = ReadLine(); - if (s.Length == 0) - { - return null; - } - - return s; - } - - private static bool CompareBytes(byte[] orig, byte[] other) - { - for (int i = orig.Length - 1; i >= 0; i--) - { - if (orig[i] != other[i]) - { - return false; - } - } - - return true; - } - - private long MoveToNextBoundary() - { - long retval = 0; - bool got_cr = false; - - int state = 0; - int c = data.ReadByte(); - while (true) - { - if (c == -1) - { - return -1; - } - - if (state == 0 && c == LF) - { - retval = data.Position - 1; - if (got_cr) - { - retval--; - } - - state = 1; - c = data.ReadByte(); - } - else if (state == 0) - { - got_cr = c == CR; - c = data.ReadByte(); - } - else if (state == 1 && c == '-') - { - c = data.ReadByte(); - if (c == -1) - { - return -1; - } - - if (c != '-') - { - state = 0; - got_cr = false; - continue; // no ReadByte() here - } - - int nread = data.Read(buffer, 0, buffer.Length); - int bl = buffer.Length; - if (nread != bl) - { - return -1; - } - - if (!CompareBytes(boundaryBytes, buffer)) - { - state = 0; - data.Position = retval + 2; - if (got_cr) - { - data.Position++; - got_cr = false; - } - - c = data.ReadByte(); - continue; - } - - if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-') - { - atEof = true; - } - else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF) - { - state = 0; - data.Position = retval + 2; - if (got_cr) - { - data.Position++; - got_cr = false; - } - - c = data.ReadByte(); - continue; - } - - data.Position = retval + 2; - if (got_cr) - { - data.Position++; - } - - break; - } - else - { - // state == 1 - state = 0; // no ReadByte() here - } - } - - return retval; - } - - private static string StripPath(string path) - { - if (path == null || path.Length == 0) - { - return path; - } - - if (path.IndexOf(":\\", StringComparison.Ordinal) != 1 - && !path.StartsWith("\\\\", StringComparison.Ordinal)) - { - return path; - } - - return path.Substring(path.LastIndexOf('\\') + 1); - } - } - } -} diff --git a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs deleted file mode 100644 index 9b0951857f..0000000000 --- a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.Net; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Server.SocketSharp -{ - public class SharpWebSocket : IWebSocket - { - /// - /// The logger - /// - private readonly ILogger _logger; - - public event EventHandler Closed; - - /// - /// Gets or sets the web socket. - /// - /// The web socket. - private SocketHttpListener.WebSocket WebSocket { get; set; } - - private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private bool _disposed = false; - - public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) - { - if (socket == null) - { - throw new ArgumentNullException(nameof(socket)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - _logger = logger; - WebSocket = socket; - - socket.OnMessage += OnSocketMessage; - socket.OnClose += OnSocketClose; - socket.OnError += OnSocketError; - } - - public Task ConnectAsServerAsync() - => WebSocket.ConnectAsServer(); - - public Task StartReceive() - { - return _taskCompletionSource.Task; - } - - private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e) - { - _logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty); - - // Closed?.Invoke(this, EventArgs.Empty); - } - - private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e) - { - _taskCompletionSource.TrySetResult(true); - - Closed?.Invoke(this, EventArgs.Empty); - } - - private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e) - { - if (OnReceiveBytes != null) - { - OnReceiveBytes(e.RawData); - } - } - - /// - /// Gets or sets the state. - /// - /// The state. - public WebSocketState State => WebSocket.ReadyState; - - /// - /// Sends the async. - /// - /// The bytes. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) - { - return WebSocket.SendAsync(bytes); - } - - /// - /// Sends the asynchronous. - /// - /// The text. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) - { - return WebSocket.SendAsync(text); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (_disposed) - { - return; - } - - if (dispose) - { - WebSocket.OnMessage -= OnSocketMessage; - WebSocket.OnClose -= OnSocketClose; - WebSocket.OnError -= OnSocketError; - - _cancellationTokenSource.Cancel(); - - WebSocket.CloseAsync().GetAwaiter().GetResult(); - } - - _disposed = true; - } - - /// - /// Gets or sets the receive action. - /// - /// The receive action. - public Action OnReceiveBytes { get; set; } - } -} diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs deleted file mode 100644 index 4dc4103cb1..0000000000 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Collections.Generic; - using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer; -using Emby.Server.Implementations.Net; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - - namespace Jellyfin.Server.SocketSharp -{ - public class WebSocketSharpListener : IHttpListener - { - private HttpListener _listener; - - private readonly ILogger _logger; - - private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); - private CancellationToken _disposeCancellationToken; - - public WebSocketSharpListener( - ILogger logger) - { - _logger = logger; - - _disposeCancellationToken = _disposeCancellationTokenSource.Token; - } - - public Func ErrorHandler { get; set; } - public Func RequestHandler { get; set; } - - public Action WebSocketConnecting { get; set; } - - public Action WebSocketConnected { get; set; } - - public void Start(IEnumerable urlPrefixes) - { - // TODO - //if (_listener == null) - //{ - // _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); - //} - - //_listener.EnableDualMode = _enableDualMode; - - //if (_certificate != null) - //{ - // _listener.LoadCert(_certificate); - //} - - //_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); - //_listener.Prefixes.AddRange(urlPrefixes); - - //_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); - - //_listener.Start(); - - if (_listener == null) - { - _listener = new HttpListener(); - } - - _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); - - //foreach (var urlPrefix in urlPrefixes) - //{ - // _listener.Prefixes.Add(urlPrefix); - //} - _listener.Prefixes.Add("http://localhost:8096/"); - - _listener.Start(); - - // TODO how to do this in netcore? - _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), - null); - } - - private static void LogRequest(ILogger logger, HttpListenerRequest request) - { - var url = request.Url.ToString(); - - logger.LogInformation( - "{0} {1}. UserAgent: {2}", - request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, - url, - request.UserAgent ?? string.Empty); - } - - private Task InitTask(IAsyncResult asyncResult, CancellationToken cancellationToken) - { - var context = _listener.EndGetContext(asyncResult); - _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), null); - IHttpRequest httpReq = null; - var request = context.Request; - - try - { - if (request.IsWebSocketRequest) - { - LogRequest(_logger, request); - - return ProcessWebSocketRequest(context); - } - - httpReq = GetRequest(context); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error processing request"); - - httpReq = httpReq ?? GetRequest(context); - return ErrorHandler(ex, httpReq, true, true); - } - - var uri = request.Url; - - return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); - } - - private async Task ProcessWebSocketRequest(HttpListenerContext ctx) - { - try - { - var endpoint = ctx.Request.RemoteEndPoint.ToString(); - var url = ctx.Request.RawUrl; - - var queryString = new QueryParamCollection(ctx.Request.QueryString); - - var connectingArgs = new WebSocketConnectingEventArgs - { - Url = url, - QueryString = queryString, - Endpoint = endpoint - }; - - WebSocketConnecting?.Invoke(connectingArgs); - - if (connectingArgs.AllowConnection) - { - _logger.LogDebug("Web socket connection allowed"); - - var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); - - if (WebSocketConnected != null) - { - SharpWebSocket socket = null; //new SharpWebSocket(webSocketContext.WebSocket, _logger); - await socket.ConnectAsServerAsync().ConfigureAwait(false); - - WebSocketConnected(new WebSocketConnectEventArgs - { - Url = url, - QueryString = queryString, - WebSocket = null, // socket, - Endpoint = endpoint - }); - - await socket.StartReceive().ConfigureAwait(false); - } - } - else - { - _logger.LogWarning("Web socket connection not allowed"); - TryClose(ctx, 401); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "AcceptWebSocketAsync error"); - TryClose(ctx, 500); - } - } - - private void TryClose(HttpListenerContext ctx, int statusCode) - { - try - { - ctx.Response.StatusCode = statusCode; - ctx.Response.Close(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error closing web socket response"); - } - } - - private IHttpRequest GetRequest(HttpListenerContext httpContext) - { - var urlSegments = httpContext.Request.Url.Segments; - - var operationName = urlSegments[urlSegments.Length - 1]; - - var req = new WebSocketSharpRequest(httpContext, operationName, _logger); - - return req; - } - - public Task Stop() - { - _disposeCancellationTokenSource.Cancel(); - _listener?.Close(); - - return Task.CompletedTask; - } - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool _disposed; - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - /// Whether or not the managed resources should be disposed - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - Stop().GetAwaiter().GetResult(); - } - - _disposed = true; - } - } -} diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs deleted file mode 100644 index 6a00e283c6..0000000000 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ /dev/null @@ -1,535 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; -using Emby.Server.Implementations.HttpServer; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using SocketHttpListener.Net; -using IHttpFile = MediaBrowser.Model.Services.IHttpFile; -using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; -using IResponse = MediaBrowser.Model.Services.IResponse; - -namespace Jellyfin.Server.SocketSharp -{ - public partial class WebSocketSharpRequest : IHttpRequest - { - private readonly HttpListenerRequest request; - private readonly IHttpResponse response; - - public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, ILogger logger) - { - this.OperationName = operationName; - this.request = httpContext.Request; - this.response = new WebSocketSharpResponse(logger, httpContext.Response, this); - - // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); - } - - public HttpListenerRequest HttpRequest => request; - - public object OriginalRequest => request; - - public IResponse Response => response; - - public IHttpResponse HttpResponse => response; - - public string OperationName { get; set; } - - public object Dto { get; set; } - - public string RawUrl => request.RawUrl; - - public string AbsoluteUri => request.Url.AbsoluteUri.TrimEnd('/'); - - public string UserHostAddress => request.UserHostAddress; - - public string XForwardedFor - => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"]; - - public int? XForwardedPort - => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture); - - public string XForwardedProtocol => string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"]; - - public string XRealIp => string.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"]; - - private string remoteIp; - public string RemoteIp => - remoteIp ?? - (remoteIp = CheckBadChars(XForwardedFor) ?? - NormalizeIp(CheckBadChars(XRealIp) ?? - (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null))); - - private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; - - // CheckBadChars - throws on invalid chars to be not found in header name/value - internal static string CheckBadChars(string name) - { - if (name == null || name.Length == 0) - { - return name; - } - - // VALUE check - // Trim spaces from both ends - name = name.Trim(HttpTrimCharacters); - - // First, check for correctly formed multi-line value - // Second, check for absence of CTL characters - int crlf = 0; - for (int i = 0; i < name.Length; ++i) - { - char c = (char)(0x000000ff & (uint)name[i]); - switch (crlf) - { - case 0: - { - if (c == '\r') - { - crlf = 1; - } - else if (c == '\n') - { - // Technically this is bad HTTP. But it would be a breaking change to throw here. - // Is there an exploit? - crlf = 2; - } - else if (c == 127 || (c < ' ' && c != '\t')) - { - throw new ArgumentException("net_WebHeaderInvalidControlChars"); - } - - break; - } - - case 1: - { - if (c == '\n') - { - crlf = 2; - break; - } - - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); - } - - case 2: - { - if (c == ' ' || c == '\t') - { - crlf = 0; - break; - } - - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); - } - } - } - - if (crlf != 0) - { - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); - } - - return name; - } - - internal static bool ContainsNonAsciiChars(string token) - { - for (int i = 0; i < token.Length; ++i) - { - if ((token[i] < 0x20) || (token[i] > 0x7e)) - { - return true; - } - } - - return false; - } - - private string NormalizeIp(string ip) - { - if (!string.IsNullOrWhiteSpace(ip)) - { - // Handle ipv4 mapped to ipv6 - const string srch = "::ffff:"; - var index = ip.IndexOf(srch, StringComparison.OrdinalIgnoreCase); - if (index == 0) - { - ip = ip.Substring(srch.Length); - } - } - - return ip; - } - - public bool IsSecureConnection => request.IsSecureConnection || XForwardedProtocol == "https"; - - public string[] AcceptTypes => request.AcceptTypes; - - private Dictionary items; - public Dictionary Items => items ?? (items = new Dictionary()); - - private string responseContentType; - public string ResponseContentType - { - get => - responseContentType - ?? (responseContentType = GetResponseContentType(this)); - set => this.responseContentType = value; - } - - public const string FormUrlEncoded = "application/x-www-form-urlencoded"; - public const string MultiPartFormData = "multipart/form-data"; - public static string GetResponseContentType(IRequest httpReq) - { - var specifiedContentType = GetQueryStringContentType(httpReq); - if (!string.IsNullOrEmpty(specifiedContentType)) - { - return specifiedContentType; - } - - const string serverDefaultContentType = "application/json"; - - var acceptContentTypes = httpReq.AcceptTypes; - string defaultContentType = null; - if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData)) - { - defaultContentType = serverDefaultContentType; - } - - var acceptsAnything = false; - var hasDefaultContentType = defaultContentType != null; - if (acceptContentTypes != null) - { - foreach (var acceptsType in acceptContentTypes) - { - // TODO: @bond move to Span when Span.Split lands - // https://github.com/dotnet/corefx/issues/26528 - var contentType = acceptsType?.Split(';')[0].Trim(); - acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase); - - if (acceptsAnything) - { - break; - } - } - - if (acceptsAnything) - { - if (hasDefaultContentType) - { - return defaultContentType; - } - else - { - return serverDefaultContentType; - } - } - } - - if (acceptContentTypes == null && httpReq.ContentType == Soap11) - { - return Soap11; - } - - // We could also send a '406 Not Acceptable', but this is allowed also - return serverDefaultContentType; - } - - public const string Soap11 = "text/xml; charset=utf-8"; - - public static bool HasAnyOfContentTypes(IRequest request, params string[] contentTypes) - { - if (contentTypes == null || request.ContentType == null) - { - return false; - } - - foreach (var contentType in contentTypes) - { - if (IsContentType(request, contentType)) - { - return true; - } - } - - return false; - } - - public static bool IsContentType(IRequest request, string contentType) - { - return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase); - } - - private static string GetQueryStringContentType(IRequest httpReq) - { - ReadOnlySpan format = httpReq.QueryString["format"]; - if (format == null) - { - const int formatMaxLength = 4; - ReadOnlySpan pi = httpReq.PathInfo; - if (pi == null || pi.Length <= formatMaxLength) - { - return null; - } - - if (pi[0] == '/') - { - pi = pi.Slice(1); - } - - format = LeftPart(pi, '/'); - if (format.Length > formatMaxLength) - { - return null; - } - } - - format = LeftPart(format, '.'); - if (format.Contains("json", StringComparison.OrdinalIgnoreCase)) - { - return "application/json"; - } - else if (format.Contains("xml", StringComparison.OrdinalIgnoreCase)) - { - return "application/xml"; - } - - return null; - } - - public static string LeftPart(string strVal, char needle) - { - if (strVal == null) - { - return null; - } - - var pos = strVal.IndexOf(needle, StringComparison.Ordinal); - return pos == -1 ? strVal : strVal.Substring(0, pos); - } - - public static ReadOnlySpan LeftPart(ReadOnlySpan strVal, char needle) - { - if (strVal == null) - { - return null; - } - - var pos = strVal.IndexOf(needle); - return pos == -1 ? strVal : strVal.Slice(0, pos); - } - - public static string HandlerFactoryPath; - - private string pathInfo; - public string PathInfo - { - get - { - if (this.pathInfo == null) - { - var mode = HandlerFactoryPath; - - var pos = request.RawUrl.IndexOf('?', StringComparison.Ordinal); - if (pos != -1) - { - var path = request.RawUrl.Substring(0, pos); - this.pathInfo = GetPathInfo( - path, - mode, - mode ?? string.Empty); - } - else - { - this.pathInfo = request.RawUrl; - } - - this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); - this.pathInfo = NormalizePathInfo(pathInfo, mode); - } - - return this.pathInfo; - } - } - - private static string GetPathInfo(string fullPath, string mode, string appPath) - { - var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode); - if (!string.IsNullOrEmpty(pathInfo)) - { - return pathInfo; - } - - // Wildcard mode relies on this to work out the handlerPath - pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath); - if (!string.IsNullOrEmpty(pathInfo)) - { - return pathInfo; - } - - return fullPath; - } - - private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot) - { - if (mappedPathRoot == null) - { - return null; - } - - var sbPathInfo = new StringBuilder(); - var fullPathParts = fullPath.Split('/'); - var mappedPathRootParts = mappedPathRoot.Split('/'); - var fullPathIndexOffset = mappedPathRootParts.Length - 1; - var pathRootFound = false; - - for (var fullPathIndex = 0; fullPathIndex < fullPathParts.Length; fullPathIndex++) - { - if (pathRootFound) - { - sbPathInfo.Append("/" + fullPathParts[fullPathIndex]); - } - else if (fullPathIndex - fullPathIndexOffset >= 0) - { - pathRootFound = true; - for (var mappedPathRootIndex = 0; mappedPathRootIndex < mappedPathRootParts.Length; mappedPathRootIndex++) - { - if (!string.Equals(fullPathParts[fullPathIndex - fullPathIndexOffset + mappedPathRootIndex], mappedPathRootParts[mappedPathRootIndex], StringComparison.OrdinalIgnoreCase)) - { - pathRootFound = false; - break; - } - } - } - } - - if (!pathRootFound) - { - return null; - } - - var path = sbPathInfo.ToString(); - return path.Length > 1 ? path.TrimEnd('/') : "/"; - } - - private Dictionary cookies; - public IDictionary Cookies - { - get - { - if (cookies == null) - { - cookies = new Dictionary(); - foreach (var cookie in this.request.Cookies) - { - var httpCookie = (System.Net.Cookie)cookie; - cookies[httpCookie.Name] = new System.Net.Cookie(httpCookie.Name, httpCookie.Value, httpCookie.Path, httpCookie.Domain); - } - } - - return cookies; - } - } - - public string UserAgent => request.UserAgent; - - public QueryParamCollection Headers => new QueryParamCollection(request.Headers); - - private QueryParamCollection queryString; - public QueryParamCollection QueryString => queryString ?? (queryString = MyHttpUtility.ParseQueryString(request.Url.Query)); - - public bool IsLocal => request.IsLocal; - - private string httpMethod; - public string HttpMethod => - httpMethod - ?? (httpMethod = request.HttpMethod); - - public string Verb => HttpMethod; - - public string ContentType => request.ContentType; - - private Encoding contentEncoding; - public Encoding ContentEncoding - { - get => contentEncoding ?? request.ContentEncoding; - set => contentEncoding = value; - } - - public Uri UrlReferrer => request.UrlReferrer; - - public static Encoding GetEncoding(string contentTypeHeader) - { - var param = GetParameter(contentTypeHeader, "charset="); - if (param == null) - { - return null; - } - - try - { - return Encoding.GetEncoding(param); - } - catch (ArgumentException) - { - return null; - } - } - - public Stream InputStream => request.InputStream; - - public long ContentLength => request.ContentLength64; - - private IHttpFile[] httpFiles; - public IHttpFile[] Files - { - get - { - if (httpFiles == null) - { - if (files == null) - { - return httpFiles = Array.Empty(); - } - - httpFiles = new IHttpFile[files.Count]; - var i = 0; - foreach (var pair in files) - { - var reqFile = pair.Value; - httpFiles[i] = new HttpFile - { - ContentType = reqFile.ContentType, - ContentLength = reqFile.ContentLength, - FileName = reqFile.FileName, - InputStream = reqFile.InputStream, - }; - i++; - } - } - - return httpFiles; - } - } - - public static string NormalizePathInfo(string pathInfo, string handlerPath) - { - if (handlerPath != null) - { - var trimmed = pathInfo.TrimStart('/'); - if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase)) - { - return trimmed.Substring(handlerPath.Length); - } - } - - return pathInfo; - } - } -} diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs deleted file mode 100644 index 552ae1754d..0000000000 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; -using IRequest = MediaBrowser.Model.Services.IRequest; - -namespace Jellyfin.Server.SocketSharp -{ - public class WebSocketSharpResponse : IHttpResponse - { - private readonly ILogger _logger; - - private readonly HttpListenerResponse _response; - - public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request) - { - _logger = logger; - this._response = response; - Items = new Dictionary(); - Request = request; - } - - public IRequest Request { get; private set; } - - public Dictionary Items { get; private set; } - - public object OriginalResponse => _response; - - public int StatusCode - { - get => this._response.StatusCode; - set => this._response.StatusCode = value; - } - - public string StatusDescription - { - get => this._response.StatusDescription; - set => this._response.StatusDescription = value; - } - - public string ContentType - { - get => _response.ContentType; - set => _response.ContentType = value; - } - - public QueryParamCollection Headers => new QueryParamCollection(_response.Headers); - - private static string AsHeaderValue(Cookie cookie) - { - DateTime defaultExpires = DateTime.MinValue; - - var path = cookie.Expires == defaultExpires - ? "/" - : cookie.Path ?? "/"; - - var sb = new StringBuilder(); - - sb.Append($"{cookie.Name}={cookie.Value};path={path}"); - - if (cookie.Expires != defaultExpires) - { - sb.Append($";expires={cookie.Expires:R}"); - } - - if (!string.IsNullOrEmpty(cookie.Domain)) - { - sb.Append($";domain={cookie.Domain}"); - } - - if (cookie.Secure) - { - sb.Append(";Secure"); - } - - if (cookie.HttpOnly) - { - sb.Append(";HttpOnly"); - } - - return sb.ToString(); - } - - public void AddHeader(string name, string value) - { - if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) - { - ContentType = value; - return; - } - - _response.AddHeader(name, value); - } - - public string GetHeader(string name) - { - return _response.Headers[name]; - } - - public void Redirect(string url) - { - _response.Redirect(url); - } - - public Stream OutputStream => _response.OutputStream; - - public void Close() - { - if (!this.IsClosed) - { - this.IsClosed = true; - - try - { - var response = this._response; - - var outputStream = response.OutputStream; - - // This is needed with compression - outputStream.Flush(); - outputStream.Dispose(); - - response.Close(); - } - catch (SocketException) - { - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in HttpListenerResponseWrapper"); - } - } - } - - public bool IsClosed - { - get; - private set; - } - - public void SetContentLength(long contentLength) - { - // you can happily set the Content-Length header in Asp.Net - // but HttpListener will complain if you do - you have to set ContentLength64 on the response. - // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header - //_response.ContentLength64 = contentLength; - } - - public void SetCookie(Cookie cookie) - { - var cookieStr = AsHeaderValue(cookie); - _response.Headers.Add("Set-Cookie", cookieStr); - } - - public bool SendChunked - { - get => _response.SendChunked; - set => _response.SendChunked = value; - } - - public bool KeepAlive { get; set; } - - public void ClearCookies() - { - } - const int StreamCopyToBufferSize = 81920; - public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) - { - // TODO - // return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken); - var allowAsync = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - - //if (count <= 0) - //{ - // allowAsync = true; - //} - - var fileOpenOptions = FileOpenOptions.SequentialScan; - - if (allowAsync) - { - fileOpenOptions |= FileOpenOptions.Asynchronous; - } - - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - - using (var fs = fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions)) - { - if (offset > 0) - { - fs.Position = offset; - } - - if (count > 0) - { - await streamHelper.CopyToAsync(fs, OutputStream, count, cancellationToken).ConfigureAwait(false); - } - else - { - await fs.CopyToAsync(OutputStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } - } - } - } -} -- cgit v1.2.3 From e88f079da64a978d560f739e9bf17cd0a93d122a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:40:05 +0100 Subject: Remove websocket options and configure kestrel listen ports --- Emby.Server.Implementations/ApplicationHost.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ad6b8e84a8..7be39d6744 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -111,13 +111,11 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using ServiceStack; -using HttpResponse = MediaBrowser.Model.Net.HttpResponse; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; namespace Emby.Server.Implementations @@ -623,21 +621,24 @@ namespace Emby.Server.Implementations FindParts(); Host = new WebHostBuilder() - .UseKestrel() + .UseKestrel(options => + { + options.Listen(IPAddress.Any, HttpPort); + options.Listen(IPAddress.Loopback, HttpPort); + // TODO certs + options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); + options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); + }) .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) - .UseUrls("http://localhost:8096") .ConfigureServices(services => { services.AddResponseCompression(); services.AddHttpContextAccessor(); services.TryAddSingleton(); }) - .Configure( app => + .Configure(app => { - app.UseWebSockets(new WebSocketOptions { - KeepAliveInterval = TimeSpan.FromMilliseconds(1000000000), - ReceiveBufferSize = 0x10000 - }); + app.UseWebSockets(); app.UseResponseCompression(); app.Use(ExecuteWebsocketHandlerAsync); -- cgit v1.2.3 From 4e229ad86b3a3b6024ca0c29c17e4aac5386a210 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:50:10 +0100 Subject: Fix PathInfo --- Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index b07c4bfebb..146862e02b 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -341,10 +341,10 @@ namespace Emby.Server.Implementations.SocketSharp { var mode = HandlerFactoryPath; - var pos = request.Path.ToString().IndexOf("?", StringComparison.Ordinal); + var pos = RawUrl.IndexOf("?", StringComparison.Ordinal); if (pos != -1) { - var path = request.Path.ToString().Substring(0, pos); + var path = RawUrl.Substring(0, pos); this.pathInfo = GetPathInfo( path, mode, @@ -352,10 +352,10 @@ namespace Emby.Server.Implementations.SocketSharp } else { - this.pathInfo = request.Path.ToString(); + this.pathInfo = RawUrl; } - this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); + this.pathInfo = WebUtility.UrlDecode(pathInfo); this.pathInfo = NormalizePathInfo(pathInfo, mode); } -- cgit v1.2.3 From f1c93ae618f293eae4cc384fadfd4440c4e0cf2d Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 21:08:30 +0100 Subject: Remove SetContentLength and company --- .../HttpClientManager/HttpClientManager.cs | 1 - Emby.Server.Implementations/HttpServer/FileWriter.cs | 3 --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 6 +----- Emby.Server.Implementations/HttpServer/HttpResultFactory.cs | 13 ++++--------- .../HttpServer/RangeRequestWriter.cs | 3 +-- Emby.Server.Implementations/HttpServer/ResponseFilter.cs | 1 - Emby.Server.Implementations/HttpServer/StreamWriter.cs | 10 +--------- Emby.Server.Implementations/Services/HttpResult.cs | 7 +------ Emby.Server.Implementations/Services/ResponseHelper.cs | 7 ------- .../SocketSharp/WebSocketSharpResponse.cs | 8 -------- MediaBrowser.Model/Services/IRequest.cs | 2 -- 11 files changed, 8 insertions(+), 53 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 834a494f81..ef82d1d9ee 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -327,7 +327,6 @@ namespace Emby.Server.Implementations.HttpClientManager } httpWebRequest.ContentType = contentType; - // httpWebRequest.ContentLength = bytes.Length; (await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length); } catch (Exception ex) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 835c6f52ef..1375089e39 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -63,8 +63,6 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(rangeHeader)) { - // TODO - //Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); StatusCode = HttpStatusCode.OK; } else @@ -99,7 +97,6 @@ namespace Emby.Server.Implementations.HttpServer // Content-Length is the length of what we're serving, not the original content var lengthString = RangeLength.ToString(UsCulture); - // TODO Headers["Content-Length"] = lengthString; var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); Headers["Content-Range"] = rangeString; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 12c23a8693..6b69fa0f4e 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -649,11 +649,6 @@ namespace Emby.Server.Implementations.HttpServer private static Task Write(IResponse response, string text) { var bOutput = Encoding.UTF8.GetBytes(text); - response.SetContentLength(bOutput.Length); - // TODO - response.Headers.Remove("Content-Length"); // DO NOT SET THIS, IT'S DONE AUTOMATICALLY BECAUSE MS ARE NOT STUPID - response.SendChunked = true; - return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length); } @@ -672,6 +667,7 @@ namespace Emby.Server.Implementations.HttpServer } else { + // TODO what is this? var httpsUrl = url .Replace("http://", "https://", StringComparison.OrdinalIgnoreCase) .Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase); diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 5e9d2b4c36..09cdbc3c26 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.HttpServer content = Array.Empty(); } - result = new StreamWriter(content, contentType, contentLength); + result = new StreamWriter(content, contentType); } else { @@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.HttpServer bytes = Array.Empty(); } - result = new StreamWriter(bytes, contentType, contentLength); + result = new StreamWriter(bytes, contentType); } else { @@ -334,13 +334,13 @@ namespace Emby.Server.Implementations.HttpServer if (isHeadRequest) { - var result = new StreamWriter(Array.Empty(), contentType, contentLength); + var result = new StreamWriter(Array.Empty(), contentType); AddResponseHeaders(result, responseHeaders); return result; } else { - var result = new StreamWriter(content, contentType, contentLength); + var result = new StreamWriter(content, contentType); AddResponseHeaders(result, responseHeaders); return result; } @@ -590,11 +590,6 @@ namespace Emby.Server.Implementations.HttpServer } else { - if (totalContentLength.HasValue) - { - // TODO responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture); - } - if (isHeadRequest) { using (stream) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 9c8ab8d91c..8904e11d30 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -96,8 +96,7 @@ namespace Emby.Server.Implementations.HttpServer RangeLength = 1 + RangeEnd - RangeStart; // Content-Length is the length of what we're serving, not the original content - // TODO Headers["Content-Length"] = RangeLength.ToString(UsCulture); - Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); + Headers["Content-Range"] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; if (RangeStart > 0 && SourceStream.CanSeek) { diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index dbce3250d4..ae6a6576e0 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -57,7 +57,6 @@ namespace Emby.Server.Implementations.HttpServer if (length > 0) { - res.SetContentLength(length); res.SendChunked = false; } } diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 750b0f7958..90354385dc 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -53,11 +53,6 @@ namespace Emby.Server.Implementations.HttpServer SourceStream = source; Headers["Content-Type"] = contentType; - - if (source.CanSeek) - { - // TODO Headers["Content-Length"] = source.Length.ToString(UsCulture); - } } /// @@ -65,8 +60,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// The source. /// Type of the content. - /// The logger. - public StreamWriter(byte[] source, string contentType, int contentLength) + public StreamWriter(byte[] source, string contentType) { if (string.IsNullOrEmpty(contentType)) { @@ -76,8 +70,6 @@ namespace Emby.Server.Implementations.HttpServer SourceBytes = source; Headers["Content-Type"] = contentType; - - // TODO Headers["Content-Length"] = contentLength.ToString(UsCulture); } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs index 296da2f7a0..b6758486ce 100644 --- a/Emby.Server.Implementations/Services/HttpResult.cs +++ b/Emby.Server.Implementations/Services/HttpResult.cs @@ -43,14 +43,9 @@ namespace Emby.Server.Implementations.Services { var contentLength = bytesResponse.Length; - if (response != null) - { - response.SetContentLength(contentLength); - } - if (contentLength > 0) { - await responseStream.WriteAsync(bytesResponse, 0, contentLength).ConfigureAwait(false); + await responseStream.WriteAsync(bytesResponse, 0, contentLength, cancellationToken).ConfigureAwait(false); } return; } diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index dc99753477..0301ff335f 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -20,8 +20,6 @@ namespace Emby.Server.Implementations.Services { response.StatusCode = (int)HttpStatusCode.NoContent; } - - response.SetContentLength(0); return Task.CompletedTask; } @@ -55,7 +53,6 @@ namespace Emby.Server.Implementations.Services { if (string.Equals(responseHeaders.Key, "Content-Length", StringComparison.OrdinalIgnoreCase)) { - response.SetContentLength(long.Parse(responseHeaders.Value)); continue; } @@ -104,7 +101,6 @@ namespace Emby.Server.Implementations.Services if (bytes != null) { response.ContentType = "application/octet-stream"; - response.SetContentLength(bytes.Length); if (bytes.Length > 0) { @@ -117,7 +113,6 @@ namespace Emby.Server.Implementations.Services if (responseText != null) { bytes = Encoding.UTF8.GetBytes(responseText); - response.SetContentLength(bytes.Length); if (bytes.Length > 0) { return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); @@ -149,8 +144,6 @@ namespace Emby.Server.Implementations.Services var contentLength = ms.Length; - response.SetContentLength(contentLength); - if (contentLength > 0) { await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index c68b959846..f9ecb52a55 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -143,14 +143,6 @@ namespace Emby.Server.Implementations.SocketSharp private set; } - public void SetContentLength(long contentLength) - { - // you can happily set the Content-Length header in Asp.Net - // but HttpListener will complain if you do - you have to set ContentLength64 on the response. - // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header - //_response.ContentLength64 = contentLength; - } - public void SetCookie(Cookie cookie) { var cookieStr = AsHeaderValue(cookie); diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 909cebce3d..b4ad24fec8 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -145,8 +145,6 @@ namespace MediaBrowser.Model.Services /// bool IsClosed { get; } - void SetContentLength(long contentLength); - //Add Metadata to Response Dictionary Items { get; } -- cgit v1.2.3 From e342b7bc71f16286da699bcce299b76df63360dc Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 21:27:02 +0100 Subject: Extend the IHttpServer interface to avoid the typecasting --- Emby.Server.Implementations/ApplicationHost.cs | 4 ++-- .../HttpServer/IHttpListener.cs | 14 -------------- MediaBrowser.Controller/Net/IHttpServer.cs | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 16 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7be39d6744..a1ea179040 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -655,7 +655,7 @@ namespace Emby.Server.Implementations return; } - await ((HttpListenerHost)HttpServer).ProcessWebSocketRequest(context).ConfigureAwait(false); + await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { @@ -670,7 +670,7 @@ namespace Emby.Server.Implementations var localPath = context.Request.Path.ToString(); var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); + await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 8350913610..1ef65d9d73 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; -using MediaBrowser.Controller.Net; using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.HttpServer @@ -28,18 +26,6 @@ namespace Emby.Server.Implementations.HttpServer /// The web socket handler. Action WebSocketConnected { get; set; } - /// - /// Gets or sets the web socket connecting. - /// - /// The web socket connecting. - Action WebSocketConnecting { get; set; } - - /// - /// Starts this instance. - /// - /// The URL prefixes. - void Start(IEnumerable urlPrefixes); - /// /// Stops this instance. /// diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index f413030076..cede9a98fb 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Model.Events; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { @@ -35,5 +38,24 @@ namespace MediaBrowser.Controller.Net /// If set, all requests will respond with this message /// string GlobalResponse { get; set; } + + /// + /// Sends the http context to the socket listener + /// + /// + /// + Task ProcessWebSocketRequest(HttpContext ctx); + + /// + /// The HTTP request handler + /// + /// + /// + /// + /// + /// + /// + Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, + CancellationToken cancellationToken); } } -- cgit v1.2.3 From 148db8b81ac4bb52262e071e6920b1449c8611fe Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 21:51:02 +0100 Subject: Remove unused SharpSocket stuff --- .../SocketSharp/SharpWebSocket.cs | 44 +++------------------- .../SocketSharp/WebSocketSharpListener.cs | 1 - 2 files changed, 5 insertions(+), 40 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index eab903db8e..66c5cc334b 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Gets or sets the web socket. /// /// The web socket. - private WebSocket WebSocket { get; set; } + private readonly WebSocket _webSocket; private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); @@ -30,45 +30,14 @@ namespace Emby.Server.Implementations.SocketSharp public SharpWebSocket(WebSocket socket, ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - WebSocket = socket ?? throw new ArgumentNullException(nameof(socket)); + _webSocket = socket ?? throw new ArgumentNullException(nameof(socket)); } - public Task StartReceive() - { - return _taskCompletionSource.Task; - } - - private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e) - { - _logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty); - - // Closed?.Invoke(this, EventArgs.Empty); - } - - private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e) - { - _taskCompletionSource.TrySetResult(true); - - Closed?.Invoke(this, EventArgs.Empty); - } - - private void OnSocketMessage(SocketHttpListener.MessageEventArgs e) - { - if (OnReceiveBytes != null) - { - OnReceiveBytes(e.RawData); - } - } - - public Task ConnectAsServerAsync() - { - return Task.CompletedTask; - } /// /// Gets or sets the state. /// /// The state. - public WebSocketState State => WebSocket.State; + public WebSocketState State => _webSocket.State; /// /// Sends the async. @@ -79,7 +48,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken); + return _webSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken); } /// @@ -91,7 +60,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken); + return _webSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken); } /// @@ -117,9 +86,6 @@ namespace Emby.Server.Implementations.SocketSharp if (dispose) { _cancellationTokenSource.Cancel(); - - // TODO - // WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 6e5190efdb..7a6144fb23 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -71,7 +71,6 @@ using Microsoft.Extensions.Logging; var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); var socket = new SharpWebSocket(webSocketContext, _logger); - await socket.ConnectAsServerAsync().ConfigureAwait(false); WebSocketConnected(new WebSocketConnectEventArgs { -- cgit v1.2.3 From 9c02e99e35fa6811502944b19dd1479750ff8f20 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 22:40:25 +0100 Subject: Undo some of the span abuse --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Emby.Server.Implementations.csproj | 1 + .../SocketSharp/WebSocketSharpRequest.cs | 25 ++++++---------------- 3 files changed, 9 insertions(+), 19 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a1ea179040..0b4a2fd30e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1676,7 +1676,7 @@ namespace Emby.Server.Implementations LogErrorResponseBody = false, LogErrors = logPing, LogRequest = logPing, - TimeoutMs = 30000, + TimeoutMs = 5000, BufferContent = false, CancellationToken = cancellationToken diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index a4dac4a7f9..230e4892c7 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -40,6 +40,7 @@ + diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 146862e02b..35fff08216 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -273,11 +273,11 @@ namespace Emby.Server.Implementations.SocketSharp private static string GetQueryStringContentType(HttpRequest httpReq) { - string format = httpReq.Query["format"]; + ReadOnlySpan format = httpReq.Query["format"].ToString().AsSpan(); if (format == null) { const int formatMaxLength = 4; - string pi = httpReq.Path.ToString(); + ReadOnlySpan pi = httpReq.Path.ToString().AsSpan(); if (pi == null || pi.Length <= formatMaxLength) { return null; @@ -285,7 +285,7 @@ namespace Emby.Server.Implementations.SocketSharp if (pi[0] == '/') { - pi = pi.Substring(1); + pi = pi.Slice(1); } format = LeftPart(pi, '/'); @@ -296,11 +296,11 @@ namespace Emby.Server.Implementations.SocketSharp } format = LeftPart(format, '.'); - if (format.ToLower().Contains("json")) + if (format.Contains("json".AsSpan(), StringComparison.OrdinalIgnoreCase)) { return "application/json"; } - else if (format.ToLower().Contains("xml")) + else if (format.Contains("xml".AsSpan(), StringComparison.OrdinalIgnoreCase)) { return "application/xml"; } @@ -308,25 +308,14 @@ namespace Emby.Server.Implementations.SocketSharp return null; } - public static string LeftPart(string strVal, char needle) + public static ReadOnlySpan LeftPart(ReadOnlySpan strVal, char needle) { if (strVal == null) { return null; } - var pos = strVal.IndexOf(needle.ToString(), StringComparison.Ordinal); - return pos == -1 ? strVal : strVal.Substring(0, pos); - } - - public static ReadOnlySpan LeftPart(ReadOnlySpan strVal, char needle) - { - if (strVal == null) - { - return null; - } - - var pos = strVal.IndexOf(needle.ToString()); + var pos = strVal.IndexOf(needle); return pos == -1 ? strVal : strVal.Slice(0, pos); } -- cgit v1.2.3 From 5510e8ebee743a3d07571051660b514d6cce973c Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 22:53:59 +0100 Subject: Remove unused Cookies --- .../SocketSharp/WebSocketSharpRequest.cs | 19 ------------------- MediaBrowser.Model/Services/IRequest.cs | 2 -- 2 files changed, 21 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 35fff08216..38d0332308 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -412,25 +412,6 @@ namespace Emby.Server.Implementations.SocketSharp return path.Length > 1 ? path.TrimEnd('/') : "/"; } - private Dictionary cookies; - public IDictionary Cookies - { - get - { - if (cookies == null) - { - cookies = new Dictionary(); - foreach (var cookie in this.request.Cookies) - { - var httpCookie = cookie; - cookies[httpCookie.Key] = new Cookie(httpCookie.Key, httpCookie.Value, "", ""); - } - } - - return cookies; - } - } - public string UserAgent => request.Headers[HeaderNames.UserAgent]; public QueryParamCollection Headers => new QueryParamCollection(request.Headers); diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index b4ad24fec8..c7dccdb6fd 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -41,8 +41,6 @@ namespace MediaBrowser.Model.Services string UserAgent { get; } - IDictionary Cookies { get; } - /// /// The expected Response ContentType for this request /// -- cgit v1.2.3 From 848cfc32cc89327e16ff6ea281dc1d9b96cc4f7a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 22:57:59 +0100 Subject: More cleanup --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 1 - .../SocketSharp/WebSocketSharpListener.cs | 13 ++----------- 2 files changed, 2 insertions(+), 12 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 6b69fa0f4e..5b1cb433a9 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -803,7 +803,6 @@ namespace Emby.Server.Implementations.HttpServer private bool _disposed; private readonly object _disposeLock = new object(); - private readonly WebSocketSharpListener _websocketlistener; protected virtual void Dispose(bool disposing) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 7a6144fb23..77469244b9 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -17,8 +17,6 @@ using Microsoft.Extensions.Logging; { public class WebSocketSharpListener : IHttpListener { - private HttpListener _listener; - private readonly ILogger _logger; private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); @@ -86,14 +84,14 @@ using Microsoft.Extensions.Logging; do { - result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); + result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); socket.OnReceiveBytes(buffer.Array); message.AddRange(buffer.Array.Take(result.Count)); } while (!result.EndOfMessage && result.MessageType != WebSocketMessageType.Close); socket.OnReceiveBytes(message.ToArray()); await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, CancellationToken.None); + result.CloseStatusDescription, _disposeCancellationToken); socket.Dispose(); } else @@ -109,16 +107,9 @@ using Microsoft.Extensions.Logging; } } - public void Start(IEnumerable urlPrefixes) - { - throw new NotImplementedException(); - } - public Task Stop() { _disposeCancellationTokenSource.Cancel(); - _listener?.Close(); - return Task.CompletedTask; } -- cgit v1.2.3 From 77addb22835478a32c1133cfd69ae0da2ec5edea Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 07:32:36 +0100 Subject: Remove SocketHttpListener --- .../Emby.Server.Implementations.csproj | 2 - .../SocketSharp/SharpWebSocket.cs | 3 +- .../SocketSharp/WebSocketSharpRequest.cs | 1 - MediaBrowser.sln | 379 ++++----- SocketHttpListener/ByteOrder.cs | 17 - SocketHttpListener/CloseEventArgs.cs | 79 -- SocketHttpListener/CloseStatusCode.cs | 94 -- SocketHttpListener/CompressionMethod.cs | 23 - SocketHttpListener/ErrorEventArgs.cs | 42 - SocketHttpListener/Ext.cs | 946 --------------------- SocketHttpListener/Fin.cs | 8 - SocketHttpListener/HttpBase.cs | 70 -- SocketHttpListener/HttpResponse.cs | 122 --- SocketHttpListener/Mask.cs | 8 - SocketHttpListener/MessageEventArgs.cs | 84 -- SocketHttpListener/Net/CookieHelper.cs | 141 --- SocketHttpListener/Opcode.cs | 43 - SocketHttpListener/PayloadData.cs | 130 --- SocketHttpListener/Properties/AssemblyInfo.cs | 21 - SocketHttpListener/Rsv.cs | 8 - SocketHttpListener/SocketHttpListener.csproj | 18 - SocketHttpListener/SocketStream.cs | 74 -- SocketHttpListener/WebSocket.cs | 777 ----------------- SocketHttpListener/WebSocketException.cs | 61 -- SocketHttpListener/WebSocketFrame.cs | 432 ---------- 25 files changed, 188 insertions(+), 3395 deletions(-) delete mode 100644 SocketHttpListener/ByteOrder.cs delete mode 100644 SocketHttpListener/CloseEventArgs.cs delete mode 100644 SocketHttpListener/CloseStatusCode.cs delete mode 100644 SocketHttpListener/CompressionMethod.cs delete mode 100644 SocketHttpListener/ErrorEventArgs.cs delete mode 100644 SocketHttpListener/Ext.cs delete mode 100644 SocketHttpListener/Fin.cs delete mode 100644 SocketHttpListener/HttpBase.cs delete mode 100644 SocketHttpListener/HttpResponse.cs delete mode 100644 SocketHttpListener/Mask.cs delete mode 100644 SocketHttpListener/MessageEventArgs.cs delete mode 100644 SocketHttpListener/Net/CookieHelper.cs delete mode 100644 SocketHttpListener/Opcode.cs delete mode 100644 SocketHttpListener/PayloadData.cs delete mode 100644 SocketHttpListener/Properties/AssemblyInfo.cs delete mode 100644 SocketHttpListener/Rsv.cs delete mode 100644 SocketHttpListener/SocketHttpListener.csproj delete mode 100644 SocketHttpListener/SocketStream.cs delete mode 100644 SocketHttpListener/WebSocket.cs delete mode 100644 SocketHttpListener/WebSocketException.cs delete mode 100644 SocketHttpListener/WebSocketFrame.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 230e4892c7..d4db5f2fcc 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -9,7 +9,6 @@ - @@ -40,7 +39,6 @@ - diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 66c5cc334b..dcbfb80487 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -23,9 +23,8 @@ namespace Emby.Server.Implementations.SocketSharp /// The web socket. private readonly WebSocket _webSocket; - private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private bool _disposed = false; + private bool _disposed; public SharpWebSocket(WebSocket socket, ILogger logger) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 38d0332308..53dce667ba 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -using SocketHttpListener.Net; using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 62ae58d732..5edc1a25a6 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,192 +1,187 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.3 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocketHttpListener", "SocketHttpListener\SocketHttpListener.csproj", "{1D74413B-E7CF-455B-B021-F52BDF881542}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - SharedVersion.cs = SharedVersion.cs - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Release|Any CPU.Build.0 = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} - EndGlobalSection - GlobalSection(AutomaticVersions) = postSolution - UpdateAssemblyVersion = True - UpdateAssemblyFileVersion = True - UpdateAssemblyInfoVersion = True - AssemblyVersionSettings = None.None.None.None - AssemblyFileVersionSettings = None.None.None.None - AssemblyInfoVersionSettings = None.None.None.None - UpdatePackageVersion = False - AssemblyInfoVersionType = SettingsVersion - InheritWinAppVersionFrom = None - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - Policies = $0 - $0.StandardHeader = $1 - $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n - $0.DotNetNamingPolicy = $2 - $2.DirectoryNamespaceAssociation = PrefixedHierarchical - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + SharedVersion.cs = SharedVersion.cs + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} + EndGlobalSection + GlobalSection(AutomaticVersions) = postSolution + UpdateAssemblyVersion = True + UpdateAssemblyFileVersion = True + UpdateAssemblyInfoVersion = True + AssemblyVersionSettings = None.None.None.None + AssemblyFileVersionSettings = None.None.None.None + AssemblyInfoVersionSettings = None.None.None.None + UpdatePackageVersion = False + AssemblyInfoVersionType = SettingsVersion + InheritWinAppVersionFrom = None + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.StandardHeader = $1 + $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n + $0.DotNetNamingPolicy = $2 + $2.DirectoryNamespaceAssociation = PrefixedHierarchical + EndGlobalSection +EndGlobal diff --git a/SocketHttpListener/ByteOrder.cs b/SocketHttpListener/ByteOrder.cs deleted file mode 100644 index c04150c74b..0000000000 --- a/SocketHttpListener/ByteOrder.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values that indicate whether the byte order is a Little-endian or Big-endian. - /// - public enum ByteOrder : byte - { - /// - /// Indicates a Little-endian. - /// - Little, - /// - /// Indicates a Big-endian. - /// - Big - } -} diff --git a/SocketHttpListener/CloseEventArgs.cs b/SocketHttpListener/CloseEventArgs.cs deleted file mode 100644 index c6460fd230..0000000000 --- a/SocketHttpListener/CloseEventArgs.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Text; - -namespace SocketHttpListener -{ - /// - /// Contains the event data associated with a event. - /// - /// - /// A event occurs when the WebSocket connection has been closed. - /// If you would like to get the reason for the close, you should access the or - /// property. - /// - public class CloseEventArgs : EventArgs - { - #region Private Fields - - private bool _clean; - private ushort _code; - private string _reason; - - #endregion - - #region Internal Constructors - - internal CloseEventArgs(PayloadData payload) - { - var data = payload.ApplicationData; - var len = data.Length; - _code = len > 1 - ? data.SubArray(0, 2).ToUInt16(ByteOrder.Big) - : (ushort)CloseStatusCode.NoStatusCode; - - _reason = len > 2 - ? GetUtf8String(data.SubArray(2, len - 2)) - : string.Empty; - } - - private static string GetUtf8String(byte[] bytes) - { - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } - - #endregion - - #region Public Properties - - /// - /// Gets the status code for the close. - /// - /// - /// A that represents the status code for the close if any. - /// - public ushort Code => _code; - - /// - /// Gets the reason for the close. - /// - /// - /// A that represents the reason for the close if any. - /// - public string Reason => _reason; - - /// - /// Gets a value indicating whether the WebSocket connection has been closed cleanly. - /// - /// - /// true if the WebSocket connection has been closed cleanly; otherwise, false. - /// - public bool WasClean - { - get => _clean; - - internal set => _clean = value; - } - - #endregion - } -} diff --git a/SocketHttpListener/CloseStatusCode.cs b/SocketHttpListener/CloseStatusCode.cs deleted file mode 100644 index 428595bb09..0000000000 --- a/SocketHttpListener/CloseStatusCode.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values of the status code for the WebSocket connection close. - /// - /// - /// - /// The values of the status code are defined in - /// Section 7.4 - /// of RFC 6455. - /// - /// - /// "Reserved value" must not be set as a status code in a close control frame - /// by an endpoint. It's designated for use in applications expecting a status - /// code to indicate that the connection was closed due to the system grounds. - /// - /// - public enum CloseStatusCode : ushort - { - /// - /// Equivalent to close status 1000. - /// Indicates a normal close. - /// - Normal = 1000, - /// - /// Equivalent to close status 1001. - /// Indicates that an endpoint is going away. - /// - Away = 1001, - /// - /// Equivalent to close status 1002. - /// Indicates that an endpoint is terminating the connection due to a protocol error. - /// - ProtocolError = 1002, - /// - /// Equivalent to close status 1003. - /// Indicates that an endpoint is terminating the connection because it has received - /// an unacceptable type message. - /// - IncorrectData = 1003, - /// - /// Equivalent to close status 1004. - /// Still undefined. Reserved value. - /// - Undefined = 1004, - /// - /// Equivalent to close status 1005. - /// Indicates that no status code was actually present. Reserved value. - /// - NoStatusCode = 1005, - /// - /// Equivalent to close status 1006. - /// Indicates that the connection was closed abnormally. Reserved value. - /// - Abnormal = 1006, - /// - /// Equivalent to close status 1007. - /// Indicates that an endpoint is terminating the connection because it has received - /// a message that contains a data that isn't consistent with the type of the message. - /// - InconsistentData = 1007, - /// - /// Equivalent to close status 1008. - /// Indicates that an endpoint is terminating the connection because it has received - /// a message that violates its policy. - /// - PolicyViolation = 1008, - /// - /// Equivalent to close status 1009. - /// Indicates that an endpoint is terminating the connection because it has received - /// a message that is too big to process. - /// - TooBig = 1009, - /// - /// Equivalent to close status 1010. - /// Indicates that the client is terminating the connection because it has expected - /// the server to negotiate one or more extension, but the server didn't return them - /// in the handshake response. - /// - IgnoreExtension = 1010, - /// - /// Equivalent to close status 1011. - /// Indicates that the server is terminating the connection because it has encountered - /// an unexpected condition that prevented it from fulfilling the request. - /// - ServerError = 1011, - /// - /// Equivalent to close status 1015. - /// Indicates that the connection was closed due to a failure to perform a TLS handshake. - /// Reserved value. - /// - TlsHandshakeFailure = 1015 - } -} diff --git a/SocketHttpListener/CompressionMethod.cs b/SocketHttpListener/CompressionMethod.cs deleted file mode 100644 index d6bcd63d89..0000000000 --- a/SocketHttpListener/CompressionMethod.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values of the compression method used to compress the message on the WebSocket - /// connection. - /// - /// - /// The values of the compression method are defined in - /// Compression - /// Extensions for WebSocket. - /// - public enum CompressionMethod : byte - { - /// - /// Indicates non compression. - /// - None, - /// - /// Indicates using DEFLATE. - /// - Deflate - } -} diff --git a/SocketHttpListener/ErrorEventArgs.cs b/SocketHttpListener/ErrorEventArgs.cs deleted file mode 100644 index 9502d2a156..0000000000 --- a/SocketHttpListener/ErrorEventArgs.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; - -namespace SocketHttpListener -{ - /// - /// Contains the event data associated with a event. - /// - /// - /// A event occurs when the gets an error. - /// If you would like to get the error message, you should access the - /// property. - /// - public class ErrorEventArgs : EventArgs - { - #region Private Fields - - private string _message; - - #endregion - - #region Internal Constructors - - internal ErrorEventArgs(string message) - { - _message = message; - } - - #endregion - - #region Public Properties - - /// - /// Gets the error message. - /// - /// - /// A that represents the error message. - /// - public string Message => _message; - - #endregion - } -} diff --git a/SocketHttpListener/Ext.cs b/SocketHttpListener/Ext.cs deleted file mode 100644 index 697c5d5b70..0000000000 --- a/SocketHttpListener/Ext.cs +++ /dev/null @@ -1,946 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; -using WebSocketState = System.Net.WebSockets.WebSocketState; - -namespace SocketHttpListener -{ - /// - /// Provides a set of static methods for the websocket-sharp. - /// - public static class Ext - { - #region Private Const Fields - - private const string _tspecials = "()<>@,;:\\\"/[]?={} \t"; - - #endregion - - #region Private Methods - - private static MemoryStream compress(this Stream stream) - { - var output = new MemoryStream(); - if (stream.Length == 0) - return output; - - stream.Position = 0; - using (var ds = new DeflateStream(output, CompressionMode.Compress, true)) - { - stream.CopyTo(ds); - //ds.Close(); // "BFINAL" set to 1. - output.Position = 0; - - return output; - } - } - - private static byte[] decompress(this byte[] value) - { - if (value.Length == 0) - return value; - - using (var input = new MemoryStream(value)) - { - return input.decompressToArray(); - } - } - - private static MemoryStream decompress(this Stream stream) - { - var output = new MemoryStream(); - if (stream.Length == 0) - return output; - - stream.Position = 0; - using (var ds = new DeflateStream(stream, CompressionMode.Decompress, true)) - { - ds.CopyTo(output, true); - return output; - } - } - - private static byte[] decompressToArray(this Stream stream) - { - using (var decomp = stream.decompress()) - { - return decomp.ToArray(); - } - } - - private static async Task ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length) - { - var len = await stream.ReadAsync(buffer, offset, length).ConfigureAwait(false); - if (len < 1) - return buffer.SubArray(0, offset); - - var tmp = 0; - while (len < length) - { - tmp = await stream.ReadAsync(buffer, offset + len, length - len).ConfigureAwait(false); - if (tmp < 1) - { - break; - } - - len += tmp; - } - - return len < length - ? buffer.SubArray(0, offset + len) - : buffer; - } - - private static async Task ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length, Stream dest) - { - var bytes = await stream.ReadBytesAsync(buffer, offset, length).ConfigureAwait(false); - var len = bytes.Length; - dest.Write(bytes, 0, len); - - return len == offset + length; - } - - #endregion - - #region Internal Methods - - internal static async Task AppendAsync(this ushort code, string reason) - { - using (var buffer = new MemoryStream()) - { - var tmp = code.ToByteArrayInternally(ByteOrder.Big); - await buffer.WriteAsync(tmp, 0, 2).ConfigureAwait(false); - if (reason != null && reason.Length > 0) - { - tmp = Encoding.UTF8.GetBytes(reason); - await buffer.WriteAsync(tmp, 0, tmp.Length).ConfigureAwait(false); - } - - return buffer.ToArray(); - } - } - - internal static string CheckIfClosable(this WebSocketState state) - { - return state == WebSocketState.CloseSent - ? "While closing the WebSocket connection." - : state == WebSocketState.Closed - ? "The WebSocket connection has already been closed." - : null; - } - - internal static string CheckIfOpen(this WebSocketState state) - { - return state == WebSocketState.Connecting - ? "A WebSocket connection isn't established." - : state == WebSocketState.CloseSent - ? "While closing the WebSocket connection." - : state == WebSocketState.Closed - ? "The WebSocket connection has already been closed." - : null; - } - - internal static string CheckIfValidControlData(this byte[] data, string paramName) - { - return data.Length > 125 - ? string.Format("'{0}' length must be less.", paramName) - : null; - } - - internal static Stream Compress(this Stream stream, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? stream.compress() - : stream; - } - - internal static bool Contains(this IEnumerable source, Func condition) - { - foreach (T elm in source) - if (condition(elm)) - return true; - - return false; - } - - internal static void CopyTo(this Stream src, Stream dest, bool setDefaultPosition) - { - var readLen = 0; - var bufferLen = 256; - var buffer = new byte[bufferLen]; - while ((readLen = src.Read(buffer, 0, bufferLen)) > 0) - { - dest.Write(buffer, 0, readLen); - } - - if (setDefaultPosition) - dest.Position = 0; - } - - internal static byte[] Decompress(this byte[] value, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? value.decompress() - : value; - } - - internal static byte[] DecompressToArray(this Stream stream, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? stream.decompressToArray() - : stream.ToByteArray(); - } - - /// - /// Determines whether the specified equals the specified , - /// and invokes the specified Action<int> delegate at the same time. - /// - /// - /// true if equals ; - /// otherwise, false. - /// - /// - /// An to compare. - /// - /// - /// A to compare. - /// - /// - /// An Action<int> delegate that references the method(s) called at - /// the same time as comparing. An parameter to pass to - /// the method(s) is . - /// - /// - /// isn't between 0 and 255. - /// - internal static bool EqualsWith(this int value, char c, Action action) - { - if (value < 0 || value > 255) - throw new ArgumentOutOfRangeException(nameof(value)); - - action(value); - return value == c - 0; - } - - internal static string GetMessage(this CloseStatusCode code) - { - return code == CloseStatusCode.ProtocolError - ? "A WebSocket protocol error has occurred." - : code == CloseStatusCode.IncorrectData - ? "An incorrect data has been received." - : code == CloseStatusCode.Abnormal - ? "An exception has occurred." - : code == CloseStatusCode.InconsistentData - ? "An inconsistent data has been received." - : code == CloseStatusCode.PolicyViolation - ? "A policy violation has occurred." - : code == CloseStatusCode.TooBig - ? "A too big data has been received." - : code == CloseStatusCode.IgnoreExtension - ? "WebSocket client did not receive expected extension(s)." - : code == CloseStatusCode.ServerError - ? "WebSocket server got an internal error." - : code == CloseStatusCode.TlsHandshakeFailure - ? "An error has occurred while handshaking." - : string.Empty; - } - - internal static string GetNameInternal(this string nameAndValue, string separator) - { - var i = nameAndValue.IndexOf(separator); - return i > 0 - ? nameAndValue.Substring(0, i).Trim() - : null; - } - - internal static string GetValueInternal(this string nameAndValue, string separator) - { - var i = nameAndValue.IndexOf(separator); - return i >= 0 && i < nameAndValue.Length - 1 - ? nameAndValue.Substring(i + 1).Trim() - : null; - } - - internal static bool IsCompressionExtension(this string value, CompressionMethod method) - { - return value.StartsWith(method.ToExtensionString()); - } - - internal static bool IsPortNumber(this int value) - { - return value > 0 && value < 65536; - } - - internal static bool IsReserved(this ushort code) - { - return code == (ushort)CloseStatusCode.Undefined || - code == (ushort)CloseStatusCode.NoStatusCode || - code == (ushort)CloseStatusCode.Abnormal || - code == (ushort)CloseStatusCode.TlsHandshakeFailure; - } - - internal static bool IsReserved(this CloseStatusCode code) - { - return code == CloseStatusCode.Undefined || - code == CloseStatusCode.NoStatusCode || - code == CloseStatusCode.Abnormal || - code == CloseStatusCode.TlsHandshakeFailure; - } - - internal static bool IsText(this string value) - { - var len = value.Length; - for (var i = 0; i < len; i++) - { - char c = value[i]; - if (c < 0x20 && !"\r\n\t".Contains(c)) - return false; - - if (c == 0x7f) - return false; - - if (c == '\n' && ++i < len) - { - c = value[i]; - if (!" \t".Contains(c)) - return false; - } - } - - return true; - } - - internal static bool IsToken(this string value) - { - foreach (char c in value) - if (c < 0x20 || c >= 0x7f || _tspecials.Contains(c)) - return false; - - return true; - } - - internal static string Quote(this string value) - { - return value.IsToken() - ? value - : string.Format("\"{0}\"", value.Replace("\"", "\\\"")); - } - - internal static Task ReadBytesAsync(this Stream stream, int length) - => stream.ReadBytesAsync(new byte[length], 0, length); - - internal static async Task ReadBytesAsync(this Stream stream, long length, int bufferLength) - { - using (var result = new MemoryStream()) - { - var count = length / bufferLength; - var rem = (int)(length % bufferLength); - - var buffer = new byte[bufferLength]; - var end = false; - for (long i = 0; i < count; i++) - { - if (!await stream.ReadBytesAsync(buffer, 0, bufferLength, result).ConfigureAwait(false)) - { - end = true; - break; - } - } - - if (!end && rem > 0) - { - await stream.ReadBytesAsync(new byte[rem], 0, rem, result).ConfigureAwait(false); - } - - return result.ToArray(); - } - } - - internal static string RemovePrefix(this string value, params string[] prefixes) - { - var i = 0; - foreach (var prefix in prefixes) - { - if (value.StartsWith(prefix)) - { - i = prefix.Length; - break; - } - } - - return i > 0 - ? value.Substring(i) - : value; - } - - internal static T[] Reverse(this T[] array) - { - var len = array.Length; - T[] reverse = new T[len]; - - var end = len - 1; - for (var i = 0; i <= end; i++) - reverse[i] = array[end - i]; - - return reverse; - } - - internal static IEnumerable SplitHeaderValue( - this string value, params char[] separator) - { - var len = value.Length; - var separators = new string(separator); - - var buffer = new StringBuilder(32); - var quoted = false; - var escaped = false; - - char c; - for (var i = 0; i < len; i++) - { - c = value[i]; - if (c == '"') - { - if (escaped) - escaped = !escaped; - else - quoted = !quoted; - } - else if (c == '\\') - { - if (i < len - 1 && value[i + 1] == '"') - escaped = true; - } - else if (separators.Contains(c)) - { - if (!quoted) - { - yield return buffer.ToString(); - buffer.Length = 0; - - continue; - } - } - else - { - } - - buffer.Append(c); - } - - if (buffer.Length > 0) - yield return buffer.ToString(); - } - - internal static byte[] ToByteArray(this Stream stream) - { - using (var output = new MemoryStream()) - { - stream.Position = 0; - stream.CopyTo(output); - - return output.ToArray(); - } - } - - internal static byte[] ToByteArrayInternally(this ushort value, ByteOrder order) - { - var bytes = BitConverter.GetBytes(value); - if (!order.IsHostOrder()) - Array.Reverse(bytes); - - return bytes; - } - - internal static byte[] ToByteArrayInternally(this ulong value, ByteOrder order) - { - var bytes = BitConverter.GetBytes(value); - if (!order.IsHostOrder()) - Array.Reverse(bytes); - - return bytes; - } - - internal static string ToExtensionString( - this CompressionMethod method, params string[] parameters) - { - if (method == CompressionMethod.None) - return string.Empty; - - var m = string.Format("permessage-{0}", method.ToString().ToLowerInvariant()); - if (parameters == null || parameters.Length == 0) - return m; - - return string.Format("{0}; {1}", m, parameters.ToString("; ")); - } - - internal static ushort ToUInt16(this byte[] src, ByteOrder srcOrder) - { - src.ToHostOrder(srcOrder); - return BitConverter.ToUInt16(src, 0); - } - - internal static ulong ToUInt64(this byte[] src, ByteOrder srcOrder) - { - src.ToHostOrder(srcOrder); - return BitConverter.ToUInt64(src, 0); - } - - internal static string TrimEndSlash(this string value) - { - value = value.TrimEnd('/'); - return value.Length > 0 - ? value - : "/"; - } - - internal static string Unquote(this string value) - { - var start = value.IndexOf('\"'); - var end = value.LastIndexOf('\"'); - if (start < end) - value = value.Substring(start + 1, end - start - 1).Replace("\\\"", "\""); - - return value.Trim(); - } - - internal static void WriteBytes(this Stream stream, byte[] value) - { - using (var src = new MemoryStream(value)) - { - src.CopyTo(stream); - } - } - - #endregion - - #region Public Methods - - /// - /// Determines whether the specified contains any of characters - /// in the specified array of . - /// - /// - /// true if contains any of ; - /// otherwise, false. - /// - /// - /// A to test. - /// - /// - /// An array of that contains characters to find. - /// - public static bool Contains(this string value, params char[] chars) - { - return chars == null || chars.Length == 0 - ? true - : value == null || value.Length == 0 - ? false - : value.IndexOfAny(chars) != -1; - } - - /// - /// Determines whether the specified contains the entry - /// with the specified . - /// - /// - /// true if contains the entry - /// with ; otherwise, false. - /// - /// - /// A to test. - /// - /// - /// A that represents the key of the entry to find. - /// - public static bool Contains(this QueryParamCollection collection, string name) - { - return collection == null || collection.Count == 0 - ? false - : collection[name] != null; - } - - /// - /// Determines whether the specified contains the entry - /// with the specified both and . - /// - /// - /// true if contains the entry - /// with both and ; - /// otherwise, false. - /// - /// - /// A to test. - /// - /// - /// A that represents the key of the entry to find. - /// - /// - /// A that represents the value of the entry to find. - /// - public static bool Contains(this QueryParamCollection collection, string name, string value) - { - if (collection == null || collection.Count == 0) - return false; - - var values = collection[name]; - if (values == null) - return false; - - foreach (var v in values.Split(',')) - if (v.Trim().Equals(value, StringComparison.OrdinalIgnoreCase)) - return true; - - return false; - } - - /// - /// Emits the specified EventHandler<TEventArgs> delegate - /// if it isn't . - /// - /// - /// An EventHandler<TEventArgs> to emit. - /// - /// - /// An from which emits this . - /// - /// - /// A TEventArgs that represents the event data. - /// - /// - /// The type of the event data generated by the event. - /// - public static void Emit( - this EventHandler eventHandler, object sender, TEventArgs e) - where TEventArgs : EventArgs - { - if (eventHandler != null) - eventHandler(sender, e); - } - - /// - /// Gets the description of the specified HTTP status . - /// - /// - /// A that represents the description of the HTTP status code. - /// - /// - /// One of enum values, indicates the HTTP status codes. - /// - public static string GetDescription(this HttpStatusCode code) - { - return ((int)code).GetStatusDescription(); - } - - /// - /// Gets the description of the specified HTTP status . - /// - /// - /// A that represents the description of the HTTP status code. - /// - /// - /// An that represents the HTTP status code. - /// - public static string GetStatusDescription(this int code) - { - switch (code) - { - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 102: return "Processing"; - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 207: return "Multi-Status"; - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 307: return "Temporary Redirect"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-Uri Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 422: return "Unprocessable Entity"; - case 423: return "Locked"; - case 424: return "Failed Dependency"; - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "Http Version Not Supported"; - case 507: return "Insufficient Storage"; - } - - return string.Empty; - } - - /// - /// Determines whether the specified is host - /// (this computer architecture) byte order. - /// - /// - /// true if is host byte order; - /// otherwise, false. - /// - /// - /// One of the enum values, to test. - /// - public static bool IsHostOrder(this ByteOrder order) - { - // true : !(true ^ true) or !(false ^ false) - // false: !(true ^ false) or !(false ^ true) - return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little)); - } - - /// - /// Determines whether the specified is a predefined scheme. - /// - /// - /// true if is a predefined scheme; otherwise, false. - /// - /// - /// A to test. - /// - public static bool IsPredefinedScheme(this string value) - { - if (value == null || value.Length < 2) - return false; - - var c = value[0]; - if (c == 'h') - return value == "http" || value == "https"; - - if (c == 'w') - return value == "ws" || value == "wss"; - - if (c == 'f') - return value == "file" || value == "ftp"; - - if (c == 'n') - { - c = value[1]; - return c == 'e' - ? value == "news" || value == "net.pipe" || value == "net.tcp" - : value == "nntp"; - } - - return (c == 'g' && value == "gopher") || (c == 'm' && value == "mailto"); - } - - /// - /// Determines whether the specified is a URI string. - /// - /// - /// true if may be a URI string; otherwise, false. - /// - /// - /// A to test. - /// - public static bool MaybeUri(this string value) - { - if (value == null || value.Length == 0) - return false; - - var i = value.IndexOf(':'); - if (i == -1) - return false; - - if (i >= 10) - return false; - - return value.Substring(0, i).IsPredefinedScheme(); - } - - /// - /// Retrieves a sub-array from the specified . - /// A sub-array starts at the specified element position. - /// - /// - /// An array of T that receives a sub-array, or an empty array of T if any problems - /// with the parameters. - /// - /// - /// An array of T that contains the data to retrieve a sub-array. - /// - /// - /// An that contains the zero-based starting position of a sub-array - /// in . - /// - /// - /// An that contains the number of elements to retrieve a sub-array. - /// - /// - /// The type of elements in the . - /// - public static T[] SubArray(this T[] array, int startIndex, int length) - { - if (array == null || array.Length == 0) - return new T[0]; - - if (startIndex < 0 || length <= 0) - return new T[0]; - - if (startIndex + length > array.Length) - return new T[0]; - - if (startIndex == 0 && array.Length == length) - return array; - - T[] subArray = new T[length]; - Array.Copy(array, startIndex, subArray, 0, length); - - return subArray; - } - - /// - /// Converts the order of the specified array of to the host byte order. - /// - /// - /// An array of converted from . - /// - /// - /// An array of to convert. - /// - /// - /// One of the enum values, indicates the byte order of - /// . - /// - /// - /// is . - /// - public static void ToHostOrder(this byte[] src, ByteOrder srcOrder) - { - if (src == null) - { - throw new ArgumentNullException(nameof(src)); - } - - if (src.Length > 1 && !srcOrder.IsHostOrder()) - { - Array.Reverse(src); - } - } - - /// - /// Converts the specified to a that - /// concatenates the each element of across the specified - /// . - /// - /// - /// A converted from , - /// or if is empty. - /// - /// - /// An array of T to convert. - /// - /// - /// A that represents the separator string. - /// - /// - /// The type of elements in . - /// - /// - /// is . - /// - public static string ToString(this T[] array, string separator) - { - if (array == null) - throw new ArgumentNullException(nameof(array)); - - var len = array.Length; - if (len == 0) - return string.Empty; - - if (separator == null) - separator = string.Empty; - - var buff = new StringBuilder(64); - (len - 1).Times(i => buff.AppendFormat("{0}{1}", array[i].ToString(), separator)); - - buff.Append(array[len - 1].ToString()); - return buff.ToString(); - } - - /// - /// Executes the specified Action<int> delegate times. - /// - /// - /// An is the number of times to execute. - /// - /// - /// An Action<int> delegate that references the method(s) to execute. - /// An parameter to pass to the method(s) is the zero-based count of - /// iteration. - /// - public static void Times(this int n, Action action) - { - if (n > 0 && action != null) - for (int i = 0; i < n; i++) - action(i); - } - - /// - /// Converts the specified to a . - /// - /// - /// A converted from , or - /// if isn't successfully converted. - /// - /// - /// A to convert. - /// - public static Uri ToUri(this string uriString) - { - return Uri.TryCreate( - uriString, uriString.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out var res) - ? res - : null; - } - - /// - /// URL-decodes the specified . - /// - /// - /// A that receives the decoded string, or the - /// if it's or empty. - /// - /// - /// A to decode. - /// - public static string UrlDecode(this string value) - { - return value == null || value.Length == 0 - ? value - : WebUtility.UrlDecode(value); - } - - #endregion - } -} diff --git a/SocketHttpListener/Fin.cs b/SocketHttpListener/Fin.cs deleted file mode 100644 index c8ffbf2b2f..0000000000 --- a/SocketHttpListener/Fin.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener -{ - internal enum Fin : byte - { - More = 0x0, - Final = 0x1 - } -} diff --git a/SocketHttpListener/HttpBase.cs b/SocketHttpListener/HttpBase.cs deleted file mode 100644 index c386b93744..0000000000 --- a/SocketHttpListener/HttpBase.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Text; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener -{ - internal abstract class HttpBase - { - #region Private Fields - - private QueryParamCollection _headers; - private Version _version; - - #endregion - - #region Protected Fields - - protected const string CrLf = "\r\n"; - - #endregion - - #region Protected Constructors - - protected HttpBase(Version version, QueryParamCollection headers) - { - _version = version; - _headers = headers; - } - - #endregion - - #region Public Properties - - public QueryParamCollection Headers => _headers; - - public Version ProtocolVersion => _version; - - #endregion - - #region Private Methods - - private static Encoding getEncoding(string contentType) - { - if (contentType == null || contentType.Length == 0) - return Encoding.UTF8; - - var i = contentType.IndexOf("charset=", StringComparison.Ordinal); - if (i == -1) - return Encoding.UTF8; - - var charset = contentType.Substring(i + 8); - i = charset.IndexOf(';'); - if (i != -1) - charset = charset.Substring(0, i).TrimEnd(); - - return Encoding.GetEncoding(charset.Trim('"')); - } - - #endregion - - #region Public Methods - - public byte[] ToByteArray() - { - return Encoding.UTF8.GetBytes(ToString()); - } - - #endregion - } -} diff --git a/SocketHttpListener/HttpResponse.cs b/SocketHttpListener/HttpResponse.cs deleted file mode 100644 index a33b942959..0000000000 --- a/SocketHttpListener/HttpResponse.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; -using SocketHttpListener.Net; - -namespace SocketHttpListener -{ - // TODO what is the point of this class? - internal class HttpResponse : HttpBase - { - #region Private Fields - - private string _code; - private string _reason; - - #endregion - - #region Private Constructors - - private HttpResponse(string code, string reason, Version version, QueryParamCollection headers) - : base(version, headers) - { - _code = code; - _reason = reason; - } - - #endregion - - #region Internal Constructors - - internal HttpResponse(HttpStatusCode code) - : this(code, code.GetDescription()) - { - } - - internal HttpResponse(HttpStatusCode code, string reason) - : this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection()) - { - Headers["Server"] = "websocket-sharp/1.0"; - } - - #endregion - - #region Public Properties - - public CookieCollection Cookies => GetCookies(Headers, true); - - private static CookieCollection GetCookies(QueryParamCollection headers, bool response) - { - var name = response ? "Set-Cookie" : "Cookie"; - return headers == null || !headers.Contains(name) - ? new CookieCollection() - : CookieHelper.Parse(headers[name], response); - } - - public bool IsProxyAuthenticationRequired => _code == "407"; - - public bool IsUnauthorized => _code == "401"; - - public bool IsWebSocketResponse - { - get - { - var headers = Headers; - return ProtocolVersion > HttpVersion.Version10 && - _code == "101" && - headers.Contains("Upgrade", "websocket") && - headers.Contains("Connection", "Upgrade"); - } - } - - public string Reason => _reason; - - public string StatusCode => _code; - - #endregion - - #region Internal Methods - - internal static HttpResponse CreateCloseResponse(HttpStatusCode code) - { - var res = new HttpResponse(code); - res.Headers["Connection"] = "close"; - - return res; - } - - #endregion - - #region Public Methods - - public void SetCookies(CookieCollection cookies) - { - if (cookies == null || cookies.Count == 0) - return; - - var headers = Headers; - var sorted = cookies.OfType().OrderBy(i => i.Name).ToList(); - - foreach (var cookie in sorted) - headers.Add("Set-Cookie", cookie.ToString()); - } - - public override string ToString() - { - var output = new StringBuilder(64); - output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf); - - var headers = Headers; - foreach (var key in headers.Keys) - output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf); - - output.Append(CrLf); - - return output.ToString(); - } - - #endregion - } -} diff --git a/SocketHttpListener/Mask.cs b/SocketHttpListener/Mask.cs deleted file mode 100644 index c56f5b08c0..0000000000 --- a/SocketHttpListener/Mask.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener -{ - internal enum Mask : byte - { - Unmask = 0x0, - Mask = 0x1 - } -} diff --git a/SocketHttpListener/MessageEventArgs.cs b/SocketHttpListener/MessageEventArgs.cs deleted file mode 100644 index 8e2151cb7a..0000000000 --- a/SocketHttpListener/MessageEventArgs.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Text; - -namespace SocketHttpListener -{ - /// - /// Contains the event data associated with a event. - /// - /// - /// A event occurs when the receives - /// a text or binary data frame. - /// If you want to get the received data, you access the or - /// property. - /// - public class MessageEventArgs : EventArgs - { - #region Private Fields - - private string _data; - private Opcode _opcode; - private byte[] _rawData; - - #endregion - - #region Internal Constructors - - internal MessageEventArgs(Opcode opcode, byte[] data) - { - _opcode = opcode; - _rawData = data; - _data = convertToString(opcode, data); - } - - internal MessageEventArgs(Opcode opcode, PayloadData payload) - { - _opcode = opcode; - _rawData = payload.ApplicationData; - _data = convertToString(opcode, _rawData); - } - - #endregion - - #region Public Properties - - /// - /// Gets the received data as a . - /// - /// - /// A that contains the received data. - /// - public string Data => _data; - - /// - /// Gets the received data as an array of . - /// - /// - /// An array of that contains the received data. - /// - public byte[] RawData => _rawData; - - /// - /// Gets the type of the received data. - /// - /// - /// One of the values, indicates the type of the received data. - /// - public Opcode Type => _opcode; - - #endregion - - #region Private Methods - - private static string convertToString(Opcode opcode, byte[] data) - { - return data.Length == 0 - ? string.Empty - : opcode == Opcode.Text - ? Encoding.UTF8.GetString(data, 0, data.Length) - : opcode.ToString(); - } - - #endregion - } -} diff --git a/SocketHttpListener/Net/CookieHelper.cs b/SocketHttpListener/Net/CookieHelper.cs deleted file mode 100644 index 3ad76ff238..0000000000 --- a/SocketHttpListener/Net/CookieHelper.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net; -using System.Text; - -namespace SocketHttpListener.Net -{ - public static class CookieHelper - { - internal static CookieCollection Parse(string value, bool response) - { - return response - ? parseResponse(value) - : null; - } - - private static string[] splitCookieHeaderValue(string value) - { - return new List(value.SplitHeaderValue(',', ';')).ToArray(); - } - - private static CookieCollection parseResponse(string value) - { - var cookies = new CookieCollection(); - - Cookie cookie = null; - var pairs = splitCookieHeaderValue(value); - for (int i = 0; i < pairs.Length; i++) - { - var pair = pairs[i].Trim(); - if (pair.Length == 0) - continue; - - if (pair.StartsWith("version", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Version = int.Parse(pair.GetValueInternal("=").Trim('"')); - } - else if (pair.StartsWith("expires", StringComparison.OrdinalIgnoreCase)) - { - var buffer = new StringBuilder(pair.GetValueInternal("="), 32); - if (i < pairs.Length - 1) - buffer.AppendFormat(", {0}", pairs[++i].Trim()); - - if (!DateTime.TryParseExact( - buffer.ToString(), - new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" }, - new CultureInfo("en-US"), - DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, - out var expires)) - expires = DateTime.Now; - - if (cookie != null && cookie.Expires == DateTime.MinValue) - cookie.Expires = expires.ToLocalTime(); - } - else if (pair.StartsWith("max-age", StringComparison.OrdinalIgnoreCase)) - { - var max = int.Parse(pair.GetValueInternal("=").Trim('"')); - var expires = DateTime.Now.AddSeconds((double)max); - if (cookie != null) - cookie.Expires = expires; - } - else if (pair.StartsWith("path", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Path = pair.GetValueInternal("="); - } - else if (pair.StartsWith("domain", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Domain = pair.GetValueInternal("="); - } - else if (pair.StartsWith("port", StringComparison.OrdinalIgnoreCase)) - { - var port = pair.Equals("port", StringComparison.OrdinalIgnoreCase) - ? "\"\"" - : pair.GetValueInternal("="); - - if (cookie != null) - cookie.Port = port; - } - else if (pair.StartsWith("comment", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Comment = pair.GetValueInternal("=").UrlDecode(); - } - else if (pair.StartsWith("commenturl", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.CommentUri = pair.GetValueInternal("=").Trim('"').ToUri(); - } - else if (pair.StartsWith("discard", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Discard = true; - } - else if (pair.StartsWith("secure", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Secure = true; - } - else if (pair.StartsWith("httponly", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.HttpOnly = true; - } - else - { - if (cookie != null) - cookies.Add(cookie); - - string name; - string val = string.Empty; - - var pos = pair.IndexOf('='); - if (pos == -1) - { - name = pair; - } - else if (pos == pair.Length - 1) - { - name = pair.Substring(0, pos).TrimEnd(' '); - } - else - { - name = pair.Substring(0, pos).TrimEnd(' '); - val = pair.Substring(pos + 1).TrimStart(' '); - } - - cookie = new Cookie(name, val); - } - } - - if (cookie != null) - cookies.Add(cookie); - - return cookies; - } - } -} diff --git a/SocketHttpListener/Opcode.cs b/SocketHttpListener/Opcode.cs deleted file mode 100644 index 69cf3f3728..0000000000 --- a/SocketHttpListener/Opcode.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values of the opcode that indicates the type of a WebSocket frame. - /// - /// - /// The values of the opcode are defined in - /// Section 5.2 of RFC 6455. - /// - public enum Opcode : byte - { - /// - /// Equivalent to numeric value 0. - /// Indicates a continuation frame. - /// - Cont = 0x0, - /// - /// Equivalent to numeric value 1. - /// Indicates a text frame. - /// - Text = 0x1, - /// - /// Equivalent to numeric value 2. - /// Indicates a binary frame. - /// - Binary = 0x2, - /// - /// Equivalent to numeric value 8. - /// Indicates a connection close frame. - /// - Close = 0x8, - /// - /// Equivalent to numeric value 9. - /// Indicates a ping frame. - /// - Ping = 0x9, - /// - /// Equivalent to numeric value 10. - /// Indicates a pong frame. - /// - Pong = 0xa - } -} diff --git a/SocketHttpListener/PayloadData.cs b/SocketHttpListener/PayloadData.cs deleted file mode 100644 index 6d15a6bcb1..0000000000 --- a/SocketHttpListener/PayloadData.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; - -namespace SocketHttpListener -{ - internal class PayloadData : IEnumerable - { - #region Private Fields - - private byte[] _applicationData; - private byte[] _extensionData; - private bool _masked; - - #endregion - - #region Public Const Fields - - public const ulong MaxLength = long.MaxValue; - - #endregion - - #region Public Constructors - - public PayloadData() - : this(new byte[0], new byte[0], false) - { - } - - public PayloadData(byte[] applicationData) - : this(new byte[0], applicationData, false) - { - } - - public PayloadData(string applicationData) - : this(new byte[0], Encoding.UTF8.GetBytes(applicationData), false) - { - } - - public PayloadData(byte[] applicationData, bool masked) - : this(new byte[0], applicationData, masked) - { - } - - public PayloadData(byte[] extensionData, byte[] applicationData, bool masked) - { - _extensionData = extensionData; - _applicationData = applicationData; - _masked = masked; - } - - #endregion - - #region Internal Properties - - internal bool ContainsReservedCloseStatusCode => - _applicationData.Length > 1 && - _applicationData.SubArray(0, 2).ToUInt16(ByteOrder.Big).IsReserved(); - - #endregion - - #region Public Properties - - public byte[] ApplicationData => _applicationData; - - public byte[] ExtensionData => _extensionData; - - public bool IsMasked => _masked; - - public ulong Length => (ulong)(_extensionData.Length + _applicationData.Length); - - #endregion - - #region Private Methods - - private static void mask(byte[] src, byte[] key) - { - for (long i = 0; i < src.Length; i++) - src[i] = (byte)(src[i] ^ key[i % 4]); - } - - #endregion - - #region Public Methods - - public IEnumerator GetEnumerator() - { - foreach (byte b in _extensionData) - yield return b; - - foreach (byte b in _applicationData) - yield return b; - } - - public void Mask(byte[] maskingKey) - { - if (_extensionData.Length > 0) - mask(_extensionData, maskingKey); - - if (_applicationData.Length > 0) - mask(_applicationData, maskingKey); - - _masked = !_masked; - } - - public byte[] ToByteArray() - { - return _extensionData.Length > 0 - ? new List(this).ToArray() - : _applicationData; - } - - public override string ToString() - { - return BitConverter.ToString(ToByteArray()); - } - - #endregion - - #region Explicitly Implemented Interface Members - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - } -} diff --git a/SocketHttpListener/Properties/AssemblyInfo.cs b/SocketHttpListener/Properties/AssemblyInfo.cs deleted file mode 100644 index a69bd176f7..0000000000 --- a/SocketHttpListener/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SocketHttpListener")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] diff --git a/SocketHttpListener/Rsv.cs b/SocketHttpListener/Rsv.cs deleted file mode 100644 index 87283791ec..0000000000 --- a/SocketHttpListener/Rsv.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener -{ - internal enum Rsv : byte - { - Off = 0x0, - On = 0x1 - } -} diff --git a/SocketHttpListener/SocketHttpListener.csproj b/SocketHttpListener/SocketHttpListener.csproj deleted file mode 100644 index e700540a91..0000000000 --- a/SocketHttpListener/SocketHttpListener.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - netstandard2.0 - true - false - - - diff --git a/SocketHttpListener/SocketStream.cs b/SocketHttpListener/SocketStream.cs deleted file mode 100644 index f51fde97ef..0000000000 --- a/SocketHttpListener/SocketStream.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.IO; -using System.Net.Sockets; - -namespace SocketHttpListener -{ - public class SocketStream : Stream - { - private readonly Socket _socket; - - public SocketStream(Socket socket, bool ownsSocket) - { - _socket = socket; - } - - public override void Flush() - { - } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - _socket.Send(buffer, offset, count, SocketFlags.None); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _socket.BeginSend(buffer, offset, count, SocketFlags.None, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - _socket.EndSend(asyncResult); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return _socket.Receive(buffer, offset, count, SocketFlags.None); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _socket.BeginReceive(buffer, offset, count, SocketFlags.None, callback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - return _socket.EndReceive(asyncResult); - } - } -} diff --git a/SocketHttpListener/WebSocket.cs b/SocketHttpListener/WebSocket.cs deleted file mode 100644 index afce871fd6..0000000000 --- a/SocketHttpListener/WebSocket.cs +++ /dev/null @@ -1,777 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using WebSocketState = System.Net.WebSockets.WebSocketState; - -namespace SocketHttpListener -{ - /// - /// Implements the WebSocket interface. - /// - /// - /// The WebSocket class provides a set of methods and properties for two-way communication using - /// the WebSocket protocol (RFC 6455). - /// - public class WebSocket : IDisposable - { - #region Private Fields - - private Action _closeContext; - private CompressionMethod _compression; - private WebSocketContext _context; - private CookieCollection _cookies; - private AutoResetEvent _exitReceiving; - private object _forConn; - private readonly SemaphoreSlim _forEvent = new SemaphoreSlim(1, 1); - private object _forMessageEventQueue; - private readonly SemaphoreSlim _forSend = new SemaphoreSlim(1, 1); - private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - private Queue _messageEventQueue; - private string _protocol; - private volatile WebSocketState _readyState; - private AutoResetEvent _receivePong; - private bool _secure; - private Stream _stream; - private const string _version = "13"; - - #endregion - - #region Internal Fields - - internal const int FragmentLength = 1016; // Max value is int.MaxValue - 14. - - #endregion - - #region Internal Constructors - - // As server - internal WebSocket(string protocol) - { - _protocol = protocol; - } - - public void SetContext(HttpListenerWebSocketContext context, Action closeContextFn, Stream stream) - { - _context = context; - - _closeContext = closeContextFn; - _secure = context.IsSecureConnection; - _stream = stream; - - init(); - } - - // In the .NET Framework, this pulls the value from a P/Invoke. Here we just hardcode it to a reasonable default. - public static TimeSpan DefaultKeepAliveInterval => TimeSpan.FromSeconds(30); - - #endregion - - /// - /// Gets the state of the WebSocket connection. - /// - /// - /// One of the enum values, indicates the state of the WebSocket - /// connection. The default value is . - /// - public WebSocketState ReadyState => _readyState; - - #region Public Events - - /// - /// Occurs when the WebSocket connection has been closed. - /// - public event EventHandler OnClose; - - /// - /// Occurs when the gets an error. - /// - public event EventHandler OnError; - - /// - /// Occurs when the receives a message. - /// - public event EventHandler OnMessage; - - /// - /// Occurs when the WebSocket connection has been established. - /// - public event EventHandler OnOpen; - - #endregion - - #region Private Methods - - private async Task CloseAsync(CloseStatusCode code, string reason, bool wait) - { - await CloseAsync(new PayloadData( - await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)), - !code.IsReserved(), - wait).ConfigureAwait(false); - } - - private async Task CloseAsync(PayloadData payload, bool send, bool wait) - { - lock (_forConn) - { - if (_readyState == WebSocketState.CloseSent || _readyState == WebSocketState.Closed) - { - return; - } - - _readyState = WebSocketState.CloseSent; - } - - var e = new CloseEventArgs(payload) - { - WasClean = await CloseHandshakeAsync( - send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null, - wait ? 1000 : 0).ConfigureAwait(false) - }; - - _readyState = WebSocketState.Closed; - try - { - OnClose.Emit(this, e); - } - catch (Exception ex) - { - error("An exception has occurred while OnClose.", ex); - } - } - - private async Task CloseHandshakeAsync(byte[] frameAsBytes, int millisecondsTimeout) - { - var sent = frameAsBytes != null && await WriteBytesAsync(frameAsBytes).ConfigureAwait(false); - var received = - millisecondsTimeout == 0 || - (sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout)); - - closeServerResources(); - - if (_receivePong != null) - { - _receivePong.Dispose(); - _receivePong = null; - } - - if (_exitReceiving != null) - { - _exitReceiving.Dispose(); - _exitReceiving = null; - } - - var result = sent && received; - - return result; - } - - // As server - private void closeServerResources() - { - if (_closeContext == null) - return; - - try - { - _closeContext(); - } - catch (SocketException) - { - // it could be unable to send the handshake response - } - - _closeContext = null; - _stream = null; - _context = null; - } - - private async Task ConcatenateFragmentsIntoAsync(Stream dest) - { - while (true) - { - var frame = await WebSocketFrame.ReadAsync(_stream, true).ConfigureAwait(false); - if (frame.IsFinal) - { - /* FINAL */ - - // CONT - if (frame.IsContinuation) - { - dest.WriteBytes(frame.PayloadData.ApplicationData); - break; - } - - // PING - if (frame.IsPing) - { - processPingFrame(frame); - continue; - } - - // PONG - if (frame.IsPong) - { - processPongFrame(frame); - continue; - } - - // CLOSE - if (frame.IsClose) - return await ProcessCloseFrameAsync(frame).ConfigureAwait(false); - } - else - { - /* MORE */ - - // CONT - if (frame.IsContinuation) - { - dest.WriteBytes(frame.PayloadData.ApplicationData); - continue; - } - } - - // ? - return await ProcessUnsupportedFrameAsync( - frame, - CloseStatusCode.IncorrectData, - "An incorrect data has been received while receiving fragmented data.").ConfigureAwait(false); - } - - return true; - } - - // As server - private HttpResponse createHandshakeCloseResponse(HttpStatusCode code) - { - var res = HttpResponse.CreateCloseResponse(code); - res.Headers["Sec-WebSocket-Version"] = _version; - - return res; - } - - private MessageEventArgs dequeueFromMessageEventQueue() - { - lock (_forMessageEventQueue) - return _messageEventQueue.Count > 0 - ? _messageEventQueue.Dequeue() - : null; - } - - private void enqueueToMessageEventQueue(MessageEventArgs e) - { - lock (_forMessageEventQueue) - _messageEventQueue.Enqueue(e); - } - - private void error(string message, Exception exception) - { - try - { - if (exception != null) - { - message += ". Exception.Message: " + exception.Message; - } - OnError.Emit(this, new ErrorEventArgs(message)); - } - catch (Exception) - { - } - } - - private void error(string message) - { - try - { - OnError.Emit(this, new ErrorEventArgs(message)); - } - catch (Exception) - { - } - } - - private void init() - { - _compression = CompressionMethod.None; - _cookies = new CookieCollection(); - _forConn = new object(); - _messageEventQueue = new Queue(); - _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot; - _readyState = WebSocketState.Connecting; - } - - private async Task OpenAsync() - { - try - { - startReceiving(); - - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while opening.").ConfigureAwait(false); - } - - await _forEvent.WaitAsync().ConfigureAwait(false); - try - { - OnOpen?.Invoke(this, EventArgs.Empty); - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while OnOpen.").ConfigureAwait(false); - } - finally - { - _forEvent.Release(); - } - } - - private async Task ProcessCloseFrameAsync(WebSocketFrame frame) - { - var payload = frame.PayloadData; - await CloseAsync(payload, !payload.ContainsReservedCloseStatusCode, false).ConfigureAwait(false); - - return false; - } - - private bool processDataFrame(WebSocketFrame frame) - { - var e = frame.IsCompressed - ? new MessageEventArgs( - frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression)) - : new MessageEventArgs(frame.Opcode, frame.PayloadData); - - enqueueToMessageEventQueue(e); - return true; - } - - private async Task ProcessExceptionAsync(Exception exception, string message) - { - var code = CloseStatusCode.Abnormal; - var reason = message; - if (exception is WebSocketException) - { - var wsex = (WebSocketException)exception; - code = wsex.Code; - reason = wsex.Message; - } - - error(message ?? code.GetMessage(), exception); - if (_readyState == WebSocketState.Connecting) - { - await CloseAsync(HttpStatusCode.BadRequest).ConfigureAwait(false); - } - else - { - await CloseAsync(code, reason ?? code.GetMessage(), false).ConfigureAwait(false); - } - } - - private Task ProcessFragmentedFrameAsync(WebSocketFrame frame) - { - return frame.IsContinuation // Not first fragment - ? Task.FromResult(true) - : ProcessFragmentsAsync(frame); - } - - private async Task ProcessFragmentsAsync(WebSocketFrame first) - { - using (var buff = new MemoryStream()) - { - buff.WriteBytes(first.PayloadData.ApplicationData); - if (!await ConcatenateFragmentsIntoAsync(buff).ConfigureAwait(false)) - { - return false; - } - - byte[] data; - if (_compression != CompressionMethod.None) - { - data = buff.DecompressToArray(_compression); - } - else - { - data = buff.ToArray(); - } - - enqueueToMessageEventQueue(new MessageEventArgs(first.Opcode, data)); - return true; - } - } - - private bool processPingFrame(WebSocketFrame frame) - { - return true; - } - - private bool processPongFrame(WebSocketFrame frame) - { - _receivePong.Set(); - - return true; - } - - private async Task ProcessUnsupportedFrameAsync(WebSocketFrame frame, CloseStatusCode code, string reason) - { - await ProcessExceptionAsync(new WebSocketException(code, reason), null).ConfigureAwait(false); - - return false; - } - - private Task ProcessWebSocketFrameAsync(WebSocketFrame frame) - { - // TODO: @bond change to if/else chain - return frame.IsCompressed && _compression == CompressionMethod.None - ? ProcessUnsupportedFrameAsync( - frame, - CloseStatusCode.IncorrectData, - "A compressed data has been received without available decompression method.") - : frame.IsFragmented - ? ProcessFragmentedFrameAsync(frame) - : frame.IsData - ? Task.FromResult(processDataFrame(frame)) - : frame.IsPing - ? Task.FromResult(processPingFrame(frame)) - : frame.IsPong - ? Task.FromResult(processPongFrame(frame)) - : frame.IsClose - ? ProcessCloseFrameAsync(frame) - : ProcessUnsupportedFrameAsync(frame, CloseStatusCode.PolicyViolation, null); - } - - private async Task SendAsync(Opcode opcode, Stream stream) - { - await _forSend.WaitAsync().ConfigureAwait(false); - try - { - var src = stream; - var compressed = false; - var sent = false; - try - { - if (_compression != CompressionMethod.None) - { - stream = stream.Compress(_compression); - compressed = true; - } - - sent = await SendAsync(opcode, Mask.Unmask, stream, compressed).ConfigureAwait(false); - if (!sent) - error("Sending a data has been interrupted."); - } - catch (Exception ex) - { - error("An exception has occurred while sending a data.", ex); - } - finally - { - if (compressed) - stream.Dispose(); - - src.Dispose(); - } - - return sent; - } - finally - { - _forSend.Release(); - } - } - - private async Task SendAsync(Opcode opcode, Mask mask, Stream stream, bool compressed) - { - var len = stream.Length; - - /* Not fragmented */ - - if (len == 0) - return await SendAsync(Fin.Final, opcode, mask, new byte[0], compressed).ConfigureAwait(false); - - var quo = len / FragmentLength; - var rem = (int)(len % FragmentLength); - - byte[] buff = null; - if (quo == 0) - { - buff = new byte[rem]; - return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem && - await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false); - } - - buff = new byte[FragmentLength]; - if (quo == 1 && rem == 0) - return await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) == FragmentLength && - await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false); - - /* Send fragmented */ - - // Begin - if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength || - !await SendAsync(Fin.More, opcode, mask, buff, compressed).ConfigureAwait(false)) - return false; - - var n = rem == 0 ? quo - 2 : quo - 1; - for (long i = 0; i < n; i++) - if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength || - !await SendAsync(Fin.More, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false)) - return false; - - // End - if (rem == 0) - rem = FragmentLength; - else - buff = new byte[rem]; - - return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem && - await SendAsync(Fin.Final, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false); - } - - private Task SendAsync(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed) - { - lock (_forConn) - { - if (_readyState != WebSocketState.Open) - { - return Task.FromResult(false); - } - - return WriteBytesAsync( - WebSocketFrame.CreateWebSocketFrame(fin, opcode, mask, data, compressed).ToByteArray()); - } - } - - // As server - private Task SendHttpResponseAsync(HttpResponse response) - => WriteBytesAsync(response.ToByteArray()); - - private void startReceiving() - { - if (_messageEventQueue.Count > 0) - { - _messageEventQueue.Clear(); - } - - _exitReceiving = new AutoResetEvent(false); - _receivePong = new AutoResetEvent(false); - - Action receive = null; - receive = async () => await WebSocketFrame.ReadAsync( - _stream, - true, - async frame => - { - if (await ProcessWebSocketFrameAsync(frame).ConfigureAwait(false) && _readyState != WebSocketState.Closed) - { - receive(); - - if (!frame.IsData) - { - return; - } - - await _forEvent.WaitAsync().ConfigureAwait(false); - - try - { - var e = dequeueFromMessageEventQueue(); - if (e != null && _readyState == WebSocketState.Open) - { - OnMessage.Emit(this, e); - } - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while OnMessage.").ConfigureAwait(false); - } - finally - { - _forEvent.Release(); - } - - } - else if (_exitReceiving != null) - { - _exitReceiving.Set(); - } - }, - async ex => await ProcessExceptionAsync(ex, "An exception has occurred while receiving a message.")).ConfigureAwait(false); - - receive(); - } - - private async Task WriteBytesAsync(byte[] data) - { - try - { - await _stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false); - return true; - } - catch (Exception) - { - return false; - } - } - - #endregion - - #region Internal Methods - - // As server - internal async Task CloseAsync(HttpResponse response) - { - _readyState = WebSocketState.CloseSent; - await SendHttpResponseAsync(response).ConfigureAwait(false); - - closeServerResources(); - - _readyState = WebSocketState.Closed; - } - - // As server - internal Task CloseAsync(HttpStatusCode code) - => CloseAsync(createHandshakeCloseResponse(code)); - - // As server - public async Task ConnectAsServer() - { - try - { - _readyState = WebSocketState.Open; - await OpenAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while connecting.").ConfigureAwait(false); - } - } - - #endregion - - #region Public Methods - - /// - /// Closes the WebSocket connection, and releases all associated resources. - /// - public Task CloseAsync() - { - var msg = _readyState.CheckIfClosable(); - if (msg != null) - { - error(msg); - - return Task.CompletedTask; - } - - var send = _readyState == WebSocketState.Open; - return CloseAsync(new PayloadData(), send, send); - } - - /// - /// Closes the WebSocket connection with the specified - /// and , and releases all associated resources. - /// - /// - /// This method emits a event if the size - /// of is greater than 123 bytes. - /// - /// - /// One of the enum values, represents the status code - /// indicating the reason for the close. - /// - /// - /// A that represents the reason for the close. - /// - public async Task CloseAsync(CloseStatusCode code, string reason) - { - byte[] data = null; - var msg = _readyState.CheckIfClosable() ?? - (data = await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)).CheckIfValidControlData("reason"); - - if (msg != null) - { - error(msg); - - return; - } - - var send = _readyState == WebSocketState.Open && !code.IsReserved(); - await CloseAsync(new PayloadData(data), send, send).ConfigureAwait(false); - } - - /// - /// Sends a binary asynchronously using the WebSocket connection. - /// - /// - /// This method doesn't wait for the send to be complete. - /// - /// - /// An array of that represents the binary data to send. - /// - public Task SendAsync(byte[] data) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - var msg = _readyState.CheckIfOpen(); - if (msg != null) - { - throw new Exception(msg); - } - - return SendAsync(Opcode.Binary, new MemoryStream(data)); - } - - /// - /// Sends a text asynchronously using the WebSocket connection. - /// - /// - /// This method doesn't wait for the send to be complete. - /// - /// - /// A that represents the text data to send. - /// - public Task SendAsync(string data) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - var msg = _readyState.CheckIfOpen(); - if (msg != null) - { - throw new Exception(msg); - } - - return SendAsync(Opcode.Text, new MemoryStream(Encoding.UTF8.GetBytes(data))); - } - - #endregion - - #region Explicit Interface Implementation - - /// - /// Closes the WebSocket connection, and releases all associated resources. - /// - /// - /// This method closes the WebSocket connection with . - /// - void IDisposable.Dispose() - { - CloseAsync(CloseStatusCode.Away, null).GetAwaiter().GetResult(); - } - - #endregion - } -} diff --git a/SocketHttpListener/WebSocketException.cs b/SocketHttpListener/WebSocketException.cs deleted file mode 100644 index e86c46d0f5..0000000000 --- a/SocketHttpListener/WebSocketException.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; - -namespace SocketHttpListener -{ - /// - /// The exception that is thrown when a gets a fatal error. - /// - public class WebSocketException : Exception - { - #region Internal Constructors - - internal WebSocketException() - : this(CloseStatusCode.Abnormal, null, null) - { - } - - internal WebSocketException(string message) - : this(CloseStatusCode.Abnormal, message, null) - { - } - - internal WebSocketException(CloseStatusCode code) - : this(code, null, null) - { - } - - internal WebSocketException(string message, Exception innerException) - : this(CloseStatusCode.Abnormal, message, innerException) - { - } - - internal WebSocketException(CloseStatusCode code, string message) - : this(code, message, null) - { - } - - internal WebSocketException(CloseStatusCode code, string message, Exception innerException) - : base(message ?? code.GetMessage(), innerException) - { - Code = code; - } - - #endregion - - #region Public Properties - - /// - /// Gets the status code indicating the cause for the exception. - /// - /// - /// One of the enum values, represents the status code indicating - /// the cause for the exception. - /// - public CloseStatusCode Code - { - get; private set; - } - - #endregion - } -} diff --git a/SocketHttpListener/WebSocketFrame.cs b/SocketHttpListener/WebSocketFrame.cs deleted file mode 100644 index 8ec64026bd..0000000000 --- a/SocketHttpListener/WebSocketFrame.cs +++ /dev/null @@ -1,432 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; - -namespace SocketHttpListener -{ - internal class WebSocketFrame : IEnumerable - { - #region Private Fields - - private byte[] _extPayloadLength; - private Fin _fin; - private Mask _mask; - private byte[] _maskingKey; - private Opcode _opcode; - private PayloadData _payloadData; - private byte _payloadLength; - private Rsv _rsv1; - private Rsv _rsv2; - private Rsv _rsv3; - - #endregion - - #region Internal Fields - - internal static readonly byte[] EmptyUnmaskPingData; - - #endregion - - #region Static Constructor - - static WebSocketFrame() - { - EmptyUnmaskPingData = CreatePingFrame(Mask.Unmask).ToByteArray(); - } - - #endregion - - #region Private Constructors - - private WebSocketFrame() - { - } - - #endregion - - #region Internal Constructors - - internal WebSocketFrame(Opcode opcode, PayloadData payload) - : this(Fin.Final, opcode, Mask.Mask, payload, false) - { - } - - internal WebSocketFrame(Opcode opcode, Mask mask, PayloadData payload) - : this(Fin.Final, opcode, mask, payload, false) - { - } - - internal WebSocketFrame(Fin fin, Opcode opcode, Mask mask, PayloadData payload) - : this(fin, opcode, mask, payload, false) - { - } - - internal WebSocketFrame( - Fin fin, Opcode opcode, Mask mask, PayloadData payload, bool compressed) - { - _fin = fin; - _rsv1 = isData(opcode) && compressed ? Rsv.On : Rsv.Off; - _rsv2 = Rsv.Off; - _rsv3 = Rsv.Off; - _opcode = opcode; - _mask = mask; - - var len = payload.Length; - if (len < 126) - { - _payloadLength = (byte)len; - _extPayloadLength = new byte[0]; - } - else if (len < 0x010000) - { - _payloadLength = (byte)126; - _extPayloadLength = ((ushort)len).ToByteArrayInternally(ByteOrder.Big); - } - else - { - _payloadLength = (byte)127; - _extPayloadLength = len.ToByteArrayInternally(ByteOrder.Big); - } - - if (mask == Mask.Mask) - { - _maskingKey = createMaskingKey(); - payload.Mask(_maskingKey); - } - else - { - _maskingKey = new byte[0]; - } - - _payloadData = payload; - } - - #endregion - - #region Public Properties - - public byte[] ExtendedPayloadLength => _extPayloadLength; - - public Fin Fin => _fin; - - public bool IsBinary => _opcode == Opcode.Binary; - - public bool IsClose => _opcode == Opcode.Close; - - public bool IsCompressed => _rsv1 == Rsv.On; - - public bool IsContinuation => _opcode == Opcode.Cont; - - public bool IsControl => _opcode == Opcode.Close || _opcode == Opcode.Ping || _opcode == Opcode.Pong; - - public bool IsData => _opcode == Opcode.Binary || _opcode == Opcode.Text; - - public bool IsFinal => _fin == Fin.Final; - - public bool IsFragmented => _fin == Fin.More || _opcode == Opcode.Cont; - - public bool IsMasked => _mask == Mask.Mask; - - public bool IsPerMessageCompressed => (_opcode == Opcode.Binary || _opcode == Opcode.Text) && _rsv1 == Rsv.On; - - public bool IsPing => _opcode == Opcode.Ping; - - public bool IsPong => _opcode == Opcode.Pong; - - public bool IsText => _opcode == Opcode.Text; - - public ulong Length => 2 + (ulong)(_extPayloadLength.Length + _maskingKey.Length) + _payloadData.Length; - - public Mask Mask => _mask; - - public byte[] MaskingKey => _maskingKey; - - public Opcode Opcode => _opcode; - - public PayloadData PayloadData => _payloadData; - - public byte PayloadLength => _payloadLength; - - public Rsv Rsv1 => _rsv1; - - public Rsv Rsv2 => _rsv2; - - public Rsv Rsv3 => _rsv3; - - #endregion - - #region Private Methods - - private byte[] createMaskingKey() - { - var key = new byte[4]; - var rand = new Random(); - rand.NextBytes(key); - - return key; - } - - private static bool isControl(Opcode opcode) - { - return opcode == Opcode.Close || opcode == Opcode.Ping || opcode == Opcode.Pong; - } - - private static bool isData(Opcode opcode) - { - return opcode == Opcode.Text || opcode == Opcode.Binary; - } - - private static async Task ReadAsync(byte[] header, Stream stream, bool unmask) - { - /* Header */ - - // FIN - var fin = (header[0] & 0x80) == 0x80 ? Fin.Final : Fin.More; - // RSV1 - var rsv1 = (header[0] & 0x40) == 0x40 ? Rsv.On : Rsv.Off; - // RSV2 - var rsv2 = (header[0] & 0x20) == 0x20 ? Rsv.On : Rsv.Off; - // RSV3 - var rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.On : Rsv.Off; - // Opcode - var opcode = (Opcode)(header[0] & 0x0f); - // MASK - var mask = (header[1] & 0x80) == 0x80 ? Mask.Mask : Mask.Unmask; - // Payload Length - var payloadLen = (byte)(header[1] & 0x7f); - - // Check if correct frame. - var incorrect = isControl(opcode) && fin == Fin.More - ? "A control frame is fragmented." - : !isData(opcode) && rsv1 == Rsv.On - ? "A non data frame is compressed." - : null; - - if (incorrect != null) - throw new WebSocketException(CloseStatusCode.IncorrectData, incorrect); - - // Check if consistent frame. - if (isControl(opcode) && payloadLen > 125) - throw new WebSocketException( - CloseStatusCode.InconsistentData, - "The length of payload data of a control frame is greater than 125 bytes."); - - var frame = new WebSocketFrame(); - frame._fin = fin; - frame._rsv1 = rsv1; - frame._rsv2 = rsv2; - frame._rsv3 = rsv3; - frame._opcode = opcode; - frame._mask = mask; - frame._payloadLength = payloadLen; - - /* Extended Payload Length */ - - var size = payloadLen < 126 - ? 0 - : payloadLen == 126 - ? 2 - : 8; - - var extPayloadLen = size > 0 ? await stream.ReadBytesAsync(size).ConfigureAwait(false) : Array.Empty(); - if (size > 0 && extPayloadLen.Length != size) - throw new WebSocketException( - "The 'Extended Payload Length' of a frame cannot be read from the data source."); - - frame._extPayloadLength = extPayloadLen; - - /* Masking Key */ - - var masked = mask == Mask.Mask; - var maskingKey = masked ? await stream.ReadBytesAsync(4).ConfigureAwait(false) : Array.Empty(); - if (masked && maskingKey.Length != 4) - throw new WebSocketException( - "The 'Masking Key' of a frame cannot be read from the data source."); - - frame._maskingKey = maskingKey; - - /* Payload Data */ - - ulong len = payloadLen < 126 - ? payloadLen - : payloadLen == 126 - ? extPayloadLen.ToUInt16(ByteOrder.Big) - : extPayloadLen.ToUInt64(ByteOrder.Big); - - byte[] data = null; - if (len > 0) - { - // Check if allowable payload data length. - if (payloadLen > 126 && len > PayloadData.MaxLength) - throw new WebSocketException( - CloseStatusCode.TooBig, - "The length of 'Payload Data' of a frame is greater than the allowable length."); - - data = payloadLen > 126 - ? await stream.ReadBytesAsync((long)len, 1024).ConfigureAwait(false) - : await stream.ReadBytesAsync((int)len).ConfigureAwait(false); - - //if (data.LongLength != (long)len) - // throw new WebSocketException( - // "The 'Payload Data' of a frame cannot be read from the data source."); - } - else - { - data = Array.Empty(); - } - - var payload = new PayloadData(data, masked); - if (masked && unmask) - { - payload.Mask(maskingKey); - frame._mask = Mask.Unmask; - frame._maskingKey = Array.Empty(); - } - - frame._payloadData = payload; - return frame; - } - - #endregion - - #region Internal Methods - - internal static WebSocketFrame CreateCloseFrame(Mask mask, byte[] data) - { - return new WebSocketFrame(Opcode.Close, mask, new PayloadData(data)); - } - - internal static WebSocketFrame CreateCloseFrame(Mask mask, PayloadData payload) - { - return new WebSocketFrame(Opcode.Close, mask, payload); - } - - internal static async Task CreateCloseFrameAsync(Mask mask, CloseStatusCode code, string reason) - { - return new WebSocketFrame( - Opcode.Close, mask, new PayloadData(await ((ushort)code).AppendAsync(reason).ConfigureAwait(false))); - } - - internal static WebSocketFrame CreatePingFrame(Mask mask) - { - return new WebSocketFrame(Opcode.Ping, mask, new PayloadData()); - } - - internal static WebSocketFrame CreatePingFrame(Mask mask, byte[] data) - { - return new WebSocketFrame(Opcode.Ping, mask, new PayloadData(data)); - } - - internal static WebSocketFrame CreatePongFrame(Mask mask, PayloadData payload) - { - return new WebSocketFrame(Opcode.Pong, mask, payload); - } - - internal static WebSocketFrame CreateWebSocketFrame( - Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed) - { - return new WebSocketFrame(fin, opcode, mask, new PayloadData(data), compressed); - } - - internal static Task ReadAsync(Stream stream) - => ReadAsync(stream, true); - - internal static async Task ReadAsync(Stream stream, bool unmask) - { - var header = await stream.ReadBytesAsync(2).ConfigureAwait(false); - if (header.Length != 2) - { - throw new WebSocketException( - "The header part of a frame cannot be read from the data source."); - } - - return await ReadAsync(header, stream, unmask).ConfigureAwait(false); - } - - internal static async Task ReadAsync( - Stream stream, bool unmask, Action completed, Action error) - { - try - { - var header = await stream.ReadBytesAsync(2).ConfigureAwait(false); - if (header.Length != 2) - { - throw new WebSocketException( - "The header part of a frame cannot be read from the data source."); - } - - var frame = await ReadAsync(header, stream, unmask).ConfigureAwait(false); - completed?.Invoke(frame); - } - catch (Exception ex) - { - error.Invoke(ex); - } - } - - #endregion - - #region Public Methods - - public IEnumerator GetEnumerator() - { - foreach (var b in ToByteArray()) - yield return b; - } - - public void Print(bool dumped) - { - //Console.WriteLine(dumped ? dump(this) : print(this)); - } - - public byte[] ToByteArray() - { - using (var buff = new MemoryStream()) - { - var header = (int)_fin; - header = (header << 1) + (int)_rsv1; - header = (header << 1) + (int)_rsv2; - header = (header << 1) + (int)_rsv3; - header = (header << 4) + (int)_opcode; - header = (header << 1) + (int)_mask; - header = (header << 7) + (int)_payloadLength; - buff.Write(((ushort)header).ToByteArrayInternally(ByteOrder.Big), 0, 2); - - if (_payloadLength > 125) - buff.Write(_extPayloadLength, 0, _extPayloadLength.Length); - - if (_mask == Mask.Mask) - buff.Write(_maskingKey, 0, _maskingKey.Length); - - if (_payloadLength > 0) - { - var payload = _payloadData.ToByteArray(); - if (_payloadLength < 127) - buff.Write(payload, 0, payload.Length); - else - buff.WriteBytes(payload); - } - - return buff.ToArray(); - } - } - - public override string ToString() - { - return BitConverter.ToString(ToByteArray()); - } - - #endregion - - #region Explicitly Implemented Interface Members - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - } -} -- cgit v1.2.3 From 25d3d0b731db793e4afc39ff40e0b6a1a27d6ad8 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 08:02:32 +0100 Subject: Remove some unused stuff --- Emby.Server.Implementations/SocketSharp/RequestMono.cs | 17 ----------------- .../SocketSharp/WebSocketSharpRequest.cs | 17 ----------------- MediaBrowser.Model/Services/IRequest.cs | 10 ---------- 3 files changed, 44 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index a8142aef68..113f76b10b 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -105,14 +105,6 @@ namespace Emby.Server.Implementations.SocketSharp await LoadWwwForm(form).ConfigureAwait(false); } -#if NET_4_0 - if (validateRequestNewMode && !checked_form) { - // Setting this before calling the validator prevents - // possible endless recursion - checked_form = true; - ValidateNameValueCollection("Form", query_string_nvc, RequestValidationSource.Form); - } else -#endif if (validate_form && !checked_form) { checked_form = true; @@ -129,8 +121,6 @@ namespace Emby.Server.Implementations.SocketSharp protected bool validate_cookies { get; set; } protected bool validate_query_string { get; set; } protected bool validate_form { get; set; } - protected bool checked_cookies { get; set; } - protected bool checked_query_string { get; set; } protected bool checked_form { get; set; } private static void ThrowValidationException(string name, string key, string value) @@ -210,13 +200,6 @@ namespace Emby.Server.Implementations.SocketSharp return false; } - public void ValidateInput() - { - validate_cookies = true; - validate_query_string = true; - validate_form = true; - } - private bool IsContentType(string ct, bool starts_with) { if (ct == null || ContentType == null) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 53dce667ba..bddccf68ba 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -48,8 +48,6 @@ namespace Emby.Server.Implementations.SocketSharp public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); - public string UserHostAddress => request.HttpContext.Connection.RemoteIpAddress.ToString(); - public string XForwardedFor => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString(); @@ -142,19 +140,6 @@ namespace Emby.Server.Implementations.SocketSharp return name; } - internal static bool ContainsNonAsciiChars(string token) - { - for (int i = 0; i < token.Length; ++i) - { - if ((token[i] < 0x20) || (token[i] > 0x7e)) - { - return true; - } - } - - return false; - } - private string NormalizeIp(string ip) { if (!string.IsNullOrWhiteSpace(ip)) @@ -171,8 +156,6 @@ namespace Emby.Server.Implementations.SocketSharp return ip; } - public bool IsSecureConnection => request.IsHttps || XForwardedProtocol == "https"; - public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); private Dictionary items; diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index c7dccdb6fd..0fd4ea37b1 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -61,11 +61,6 @@ namespace MediaBrowser.Model.Services string AbsoluteUri { get; } - /// - /// The Remote Ip as reported by Request.UserHostAddress - /// - string UserHostAddress { get; } - /// /// The Remote Ip as reported by X-Forwarded-For, X-Real-IP or Request.UserHostAddress /// @@ -76,11 +71,6 @@ namespace MediaBrowser.Model.Services /// string Authorization { get; } - /// - /// e.g. is https or not - /// - bool IsSecureConnection { get; } - string[] AcceptTypes { get; } string PathInfo { get; } -- cgit v1.2.3 From 333bd2107a05c656b8fe27364dcd05fc7ca249d5 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 12:40:18 +0100 Subject: Remove HttpUtility --- Emby.Dlna/PlayTo/PlayToController.cs | 22 +- .../HttpServer/HttpListenerHost.cs | 3 +- MediaBrowser.Model/Services/HttpUtility.cs | 691 --------------------- 3 files changed, 14 insertions(+), 702 deletions(-) delete mode 100644 MediaBrowser.Model/Services/HttpUtility.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index be86dde16a..be6810d0e9 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -19,7 +19,9 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Services; using MediaBrowser.Model.Session; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; namespace Emby.Dlna.PlayTo { @@ -847,13 +849,13 @@ namespace Emby.Dlna.PlayTo if (index == -1) return request; var query = url.Substring(index + 1); - QueryParamCollection values = MyHttpUtility.ParseQueryString(query); + var values = QueryHelpers.ParseQuery(query); - request.DeviceProfileId = values.Get("DeviceProfileId"); - request.DeviceId = values.Get("DeviceId"); - request.MediaSourceId = values.Get("MediaSourceId"); - request.LiveStreamId = values.Get("LiveStreamId"); - request.IsDirectStream = string.Equals("true", values.Get("Static"), StringComparison.OrdinalIgnoreCase); + request.DeviceProfileId = values["DeviceProfileId"].ToString(); + request.DeviceId = values["DeviceId"].ToString(); + request.MediaSourceId = values["MediaSourceId"].ToString(); + request.LiveStreamId = values["LiveStreamId"].ToString(); + request.IsDirectStream = string.Equals("true", values["Static"].ToString(), StringComparison.OrdinalIgnoreCase); request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex"); request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex"); @@ -867,9 +869,9 @@ namespace Emby.Dlna.PlayTo } } - private static int? GetIntValue(QueryParamCollection values, string name) + private static int? GetIntValue(IReadOnlyDictionary values, string name) { - var value = values.Get(name); + var value = values[name].ToString(); if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { @@ -879,9 +881,9 @@ namespace Emby.Dlna.PlayTo return null; } - private static long GetLongValue(QueryParamCollection values, string name) + private static long GetLongValue(IReadOnlyDictionary values, string name) { - var value = values.Get(name); + var value = values[name].ToString(); if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 5b1cb433a9..2abc6c2f4f 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -22,6 +22,7 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using ServiceStack.Text.Jsv; @@ -298,7 +299,7 @@ namespace Emby.Server.Implementations.HttpServer var uri = new Uri(url); // this gets all the query string key value pairs as a collection - var newQueryString = MyHttpUtility.ParseQueryString(uri.Query); + var newQueryString = QueryHelpers.ParseQuery(uri.Query); var originalCount = newQueryString.Count; diff --git a/MediaBrowser.Model/Services/HttpUtility.cs b/MediaBrowser.Model/Services/HttpUtility.cs deleted file mode 100644 index be180334c6..0000000000 --- a/MediaBrowser.Model/Services/HttpUtility.cs +++ /dev/null @@ -1,691 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; - -namespace MediaBrowser.Model.Services -{ - public static class MyHttpUtility - { - // Must be sorted - static readonly long[] entities = new long[] { - (long)'A' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'A' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'A' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24, - (long)'A' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24, - (long)'A' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'B' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'C' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'C' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'D' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16, - (long)'D' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'E' << 56 | (long)'T' << 48 | (long)'H' << 40, - (long)'E' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'E' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'E' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'E' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'E' << 56 | (long)'t' << 48 | (long)'a' << 40, - (long)'E' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'G' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'I' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'I' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'I' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'I' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'I' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'K' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24, - (long)'L' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16, - (long)'M' << 56 | (long)'u' << 48, - (long)'N' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'N' << 56 | (long)'u' << 48, - (long)'O' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'O' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'O' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24, - (long)'O' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'O' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16, - (long)'O' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'P' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'P' << 56 | (long)'i' << 48, - (long)'P' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24, - (long)'P' << 56 | (long)'s' << 48 | (long)'i' << 40, - (long)'R' << 56 | (long)'h' << 48 | (long)'o' << 40, - (long)'S' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16, - (long)'S' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'T' << 56 | (long)'H' << 48 | (long)'O' << 40 | (long)'R' << 32 | (long)'N' << 24, - (long)'T' << 56 | (long)'a' << 48 | (long)'u' << 40, - (long)'T' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'U' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'U' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'U' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'U' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'U' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'X' << 56 | (long)'i' << 48, - (long)'Y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'Y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'Z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'a' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'a' << 56 | (long)'c' << 48 | (long)'u' << 40 | (long)'t' << 32 | (long)'e' << 24, - (long)'a' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'a' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'l' << 48 | (long)'e' << 40 | (long)'f' << 32 | (long)'s' << 24 | (long)'y' << 16 | (long)'m' << 8, - (long)'a' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24, - (long)'a' << 56 | (long)'m' << 48 | (long)'p' << 40, - (long)'a' << 56 | (long)'n' << 48 | (long)'d' << 40, - (long)'a' << 56 | (long)'n' << 48 | (long)'g' << 40, - (long)'a' << 56 | (long)'p' << 48 | (long)'o' << 40 | (long)'s' << 32, - (long)'a' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24, - (long)'a' << 56 | (long)'s' << 48 | (long)'y' << 40 | (long)'m' << 32 | (long)'p' << 24, - (long)'a' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'b' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'b' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'b' << 56 | (long)'r' << 48 | (long)'v' << 40 | (long)'b' << 32 | (long)'a' << 24 | (long)'r' << 16, - (long)'b' << 56 | (long)'u' << 48 | (long)'l' << 40 | (long)'l' << 32, - (long)'c' << 56 | (long)'a' << 48 | (long)'p' << 40, - (long)'c' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'c' << 56 | (long)'e' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'c' << 56 | (long)'e' << 48 | (long)'n' << 40 | (long)'t' << 32, - (long)'c' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'c' << 56 | (long)'i' << 48 | (long)'r' << 40 | (long)'c' << 32, - (long)'c' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'b' << 32 | (long)'s' << 24, - (long)'c' << 56 | (long)'o' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'c' << 56 | (long)'o' << 48 | (long)'p' << 40 | (long)'y' << 32, - (long)'c' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'r' << 24, - (long)'c' << 56 | (long)'u' << 48 | (long)'p' << 40, - (long)'c' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'n' << 16, - (long)'d' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'d' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16, - (long)'d' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'d' << 56 | (long)'e' << 48 | (long)'g' << 40, - (long)'d' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'d' << 56 | (long)'i' << 48 | (long)'a' << 40 | (long)'m' << 32 | (long)'s' << 24, - (long)'d' << 56 | (long)'i' << 48 | (long)'v' << 40 | (long)'i' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'e' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'m' << 48 | (long)'p' << 40 | (long)'t' << 32 | (long)'y' << 24, - (long)'e' << 56 | (long)'m' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'e' << 56 | (long)'n' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'e' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'e' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'i' << 32 | (long)'v' << 24, - (long)'e' << 56 | (long)'t' << 48 | (long)'a' << 40, - (long)'e' << 56 | (long)'t' << 48 | (long)'h' << 40, - (long)'e' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'e' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'o' << 32, - (long)'e' << 56 | (long)'x' << 48 | (long)'i' << 40 | (long)'s' << 32 | (long)'t' << 24, - (long)'f' << 56 | (long)'n' << 48 | (long)'o' << 40 | (long)'f' << 32, - (long)'f' << 56 | (long)'o' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'l' << 24 | (long)'l' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'2' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'4' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'3' << 24 | (long)'4' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'l' << 24, - (long)'g' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'g' << 56 | (long)'e' << 48, - (long)'g' << 56 | (long)'t' << 48, - (long)'h' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'h' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'h' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'t' << 24 | (long)'s' << 16, - (long)'h' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'l' << 32 | (long)'i' << 24 | (long)'p' << 16, - (long)'i' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'i' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'i' << 56 | (long)'e' << 48 | (long)'x' << 40 | (long)'c' << 32 | (long)'l' << 24, - (long)'i' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'i' << 56 | (long)'m' << 48 | (long)'a' << 40 | (long)'g' << 32 | (long)'e' << 24, - (long)'i' << 56 | (long)'n' << 48 | (long)'f' << 40 | (long)'i' << 32 | (long)'n' << 24, - (long)'i' << 56 | (long)'n' << 48 | (long)'t' << 40, - (long)'i' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'i' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'e' << 32 | (long)'s' << 24 | (long)'t' << 16, - (long)'i' << 56 | (long)'s' << 48 | (long)'i' << 40 | (long)'n' << 32, - (long)'i' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'k' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24, - (long)'l' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'l' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16, - (long)'l' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'l' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'l' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'l' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'e' << 48, - (long)'l' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16, - (long)'l' << 56 | (long)'o' << 48 | (long)'w' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'t' << 16, - (long)'l' << 56 | (long)'o' << 48 | (long)'z' << 40, - (long)'l' << 56 | (long)'r' << 48 | (long)'m' << 40, - (long)'l' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16, - (long)'l' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'t' << 48, - (long)'m' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'r' << 32, - (long)'m' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24, - (long)'m' << 56 | (long)'i' << 48 | (long)'c' << 40 | (long)'r' << 32 | (long)'o' << 24, - (long)'m' << 56 | (long)'i' << 48 | (long)'d' << 40 | (long)'d' << 32 | (long)'o' << 24 | (long)'t' << 16, - (long)'m' << 56 | (long)'i' << 48 | (long)'n' << 40 | (long)'u' << 32 | (long)'s' << 24, - (long)'m' << 56 | (long)'u' << 48, - (long)'n' << 56 | (long)'a' << 48 | (long)'b' << 40 | (long)'l' << 32 | (long)'a' << 24, - (long)'n' << 56 | (long)'b' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'n' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24, - (long)'n' << 56 | (long)'e' << 48, - (long)'n' << 56 | (long)'i' << 48, - (long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40, - (long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'i' << 32 | (long)'n' << 24, - (long)'n' << 56 | (long)'s' << 48 | (long)'u' << 40 | (long)'b' << 32, - (long)'n' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'n' << 56 | (long)'u' << 48, - (long)'o' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'o' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'o' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'l' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'e' << 24, - (long)'o' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24, - (long)'o' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'o' << 56 | (long)'p' << 48 | (long)'l' << 40 | (long)'u' << 32 | (long)'s' << 24, - (long)'o' << 56 | (long)'r' << 48, - (long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'f' << 32, - (long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'m' << 32, - (long)'o' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16, - (long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24 | (long)'s' << 16, - (long)'o' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'a' << 32, - (long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'t' << 32, - (long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'m' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'p' << 32, - (long)'p' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'p' << 56 | (long)'i' << 48, - (long)'p' << 56 | (long)'i' << 48 | (long)'v' << 40, - (long)'p' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'s' << 32 | (long)'m' << 24 | (long)'n' << 16, - (long)'p' << 56 | (long)'o' << 48 | (long)'u' << 40 | (long)'n' << 32 | (long)'d' << 24, - (long)'p' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24, - (long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'d' << 32, - (long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'p' << 32, - (long)'p' << 56 | (long)'s' << 48 | (long)'i' << 40, - (long)'q' << 56 | (long)'u' << 48 | (long)'o' << 40 | (long)'t' << 32, - (long)'r' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'r' << 56 | (long)'a' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'c' << 24, - (long)'r' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'r' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'r' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'r' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'r' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'r' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'l' << 32, - (long)'r' << 56 | (long)'e' << 48 | (long)'g' << 40, - (long)'r' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16, - (long)'r' << 56 | (long)'h' << 48 | (long)'o' << 40, - (long)'r' << 56 | (long)'l' << 48 | (long)'m' << 40, - (long)'r' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16, - (long)'r' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'s' << 56 | (long)'b' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'s' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16, - (long)'s' << 56 | (long)'d' << 48 | (long)'o' << 40 | (long)'t' << 32, - (long)'s' << 56 | (long)'e' << 48 | (long)'c' << 40 | (long)'t' << 32, - (long)'s' << 56 | (long)'h' << 48 | (long)'y' << 40, - (long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24 | (long)'f' << 16, - (long)'s' << 56 | (long)'i' << 48 | (long)'m' << 40, - (long)'s' << 56 | (long)'p' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24 | (long)'s' << 16, - (long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40 | (long)'e' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'m' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'1' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'2' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'3' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'e' << 32, - (long)'s' << 56 | (long)'z' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'t' << 56 | (long)'a' << 48 | (long)'u' << 40, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'4' << 16, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24 | (long)'s' << 16 | (long)'y' << 8 | (long)'m' << 0, - (long)'t' << 56 | (long)'h' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'s' << 24 | (long)'p' << 16, - (long)'t' << 56 | (long)'h' << 48 | (long)'o' << 40 | (long)'r' << 32 | (long)'n' << 24, - (long)'t' << 56 | (long)'i' << 48 | (long)'l' << 40 | (long)'d' << 32 | (long)'e' << 24, - (long)'t' << 56 | (long)'i' << 48 | (long)'m' << 40 | (long)'e' << 32 | (long)'s' << 24, - (long)'t' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24, - (long)'u' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'u' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'u' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'u' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'u' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'u' << 56 | (long)'m' << 48 | (long)'l' << 40, - (long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'h' << 24, - (long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'u' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'w' << 56 | (long)'e' << 48 | (long)'i' << 40 | (long)'e' << 32 | (long)'r' << 24 | (long)'p' << 16, - (long)'x' << 56 | (long)'i' << 48, - (long)'y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'y' << 56 | (long)'e' << 48 | (long)'n' << 40, - (long)'y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'z' << 56 | (long)'w' << 48 | (long)'j' << 40, - (long)'z' << 56 | (long)'w' << 48 | (long)'n' << 40 | (long)'j' << 32 - }; - - static readonly char[] entities_values = new char[] { - '\u00C6', '\u00C1', '\u00C2', '\u00C0', '\u0391', '\u00C5', '\u00C3', '\u00C4', '\u0392', '\u00C7', '\u03A7', - '\u2021', '\u0394', '\u00D0', '\u00C9', '\u00CA', '\u00C8', '\u0395', '\u0397', '\u00CB', '\u0393', '\u00CD', - '\u00CE', '\u00CC', '\u0399', '\u00CF', '\u039A', '\u039B', '\u039C', '\u00D1', '\u039D', '\u0152', '\u00D3', - '\u00D4', '\u00D2', '\u03A9', '\u039F', '\u00D8', '\u00D5', '\u00D6', '\u03A6', '\u03A0', '\u2033', '\u03A8', - '\u03A1', '\u0160', '\u03A3', '\u00DE', '\u03A4', '\u0398', '\u00DA', '\u00DB', '\u00D9', '\u03A5', '\u00DC', - '\u039E', '\u00DD', '\u0178', '\u0396', '\u00E1', '\u00E2', '\u00B4', '\u00E6', '\u00E0', '\u2135', '\u03B1', - '\u0026', '\u2227', '\u2220', '\u0027', '\u00E5', '\u2248', '\u00E3', '\u00E4', '\u201E', '\u03B2', '\u00A6', - '\u2022', '\u2229', '\u00E7', '\u00B8', '\u00A2', '\u03C7', '\u02C6', '\u2663', '\u2245', '\u00A9', '\u21B5', - '\u222A', '\u00A4', '\u21D3', '\u2020', '\u2193', '\u00B0', '\u03B4', '\u2666', '\u00F7', '\u00E9', '\u00EA', - '\u00E8', '\u2205', '\u2003', '\u2002', '\u03B5', '\u2261', '\u03B7', '\u00F0', '\u00EB', '\u20AC', '\u2203', - '\u0192', '\u2200', '\u00BD', '\u00BC', '\u00BE', '\u2044', '\u03B3', '\u2265', '\u003E', '\u21D4', '\u2194', - '\u2665', '\u2026', '\u00ED', '\u00EE', '\u00A1', '\u00EC', '\u2111', '\u221E', '\u222B', '\u03B9', '\u00BF', - '\u2208', '\u00EF', '\u03BA', '\u21D0', '\u03BB', '\u2329', '\u00AB', '\u2190', '\u2308', '\u201C', '\u2264', - '\u230A', '\u2217', '\u25CA', '\u200E', '\u2039', '\u2018', '\u003C', '\u00AF', '\u2014', '\u00B5', '\u00B7', - '\u2212', '\u03BC', '\u2207', '\u00A0', '\u2013', '\u2260', '\u220B', '\u00AC', '\u2209', '\u2284', '\u00F1', - '\u03BD', '\u00F3', '\u00F4', '\u0153', '\u00F2', '\u203E', '\u03C9', '\u03BF', '\u2295', '\u2228', '\u00AA', - '\u00BA', '\u00F8', '\u00F5', '\u2297', '\u00F6', '\u00B6', '\u2202', '\u2030', '\u22A5', '\u03C6', '\u03C0', - '\u03D6', '\u00B1', '\u00A3', '\u2032', '\u220F', '\u221D', '\u03C8', '\u0022', '\u21D2', '\u221A', '\u232A', - '\u00BB', '\u2192', '\u2309', '\u201D', '\u211C', '\u00AE', '\u230B', '\u03C1', '\u200F', '\u203A', '\u2019', - '\u201A', '\u0161', '\u22C5', '\u00A7', '\u00AD', '\u03C3', '\u03C2', '\u223C', '\u2660', '\u2282', '\u2286', - '\u2211', '\u2283', '\u00B9', '\u00B2', '\u00B3', '\u2287', '\u00DF', '\u03C4', '\u2234', '\u03B8', '\u03D1', - '\u2009', '\u00FE', '\u02DC', '\u00D7', '\u2122', '\u21D1', '\u00FA', '\u2191', '\u00FB', '\u00F9', '\u00A8', - '\u03D2', '\u03C5', '\u00FC', '\u2118', '\u03BE', '\u00FD', '\u00A5', '\u00FF', '\u03B6', '\u200D', '\u200C' - }; - - #region Methods - - static void WriteCharBytes(IList buf, char ch, Encoding e) - { - if (ch > 255) - { - foreach (byte b in e.GetBytes(new char[] { ch })) - buf.Add(b); - } - else - buf.Add((byte)ch); - } - - public static string UrlDecode(string s, Encoding e) - { - if (null == s) - return null; - - if (s.IndexOf('%') == -1 && s.IndexOf('+') == -1) - return s; - - if (e == null) - e = Encoding.UTF8; - - long len = s.Length; - var bytes = new List(); - int xchar; - char ch; - - for (int i = 0; i < len; i++) - { - ch = s[i]; - if (ch == '%' && i + 2 < len && s[i + 1] != '%') - { - if (s[i + 1] == 'u' && i + 5 < len) - { - // unicode hex sequence - xchar = GetChar(s, i + 2, 4); - if (xchar != -1) - { - WriteCharBytes(bytes, (char)xchar, e); - i += 5; - } - else - WriteCharBytes(bytes, '%', e); - } - else if ((xchar = GetChar(s, i + 1, 2)) != -1) - { - WriteCharBytes(bytes, (char)xchar, e); - i += 2; - } - else - { - WriteCharBytes(bytes, '%', e); - } - continue; - } - - if (ch == '+') - WriteCharBytes(bytes, ' ', e); - else - WriteCharBytes(bytes, ch, e); - } - - byte[] buf = bytes.ToArray(); - bytes = null; - return e.GetString(buf, 0, buf.Length); - - } - - static int GetInt(byte b) - { - char c = (char)b; - if (c >= '0' && c <= '9') - return c - '0'; - - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - - return -1; - } - - static int GetChar(string str, int offset, int length) - { - int val = 0; - int end = length + offset; - for (int i = offset; i < end; i++) - { - char c = str[i]; - if (c > 127) - return -1; - - int current = GetInt((byte)c); - if (current == -1) - return -1; - val = (val << 4) + current; - } - - return val; - } - - static bool TryConvertKeyToEntity(string key, out char value) - { - var token = CalculateKeyValue(key); - if (token == 0) - { - value = '\0'; - return false; - } - - var idx = Array.BinarySearch(entities, token); - if (idx < 0) - { - value = '\0'; - return false; - } - - value = entities_values[idx]; - return true; - } - - static long CalculateKeyValue(string s) - { - if (s.Length > 8) - return 0; - - long key = 0; - for (int i = 0; i < s.Length; ++i) - { - long ch = s[i]; - if (ch > 'z' || ch < '0') - return 0; - - key |= ch << ((7 - i) * 8); - } - - return key; - } - - /// - /// Decodes an HTML-encoded string and returns the decoded string. - /// - /// The HTML string to decode. - /// The decoded text. - public static string HtmlDecode(string s) - { - if (s == null) - throw new ArgumentNullException(nameof(s)); - - if (s.IndexOf('&') == -1) - return s; - - var entity = new StringBuilder(); - var output = new StringBuilder(); - int len = s.Length; - // 0 -> nothing, - // 1 -> right after '&' - // 2 -> between '&' and ';' but no '#' - // 3 -> '#' found after '&' and getting numbers - int state = 0; - int number = 0; - int digit_start = 0; - bool hex_number = false; - - for (int i = 0; i < len; i++) - { - char c = s[i]; - if (state == 0) - { - if (c == '&') - { - entity.Append(c); - state = 1; - } - else - { - output.Append(c); - } - continue; - } - - if (c == '&') - { - state = 1; - if (digit_start > 0) - { - entity.Append(s, digit_start, i - digit_start); - digit_start = 0; - } - - output.Append(entity.ToString()); - entity.Length = 0; - entity.Append('&'); - continue; - } - - switch (state) - { - case 1: - if (c == ';') - { - state = 0; - output.Append(entity.ToString()); - output.Append(c); - entity.Length = 0; - break; - } - - number = 0; - hex_number = false; - if (c != '#') - { - state = 2; - } - else - { - state = 3; - } - entity.Append(c); - - break; - case 2: - entity.Append(c); - if (c == ';') - { - string key = entity.ToString(); - state = 0; - entity.Length = 0; - - if (key.Length > 1) - { - var skey = key.Substring(1, key.Length - 2); - if (TryConvertKeyToEntity(skey, out c)) - { - output.Append(c); - break; - } - } - - output.Append(key); - } - - break; - case 3: - if (c == ';') - { - if (number < 0x10000) - { - output.Append((char)number); - } - else - { - output.Append((char)(0xd800 + ((number - 0x10000) >> 10))); - output.Append((char)(0xdc00 + ((number - 0x10000) & 0x3ff))); - } - state = 0; - entity.Length = 0; - digit_start = 0; - break; - } - - if (c == 'x' || c == 'X' && !hex_number) - { - digit_start = i; - hex_number = true; - break; - } - - if (char.IsDigit(c)) - { - if (digit_start == 0) - digit_start = i; - - number = number * (hex_number ? 16 : 10) + ((int)c - '0'); - break; - } - - if (hex_number) - { - if (c >= 'a' && c <= 'f') - { - number = number * 16 + 10 + ((int)c - 'a'); - break; - } - if (c >= 'A' && c <= 'F') - { - number = number * 16 + 10 + ((int)c - 'A'); - break; - } - } - - state = 2; - if (digit_start > 0) - { - entity.Append(s, digit_start, i - digit_start); - digit_start = 0; - } - - entity.Append(c); - break; - } - } - - if (entity.Length > 0) - { - output.Append(entity); - } - else if (digit_start > 0) - { - output.Append(s, digit_start, s.Length - digit_start); - } - return output.ToString(); - } - - public static QueryParamCollection ParseQueryString(string query) - { - return ParseQueryString(query, Encoding.UTF8); - } - - public static QueryParamCollection ParseQueryString(string query, Encoding encoding) - { - if (query == null) - throw new ArgumentNullException(nameof(query)); - if (encoding == null) - throw new ArgumentNullException(nameof(encoding)); - if (query.Length == 0 || (query.Length == 1 && query[0] == '?')) - return new QueryParamCollection(); - if (query[0] == '?') - query = query.Substring(1); - - var result = new QueryParamCollection(); - ParseQueryString(query, encoding, result); - return result; - } - - internal static void ParseQueryString(string query, Encoding encoding, QueryParamCollection result) - { - if (query.Length == 0) - return; - - string decoded = HtmlDecode(query); - int decodedLength = decoded.Length; - int namePos = 0; - bool first = true; - while (namePos <= decodedLength) - { - int valuePos = -1, valueEnd = -1; - for (int q = namePos; q < decodedLength; q++) - { - if (valuePos == -1 && decoded[q] == '=') - { - valuePos = q + 1; - } - else if (decoded[q] == '&') - { - valueEnd = q; - break; - } - } - - if (first) - { - first = false; - if (decoded[namePos] == '?') - namePos++; - } - - string name, value; - if (valuePos == -1) - { - name = null; - valuePos = namePos; - } - else - { - name = UrlDecode(decoded.Substring(namePos, valuePos - namePos - 1), encoding); - } - if (valueEnd < 0) - { - namePos = -1; - valueEnd = decoded.Length; - } - else - { - namePos = valueEnd + 1; - } - value = UrlDecode(decoded.Substring(valuePos, valueEnd - valuePos), encoding); - - result.Add(name, value); - if (namePos == -1) - break; - } - } - #endregion // Methods - } -} -- cgit v1.2.3 From 7429c07c05ad12fbacef7952574edd75c294eb8a Mon Sep 17 00:00:00 2001 From: Xu Fasheng Date: Wed, 27 Feb 2019 20:16:54 +0800 Subject: Remove redundant parenthesis --- Emby.Server.Implementations/Networking/NetworkManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 8696d18968..ace93ebdee 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.Networking // Try to exclude virtual adapters // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms var addr = ipProperties.GatewayAddresses.FirstOrDefault(); - if (addr == null || (ignoreVirtualInterface && string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))) + if (addr == null || ignoreVirtualInterface && string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase)) { return new List(); } -- cgit v1.2.3 From 27e7e792b3d95912787c613f849548809d48f6b1 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 14:23:39 +0100 Subject: Replace some usage of QueryParamCollection --- Emby.Dlna/Api/DlnaServerService.cs | 10 +-- Emby.Dlna/ConnectionManager/ConnectionManager.cs | 3 +- Emby.Dlna/ContentDirectory/ContentDirectory.cs | 2 +- Emby.Dlna/ControlRequest.cs | 5 +- Emby.Dlna/DlnaManager.cs | 17 +++-- Emby.Dlna/IUpnpService.cs | 5 +- .../MediaReceiverRegistrar.cs | 3 +- .../HttpServer/HttpListenerHost.cs | 7 +- .../HttpServer/HttpResultFactory.cs | 19 +++-- .../HttpServer/WebSocketConnection.cs | 3 +- .../Net/WebSocketConnectEventArgs.cs | 3 +- .../Services/ServiceHandler.cs | 2 +- .../Session/SessionWebSocketListener.cs | 3 +- .../SocketSharp/RequestMono.cs | 2 - .../SocketSharp/WebSocketSharpListener.cs | 6 +- .../SocketSharp/WebSocketSharpRequest.cs | 12 ++-- .../SocketSharp/WebSocketSharpResponse.cs | 83 +--------------------- MediaBrowser.Api/Playback/BaseStreamingService.cs | 6 +- MediaBrowser.Controller/Dlna/IDlnaManager.cs | 5 +- .../Net/IWebSocketConnection.cs | 3 +- .../Net/WebSocketConnectEventArgs.cs | 6 +- MediaBrowser.Model/Services/IHttpRequest.cs | 5 -- MediaBrowser.Model/Services/IHttpResponse.cs | 12 ---- MediaBrowser.Model/Services/IRequest.cs | 21 ++---- .../Services/QueryParamCollection.cs | 20 ------ 25 files changed, 66 insertions(+), 197 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs index 68bf801637..8bf3797f85 100644 --- a/Emby.Dlna/Api/DlnaServerService.cs +++ b/Emby.Dlna/Api/DlnaServerService.cs @@ -136,7 +136,7 @@ namespace Emby.Dlna.Api { var url = Request.AbsoluteUri; var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); - var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress); + var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress); var cacheLength = TimeSpan.FromDays(1); var cacheKey = Request.RawUrl.GetMD5(); @@ -147,21 +147,21 @@ namespace Emby.Dlna.Api public object Get(GetContentDirectory request) { - var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary()); + var xml = ContentDirectory.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetMediaReceiverRegistrar request) { - var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary()); + var xml = MediaReceiverRegistrar.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetConnnectionManager request) { - var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary()); + var xml = ConnectionManager.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } @@ -193,7 +193,7 @@ namespace Emby.Dlna.Api return service.ProcessControlRequest(new ControlRequest { - Headers = Request.Headers.ToDictionary(), + Headers = Request.Headers, InputXml = requestStream, TargetServerUuId = id, RequestedUrl = Request.AbsoluteUri diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs index cc427f2a15..e138b91d6a 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManager.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -24,7 +23,7 @@ namespace Emby.Dlna.ConnectionManager XmlReaderSettingsFactory = xmlReaderSettingsFactory; } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new ConnectionManagerXmlBuilder().GetXml(); } diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index b0fec90e69..867e6112fd 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -65,7 +65,7 @@ namespace Emby.Dlna.ContentDirectory } } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new ContentDirectoryXmlBuilder().GetXml(); } diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs index afd9a0b874..907d437f85 100644 --- a/Emby.Dlna/ControlRequest.cs +++ b/Emby.Dlna/ControlRequest.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; using System.IO; +using Microsoft.AspNetCore.Http; namespace Emby.Dlna { public class ControlRequest { - public IDictionary Headers { get; set; } + public IHeaderDictionary Headers { get; set; } public Stream InputXml { get; set; } @@ -15,7 +16,7 @@ namespace Emby.Dlna public ControlRequest() { - Headers = new Dictionary(); + Headers = new HeaderDictionary(); } } } diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index f53d274516..770a901521 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -17,7 +17,9 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.IO; using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; namespace Emby.Dlna { @@ -205,16 +207,13 @@ namespace Emby.Dlna } } - public DeviceProfile GetProfile(IDictionary headers) + public DeviceProfile GetProfile(IHeaderDictionary headers) { if (headers == null) { throw new ArgumentNullException(nameof(headers)); } - // Convert to case insensitive - headers = new Dictionary(headers, StringComparer.OrdinalIgnoreCase); - var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification)); if (profile != null) @@ -230,12 +229,12 @@ namespace Emby.Dlna return profile; } - private bool IsMatch(IDictionary headers, DeviceIdentification profileInfo) + private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo) { return profileInfo.Headers.Any(i => IsMatch(headers, i)); } - private bool IsMatch(IDictionary headers, HttpHeaderInfo header) + private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header) { // Handle invalid user setup if (string.IsNullOrEmpty(header.Name)) @@ -243,14 +242,14 @@ namespace Emby.Dlna return false; } - if (headers.TryGetValue(header.Name, out string value)) + if (headers.TryGetValue(header.Name, out StringValues value)) { switch (header.Match) { case HeaderMatchType.Equals: return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase); case HeaderMatchType.Substring: - var isMatch = value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; + var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; //_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch); return isMatch; case HeaderMatchType.Regex: @@ -493,7 +492,7 @@ namespace Emby.Dlna internal string Path { get; set; } } - public string GetServerDescriptionXml(IDictionary headers, string serverUuId, string serverAddress) + public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress) { var profile = GetProfile(headers) ?? GetDefaultProfile(); diff --git a/Emby.Dlna/IUpnpService.cs b/Emby.Dlna/IUpnpService.cs index ab8aa46192..ae90e95c79 100644 --- a/Emby.Dlna/IUpnpService.cs +++ b/Emby.Dlna/IUpnpService.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace Emby.Dlna { public interface IUpnpService @@ -7,9 +5,8 @@ namespace Emby.Dlna /// /// Gets the content directory XML. /// - /// The headers. /// System.String. - string GetServiceXml(IDictionary headers); + string GetServiceXml(); /// /// Processes the control request. diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index 2b84528eab..9c6022b6cb 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -3,6 +3,7 @@ using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Xml; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar @@ -19,7 +20,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar XmlReaderSettingsFactory = xmlReaderSettingsFactory; } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new MediaReceiverRegistrarXmlBuilder().GetXml(); } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 2abc6c2f4f..70753e5634 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -22,6 +22,7 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -154,7 +155,7 @@ namespace Emby.Server.Implementations.HttpServer { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, - QueryString = e.QueryString ?? new QueryParamCollection() + QueryString = e.QueryString ?? new QueryCollection() }; connection.Closed += Connection_Closed; @@ -606,8 +607,8 @@ namespace Emby.Server.Implementations.HttpServer } finally { - httpRes.Close(); - + // TODO + httpRes.IsClosed = true; stopWatch.Stop(); var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 09cdbc3c26..52c8221f68 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -16,6 +16,8 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; using IRequest = MediaBrowser.Model.Services.IRequest; using MimeTypes = MediaBrowser.Model.Net.MimeTypes; @@ -246,9 +248,9 @@ namespace Emby.Server.Implementations.HttpServer private static string GetCompressionType(IRequest request) { - var acceptEncoding = request.Headers["Accept-Encoding"]; + var acceptEncoding = request.Headers["Accept-Encoding"].ToString(); - if (acceptEncoding != null) + if (string.IsNullOrEmpty(acceptEncoding)) { //if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1) // return "br"; @@ -424,12 +426,12 @@ namespace Emby.Server.Implementations.HttpServer /// private object GetCachedResult(IRequest requestContext, IDictionary responseHeaders, StaticResultOptions options) { - bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1; + bool noCache = (requestContext.Headers[HeaderNames.CacheControl].ToString()).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1; AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified); if (!noCache) { - DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader); + DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader); if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified)) { @@ -530,7 +532,7 @@ namespace Emby.Server.Implementations.HttpServer options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary(StringComparer.OrdinalIgnoreCase); var contentType = options.ContentType; - if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since"))) + if (!StringValues.IsNullOrEmpty(requestContext.Headers[HeaderNames.IfModifiedSince])) { // See if the result is already cached in the browser var result = GetCachedResult(requestContext, options.ResponseHeaders, options); @@ -548,7 +550,7 @@ namespace Emby.Server.Implementations.HttpServer AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified); AddAgeHeader(responseHeaders, options.DateLastModified); - var rangeHeader = requestContext.Headers.Get("Range"); + var rangeHeader = requestContext.Headers["Range"]; if (!isHeadRequest && !string.IsNullOrEmpty(options.Path)) { @@ -609,11 +611,6 @@ namespace Emby.Server.Implementations.HttpServer } } - /// - /// The us culture - /// - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - /// /// Adds the caching responseHeaders. /// diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index e9d0bac74d..2bf460bd11 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -8,6 +8,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using UtfUnknown; @@ -67,7 +68,7 @@ namespace Emby.Server.Implementations.HttpServer /// Gets or sets the query string. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs index 666f1f6011..e3047d3926 100644 --- a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs +++ b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs @@ -1,6 +1,7 @@ using System; using System.Net.WebSockets; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace Emby.Server.Implementations.Net { @@ -15,7 +16,7 @@ namespace Emby.Server.Implementations.Net /// Gets or sets the query string. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Gets or sets the web socket. /// diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 7e836e22c3..3c8adfc983 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -154,7 +154,7 @@ namespace Emby.Server.Implementations.Services { if (name == null) continue; //thank you ASP.NET - var values = request.QueryString.GetValues(name); + var values = request.QueryString[name]; if (values.Count == 1) { map[name] = values[0]; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 24903f5e8c..a551433ed9 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Session } } - private SessionInfo GetSession(QueryParamCollection queryString, string remoteEndpoint) + private SessionInfo GetSession(IQueryCollection queryString, string remoteEndpoint) { if (queryString == null) { diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index 113f76b10b..f73adc5ff8 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -118,8 +118,6 @@ namespace Emby.Server.Implementations.SocketSharp public string Authorization => StringValues.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"].ToString(); - protected bool validate_cookies { get; set; } - protected bool validate_query_string { get; set; } protected bool validate_form { get; set; } protected bool checked_form { get; set; } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 77469244b9..9f046c3fd0 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -52,12 +52,10 @@ using Microsoft.Extensions.Logging; var endpoint = ctx.Connection.RemoteIpAddress.ToString(); var url = ctx.Request.GetDisplayUrl(); - var queryString = new QueryParamCollection(ctx.Request.Query); - var connectingArgs = new WebSocketConnectingEventArgs { Url = url, - QueryString = queryString, + QueryString = ctx.Request.Query, Endpoint = endpoint }; @@ -73,7 +71,7 @@ using Microsoft.Extensions.Logging; WebSocketConnected(new WebSocketConnectEventArgs { Url = url, - QueryString = queryString, + QueryString = ctx.Request.Query, WebSocket = socket, Endpoint = endpoint }); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index bddccf68ba..24fd36062f 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IResponse = MediaBrowser.Model.Services.IResponse; namespace Emby.Server.Implementations.SocketSharp @@ -21,7 +20,7 @@ namespace Emby.Server.Implementations.SocketSharp public partial class WebSocketSharpRequest : IHttpRequest { private readonly HttpRequest request; - private readonly IHttpResponse response; + private readonly IResponse response; public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger) { @@ -34,11 +33,9 @@ namespace Emby.Server.Implementations.SocketSharp public HttpRequest HttpRequest => request; - public object OriginalRequest => request; - public IResponse Response => response; - public IHttpResponse HttpResponse => response; + public IResponse HttpResponse => response; public string OperationName { get; set; } @@ -396,10 +393,9 @@ namespace Emby.Server.Implementations.SocketSharp public string UserAgent => request.Headers[HeaderNames.UserAgent]; - public QueryParamCollection Headers => new QueryParamCollection(request.Headers); + public IHeaderDictionary Headers => request.Headers; - private QueryParamCollection queryString; - public QueryParamCollection QueryString => queryString ?? (queryString = new QueryParamCollection(request.Query)); + public IQueryCollection QueryString => request.Query; public bool IsLocal => string.Equals(request.HttpContext.Connection.LocalIpAddress.ToString(), request.HttpContext.Connection.RemoteIpAddress.ToString()); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index f9ecb52a55..c4fbaddd3e 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -1,23 +1,18 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net; -using System.Net.Sockets; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IRequest = MediaBrowser.Model.Services.IRequest; namespace Emby.Server.Implementations.SocketSharp { - public class WebSocketSharpResponse : IHttpResponse + public class WebSocketSharpResponse : IResponse { private readonly ILogger _logger; @@ -51,42 +46,7 @@ namespace Emby.Server.Implementations.SocketSharp set => _response.ContentType = value; } - public QueryParamCollection Headers => new QueryParamCollection(_response.Headers); - - private static string AsHeaderValue(Cookie cookie) - { - DateTime defaultExpires = DateTime.MinValue; - - var path = cookie.Expires == defaultExpires - ? "/" - : cookie.Path ?? "/"; - - var sb = new StringBuilder(); - - sb.Append($"{cookie.Name}={cookie.Value};path={path}"); - - if (cookie.Expires != defaultExpires) - { - sb.Append($";expires={cookie.Expires:R}"); - } - - if (!string.IsNullOrEmpty(cookie.Domain)) - { - sb.Append($";domain={cookie.Domain}"); - } - - if (cookie.Secure) - { - sb.Append(";Secure"); - } - - if (cookie.HttpOnly) - { - sb.Append(";HttpOnly"); - } - - return sb.ToString(); - } + public IHeaderDictionary Headers => _response.Headers; public void AddHeader(string name, string value) { @@ -111,51 +71,14 @@ namespace Emby.Server.Implementations.SocketSharp public Stream OutputStream => _response.Body; - public void Close() - { - if (!this.IsClosed) - { - this.IsClosed = true; - - try - { - var response = this._response; - - var outputStream = response.Body; - - // This is needed with compression - outputStream.Flush(); - outputStream.Dispose(); - } - catch (SocketException) - { - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in HttpListenerResponseWrapper"); - } - } - } - public bool IsClosed { get; - private set; - } - - public void SetCookie(Cookie cookie) - { - var cookieStr = AsHeaderValue(cookie); - _response.Headers.Add("Set-Cookie", cookieStr); + set; } public bool SendChunked { get; set; } - public bool KeepAlive { get; set; } - - public void ClearCookies() - { - } const int StreamCopyToBufferSize = 81920; public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) { diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index a6be071b84..ae259a4f59 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -609,12 +609,12 @@ namespace MediaBrowser.Api.Playback { foreach (var param in Request.QueryString) { - if (char.IsLower(param.Name[0])) + if (char.IsLower(param.Key[0])) { // This was probably not parsed initially and should be a StreamOptions // TODO: This should be incorporated either in the lower framework for parsing requests // or the generated URL should correctly serialize it - request.StreamOptions[param.Name] = param.Value; + request.StreamOptions[param.Key] = param.Value; } } } @@ -867,7 +867,7 @@ namespace MediaBrowser.Api.Playback private void ApplyDeviceProfileSettings(StreamState state) { - var headers = Request.Headers.ToDictionary(); + var headers = Request.Headers; if (!string.IsNullOrWhiteSpace(state.Request.DeviceProfileId)) { diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index a6ee7c5050..41a7686a37 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Dlna; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Dlna { @@ -17,7 +18,7 @@ namespace MediaBrowser.Controller.Dlna /// /// The headers. /// DeviceProfile. - DeviceProfile GetProfile(IDictionary headers); + DeviceProfile GetProfile(IHeaderDictionary headers); /// /// Gets the default profile. @@ -64,7 +65,7 @@ namespace MediaBrowser.Controller.Dlna /// The server uu identifier. /// The server address. /// System.String. - string GetServerDescriptionXml(IDictionary headers, string serverUuId, string serverAddress); + string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress); /// /// Gets the icon. diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index a09b2f7a22..566897b31f 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { @@ -35,7 +36,7 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the query string. /// /// The query string. - QueryParamCollection QueryString { get; set; } + IQueryCollection QueryString { get; set; } /// /// Gets or sets the receive action. diff --git a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs index f26b764bb3..107e674216 100644 --- a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs +++ b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs @@ -1,5 +1,7 @@ using System; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Internal; namespace MediaBrowser.Controller.Net { @@ -22,7 +24,7 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the query string. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Gets or sets a value indicating whether [allow connection]. /// @@ -31,7 +33,7 @@ namespace MediaBrowser.Controller.Net public WebSocketConnectingEventArgs() { - QueryString = new QueryParamCollection(); + QueryString = new QueryCollection(); AllowConnection = true; } } diff --git a/MediaBrowser.Model/Services/IHttpRequest.cs b/MediaBrowser.Model/Services/IHttpRequest.cs index 579f80c968..50c6076f30 100644 --- a/MediaBrowser.Model/Services/IHttpRequest.cs +++ b/MediaBrowser.Model/Services/IHttpRequest.cs @@ -2,11 +2,6 @@ namespace MediaBrowser.Model.Services { public interface IHttpRequest : IRequest { - /// - /// The HttpResponse - /// - IHttpResponse HttpResponse { get; } - /// /// The HTTP Verb /// diff --git a/MediaBrowser.Model/Services/IHttpResponse.cs b/MediaBrowser.Model/Services/IHttpResponse.cs index a8b79f3949..b99d12525d 100644 --- a/MediaBrowser.Model/Services/IHttpResponse.cs +++ b/MediaBrowser.Model/Services/IHttpResponse.cs @@ -4,17 +4,5 @@ namespace MediaBrowser.Model.Services { public interface IHttpResponse : IResponse { - //ICookies Cookies { get; } - - /// - /// Adds a new Set-Cookie instruction to Response - /// - /// - void SetCookie(Cookie cookie); - - /// - /// Removes all pending Set-Cookie instructions - /// - void ClearCookies(); } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 0fd4ea37b1..edb5a2509b 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -1,20 +1,15 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services { public interface IRequest { - /// - /// The underlying ASP.NET or HttpListener HttpRequest - /// - object OriginalRequest { get; } - IResponse Response { get; } /// @@ -51,9 +46,9 @@ namespace MediaBrowser.Model.Services /// Dictionary Items { get; } - QueryParamCollection Headers { get; } + IHeaderDictionary Headers { get; } - QueryParamCollection QueryString { get; } + IQueryCollection QueryString { get; } Task GetFormData(); @@ -122,21 +117,15 @@ namespace MediaBrowser.Model.Services Stream OutputStream { get; } - /// - /// Signal that this response has been handled and no more processing should be done. - /// When used in a request or response filter, no more filters or processing is done on this request. - /// - void Close(); - /// /// Gets a value indicating whether this instance is closed. /// - bool IsClosed { get; } + bool IsClosed { get; set; } //Add Metadata to Response Dictionary Items { get; } - QueryParamCollection Headers { get; } + IHeaderDictionary Headers { get; } Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 9f23b2420f..4631a3b633 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; -using System.Net; using MediaBrowser.Model.Dto; -using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services { @@ -13,23 +10,6 @@ namespace MediaBrowser.Model.Services { public QueryParamCollection() { - - } - - public QueryParamCollection(IHeaderDictionary headers) - { - foreach (var pair in headers) - { - Add(pair.Key, pair.Value); - } - } - - public QueryParamCollection(IQueryCollection queryCollection) - { - foreach (var pair in queryCollection) - { - Add(pair.Key, pair.Value); - } } private static StringComparison GetStringComparison() -- cgit v1.2.3 From 647adc51c8681113afbc2c45b7637cc2c50f11f0 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 19:55:25 +0100 Subject: Fix query log --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 70753e5634..fd46c00578 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.HttpServer string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0]; return newQueryString.Count > 0 - ? string.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString) + ? $"{pagePathWithoutQueryString}?{newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value))}" : pagePathWithoutQueryString; } -- cgit v1.2.3 From c0b95dbc791acae1c28bf50fb3a33c06935e7192 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 20:11:40 +0100 Subject: Fix query log for real --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index fd46c00578..dbfb5e2438 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.HttpServer string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0]; return newQueryString.Count > 0 - ? $"{pagePathWithoutQueryString}?{newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value))}" + ? $"{pagePathWithoutQueryString}?{string.Join("&", newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value)))}" : pagePathWithoutQueryString; } -- cgit v1.2.3 From e47d12198556abfbfb57d2c1a9e45f6f7230bf63 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 21:40:47 +0100 Subject: Fix websockets --- Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs | 5 +++++ .../SocketSharp/WebSocketSharpListener.cs | 13 +++++++++---- .../Net/BasePeriodicWebSocketListener.cs | 13 ++----------- 3 files changed, 16 insertions(+), 15 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index dcbfb80487..eebbe8b780 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -85,6 +85,11 @@ namespace Emby.Server.Implementations.SocketSharp if (dispose) { _cancellationTokenSource.Cancel(); + if (_webSocket.State == WebSocketState.Open) + { + _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", + CancellationToken.None); + } } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 9f046c3fd0..c5abad5d33 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -76,18 +76,23 @@ using Microsoft.Extensions.Logging; Endpoint = endpoint }); - var buffer = WebSocket.CreateClientBuffer(4096, 4096); WebSocketReceiveResult result; var message = new List(); do { + var buffer = WebSocket.CreateServerBuffer(4096); result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); - socket.OnReceiveBytes(buffer.Array); message.AddRange(buffer.Array.Take(result.Count)); - } while (!result.EndOfMessage && result.MessageType != WebSocketMessageType.Close); - socket.OnReceiveBytes(message.ToArray()); + if (result.EndOfMessage) + { + socket.OnReceiveBytes(message.ToArray()); + message.Clear(); + } + } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); + + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, result.CloseStatusDescription, _disposeCancellationToken); socket.Dispose(); diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 4242a00e21..d2d0fbbc6c 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -80,12 +80,7 @@ namespace MediaBrowser.Controller.Net protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - protected virtual bool SendOnTimer => false; - - protected virtual void ParseMessageParams(string[] values) - { - - } + protected bool SendOnTimer => false; /// /// Starts sending messages over a web socket @@ -98,11 +93,6 @@ namespace MediaBrowser.Controller.Net var dueTimeMs = long.Parse(vals[0], UsCulture); var periodMs = long.Parse(vals[1], UsCulture); - if (vals.Length > 2) - { - ParseMessageParams(vals.Skip(2).ToArray()); - } - var cancellationTokenSource = new CancellationTokenSource(); Logger.LogDebug("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name); @@ -250,6 +240,7 @@ namespace MediaBrowser.Controller.Net { Logger.LogDebug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name); + connection.Item1.Dispose(); var timer = connection.Item3; if (timer != null) -- cgit v1.2.3 From dab8e150520f5a76d875a926530632783e17006f Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 21:48:28 +0100 Subject: Check websocket state before closing --- Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index c5abad5d33..9422673f52 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -93,8 +93,12 @@ using Microsoft.Extensions.Logging; } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); - await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, _disposeCancellationToken); + if (webSocketContext.State == WebSocketState.Open) + { + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, _disposeCancellationToken); + } + socket.Dispose(); } else -- cgit v1.2.3 From fb1de5a9213f7da98ed15a6975201d6bca3537d4 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 23:22:55 +0100 Subject: Remove more cruft and add the beginnings of a socket middleware --- Emby.Server.Implementations/ApplicationHost.cs | 2 + .../HttpServer/WebSocketConnection.cs | 36 +----------- .../Middleware/WebSocketMiddleware.cs | 36 ++++++++++++ Emby.Server.Implementations/Net/IWebSocket.cs | 5 -- .../SocketSharp/WebSocketSharpListener.cs | 65 ++++++++-------------- .../WebSocket/WebSocketManager.cs | 7 +++ .../WebSockets/WebSocketManager.cs | 22 ++++++++ .../MediaBrowser.Controller.csproj | 8 ++- 8 files changed, 97 insertions(+), 84 deletions(-) create mode 100644 Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs create mode 100644 Emby.Server.Implementations/WebSocket/WebSocketManager.cs create mode 100644 Emby.Server.Implementations/WebSockets/WebSocketManager.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0b4a2fd30e..e558b43544 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -35,6 +35,7 @@ using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Library; using Emby.Server.Implementations.LiveTv; using Emby.Server.Implementations.Localization; +using Emby.Server.Implementations.Middleware; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Reflection; @@ -641,6 +642,7 @@ namespace Emby.Server.Implementations app.UseWebSockets(); app.UseResponseCompression(); + // TODO app.UseMiddleware(); app.Use(ExecuteWebsocketHandlerAsync); app.Use(ExecuteHttpHandlerAsync); }) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 2bf460bd11..54a16040f7 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -102,12 +102,6 @@ namespace Emby.Server.Implementations.HttpServer _socket = socket; _socket.OnReceiveBytes = OnReceiveInternal; - var memorySocket = socket as IMemoryWebSocket; - if (memorySocket != null) - { - memorySocket.OnReceiveMemoryBytes = OnReceiveInternal; - } - RemoteEndPoint = remoteEndPoint; _logger = logger; @@ -143,34 +137,6 @@ namespace Emby.Server.Implementations.HttpServer } } - /// - /// Called when [receive]. - /// - /// The memory block. - /// The length of the memory block. - private void OnReceiveInternal(Memory memory, int length) - { - LastActivityDate = DateTime.UtcNow; - - if (OnReceive == null) - { - return; - } - - var bytes = memory.Slice(0, length).ToArray(); - - var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName; - - if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase)) - { - OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length)); - } - else - { - OnReceiveInternal(Encoding.ASCII.GetString(bytes, 0, bytes.Length)); - } - } - private void OnReceiveInternal(string message) { LastActivityDate = DateTime.UtcNow; @@ -194,7 +160,7 @@ namespace Emby.Server.Implementations.HttpServer var info = new WebSocketMessageInfo { MessageType = stub.MessageType, - Data = stub.Data == null ? null : stub.Data.ToString(), + Data = stub.Data?.ToString(), Connection = this }; diff --git a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs new file mode 100644 index 0000000000..a1d0e77d65 --- /dev/null +++ b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using WebSocketManager = Emby.Server.Implementations.WebSockets.WebSocketManager; + +namespace Emby.Server.Implementations.Middleware +{ + public class WebSocketMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly WebSocketManager _webSocketManager; + + public WebSocketMiddleware(RequestDelegate next, ILogger logger, WebSocketManager webSocketManager) + { + _next = next; + _logger = logger; + _webSocketManager = webSocketManager; + } + + public async Task Invoke(HttpContext httpContext) + { + _logger.LogInformation("Handling request: " + httpContext.Request.Path); + + if (httpContext.WebSockets.IsWebSocketRequest) + { + var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); + _webSocketManager.AddSocket(webSocketContext); + } + else + { + await _next.Invoke(httpContext); + } + } + } +} diff --git a/Emby.Server.Implementations/Net/IWebSocket.cs b/Emby.Server.Implementations/Net/IWebSocket.cs index 4671de07c5..4d160aa66f 100644 --- a/Emby.Server.Implementations/Net/IWebSocket.cs +++ b/Emby.Server.Implementations/Net/IWebSocket.cs @@ -45,9 +45,4 @@ namespace Emby.Server.Implementations.Net /// Task. Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken); } - - public interface IMemoryWebSocket - { - Action, int> OnReceiveMemoryBytes { get; set; } - } } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 9422673f52..5ddd316477 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -33,8 +33,6 @@ using Microsoft.Extensions.Logging; public Func ErrorHandler { get; set; } public Func RequestHandler { get; set; } - public Action WebSocketConnecting { get; set; } - public Action WebSocketConnected { get; set; } private static void LogRequest(ILogger logger, HttpRequest request) @@ -52,60 +50,41 @@ using Microsoft.Extensions.Logging; var endpoint = ctx.Connection.RemoteIpAddress.ToString(); var url = ctx.Request.GetDisplayUrl(); - var connectingArgs = new WebSocketConnectingEventArgs + var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); + var socket = new SharpWebSocket(webSocketContext, _logger); + + WebSocketConnected(new WebSocketConnectEventArgs { Url = url, QueryString = ctx.Request.Query, + WebSocket = socket, Endpoint = endpoint - }; + }); - WebSocketConnecting?.Invoke(connectingArgs); + WebSocketReceiveResult result; + var message = new List(); - if (connectingArgs.AllowConnection) + do { - _logger.LogDebug("Web socket connection allowed"); + var buffer = WebSocket.CreateServerBuffer(4096); + result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); + message.AddRange(buffer.Array.Take(result.Count)); - var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); - var socket = new SharpWebSocket(webSocketContext, _logger); - - WebSocketConnected(new WebSocketConnectEventArgs + if (result.EndOfMessage) { - Url = url, - QueryString = ctx.Request.Query, - WebSocket = socket, - Endpoint = endpoint - }); - - WebSocketReceiveResult result; - var message = new List(); - - do - { - var buffer = WebSocket.CreateServerBuffer(4096); - result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); - message.AddRange(buffer.Array.Take(result.Count)); - - if (result.EndOfMessage) - { - socket.OnReceiveBytes(message.ToArray()); - message.Clear(); - } - } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); - - - if (webSocketContext.State == WebSocketState.Open) - { - await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, _disposeCancellationToken); + socket.OnReceiveBytes(message.ToArray()); + message.Clear(); } + } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); - socket.Dispose(); - } - else + + if (webSocketContext.State == WebSocketState.Open) { - _logger.LogWarning("Web socket connection not allowed"); - ctx.Response.StatusCode = 401; + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, _disposeCancellationToken); } + + socket.Dispose(); } catch (Exception ex) { diff --git a/Emby.Server.Implementations/WebSocket/WebSocketManager.cs b/Emby.Server.Implementations/WebSocket/WebSocketManager.cs new file mode 100644 index 0000000000..7472820cf2 --- /dev/null +++ b/Emby.Server.Implementations/WebSocket/WebSocketManager.cs @@ -0,0 +1,7 @@ +namespace Emby.Server.Implementations.WebSocket +{ + public class WebSocketManager + { + + } +} diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs new file mode 100644 index 0000000000..7e74a45277 --- /dev/null +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Concurrent; +using System.Net.WebSockets; + +namespace Emby.Server.Implementations.WebSockets +{ + public class WebSocketManager + { + private readonly ConcurrentDictionary _activeWebSockets; + + public WebSocketManager() + { + _activeWebSockets = new ConcurrentDictionary(); + } + + public void AddSocket(WebSocket webSocket) + { + var guid = Guid.NewGuid(); + _activeWebSockets.TryAdd(guid, webSocket); + } + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 01893f1b55..81e255d525 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors @@ -16,6 +16,12 @@ + + + ..\..\..\..\..\usr\local\share\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.http.extensions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Extensions.dll + + + netstandard2.0 false -- cgit v1.2.3 From 588a13377c347cbd56462c64b80f0b9a563e534f Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 23:24:43 +0100 Subject: Remove a file that shouldn't have been added --- Emby.Server.Implementations/WebSocket/WebSocketManager.cs | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 Emby.Server.Implementations/WebSocket/WebSocketManager.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/WebSocket/WebSocketManager.cs b/Emby.Server.Implementations/WebSocket/WebSocketManager.cs deleted file mode 100644 index 7472820cf2..0000000000 --- a/Emby.Server.Implementations/WebSocket/WebSocketManager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Emby.Server.Implementations.WebSocket -{ - public class WebSocketManager - { - - } -} -- cgit v1.2.3 From 1ac282b12e20abc91402015369dc17543604f9fb Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 23:39:20 +0100 Subject: Call SharpWebSocket's Closed event handler before disposing --- Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index eebbe8b780..62b16ed8c8 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -90,6 +90,7 @@ namespace Emby.Server.Implementations.SocketSharp _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", CancellationToken.None); } + Closed?.Invoke(this, EventArgs.Empty); } _disposed = true; -- cgit v1.2.3 From edba82db373da8fbab8159d6ab2483052ebab231 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 27 Feb 2019 23:05:12 -0800 Subject: fixed logic flip in auth empty check and fixed crypto algo choice --- .../Cryptography/CryptographyProvider.cs | 34 +++++++++++++++------- .../Library/DefaultAuthenticationProvider.cs | 2 +- 2 files changed, 24 insertions(+), 12 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index ea719309cd..3c9403ba8e 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Text; +using System.Linq; using MediaBrowser.Model.Cryptography; namespace Emby.Server.Implementations.Cryptography @@ -11,16 +12,18 @@ namespace Emby.Server.Implementations.Cryptography public class CryptographyProvider : ICryptoProvider { private HashSet SupportedHashMethods; - public string DefaultHashMethod => "SHA256"; + public string DefaultHashMethod => "PBKDF2"; private RandomNumberGenerator rng; private int defaultiterations = 1000; public CryptographyProvider() { + //FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one + //Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 SupportedHashMethods = new HashSet() { - "MD5" + "MD5" ,"System.Security.Cryptography.MD5" ,"SHA" ,"SHA1" @@ -75,10 +78,15 @@ namespace Emby.Server.Implementations.Cryptography private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) { //downgrading for now as we need this library to be dotnetstandard compliant - using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) + //with this downgrade we'll add a check to make sure we're on the downgrade method at the moment + if(method == DefaultHashMethod) { - return r.GetBytes(32); + using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) + { + return r.GetBytes(32); + } } + throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}"); } public byte[] ComputeHash(string HashMethod, byte[] bytes) @@ -93,18 +101,22 @@ namespace Emby.Server.Implementations.Cryptography public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) { - if (SupportedHashMethods.Contains(HashMethod)) + if(HashMethod == DefaultHashMethod) + { + return PBKDF2(HashMethod, bytes, salt, defaultiterations); + } + else if (SupportedHashMethods.Contains(HashMethod)) { - if (salt.Length == 0) + using (var h = HashAlgorithm.Create(HashMethod)) { - using (var h = HashAlgorithm.Create(HashMethod)) + if (salt.Length == 0) { return h.ComputeHash(bytes); } - } - else - { - return PBKDF2(HashMethod, bytes, salt, defaultiterations); + else + { + return h.ComputeHash(bytes.Concat(salt).ToArray()); + } } } else diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index b58374adb4..7ccdccc0ad 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Library //but at least they are in the new format. private void ConvertPasswordFormat(User user) { - if (!string.IsNullOrEmpty(user.Password)) + if (string.IsNullOrEmpty(user.Password)) { return; } -- cgit v1.2.3 From 331260cf803f7d4500d2b88eabb78be994425081 Mon Sep 17 00:00:00 2001 From: TheBird956 Date: Tue, 26 Feb 2019 03:38:39 +0000 Subject: Translated using Weblate (French) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 52afb4e492..e434b7605b 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -44,7 +44,7 @@ "NameInstallFailed": "{0} échec d'installation", "NameSeasonNumber": "Saison {0}", "NameSeasonUnknown": "Saison Inconnue", - "NewVersionIsAvailable": "Une nouvelle version d'Jellyfin Serveur est disponible au téléchargement.", + "NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.", "NotificationOptionApplicationUpdateAvailable": "Mise à jour de l'application disponible", "NotificationOptionApplicationUpdateInstalled": "Mise à jour de l'application installée", "NotificationOptionAudioPlayback": "Lecture audio démarrée", @@ -89,7 +89,7 @@ "UserOnlineFromDevice": "{0} s'est connecté depuis {1}", "UserPasswordChangedWithName": "Le mot de passe pour l'utilisateur {0} a été modifié", "UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}", - "UserStartedPlayingItemWithValues": "{0} est entrain de lire {1} sur {2}", + "UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}", "UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}", "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre librairie", "ValueSpecialEpisodeName": "Spécial - {0}", -- cgit v1.2.3 From c8788d83fbd921fccf80b7f49be83db2c73a210b Mon Sep 17 00:00:00 2001 From: TheBird956 Date: Tue, 26 Feb 2019 03:23:03 +0000 Subject: Translated using Weblate (French (Canada)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr_CA/ --- .../Localization/Core/fr-CA.json | 170 ++++++++++----------- 1 file changed, 85 insertions(+), 85 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json index 7202be9f56..4b4db39a8b 100644 --- a/Emby.Server.Implementations/Localization/Core/fr-CA.json +++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json @@ -1,97 +1,97 @@ { "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", + "AppDeviceValues": "Application : {0}, Appareil : {1}", "Application": "Application", - "Artists": "Artists", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", - "ChapterNameValue": "Chapter {0}", + "Artists": "Artistes", + "AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès", + "Books": "Livres", + "CameraImageUploadedFrom": "Une nouvelle image de caméra a été téléchargée depuis {0}", + "Channels": "Chaînes", + "ChapterNameValue": "Chapitre {0}", "Collections": "Collections", - "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", + "DeviceOfflineWithName": "{0} s'est déconnecté", + "DeviceOnlineWithName": "{0} est connecté", + "FailedLoginAttemptWithUserName": "Échec d'une tentative de connexion de {0}", + "Favorites": "Favoris", + "Folders": "Dossiers", "Genres": "Genres", - "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", + "HeaderAlbumArtists": "Artistes de l'album", + "HeaderCameraUploads": "Photos transférées", "HeaderContinueWatching": "Continuer à regarder", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteArtists": "Favorite Artists", - "HeaderFavoriteEpisodes": "Favorite Episodes", - "HeaderFavoriteShows": "Favorite Shows", - "HeaderFavoriteSongs": "Favorite Songs", - "HeaderLiveTV": "Live TV", + "HeaderFavoriteAlbums": "Albums favoris", + "HeaderFavoriteArtists": "Artistes favoris", + "HeaderFavoriteEpisodes": "Épisodes favoris", + "HeaderFavoriteShows": "Séries favorites", + "HeaderFavoriteSongs": "Chansons favorites", + "HeaderLiveTV": "TV en direct", "HeaderNextUp": "À Suivre", - "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", - "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", - "Latest": "Latest", - "MessageApplicationUpdated": "Jellyfin Server has been updated", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MixedContent": "Mixed content", - "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", + "HeaderRecordingGroups": "Groupes d'enregistrements", + "HomeVideos": "Vidéos personnelles", + "Inherit": "Hériter", + "ItemAddedWithName": "{0} a été ajouté à la médiathèque", + "ItemRemovedWithName": "{0} a été supprimé de la médiathèque", + "LabelIpAddressValue": "Adresse IP : {0}", + "LabelRunningTimeValue": "Durée : {0}", + "Latest": "Derniers", + "MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour", + "MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour vers la version {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour", + "MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour", + "MixedContent": "Contenu mixte", + "Movies": "Films", + "Music": "Musique", + "MusicVideos": "Vidéos musicales", + "NameInstallFailed": "{0} échec d'installation", + "NameSeasonNumber": "Saison {0}", + "NameSeasonUnknown": "Saison Inconnue", + "NewVersionIsAvailable": "Une nouvelle version du serveur Jellyfin est disponible au téléchargement.", + "NotificationOptionApplicationUpdateAvailable": "Mise à jour de l'application disponible", + "NotificationOptionApplicationUpdateInstalled": "Mise à jour de l'application installée", + "NotificationOptionAudioPlayback": "Lecture audio démarrée", + "NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée", + "NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été transférée", + "NotificationOptionInstallationFailed": "Échec d'installation", + "NotificationOptionNewLibraryContent": "Nouveau contenu ajouté", + "NotificationOptionPluginError": "Erreur d'extension", + "NotificationOptionPluginInstalled": "Extension installée", + "NotificationOptionPluginUninstalled": "Extension désinstallée", + "NotificationOptionPluginUpdateInstalled": "Mise à jour d'extension installée", + "NotificationOptionServerRestartRequired": "Un redémarrage du serveur est requis", + "NotificationOptionTaskFailed": "Échec de tâche planifiée", + "NotificationOptionUserLockedOut": "Utilisateur verrouillé", + "NotificationOptionVideoPlayback": "Lecture vidéo démarrée", + "NotificationOptionVideoPlaybackStopped": "Lecture vidéo arrêtée", "Photos": "Photos", - "Playlists": "Playlists", - "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", - "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", - "Shows": "Series", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "Playlists": "Listes de lecture", + "Plugin": "Extension", + "PluginInstalledWithName": "{0} a été installé", + "PluginUninstalledWithName": "{0} a été désinstallé", + "PluginUpdatedWithName": "{0} a été mis à jour", + "ProviderValue": "Fournisseur : {0}", + "ScheduledTaskFailedWithName": "{0} a échoué", + "ScheduledTaskStartedWithName": "{0} a commencé", + "ServerNameNeedsToBeRestarted": "{0} doit être redémarré", + "Shows": "Émissions", + "Songs": "Chansons", + "StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", - "Sync": "Sync", - "System": "System", - "TvShows": "TV Shows", - "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", - "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", - "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}", + "SubtitlesDownloadedForItem": "Les sous-titres de {0} ont été téléchargés", + "Sync": "Synchroniser", + "System": "Système", + "TvShows": "Séries Télé", + "User": "Utilisateur", + "UserCreatedWithName": "L'utilisateur {0} a été créé", + "UserDeletedWithName": "L'utilisateur {0} a été supprimé", + "UserDownloadingItemWithValues": "{0} est en train de télécharger {1}", + "UserLockedOutWithName": "L'utilisateur {0} a été verrouillé", + "UserOfflineFromDevice": "{0} s'est déconnecté depuis {1}", + "UserOnlineFromDevice": "{0} s'est connecté depuis {1}", + "UserPasswordChangedWithName": "Le mot de passe pour l'utilisateur {0} a été modifié", + "UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}", + "UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}", + "UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}", + "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque", "ValueSpecialEpisodeName": "Spécial - {0}", "VersionNumber": "Version {0}" } -- cgit v1.2.3 From 7c13021def4432123917b6cf18c129e19ddff756 Mon Sep 17 00:00:00 2001 From: EffeF Date: Sun, 24 Feb 2019 17:44:06 +0000 Subject: Translated using Weblate (Italian) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- Emby.Server.Implementations/Localization/Core/it.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index a5f1e8f94d..357883cd37 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -34,17 +34,17 @@ "LabelRunningTimeValue": "Durata: {0}", "Latest": "Più recenti", "MessageApplicationUpdated": "Il Server Jellyfin è stato aggiornato", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}", "MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata", "MessageServerConfigurationUpdated": "La configurazione del server è stata aggiornata", "MixedContent": "Contenuto misto", "Movies": "Film", "Music": "Musica", "MusicVideos": "Video musicali", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "{0} installazione fallita", "NameSeasonNumber": "Stagione {0}", "NameSeasonUnknown": "Stagione sconosciuto", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.", "NotificationOptionApplicationUpdateAvailable": "Aggiornamento dell'applicazione disponibile", "NotificationOptionApplicationUpdateInstalled": "Aggiornamento dell'applicazione installato", "NotificationOptionAudioPlayback": "La riproduzione audio è iniziata", @@ -70,12 +70,12 @@ "ProviderValue": "Provider: {0}", "ScheduledTaskFailedWithName": "{0} fallito", "ScheduledTaskStartedWithName": "{0} avviati", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "{0} deve essere riavviato", "Shows": "Programmi", "Songs": "Canzoni", "StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.", "SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Impossibile scaricare i sottotitoli da {0} per {1}", "SubtitlesDownloadedForItem": "Sottotitoli scaricati per {0}", "Sync": "Sincronizza", "System": "Sistema", @@ -91,7 +91,7 @@ "UserPolicyUpdatedWithName": "La politica dell'utente è stata aggiornata per {0}", "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1}", "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale", "ValueSpecialEpisodeName": "Speciale - {0}", "VersionNumber": "Versione {0}" } -- cgit v1.2.3 From 311caf3fc3e45cc332e922897f963b95cb60f98a Mon Sep 17 00:00:00 2001 From: v1tin Date: Mon, 18 Feb 2019 19:02:01 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- Emby.Server.Implementations/Localization/Core/pt-BR.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index aaedf08505..dbc9c4c4b8 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -5,7 +5,7 @@ "Artists": "Artistas", "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", "Books": "Livros", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "Uma nova imagem da câmera foi submetida de {0}", "Channels": "Canais", "ChapterNameValue": "Capítulo {0}", "Collections": "Coletâneas", @@ -30,21 +30,21 @@ "Inherit": "Herdar", "ItemAddedWithName": "{0} foi adicionado à biblioteca", "ItemRemovedWithName": "{0} foi removido da biblioteca", - "LabelIpAddressValue": "Endereço ip: {0}", + "LabelIpAddressValue": "Endereço IP: {0}", "LabelRunningTimeValue": "Tempo de execução: {0}", "Latest": "Recente", "MessageApplicationUpdated": "O servidor Jellyfin foi atualizado", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "O Servidor Jellyfin foi atualizado para {0}", "MessageNamedServerConfigurationUpdatedWithValue": "A seção {0} da configuração do servidor foi atualizada", "MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada", "MixedContent": "Conteúdo misto", "Movies": "Filmes", "Music": "Música", "MusicVideos": "Vídeos musicais", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "A instalação de {0} falhou", "NameSeasonNumber": "Temporada {0}", "NameSeasonUnknown": "Temporada Desconhecida", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NewVersionIsAvailable": "Uma nova versão do servidor Jellyfin está disponível para download.", "NotificationOptionApplicationUpdateAvailable": "Atualização de aplicativo disponível", "NotificationOptionApplicationUpdateInstalled": "Atualização de aplicativo instalada", "NotificationOptionAudioPlayback": "Reprodução de áudio iniciada", @@ -70,12 +70,12 @@ "ProviderValue": "Provedor: {0}", "ScheduledTaskFailedWithName": "{0} falhou", "ScheduledTaskStartedWithName": "{0} iniciada", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "O servidor {0} precisa ser reiniciado", "Shows": "Séries", "Songs": "Músicas", "StartupEmbyServerIsLoading": "O Servidor Jellyfin está carregando. Por favor tente novamente em breve.", "SubtitleDownloadFailureForItem": "Download de legendas falhou para {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Houve um problema ao baixar as legendas de {0} para {1}", "SubtitlesDownloadedForItem": "Legendas baixadas para {0}", "Sync": "Sincronizar", "System": "Sistema", @@ -91,7 +91,7 @@ "UserPolicyUpdatedWithName": "A política de usuário foi atualizada para {0}", "UserStartedPlayingItemWithValues": "{0} iniciou a reprodução de {1}", "UserStoppedPlayingItemWithValues": "{0} parou de reproduzir {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueHasBeenAddedToLibrary": "{0} foi adicionado a sua biblioteca", "ValueSpecialEpisodeName": "Especial - {0}", "VersionNumber": "Versão {0}" } -- cgit v1.2.3 From d0e4e0f6004644b58cd5f64a89f82467759e5876 Mon Sep 17 00:00:00 2001 From: SaddFox Date: Tue, 26 Feb 2019 23:05:46 +0000 Subject: Translated using Weblate (Slovenian) Currently translated at 87.2% (82 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sl/ --- .../Localization/Core/sl-SI.json | 112 ++++++++++----------- 1 file changed, 56 insertions(+), 56 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index e850257d4a..b50ff4706e 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -1,62 +1,62 @@ { - "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", - "Artists": "Artists", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", - "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", + "Albums": "Albumi", + "AppDeviceValues": "Aplikacija: {0}, Naprava: {1}", + "Application": "Aplikacija", + "Artists": "Izvajalci", + "AuthenticationSucceededWithUserName": "{0} preverjanje uspešno", + "Books": "Knjige", + "CameraImageUploadedFrom": "Nova fotografija je bila naložena z {0}", + "Channels": "Kanali", + "ChapterNameValue": "Poglavje {0}", + "Collections": "Zbirke", "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", - "Genres": "Genres", - "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "Continue Watching", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteArtists": "Favorite Artists", - "HeaderFavoriteEpisodes": "Favorite Episodes", - "HeaderFavoriteShows": "Favorite Shows", - "HeaderFavoriteSongs": "Favorite Songs", - "HeaderLiveTV": "Live TV", - "HeaderNextUp": "Next Up", - "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", - "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", - "Latest": "Latest", - "MessageApplicationUpdated": "Jellyfin Server has been updated", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "DeviceOnlineWithName": "{0} je povezan", + "FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}", + "Favorites": "Priljubljeni", + "Folders": "Mape", + "Genres": "Zvrsti", + "HeaderAlbumArtists": "Izvajalci albuma", + "HeaderCameraUploads": "Posnetki kamere", + "HeaderContinueWatching": "Nadaljuj gledanje", + "HeaderFavoriteAlbums": "Priljubljeni albumi", + "HeaderFavoriteArtists": "Priljubljeni izvajalci", + "HeaderFavoriteEpisodes": "Priljubljene epizode", + "HeaderFavoriteShows": "Priljubljene serije", + "HeaderFavoriteSongs": "Priljubljene pesmi", + "HeaderLiveTV": "TV v živo", + "HeaderNextUp": "Sledi", + "HeaderRecordingGroups": "Zbirke posnetkov", + "HomeVideos": "Domači posnetki", + "Inherit": "Podeduj", + "ItemAddedWithName": "{0} je dodan v knjižnico", + "ItemRemovedWithName": "{0} je bil odstranjen iz knjižnice", + "LabelIpAddressValue": "IP naslov: {0}", + "LabelRunningTimeValue": "Čas trajanja: {0}", + "Latest": "Najnovejše", + "MessageApplicationUpdated": "Jellyfin strežnik je bil posodobljen", + "MessageApplicationUpdatedTo": "Jellyfin strežnik je bil posodobljen na {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MixedContent": "Mixed content", - "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", + "MessageServerConfigurationUpdated": "Nastavitve strežnika so bile posodobljene", + "MixedContent": "Razne vsebine", + "Movies": "Filmi", + "Music": "Glasba", + "MusicVideos": "Glasbeni posnetki", + "NameInstallFailed": "{0} namestitev neuspešna", + "NameSeasonNumber": "Sezona {0}", + "NameSeasonUnknown": "Season neznana", + "NewVersionIsAvailable": "Nova razničica Jellyfin strežnika je na voljo za prenos.", + "NotificationOptionApplicationUpdateAvailable": "Posodobitev aplikacije je na voljo", + "NotificationOptionApplicationUpdateInstalled": "Posodobitev aplikacije je bila nameščena", + "NotificationOptionAudioPlayback": "Predvajanje zvoka začeto", + "NotificationOptionAudioPlaybackStopped": "Predvajanje zvoka zaustavljeno", + "NotificationOptionCameraImageUploaded": "Posnetek kamere naložen", + "NotificationOptionInstallationFailed": "Napaka pri nameščanju", + "NotificationOptionNewLibraryContent": "Nove vsebine dodane", + "NotificationOptionPluginError": "Napaka dodatka", + "NotificationOptionPluginInstalled": "Dodatek nameščen", + "NotificationOptionPluginUninstalled": "Dodatek odstranjen", + "NotificationOptionPluginUpdateInstalled": "Posodobitev dodatka nameščena", + "NotificationOptionServerRestartRequired": "Potreben je ponovni zagon strežnika", "NotificationOptionTaskFailed": "Scheduled task failure", "NotificationOptionUserLockedOut": "User locked out", "NotificationOptionVideoPlayback": "Video playback started", -- cgit v1.2.3 From 53ed6e5e6efb69572a61cb57a31b600f4ba5c02b Mon Sep 17 00:00:00 2001 From: ElFantasma Date: Fri, 22 Feb 2019 18:49:18 +0000 Subject: Translated using Weblate (Spanish (Argentina)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_AR/ --- .../Localization/Core/es-AR.json | 184 ++++++++++----------- 1 file changed, 92 insertions(+), 92 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index c01bb0c501..dc73ba6b34 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -1,97 +1,97 @@ { - "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", - "Artists": "Artists", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", - "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", - "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", - "Genres": "Genres", - "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "Continue Watching", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteArtists": "Favorite Artists", - "HeaderFavoriteEpisodes": "Favorite Episodes", - "HeaderFavoriteShows": "Favorite Shows", - "HeaderFavoriteSongs": "Favorite Songs", - "HeaderLiveTV": "Live TV", - "HeaderNextUp": "Next Up", - "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", - "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", - "Latest": "Latest", - "MessageApplicationUpdated": "Jellyfin Server has been updated", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MixedContent": "Mixed content", - "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "Photos": "Photos", - "Playlists": "Playlists", + "Albums": "Álbumes", + "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}", + "Application": "Aplicación", + "Artists": "Artistas", + "AuthenticationSucceededWithUserName": "{0} autenticado correctamente", + "Books": "Libros", + "CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}", + "Channels": "Canales", + "ChapterNameValue": "Capítulo {0}", + "Collections": "Colecciones", + "DeviceOfflineWithName": "{0} se ha desconectado", + "DeviceOnlineWithName": "{0} está conectado", + "FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión desde {0}", + "Favorites": "Favoritos", + "Folders": "Carpetas", + "Genres": "Géneros", + "HeaderAlbumArtists": "Artistas de álbumes", + "HeaderCameraUploads": "Subidas de cámara", + "HeaderContinueWatching": "Continuar viendo", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteShows": "Programas favoritos", + "HeaderFavoriteSongs": "Canciones favoritas", + "HeaderLiveTV": "TV en vivo", + "HeaderNextUp": "Continuar Viendo", + "HeaderRecordingGroups": "Grupos de grabación", + "HomeVideos": "Videos caseros", + "Inherit": "Heredar", + "ItemAddedWithName": "{0} se ha añadido a la biblioteca", + "ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca", + "LabelIpAddressValue": "Dirección IP: {0}", + "LabelRunningTimeValue": "Tiempo de funcionamiento: {0}", + "Latest": "Últimos", + "MessageApplicationUpdated": "El servidor Jellyfin fue actualizado", + "MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Fue actualizada la sección {0} de la configuración del servidor", + "MessageServerConfigurationUpdated": "Fue actualizada la configuración del servidor", + "MixedContent": "Contenido mixto", + "Movies": "Películas", + "Music": "Música", + "MusicVideos": "Videos musicales", + "NameInstallFailed": "{0} error de instalación", + "NameSeasonNumber": "Temporada {0}", + "NameSeasonUnknown": "Temporada desconocida", + "NewVersionIsAvailable": "Disponible una nueva versión de Jellyfin para descargar.", + "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", + "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", + "NotificationOptionAudioPlayback": "Se inició la reproducción de audio", + "NotificationOptionAudioPlaybackStopped": "Se detuvo la reproducción de audio", + "NotificationOptionCameraImageUploaded": "Imagen de la cámara cargada", + "NotificationOptionInstallationFailed": "Error de instalación", + "NotificationOptionNewLibraryContent": "Nuevo contenido añadido", + "NotificationOptionPluginError": "Error en plugin", + "NotificationOptionPluginInstalled": "Plugin instalado", + "NotificationOptionPluginUninstalled": "Plugin desinstalado", + "NotificationOptionPluginUpdateInstalled": "Actualización del complemento instalada", + "NotificationOptionServerRestartRequired": "Se requiere reinicio del servidor", + "NotificationOptionTaskFailed": "Error de tarea programada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionVideoPlayback": "Se inició la reproducción de video", + "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", + "Photos": "Fotos", + "Playlists": "Listas de reproducción", "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", - "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "PluginInstalledWithName": "{0} fue instalado", + "PluginUninstalledWithName": "{0} fue desinstalado", + "PluginUpdatedWithName": "{0} fue actualizado", + "ProviderValue": "Proveedor: {0}", + "ScheduledTaskFailedWithName": "{0} falló", + "ScheduledTaskStartedWithName": "{0} iniciada", + "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado", "Shows": "Series", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "Songs": "Canciones", + "StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", - "Sync": "Sync", - "System": "System", - "TvShows": "TV Shows", - "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", - "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", - "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", - "ValueSpecialEpisodeName": "Special - {0}", - "VersionNumber": "Version {0}" + "SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}", + "SubtitlesDownloadedForItem": "Descargar subtítulos para {0}", + "Sync": "Sincronizar", + "System": "Sistema", + "TvShows": "Series de TV", + "User": "Usuario", + "UserCreatedWithName": "El usuario {0} ha sido creado", + "UserDeletedWithName": "El usuario {0} ha sido borrado", + "UserDownloadingItemWithValues": "{0} está descargando {1}", + "UserLockedOutWithName": "El usuario {0} ha sido bloqueado", + "UserOfflineFromDevice": "{0} se ha desconectado de {1}", + "UserOnlineFromDevice": "{0} está en línea desde {1}", + "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", + "UserPolicyUpdatedWithName": "Actualizada política de usuario para {0}", + "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", + "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", + "ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia", + "ValueSpecialEpisodeName": "Especial - {0}", + "VersionNumber": "Versión {0}" } -- cgit v1.2.3 From 3d3d879b99776e6d156773aec5693ee7c63479ea Mon Sep 17 00:00:00 2001 From: Deniz Date: Fri, 22 Feb 2019 08:15:26 +0000 Subject: Translated using Weblate (Turkish) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/ --- .../Localization/Core/tr.json | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 495f82db6d..9e00eba62f 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -1,12 +1,12 @@ { - "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", - "Artists": "Artists", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", + "Albums": "Albümler", + "AppDeviceValues": "Uygulama: {0}, Aygıt: {1}", + "Application": "Uygulama", + "Artists": "Sanatçılar", + "AuthenticationSucceededWithUserName": "{0} başarı ile giriş yaptı", + "Books": "Kitaplar", "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", + "Channels": "Kanallar", "ChapterNameValue": "Chapter {0}", "Collections": "Collections", "DeviceOfflineWithName": "{0} has disconnected", @@ -17,8 +17,8 @@ "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "Continue Watching", - "HeaderFavoriteAlbums": "Favorite Albums", + "HeaderContinueWatching": "İzlemeye Devam Et", + "HeaderFavoriteAlbums": "Favori Albümler", "HeaderFavoriteArtists": "Favorite Artists", "HeaderFavoriteEpisodes": "Favorite Episodes", "HeaderFavoriteShows": "Favori Showlar", @@ -30,21 +30,21 @@ "Inherit": "Inherit", "ItemAddedWithName": "{0} was added to the library", "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", + "LabelIpAddressValue": "Ip adresi: {0}", + "LabelRunningTimeValue": "Çalışma süresi: {0}", "Latest": "Latest", - "MessageApplicationUpdated": "Jellyfin Server has been updated", + "MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi", "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", "MessageServerConfigurationUpdated": "Server configuration has been updated", "MixedContent": "Mixed content", "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "Music": "Müzik", + "MusicVideos": "Müzik videoları", + "NameInstallFailed": "{0} kurulum başarısız", + "NameSeasonNumber": "Sezon {0}", + "NameSeasonUnknown": "Bilinmeyen Sezon", + "NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.", "NotificationOptionApplicationUpdateAvailable": "Application update available", "NotificationOptionApplicationUpdateInstalled": "Application update installed", "NotificationOptionAudioPlayback": "Audio playback started", -- cgit v1.2.3 From 6bdb5debd2492b71d11f9628889b8c29b6321a77 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 1 Mar 2019 14:08:51 +0100 Subject: Add some websocket manager boilerplate --- .../Middleware/WebSocketMiddleware.cs | 5 +- .../WebSockets/WebSocketHandler.cs | 10 +++ .../WebSockets/WebSocketManager.cs | 90 ++++++++++++++++++++-- 3 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 Emby.Server.Implementations/WebSockets/WebSocketHandler.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs index a1d0e77d65..268bf4042d 100644 --- a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs +++ b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs @@ -25,7 +25,10 @@ namespace Emby.Server.Implementations.Middleware if (httpContext.WebSockets.IsWebSocketRequest) { var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); - _webSocketManager.AddSocket(webSocketContext); + if (webSocketContext != null) + { + await _webSocketManager.OnWebSocketConnected(webSocketContext); + } } else { diff --git a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs new file mode 100644 index 0000000000..70b9e85aa9 --- /dev/null +++ b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using MediaBrowser.Model.Net; + +namespace Emby.Server.Implementations.WebSockets +{ + public interface IWebSocketHandler + { + Task ProcessMessage(WebSocketMessage message); + } +} diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs index 7e74a45277..888f2f0fce 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -1,22 +1,100 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using UtfUnknown; namespace Emby.Server.Implementations.WebSockets { public class WebSocketManager { - private readonly ConcurrentDictionary _activeWebSockets; + private readonly IWebSocketHandler[] _webSocketHandlers; + private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; + private const int BufferSize = 4096; - public WebSocketManager() + public WebSocketManager(IWebSocketHandler[] webSocketHandlers, IJsonSerializer jsonSerializer, ILogger logger) { - _activeWebSockets = new ConcurrentDictionary(); + _webSocketHandlers = webSocketHandlers; + _jsonSerializer = jsonSerializer; + _logger = logger; } - public void AddSocket(WebSocket webSocket) + public async Task OnWebSocketConnected(WebSocket webSocket) { - var guid = Guid.NewGuid(); - _activeWebSockets.TryAdd(guid, webSocket); + var taskCompletionSource = new TaskCompletionSource(); + var cancellationToken = new CancellationTokenSource().Token; + WebSocketReceiveResult result; + var message = new List(); + + do + { + var buffer = WebSocket.CreateServerBuffer(BufferSize); + result = await webSocket.ReceiveAsync(buffer, cancellationToken); + message.AddRange(buffer.Array.Take(result.Count)); + + if (result.EndOfMessage) + { + await ProcessMessage(message.ToArray(), taskCompletionSource); + message.Clear(); + } + } while (!taskCompletionSource.Task.IsCompleted && + webSocket.State == WebSocketState.Open && + result.MessageType != WebSocketMessageType.Close); + + if (webSocket.State == WebSocketState.Open) + { + await webSocket.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, cancellationToken); + } + } + + public async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource taskCompletionSource) + { + var charset = CharsetDetector.DetectFromBytes(messageBytes).Detected?.EncodingName; + var message = string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase) + ? Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length) + : Encoding.ASCII.GetString(messageBytes, 0, messageBytes.Length); + + // All messages are expected to be json + if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Received web socket message that is not a json structure: {Message}", message); + return; + } + + try + { + var info = _jsonSerializer.DeserializeFromString>(message); + + _logger.LogDebug("Websocket message received: {0}", info.MessageType); + + var tasks = _webSocketHandlers.Select(handler => Task.Run(() => + { + try + { + handler.ProcessMessage(info).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "{0} failed processing WebSocket message {1}", handler.GetType().Name, info.MessageType ?? string.Empty); + } + })); + + await Task.WhenAll(tasks); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing web socket message"); + } } } } -- cgit v1.2.3 From 9993dafe54fe4310d1008434405198d822ef51cc Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 1 Mar 2019 17:12:22 +0100 Subject: Don't mix LINQ and roreach loops for readability --- DvdLib/Ifo/Dvd.cs | 16 +++++------ Emby.Dlna/ContentDirectory/ControlHandler.cs | 4 +-- Emby.Dlna/Didl/DidlBuilder.cs | 3 +- Emby.Dlna/DlnaManager.cs | 21 +++++++------- Emby.Dlna/Main/DlnaEntryPoint.cs | 2 +- Emby.Dlna/PlayTo/TransportCommands.cs | 19 +++++++++++++ Emby.Naming/TV/EpisodePathParser.cs | 32 ++++++++++++++-------- .../Activity/ActivityManager.cs | 7 ++++- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Channels/ChannelManager.cs | 11 ++------ .../Data/SqliteUserDataRepository.cs | 4 +-- 11 files changed, 74 insertions(+), 47 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs index f784be83e7..90125fa3e3 100644 --- a/DvdLib/Ifo/Dvd.cs +++ b/DvdLib/Ifo/Dvd.cs @@ -26,17 +26,17 @@ namespace DvdLib.Ifo if (vmgPath == null) { - var allIfos = allFiles.Where(i => string.Equals(i.Extension, ".ifo", StringComparison.OrdinalIgnoreCase)); - - foreach (var ifo in allIfos) + foreach (var ifo in allFiles) { - var num = ifo.Name.Split('_').ElementAtOrDefault(1); - var numbersRead = new List(); + if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase)) + { + continue; + } - if (!string.IsNullOrEmpty(num) && ushort.TryParse(num, out var ifoNumber) && !numbersRead.Contains(ifoNumber)) + var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries); + if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber)) { ReadVTS(ifoNumber, ifo.FullName); - numbersRead.Add(ifoNumber); } } } @@ -76,7 +76,7 @@ namespace DvdLib.Ifo } } - private void ReadVTS(ushort vtsNum, List allFiles) + private void ReadVTS(ushort vtsNum, IEnumerable allFiles) { var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum); diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 1150afdbab..84f38ff769 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -260,7 +260,7 @@ namespace Emby.Dlna.ContentDirectory if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue) { - var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount)); + var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount); _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id); } @@ -273,7 +273,7 @@ namespace Emby.Dlna.ContentDirectory } else { - var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount)); + var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount); totalCount = childrenResult.TotalRecordCount; provided = childrenResult.Items.Length; diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 605f4f37b2..1268f3d5c2 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -818,10 +818,9 @@ namespace Emby.Dlna.Didl { AddCommonFields(item, itemStubType, context, writer, filter); - var hasArtists = item as IHasArtist; var hasAlbumArtists = item as IHasAlbumArtist; - if (hasArtists != null) + if (item is IHasArtist hasArtists) { foreach (var artist in hasArtists.Artists) { diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index f53d274516..d6ee5d13ac 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -15,7 +16,6 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -29,7 +29,7 @@ namespace Emby.Dlna private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; - private readonly IAssemblyInfo _assemblyInfo; + private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; private readonly Dictionary> _profiles = new Dictionary>(StringComparer.Ordinal); @@ -39,8 +39,7 @@ namespace Emby.Dlna IApplicationPaths appPaths, ILoggerFactory loggerFactory, IJsonSerializer jsonSerializer, - IServerApplicationHost appHost, - IAssemblyInfo assemblyInfo) + IServerApplicationHost appHost) { _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; @@ -48,7 +47,6 @@ namespace Emby.Dlna _logger = loggerFactory.CreateLogger("Dlna"); _jsonSerializer = jsonSerializer; _appHost = appHost; - _assemblyInfo = assemblyInfo; } public async Task InitProfilesAsync() @@ -368,15 +366,18 @@ namespace Emby.Dlna var systemProfilesPath = SystemProfilesPath; - foreach (var name in _assemblyInfo.GetManifestResourceNames(GetType()) - .Where(i => i.StartsWith(namespaceName)) - .ToList()) + foreach (var name in _assembly.GetManifestResourceNames()) { + if (!name.StartsWith(namespaceName)) + { + continue; + } + var filename = Path.GetFileName(name).Substring(namespaceName.Length); var path = Path.Combine(systemProfilesPath, filename); - using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), name)) + using (var stream = _assembly.GetManifestResourceStream(name)) { var fileInfo = _fileSystem.GetFileInfo(path); @@ -514,7 +515,7 @@ namespace Emby.Dlna return new ImageStream { Format = format, - Stream = _assemblyInfo.GetManifestResourceStream(GetType(), resource) + Stream = _assembly.GetManifestResourceStream(resource) }; } } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 5a7c9b617e..57ed0097a0 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -246,7 +246,7 @@ namespace Emby.Dlna.Main private async Task RegisterServerEndpoints() { - var addresses = (await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false)).ToList(); + var addresses = await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false); var udn = CreateUuid(_appHost.SystemId); diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs index b96fa43e50..4f9e398e90 100644 --- a/Emby.Dlna/PlayTo/TransportCommands.cs +++ b/Emby.Dlna/PlayTo/TransportCommands.cs @@ -107,12 +107,18 @@ namespace Emby.Dlna.PlayTo foreach (var arg in action.ArgumentList) { if (arg.Direction == "out") + { continue; + } if (arg.Name == "InstanceID") + { stateString += BuildArgumentXml(arg, "0"); + } else + { stateString += BuildArgumentXml(arg, null); + } } return string.Format(CommandBase, action.Name, xmlNamespace, stateString); @@ -125,11 +131,18 @@ namespace Emby.Dlna.PlayTo foreach (var arg in action.ArgumentList) { if (arg.Direction == "out") + { continue; + } + if (arg.Name == "InstanceID") + { stateString += BuildArgumentXml(arg, "0"); + } else + { stateString += BuildArgumentXml(arg, value.ToString(), commandParameter); + } } return string.Format(CommandBase, action.Name, xmlNamesapce, stateString); @@ -142,11 +155,17 @@ namespace Emby.Dlna.PlayTo foreach (var arg in action.ArgumentList) { if (arg.Name == "InstanceID") + { stateString += BuildArgumentXml(arg, "0"); + } else if (dictionary.ContainsKey(arg.Name)) + { stateString += BuildArgumentXml(arg, dictionary[arg.Name]); + } else + { stateString += BuildArgumentXml(arg, value.ToString()); + } } return string.Format(CommandBase, action.Name, xmlNamesapce, stateString); diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs index 9485d697b1..a8f81a3b85 100644 --- a/Emby.Naming/TV/EpisodePathParser.cs +++ b/Emby.Naming/TV/EpisodePathParser.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text.RegularExpressions; using Emby.Naming.Common; namespace Emby.Naming.TV @@ -22,7 +21,9 @@ namespace Emby.Naming.TV // There were no failed tests without this block, but to be safe, we can keep it until // the regex which require file extensions are modified so that they don't need them. if (IsDirectory) + { path += ".mp4"; + } EpisodePathParserResult result = null; @@ -35,6 +36,7 @@ namespace Emby.Naming.TV continue; } } + if (isNamed.HasValue) { if (expression.IsNamed != isNamed.Value) @@ -42,6 +44,7 @@ namespace Emby.Naming.TV continue; } } + if (isOptimistic.HasValue) { if (expression.IsOptimistic != isOptimistic.Value) @@ -191,13 +194,20 @@ namespace Emby.Naming.TV private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable expressions) { - var results = expressions - .Where(i => i.IsNamed) - .Select(i => Parse(path, i)) - .Where(i => i.Success); - - foreach (var result in results) + foreach (var i in expressions) { + if (!i.IsNamed) + { + continue; + } + + var result = Parse(path, i); + + if (!result.Success) + { + continue; + } + if (string.IsNullOrEmpty(info.SeriesName)) { info.SeriesName = result.SeriesName; @@ -208,12 +218,10 @@ namespace Emby.Naming.TV info.EndingEpsiodeNumber = result.EndingEpsiodeNumber; } - if (!string.IsNullOrEmpty(info.SeriesName)) + if (!string.IsNullOrEmpty(info.SeriesName) + && (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue)) { - if (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue) - { - break; - } + break; } } } diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs index 6febcc2f7b..0c513ea127 100644 --- a/Emby.Server.Implementations/Activity/ActivityManager.cs +++ b/Emby.Server.Implementations/Activity/ActivityManager.cs @@ -39,8 +39,13 @@ namespace Emby.Server.Implementations.Activity { var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit); - foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty))) + foreach (var item in result.Items) { + if (item.UserId == Guid.Empty) + { + continue; + } + var user = _userManager.GetUserById(item.UserId); if (user != null) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b5a64cbddc..94d2cd5daf 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -769,7 +769,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(SessionManager); serviceCollection.AddSingleton( - new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo)); + new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this)); CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager); serviceCollection.AddSingleton(CollectionManager); diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 949b892265..7e50650d70 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -243,8 +243,7 @@ namespace Emby.Server.Implementations.Channels { foreach (var item in returnItems) { - var task = RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None); - Task.WaitAll(task); + RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None).GetAwaiter().GetResult(); } } @@ -303,9 +302,7 @@ namespace Emby.Server.Implementations.Channels } numComplete++; - double percent = numComplete; - percent /= allChannelsList.Count; - + double percent = (double)numComplete / allChannelsList.Count; progress.Report(100 * percent); } @@ -658,9 +655,7 @@ namespace Emby.Server.Implementations.Channels foreach (var item in result.Items) { - var folder = item as Folder; - - if (folder != null) + if (item is Folder folder) { await GetChannelItemsInternal(new InternalItemsQuery { diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 7a9b722440..4109b7ad1f 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -119,9 +119,9 @@ namespace Emby.Server.Implementations.Data { list.Add(row[0].ReadGuidFromBlob()); } - catch + catch (Exception ex) { - + Logger.LogError(ex, "Error while getting user"); } } } -- cgit v1.2.3 From ed07ed44ae99d3c53a95774cdbd6254c9cb0a76e Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 1 Mar 2019 19:30:48 +0100 Subject: Simplify rating loading --- .../Localization/LocalizationManager.cs | 153 ++++----------------- .../Localization/Ratings/au.csv | 8 ++ .../Localization/Ratings/be.csv | 6 + .../Localization/Ratings/de.csv | 10 ++ .../Localization/Ratings/ru.csv | 5 + 5 files changed, 54 insertions(+), 128 deletions(-) create mode 100644 Emby.Server.Implementations/Localization/Ratings/au.csv create mode 100644 Emby.Server.Implementations/Localization/Ratings/be.csv create mode 100644 Emby.Server.Implementations/Localization/Ratings/de.csv create mode 100644 Emby.Server.Implementations/Localization/Ratings/ru.csv (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 31217730bf..2c59579a89 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -62,10 +62,6 @@ namespace Emby.Server.Implementations.Localization { const string ratingsResource = "Emby.Server.Implementations.Localization.Ratings."; - Directory.CreateDirectory(LocalizationPath); - - var existingFiles = GetRatingsFiles(LocalizationPath).Select(Path.GetFileName); - // Extract from the assembly foreach (var resource in _assembly.GetManifestResourceNames()) { @@ -74,100 +70,42 @@ namespace Emby.Server.Implementations.Localization continue; } - string filename = "ratings-" + resource.Substring(ratingsResource.Length); - - if (existingFiles.Contains(filename)) - { - continue; - } + string countryCode = resource.Substring(ratingsResource.Length, 2); + var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); - using (var stream = _assembly.GetManifestResourceStream(resource)) + using (var str = _assembly.GetManifestResourceStream(resource)) + using (var reader = new StreamReader(str)) { - string target = Path.Combine(LocalizationPath, filename); - _logger.LogInformation("Extracting ratings to {0}", target); - - using (var fs = _fileSystem.GetFileStream(target, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) + string line; + while ((line = await reader.ReadLineAsync()) != null) { - await stream.CopyToAsync(fs); + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + string[] parts = line.Split(','); + if (parts.Length == 2 + && int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value)) + { + dict.Add(parts[0], (new ParentalRating { Name = parts[0], Value = value })); + } +#if DEBUG + else + { + _logger.LogWarning("Misformed line in ratings file for country {CountryCode}", countryCode); + } +#endif } } - } + _logger.LogWarning("{t}", countryCode); - foreach (var file in GetRatingsFiles(LocalizationPath)) - { - await LoadRatings(file); + _allParentalRatings[countryCode] = dict; } - LoadAdditionalRatings(); - await LoadCultures(); } - private void LoadAdditionalRatings() - { - LoadRatings("au", new[] - { - new ParentalRating("AU-G", 1), - new ParentalRating("AU-PG", 5), - new ParentalRating("AU-M", 6), - new ParentalRating("AU-MA15+", 7), - new ParentalRating("AU-M15+", 8), - new ParentalRating("AU-R18+", 9), - new ParentalRating("AU-X18+", 10), - new ParentalRating("AU-RC", 11) - }); - - LoadRatings("be", new[] - { - new ParentalRating("BE-AL", 1), - new ParentalRating("BE-MG6", 2), - new ParentalRating("BE-6", 3), - new ParentalRating("BE-9", 5), - new ParentalRating("BE-12", 6), - new ParentalRating("BE-16", 8) - }); - - LoadRatings("de", new[] - { - new ParentalRating("DE-0", 1), - new ParentalRating("FSK-0", 1), - new ParentalRating("DE-6", 5), - new ParentalRating("FSK-6", 5), - new ParentalRating("DE-12", 7), - new ParentalRating("FSK-12", 7), - new ParentalRating("DE-16", 8), - new ParentalRating("FSK-16", 8), - new ParentalRating("DE-18", 9), - new ParentalRating("FSK-18", 9) - }); - - LoadRatings("ru", new[] - { - new ParentalRating("RU-0+", 1), - new ParentalRating("RU-6+", 3), - new ParentalRating("RU-12+", 7), - new ParentalRating("RU-16+", 9), - new ParentalRating("RU-18+", 10) - }); - } - - private void LoadRatings(string country, ParentalRating[] ratings) - { - _allParentalRatings[country] = ratings.ToDictionary(i => i.Name); - } - - private IEnumerable GetRatingsFiles(string directory) - => _fileSystem.GetFilePaths(directory, false) - .Where(i => string.Equals(Path.GetExtension(i), ".csv", StringComparison.OrdinalIgnoreCase)) - .Where(i => Path.GetFileName(i).StartsWith("ratings-", StringComparison.OrdinalIgnoreCase)); - - /// - /// Gets the localization path. - /// - /// The localization path. - public string LocalizationPath - => Path.Combine(_configurationManager.ApplicationPaths.ProgramDataPath, "localization"); - public string NormalizeFormKD(string text) => text.Normalize(NormalizationForm.FormKD); @@ -288,47 +226,6 @@ namespace Emby.Server.Implementations.Localization return value; } - /// - /// Loads the ratings. - /// - /// The file. - /// Dictionary{System.StringParentalRating}. - private async Task LoadRatings(string file) - { - Dictionary dict - = new Dictionary(StringComparer.OrdinalIgnoreCase); - - using (var str = File.OpenRead(file)) - using (var reader = new StreamReader(str)) - { - string line; - while ((line = await reader.ReadLineAsync()) != null) - { - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - string[] parts = line.Split(','); - if (parts.Length == 2 - && int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value)) - { - dict.Add(parts[0], (new ParentalRating { Name = parts[0], Value = value })); - } -#if DEBUG - else - { - _logger.LogWarning("Misformed line in {Path}", file); - } -#endif - } - } - - var countryCode = Path.GetFileNameWithoutExtension(file).Split('-')[1]; - - _allParentalRatings[countryCode] = dict; - } - private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; /// diff --git a/Emby.Server.Implementations/Localization/Ratings/au.csv b/Emby.Server.Implementations/Localization/Ratings/au.csv new file mode 100644 index 0000000000..940375e268 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/au.csv @@ -0,0 +1,8 @@ +AU-G,1 +AU-PG,5 +AU-M,6 +AU-MA15+,7 +AU-M15+,8 +AU-R18+,9 +AU-X18+,10 +AU-RC,11 diff --git a/Emby.Server.Implementations/Localization/Ratings/be.csv b/Emby.Server.Implementations/Localization/Ratings/be.csv new file mode 100644 index 0000000000..d3937caf78 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/be.csv @@ -0,0 +1,6 @@ +BE-AL,1 +BE-MG6,2 +BE-6,3 +BE-9,5 +BE-12,6 +BE-16,8 diff --git a/Emby.Server.Implementations/Localization/Ratings/de.csv b/Emby.Server.Implementations/Localization/Ratings/de.csv new file mode 100644 index 0000000000..f944a140d0 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/de.csv @@ -0,0 +1,10 @@ +DE-0,1 +FSK-0,1 +DE-6,5 +FSK-6,5 +DE-12,7 +FSK-12,7 +DE-16,8 +FSK-16,8 +DE-18,9 +FSK-18,9 diff --git a/Emby.Server.Implementations/Localization/Ratings/ru.csv b/Emby.Server.Implementations/Localization/Ratings/ru.csv new file mode 100644 index 0000000000..1bc94affd6 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Ratings/ru.csv @@ -0,0 +1,5 @@ +RU-0+,1 +RU-6+,3 +RU-12+,7 +RU-16+,9 +RU-18+,10 -- cgit v1.2.3 From 5368112d903fc29a1ab3e4fa11abed08358a6cbb Mon Sep 17 00:00:00 2001 From: Lynxy Date: Fri, 1 Mar 2019 22:28:25 -0500 Subject: Correct the list of series types --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 70e5fa6406..06f6563a32 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2279,11 +2279,10 @@ namespace Emby.Server.Implementations.Data private static readonly HashSet _seriesTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { - "Audio", - "MusicAlbum", - "MusicVideo", + "Book", "AudioBook", - "AudioPodcast" + "Episode", + "Season" }; private bool HasSeriesFields(InternalItemsQuery query) -- cgit v1.2.3 From e823c11b46ccf0473aa72cb52cd5a3a9f977e61b Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 08:29:23 +0100 Subject: Add certificate to https and minor cleanup --- Emby.Server.Implementations/ApplicationHost.cs | 26 ++++++++++------------ .../HttpServer/HttpListenerHost.cs | 11 --------- .../WebSockets/WebSocketHandler.cs | 2 +- .../WebSockets/WebSocketManager.cs | 8 ++++--- Jellyfin.Server/CoreAppHost.cs | 2 -- 5 files changed, 18 insertions(+), 31 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index e558b43544..05d8ee410c 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -626,9 +626,12 @@ namespace Emby.Server.Implementations { options.Listen(IPAddress.Any, HttpPort); options.Listen(IPAddress.Loopback, HttpPort); - // TODO certs - options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); - options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); + + if (CertificateInfo != null) + { + options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); + options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); + } }) .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) .ConfigureServices(services => @@ -927,11 +930,9 @@ namespace Emby.Server.Implementations } } - protected virtual bool SupportsDualModeSockets => true; - - private X509Certificate GetCertificate(CertificateInfo info) + private X509Certificate2 GetCertificate(CertificateInfo info) { - var certificateLocation = info == null ? null : info.Path; + var certificateLocation = info?.Path; if (string.IsNullOrWhiteSpace(certificateLocation)) { @@ -1004,7 +1005,7 @@ namespace Emby.Server.Implementations return info; } - protected virtual FFMpegInfo GetFFMpegInfo() + protected FFMpegInfo GetFFMpegInfo() { return new FFMpegLoader(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo()) .GetFFMpegInfo(StartupOptions); @@ -1085,7 +1086,7 @@ namespace Emby.Server.Implementations /// private void SetStaticProperties() { - ((SqliteItemRepository)ItemRepository).ImageProcessor = ImageProcessor; + ItemRepository.ImageProcessor = ImageProcessor; // For now there's no real way to inject these properly BaseItem.Logger = LoggerFactory.CreateLogger("BaseItem"); @@ -1211,15 +1212,12 @@ namespace Emby.Server.Implementations AllConcreteTypes = GetComposablePartAssemblies() .SelectMany(x => x.ExportedTypes) - .Where(type => - { - return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType; - }) + .Where(type => type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType) .ToArray(); } private CertificateInfo CertificateInfo { get; set; } - protected X509Certificate Certificate { get; private set; } + protected X509Certificate2 Certificate { get; private set; } private IEnumerable GetUrlPrefixes() { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index dbfb5e2438..263fcdbe9e 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -855,16 +855,5 @@ namespace Emby.Server.Implementations.HttpServer { Dispose(true); } - - public void StartServer(string[] urlPrefixes, IHttpListener httpListener) - { - UrlPrefixes = urlPrefixes; - - _listener = httpListener; - - _listener.WebSocketConnected = OnWebSocketConnected; - _listener.ErrorHandler = ErrorHandler; - _listener.RequestHandler = RequestHandler; - } } } diff --git a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs index 70b9e85aa9..eb18774408 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs @@ -5,6 +5,6 @@ namespace Emby.Server.Implementations.WebSockets { public interface IWebSocketHandler { - Task ProcessMessage(WebSocketMessage message); + Task ProcessMessage(WebSocketMessage message, TaskCompletionSource taskCompletionSource); } } diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs index 888f2f0fce..5db2c8da73 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -35,6 +35,7 @@ namespace Emby.Server.Implementations.WebSockets WebSocketReceiveResult result; var message = new List(); + // Keep listening for incoming messages, otherwise the socket closes automatically do { var buffer = WebSocket.CreateServerBuffer(BufferSize); @@ -57,7 +58,7 @@ namespace Emby.Server.Implementations.WebSockets } } - public async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource taskCompletionSource) + private async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource taskCompletionSource) { var charset = CharsetDetector.DetectFromBytes(messageBytes).Detected?.EncodingName; var message = string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase) @@ -81,11 +82,12 @@ namespace Emby.Server.Implementations.WebSockets { try { - handler.ProcessMessage(info).ConfigureAwait(false); + handler.ProcessMessage(info, taskCompletionSource).ConfigureAwait(false); } catch (Exception ex) { - _logger.LogError(ex, "{0} failed processing WebSocket message {1}", handler.GetType().Name, info.MessageType ?? string.Empty); + _logger.LogError(ex, "{HandlerType} failed processing WebSocket message {MessageType}", + handler.GetType().Name, info.MessageType ?? string.Empty); } })); diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 9e52247900..17259c7370 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -34,8 +34,6 @@ namespace Jellyfin.Server public override bool CanSelfRestart => StartupOptions.RestartPath != null; - protected override bool SupportsDualModeSockets => true; - protected override void RestartInternal() => Program.Restart(); protected override IEnumerable GetAssembliesWithPartsInternal() -- cgit v1.2.3 From d450169964680ed249f140de8422bf415b44958c Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 08:46:17 +0100 Subject: Use EnableHttps instead of CertificateInfo --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 05d8ee410c..284900e870 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -627,7 +627,7 @@ namespace Emby.Server.Implementations options.Listen(IPAddress.Any, HttpPort); options.Listen(IPAddress.Loopback, HttpPort); - if (CertificateInfo != null) + if (EnableHttps) { options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); -- cgit v1.2.3 From 1cc433eabc4813fd175b8ce63b27490663cbbcee Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 13:54:14 +0100 Subject: Start the webhost before RunStartupTasks and fix ContentEncoding --- .../SocketSharp/WebSocketSharpRequest.cs | 25 ++++++++++++++++++---- Jellyfin.Server/Program.cs | 1 - 2 files changed, 21 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 24fd36062f..976f2ec065 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -408,11 +408,28 @@ namespace Emby.Server.Implementations.SocketSharp public string ContentType => request.ContentType; - private Encoding contentEncoding; - public Encoding ContentEncoding + private Encoding ContentEncoding { - get => contentEncoding ?? Encoding.GetEncoding(request.Headers[HeaderNames.ContentEncoding].ToString()); - set => contentEncoding = value; + get + { + // TODO is this necessary? + if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP")) + { + string postDataCharset = Headers["x-up-devcap-post-charset"]; + if (!string.IsNullOrEmpty(postDataCharset)) + { + try + { + return Encoding.GetEncoding(postDataCharset); + } + catch (ArgumentException) + { + } + } + } + + return request.GetTypedHeaders().ContentType.Encoding ?? Encoding.UTF8; + } } public Uri UrlReferrer => request.GetTypedHeaders().Referer; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index bfc50468f7..a43c3fea5c 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -144,7 +144,6 @@ namespace Jellyfin.Server appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); await appHost.RunStartupTasks().ConfigureAwait(false); - appHost.Host.Run(); // TODO: read input for a stop command try -- cgit v1.2.3 From 6263b73d9c5659f2a6216dd2bea55df230ae3fcb Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 14:30:41 +0100 Subject: Await host startup --- Emby.Server.Implementations/ApplicationHost.cs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 284900e870..c2e6481af7 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -650,6 +650,8 @@ namespace Emby.Server.Implementations app.Use(ExecuteHttpHandlerAsync); }) .Build(); + + await Host.StartAsync(); } public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) -- cgit v1.2.3 From 040871459b572ce0dad09ae8f27938cb424b98e7 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 14:35:54 +0100 Subject: Remove some unused references --- .../MediaReceiverRegistrar.cs | 2 -- Emby.Server.Implementations/ApplicationHost.cs | 3 -- .../Emby.Server.Implementations.csproj | 4 --- Emby.Server.Implementations/Startup.cs | 34 ---------------------- 4 files changed, 43 deletions(-) delete mode 100644 Emby.Server.Implementations/Startup.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index 9c6022b6cb..e47143c16a 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -1,9 +1,7 @@ -using System.Collections.Generic; using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Xml; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c2e6481af7..00a5f864df 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -111,13 +111,11 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using ServiceStack; -using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; namespace Emby.Server.Implementations { @@ -638,7 +636,6 @@ namespace Emby.Server.Implementations { services.AddResponseCompression(); services.AddHttpContextAccessor(); - services.TryAddSingleton(); }) .Configure(app => { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d4db5f2fcc..39e9ed3756 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -26,11 +26,7 @@ - - - - diff --git a/Emby.Server.Implementations/Startup.cs b/Emby.Server.Implementations/Startup.cs deleted file mode 100644 index 164c7eeaa7..0000000000 --- a/Emby.Server.Implementations/Startup.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Linq; -using MediaBrowser.Api; -using MediaBrowser.Controller; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations -{ - public class Startup - { - public IConfiguration Configuration { get; } - - public Startup(IConfiguration configuration) => Configuration = configuration; - - // Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddRouting(); - } - - // Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app) - { - - } - } - -} -- cgit v1.2.3 From 6cc1bd544add65fdf7758c04655586c206675193 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 19:31:26 +0100 Subject: Fix a logging statement --- Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 5ddd316477..2df826957a 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -12,8 +12,9 @@ using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; - namespace Emby.Server.Implementations.SocketSharp +namespace Emby.Server.Implementations.SocketSharp { public class WebSocketSharpListener : IHttpListener { @@ -39,7 +40,7 @@ using Microsoft.Extensions.Logging; { var url = request.GetDisplayUrl(); - logger.LogInformation("{0} {1}. UserAgent: {2}", "WS", url, request.Headers["User-Agent"].ToString()); + logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, request.Headers[HeaderNames.UserAgent].ToString()); } public async Task ProcessWebSocketRequest(HttpContext ctx) -- cgit v1.2.3 From 557c4d065ddba0ccc6780c6a4c7811fe45351c88 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 19:55:59 +0100 Subject: Review comments --- .../SocketSharp/WebSocketSharpResponse.cs | 6 +- .../MediaBrowser.Controller.csproj | 6 - MediaBrowser.sln | 374 ++++++++++----------- 3 files changed, 188 insertions(+), 198 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index c4fbaddd3e..5964107651 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -71,11 +71,7 @@ namespace Emby.Server.Implementations.SocketSharp public Stream OutputStream => _response.Body; - public bool IsClosed - { - get; - set; - } + public bool IsClosed { get; set; } public bool SendChunked { get; set; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 81e255d525..5898d8f984 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -16,12 +16,6 @@ - - - ..\..\..\..\..\usr\local\share\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.http.extensions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Extensions.dll - - - netstandard2.0 false diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 5edc1a25a6..b1d3fcdda8 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,187 +1,187 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.3 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - SharedVersion.cs = SharedVersion.cs - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} - EndGlobalSection - GlobalSection(AutomaticVersions) = postSolution - UpdateAssemblyVersion = True - UpdateAssemblyFileVersion = True - UpdateAssemblyInfoVersion = True - AssemblyVersionSettings = None.None.None.None - AssemblyFileVersionSettings = None.None.None.None - AssemblyInfoVersionSettings = None.None.None.None - UpdatePackageVersion = False - AssemblyInfoVersionType = SettingsVersion - InheritWinAppVersionFrom = None - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - Policies = $0 - $0.StandardHeader = $1 - $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n - $0.DotNetNamingPolicy = $2 - $2.DirectoryNamespaceAssociation = PrefixedHierarchical - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + SharedVersion.cs = SharedVersion.cs + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} + EndGlobalSection + GlobalSection(AutomaticVersions) = postSolution + UpdateAssemblyVersion = True + UpdateAssemblyFileVersion = True + UpdateAssemblyInfoVersion = True + AssemblyVersionSettings = None.None.None.None + AssemblyFileVersionSettings = None.None.None.None + AssemblyInfoVersionSettings = None.None.None.None + UpdatePackageVersion = False + AssemblyInfoVersionType = SettingsVersion + InheritWinAppVersionFrom = None + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.StandardHeader = $1 + $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n + $0.DotNetNamingPolicy = $2 + $2.DirectoryNamespaceAssociation = PrefixedHierarchical + EndGlobalSection +EndGlobal -- cgit v1.2.3 From 9020f68ce16b8184c2b9f5cefde2b966b15fc741 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 20:08:54 +0100 Subject: Use QueryHelpers.AddQueryString --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 263fcdbe9e..16e3c383b8 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.HttpServer string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0]; return newQueryString.Count > 0 - ? $"{pagePathWithoutQueryString}?{string.Join("&", newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value)))}" + ? QueryHelpers.AddQueryString(pagePathWithoutQueryString, newQueryString.ToDictionary(kv => kv.Key, kv => kv.Value.ToString())) : pagePathWithoutQueryString; } -- cgit v1.2.3 From 0419deeec41cb7021c22a65fc4b63153075fd969 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 4 Mar 2019 20:18:35 +0100 Subject: Update LocalizationManager.cs --- Emby.Server.Implementations/Localization/LocalizationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 2c59579a89..d55b258a3d 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Localization if (parts.Length == 2 && int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value)) { - dict.Add(parts[0], (new ParentalRating { Name = parts[0], Value = value })); + dict.Add(parts[0], new ParentalRating { Name = parts[0], Value = value }); } #if DEBUG else -- cgit v1.2.3 From 0250204f14e8a004c926e1ec263723459788a029 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 22:26:57 +0100 Subject: Expand todo --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 16e3c383b8..19b1b26e89 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -607,7 +607,7 @@ namespace Emby.Server.Implementations.HttpServer } finally { - // TODO + // TODO response closes automatically after the handler is done, but some functions rely on knowing if it's closed or not httpRes.IsClosed = true; stopWatch.Stop(); var elapsed = stopWatch.Elapsed; -- cgit v1.2.3 From 12df3814954a4251c30fd27ccdac89647da1a0a3 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 07:55:29 +0100 Subject: Grab content root from config and fix kestrel port bindings --- Emby.Server.Implementations/ApplicationHost.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 00a5f864df..09c40ea735 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -619,19 +619,23 @@ namespace Emby.Server.Implementations FindParts(); + string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; + if (string.IsNullOrEmpty(contentRoot)) + { + contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src"); + } + Host = new WebHostBuilder() .UseKestrel(options => { - options.Listen(IPAddress.Any, HttpPort); - options.Listen(IPAddress.Loopback, HttpPort); + options.ListenAnyIP(HttpPort); if (EnableHttps) { - options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); - options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); + options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); } }) - .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) + .UseContentRoot(contentRoot) .ConfigureServices(services => { services.AddResponseCompression(); @@ -655,7 +659,7 @@ namespace Emby.Server.Implementations { if (!context.WebSockets.IsWebSocketRequest) { - await next(); + await next().ConfigureAwait(false); return; } @@ -665,7 +669,7 @@ namespace Emby.Server.Implementations { if (context.WebSockets.IsWebSocketRequest) { - await next(); + await next().ConfigureAwait(false); return; } -- cgit v1.2.3 From 51648a2a21e4829a66b0bff0ef1e0e23be1c6245 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 08:05:42 +0100 Subject: Remove unused _listener --- .../HttpServer/HttpListenerHost.cs | 40 ++++++++-------------- 1 file changed, 14 insertions(+), 26 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 19b1b26e89..293b28e74b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -33,12 +33,8 @@ namespace Emby.Server.Implementations.HttpServer public class HttpListenerHost : IHttpServer, IDisposable { private string DefaultRedirectPath { get; set; } - - private readonly ILogger _logger; public string[] UrlPrefixes { get; private set; } - private IHttpListener _listener; - public event EventHandler> WebSocketConnected; private readonly IServerConfigurationManager _config; @@ -68,7 +64,7 @@ namespace Emby.Server.Implementations.HttpServer IHttpListener socketListener) { _appHost = applicationHost; - _logger = loggerFactory.CreateLogger("HttpServer"); + Logger = loggerFactory.CreateLogger("HttpServer"); _config = config; DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; _networkManager = networkManager; @@ -85,7 +81,7 @@ namespace Emby.Server.Implementations.HttpServer public string GlobalResponse { get; set; } - protected ILogger Logger => _logger; + protected ILogger Logger { get; } public object CreateInstance(Type type) { @@ -151,7 +147,7 @@ namespace Emby.Server.Implementations.HttpServer return; } - var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger) + var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, Logger) { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, @@ -220,11 +216,11 @@ namespace Emby.Server.Implementations.HttpServer if (logExceptionStackTrace) { - _logger.LogError(ex, "Error processing request"); + Logger.LogError(ex, "Error processing request"); } else if (logExceptionMessage) { - _logger.LogError(ex.Message); + Logger.LogError(ex.Message); } var httpRes = httpReq.Response; @@ -242,7 +238,7 @@ namespace Emby.Server.Implementations.HttpServer } catch (Exception errorEx) { - _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)"); + Logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)"); } } @@ -285,14 +281,6 @@ namespace Emby.Server.Implementations.HttpServer } } - - if (_listener != null) - { - _logger.LogInformation("Stopping HttpListener..."); - var task = _listener.Stop(); - Task.WaitAll(task); - _logger.LogInformation("HttpListener stopped"); - } } public static string RemoveQueryStringByKey(string url, string key) @@ -613,11 +601,11 @@ namespace Emby.Server.Implementations.HttpServer var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) { - _logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); + Logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); } else { - _logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); + Logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); } } } @@ -630,7 +618,7 @@ namespace Emby.Server.Implementations.HttpServer var pathParts = pathInfo.TrimStart('/').Split('/'); if (pathParts.Length == 0) { - _logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl); + Logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl); return null; } @@ -644,7 +632,7 @@ namespace Emby.Server.Implementations.HttpServer }; } - _logger.LogError("Could not find handler for {PathInfo}", pathInfo); + Logger.LogError("Could not find handler for {PathInfo}", pathInfo); return null; } @@ -696,7 +684,7 @@ namespace Emby.Server.Implementations.HttpServer ServiceController = new ServiceController(); - _logger.LogInformation("Calling ServiceStack AppHost.Init"); + Logger.LogInformation("Calling ServiceStack AppHost.Init"); var types = services.Select(r => r.GetType()).ToArray(); @@ -704,7 +692,7 @@ namespace Emby.Server.Implementations.HttpServer ResponseFilters = new Action[] { - new ResponseFilter(_logger).FilterResponse + new ResponseFilter(Logger).FilterResponse }; } @@ -834,7 +822,7 @@ namespace Emby.Server.Implementations.HttpServer return Task.CompletedTask; } - _logger.LogDebug("Websocket message received: {0}", result.MessageType); + Logger.LogDebug("Websocket message received: {0}", result.MessageType); var tasks = _webSocketListeners.Select(i => Task.Run(async () => { @@ -844,7 +832,7 @@ namespace Emby.Server.Implementations.HttpServer } catch (Exception ex) { - _logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty); + Logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty); } })); -- cgit v1.2.3 From 2c26517172ca2c2f1df1c83d9300ad7c66667866 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 4 Mar 2019 23:58:25 -0800 Subject: minor style fixes --- .../Cryptography/CryptographyProvider.cs | 42 +++++----- .../Library/DefaultAuthenticationProvider.cs | 39 ++++----- Emby.Server.Implementations/Library/UserManager.cs | 2 +- MediaBrowser.Model/Cryptography/ICryptoProvider.cs | 42 +++++----- MediaBrowser.Model/Cryptography/PasswordHash.cs | 92 +++++++++++++--------- 5 files changed, 115 insertions(+), 102 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 3c9403ba8e..cf1ea6efa3 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -11,17 +11,21 @@ namespace Emby.Server.Implementations.Cryptography { public class CryptographyProvider : ICryptoProvider { - private HashSet SupportedHashMethods; + private HashSet _supportedHashMethods; + public string DefaultHashMethod => "PBKDF2"; - private RandomNumberGenerator rng; - private int defaultiterations = 1000; + + private RandomNumberGenerator _randomNumberGenerator; + + private int _defaultIterations = 1000; + public CryptographyProvider() { //FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one //Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 - SupportedHashMethods = new HashSet() + _supportedHashMethods = new HashSet() { "MD5" ,"System.Security.Cryptography.MD5" @@ -38,7 +42,7 @@ namespace Emby.Server.Implementations.Cryptography ,"SHA-512" ,"System.Security.Cryptography.SHA512" }; - rng = RandomNumberGenerator.Create(); + _randomNumberGenerator = RandomNumberGenerator.Create(); } public Guid GetMD5(string str) @@ -72,7 +76,7 @@ namespace Emby.Server.Implementations.Cryptography public IEnumerable GetSupportedHashMethods() { - return SupportedHashMethods; + return _supportedHashMethods; } private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) @@ -86,12 +90,13 @@ namespace Emby.Server.Implementations.Cryptography return r.GetBytes(32); } } + throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}"); } - public byte[] ComputeHash(string HashMethod, byte[] bytes) + public byte[] ComputeHash(string hashMethod, byte[] bytes) { - return ComputeHash(HashMethod, bytes, new byte[0]); + return ComputeHash(hashMethod, bytes, new byte[0]); } public byte[] ComputeHashWithDefaultMethod(byte[] bytes) @@ -99,15 +104,15 @@ namespace Emby.Server.Implementations.Cryptography return ComputeHash(DefaultHashMethod, bytes); } - public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) + public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt) { - if(HashMethod == DefaultHashMethod) + if(hashMethod == DefaultHashMethod) { - return PBKDF2(HashMethod, bytes, salt, defaultiterations); + return PBKDF2(hashMethod, bytes, salt, _defaultIterations); } - else if (SupportedHashMethods.Contains(HashMethod)) + else if (_supportedHashMethods.Contains(hashMethod)) { - using (var h = HashAlgorithm.Create(HashMethod)) + using (var h = HashAlgorithm.Create(hashMethod)) { if (salt.Length == 0) { @@ -121,21 +126,21 @@ namespace Emby.Server.Implementations.Cryptography } else { - throw new CryptographicException($"Requested hash method is not supported: {HashMethod}"); + throw new CryptographicException($"Requested hash method is not supported: {hashMethod}"); } } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) { - return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations); + return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations); } public byte[] ComputeHash(PasswordHash hash) { - int iterations = defaultiterations; + int iterations = _defaultIterations; if (!hash.Parameters.ContainsKey("iterations")) { - hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture)); + hash.Parameters.Add("iterations", _defaultIterations.ToString(CultureInfo.InvariantCulture)); } else { @@ -148,13 +153,14 @@ namespace Emby.Server.Implementations.Cryptography throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e); } } + return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations); } public byte[] GenerateSalt() { byte[] salt = new byte[64]; - rng.GetBytes(salt); + _randomNumberGenerator.GetBytes(salt); return salt; } } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 7ccdccc0ad..8f10b5a84e 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -19,18 +19,16 @@ namespace Emby.Server.Implementations.Library public string Name => "Default"; public bool IsEnabled => true; - - - //This is dumb and an artifact of the backwards way auth providers were designed. - //This version of authenticate was never meant to be called, but needs to be here for interface compat - //Only the providers that don't provide local user support use this + + // This is dumb and an artifact of the backwards way auth providers were designed. + // This version of authenticate was never meant to be called, but needs to be here for interface compat + // Only the providers that don't provide local user support use this public Task Authenticate(string username, string password) { throw new NotImplementedException(); } - - - //This is the verson that we need to use for local users. Because reasons. + + // This is the verson that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) { bool success = false; @@ -39,7 +37,7 @@ namespace Emby.Server.Implementations.Library throw new Exception("Invalid username or password"); } - //As long as jellyfin supports passwordless users, we need this little block here to accomodate + // As long as jellyfin supports passwordless users, we need this little block here to accomodate if (IsPasswordEmpty(resolvedUser, password)) { return Task.FromResult(new ProviderAuthenticationResult @@ -70,7 +68,7 @@ namespace Emby.Server.Implementations.Library if (CalculatedHashString == readyHash.Hash) { success = true; - //throw new Exception("Invalid username or password"); + // throw new Exception("Invalid username or password"); } } else @@ -78,7 +76,7 @@ namespace Emby.Server.Implementations.Library throw new Exception(String.Format($"Requested crypto method not available in provider: {readyHash.Id}")); } - //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); if (!success) { @@ -91,8 +89,8 @@ namespace Emby.Server.Implementations.Library }); } - //This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change - //but at least they are in the new format. + // This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change + // but at least they are in the new format. private void ConvertPasswordFormat(User user) { if (string.IsNullOrEmpty(user.Password)) @@ -121,18 +119,13 @@ namespace Emby.Server.Implementations.Library private bool IsPasswordEmpty(User user, string password) { - if (string.IsNullOrEmpty(user.Password)) - { - return string.IsNullOrEmpty(password); - } - - return false; + return (string.IsNullOrEmpty(user.Password) && string.IsNullOrEmpty(password)); } public Task ChangePassword(User user, string newPassword) { ConvertPasswordFormat(user); - //This is needed to support changing a no password user to a password user + // This is needed to support changing a no password user to a password user if (string.IsNullOrEmpty(user.Password)) { PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider); @@ -184,7 +177,7 @@ namespace Emby.Server.Implementations.Library public string GetHashedString(User user, string str) { PasswordHash passwordHash; - if (String.IsNullOrEmpty(user.Password)) + if (string.IsNullOrEmpty(user.Password)) { passwordHash = new PasswordHash(_cryptographyProvider); } @@ -196,13 +189,13 @@ namespace Emby.Server.Implementations.Library if (passwordHash.SaltBytes != null) { - //the password is modern format with PBKDF and we should take advantage of that + // the password is modern format with PBKDF and we should take advantage of that passwordHash.HashBytes = Encoding.UTF8.GetBytes(str); return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); } else { - //the password has no salt and should be called with the older method for safety + // the password has no salt and should be called with the older method for safety return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))); } } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 0f188ca757..57bf16364d 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.Library private readonly Func _dtoServiceFactory; private readonly IServerApplicationHost _appHost; private readonly IFileSystem _fileSystem; - + private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index 8accc696e3..5988112c2e 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -1,22 +1,22 @@ -using System; -using System.IO; -using System.Collections.Generic; - -namespace MediaBrowser.Model.Cryptography -{ - public interface ICryptoProvider - { - Guid GetMD5(string str); - byte[] ComputeMD5(Stream str); - byte[] ComputeMD5(byte[] bytes); - byte[] ComputeSHA1(byte[] bytes); - IEnumerable GetSupportedHashMethods(); - byte[] ComputeHash(string HashMethod, byte[] bytes); - byte[] ComputeHashWithDefaultMethod(byte[] bytes); - byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt); - byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); - byte[] ComputeHash(PasswordHash hash); +using System; +using System.IO; +using System.Collections.Generic; + +namespace MediaBrowser.Model.Cryptography +{ + public interface ICryptoProvider + { + Guid GetMD5(string str); + byte[] ComputeMD5(Stream str); + byte[] ComputeMD5(byte[] bytes); + byte[] ComputeSHA1(byte[] bytes); + IEnumerable GetSupportedHashMethods(); + byte[] ComputeHash(string HashMethod, byte[] bytes); + byte[] ComputeHashWithDefaultMethod(byte[] bytes); + byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt); + byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); + byte[] ComputeHash(PasswordHash hash); byte[] GenerateSalt(); - string DefaultHashMethod { get; } - } -} + string DefaultHashMethod { get; } + } +} diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 49bd510e96..a528404045 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -6,27 +6,40 @@ namespace MediaBrowser.Model.Cryptography { public class PasswordHash { - //Defined from this hash storage spec - //https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md - //$[$=(,=)*][$[$]] + // Defined from this hash storage spec + // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md + // $[$=(,=)*][$[$]] + // with one slight amendment to ease the transition, we're writing out the bytes in hex + // rather than making them a BASE64 string with stripped padding - private string id; - private Dictionary parameters = new Dictionary(); - private string salt; - private byte[] saltBytes; - private string hash; - private byte[] hashBytes; - public string Id { get => id; set => id = value; } - public Dictionary Parameters { get => parameters; set => parameters = value; } - public string Salt { get => salt; set => salt = value; } - public byte[] SaltBytes { get => saltBytes; set => saltBytes = value; } - public string Hash { get => hash; set => hash = value; } - public byte[] HashBytes { get => hashBytes; set => hashBytes = value; } + private string _id; + + private Dictionary _parameters = new Dictionary(); + + private string _salt; + + private byte[] _saltBytes; + + private string _hash; + + private byte[] _hashBytes; + + public string Id { get => _id; set => _id = value; } + + public Dictionary Parameters { get => _parameters; set => _parameters = value; } + + public string Salt { get => _salt; set => _salt = value; } + + public byte[] SaltBytes { get => _saltBytes; set => _saltBytes = value; } + + public string Hash { get => _hash; set => _hash = value; } + + public byte[] HashBytes { get => _hashBytes; set => _hashBytes = value; } public PasswordHash(string storageString) { string[] splitted = storageString.Split('$'); - id = splitted[1]; + _id = splitted[1]; if (splitted[2].Contains("=")) { foreach (string paramset in (splitted[2].Split(','))) @@ -36,7 +49,7 @@ namespace MediaBrowser.Model.Cryptography string[] fields = paramset.Split('='); if (fields.Length == 2) { - parameters.Add(fields[0], fields[1]); + _parameters.Add(fields[0], fields[1]); } else { @@ -46,32 +59,32 @@ namespace MediaBrowser.Model.Cryptography } if (splitted.Length == 5) { - salt = splitted[3]; - saltBytes = ConvertFromByteString(salt); - hash = splitted[4]; - hashBytes = ConvertFromByteString(hash); + _salt = splitted[3]; + _saltBytes = ConvertFromByteString(_salt); + _hash = splitted[4]; + _hashBytes = ConvertFromByteString(_hash); } else { - salt = string.Empty; - hash = splitted[3]; - hashBytes = ConvertFromByteString(hash); + _salt = string.Empty; + _hash = splitted[3]; + _hashBytes = ConvertFromByteString(_hash); } } else { if (splitted.Length == 4) { - salt = splitted[2]; - saltBytes = ConvertFromByteString(salt); - hash = splitted[3]; - hashBytes = ConvertFromByteString(hash); + _salt = splitted[2]; + _saltBytes = ConvertFromByteString(_salt); + _hash = splitted[3]; + _hashBytes = ConvertFromByteString(_hash); } else { - salt = string.Empty; - hash = splitted[2]; - hashBytes = ConvertFromByteString(hash); + _salt = string.Empty; + _hash = splitted[2]; + _hashBytes = ConvertFromByteString(_hash); } } @@ -80,9 +93,9 @@ namespace MediaBrowser.Model.Cryptography public PasswordHash(ICryptoProvider cryptoProvider) { - id = cryptoProvider.DefaultHashMethod; - saltBytes = cryptoProvider.GenerateSalt(); - salt = ConvertToByteString(SaltBytes); + _id = cryptoProvider.DefaultHashMethod; + _saltBytes = cryptoProvider.GenerateSalt(); + _salt = ConvertToByteString(SaltBytes); } public static byte[] ConvertFromByteString(string byteString) @@ -92,6 +105,7 @@ namespace MediaBrowser.Model.Cryptography { Bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); } + return Bytes.ToArray(); } @@ -103,7 +117,7 @@ namespace MediaBrowser.Model.Cryptography private string SerializeParameters() { string ReturnString = string.Empty; - foreach (var KVP in parameters) + foreach (var KVP in _parameters) { ReturnString += $",{KVP.Key}={KVP.Value}"; } @@ -118,19 +132,19 @@ namespace MediaBrowser.Model.Cryptography public override string ToString() { - string outString = "$" +id; + string outString = "$" +_id; string paramstring = SerializeParameters(); if (!string.IsNullOrEmpty(paramstring)) { outString += $"${paramstring}"; } - if (!string.IsNullOrEmpty(salt)) + if (!string.IsNullOrEmpty(_salt)) { - outString += $"${salt}"; + outString += $"${_salt}"; } - outString += $"${hash}"; + outString += $"${_hash}"; return outString; } } -- cgit v1.2.3 From bc00617df7eee51a80302816bef9a797455e7780 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 10:26:43 +0100 Subject: Remove unused Brotli compressor --- Emby.Server.Implementations/ApplicationHost.cs | 7 +------ .../HttpServer/HttpResultFactory.cs | 15 +-------------- 2 files changed, 2 insertions(+), 20 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 09c40ea735..deb1d3cee0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -741,7 +741,7 @@ namespace Emby.Server.Implementations ZipClient = new ZipClient(); serviceCollection.AddSingleton(ZipClient); - HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor()); + HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer); serviceCollection.AddSingleton(HttpResultFactory); serviceCollection.AddSingleton(this); @@ -896,11 +896,6 @@ namespace Emby.Server.Implementations _serviceProvider = serviceCollection.BuildServiceProvider(); } - protected virtual IBrotliCompressor CreateBrotliCompressor() - { - return null; - } - public virtual string PackageRuntime => "netcore"; public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 52c8221f68..be50719d85 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -35,16 +35,13 @@ namespace Emby.Server.Implementations.HttpServer private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; - private IBrotliCompressor _brotliCompressor; - /// /// Initializes a new instance of the class. /// - public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IBrotliCompressor brotliCompressor) + public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer) { _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; - _brotliCompressor = brotliCompressor; _logger = loggerfactory.CreateLogger("HttpResultFactory"); } @@ -350,11 +347,6 @@ namespace Emby.Server.Implementations.HttpServer private byte[] Compress(byte[] bytes, string compressionType) { - if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase)) - { - return CompressBrotli(bytes); - } - if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase)) { return Deflate(bytes); @@ -368,11 +360,6 @@ namespace Emby.Server.Implementations.HttpServer throw new NotSupportedException(compressionType); } - private byte[] CompressBrotli(byte[] bytes) - { - return _brotliCompressor.Compress(bytes); - } - private static byte[] Deflate(byte[] bytes) { // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream -- cgit v1.2.3 From 78742b8e4c658b1f02c9c040b8abb8ee5742bed4 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 19:20:28 +0100 Subject: Switch to HeaderNames instead of hardcoded strings (and other header related fixes) --- .../HttpClientManager/HttpClientManager.cs | 5 +- .../HttpServer/FileWriter.cs | 9 +-- .../HttpServer/HttpResultFactory.cs | 41 +++++------ .../HttpServer/RangeRequestWriter.cs | 7 +- .../HttpServer/ResponseFilter.cs | 7 +- .../HttpServer/Security/AuthorizationContext.cs | 3 +- .../HttpServer/StreamWriter.cs | 5 +- .../LiveTv/Listings/SchedulesDirect.cs | 5 +- .../LiveTv/TunerHosts/M3UTunerHost.cs | 3 +- .../SocketSharp/RequestMono.cs | 5 +- MediaBrowser.Api/Images/ImageService.cs | 3 +- MediaBrowser.Api/Library/LibraryService.cs | 3 +- MediaBrowser.Api/LiveTv/LiveTvService.cs | 15 ++-- .../Progressive/BaseProgressiveStreamingService.cs | 81 +++++----------------- MediaBrowser.Common/Net/HttpRequestOptions.cs | 9 +-- 15 files changed, 82 insertions(+), 119 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index ef82d1d9ee..1bebdd1637 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -15,6 +15,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpClientManager { @@ -179,11 +180,11 @@ namespace Emby.Server.Implementations.HttpClientManager foreach (var header in options.RequestHeaders) { - if (string.Equals(header.Key, "Accept", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(header.Key, HeaderNames.Accept, StringComparison.OrdinalIgnoreCase)) { request.Accept = header.Value; } - else if (string.Equals(header.Key, "User-Agent", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) { SetUserAgent(request, header.Value); hasUserAgent = true; diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 1375089e39..303db99801 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -9,6 +9,7 @@ using Emby.Server.Implementations.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer { @@ -56,10 +57,10 @@ namespace Emby.Server.Implementations.HttpServer FileSystem = fileSystem; RangeHeader = rangeHeader; - Headers["Content-Type"] = contentType; + Headers[HeaderNames.ContentType] = contentType; TotalContentLength = fileSystem.GetFileInfo(path).Length; - Headers["Accept-Ranges"] = "bytes"; + Headers[HeaderNames.AcceptRanges] = "bytes"; if (string.IsNullOrWhiteSpace(rangeHeader)) { @@ -97,8 +98,8 @@ namespace Emby.Server.Implementations.HttpServer // Content-Length is the length of what we're serving, not the original content var lengthString = RangeLength.ToString(UsCulture); - var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); - Headers["Content-Range"] = rangeString; + var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; + Headers[HeaderNames.ContentRange] = rangeString; Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index be50719d85..f4fe6b611a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.HttpServer public object GetRedirectResult(string url) { var responseHeaders = new Dictionary(); - responseHeaders["Location"] = url; + responseHeaders[HeaderNames.Location] = url; var result = new HttpResult(Array.Empty(), "text/plain", HttpStatusCode.Redirect); @@ -96,9 +96,9 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires)) + if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string expires)) { - responseHeaders["Expires"] = "-1"; + responseHeaders[HeaderNames.Expires] = "-1"; } AddResponseHeaders(result, responseHeaders); @@ -142,9 +142,9 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _)) + if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _)) { - responseHeaders["Expires"] = "-1"; + responseHeaders[HeaderNames.Expires] = "-1"; } AddResponseHeaders(result, responseHeaders); @@ -186,9 +186,9 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _)) + if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _)) { - responseHeaders["Expires"] = "-1"; + responseHeaders[HeaderNames.Expires] = "-1"; } AddResponseHeaders(result, responseHeaders); @@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); } - responseHeaders["Expires"] = "-1"; + responseHeaders[HeaderNames.Expires] = "-1"; return ToOptimizedResultInternal(requestContext, result, responseHeaders); } @@ -245,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer private static string GetCompressionType(IRequest request) { - var acceptEncoding = request.Headers["Accept-Encoding"].ToString(); + var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString(); if (string.IsNullOrEmpty(acceptEncoding)) { @@ -325,9 +325,9 @@ namespace Emby.Server.Implementations.HttpServer } content = Compress(content, requestedCompressionType); - responseHeaders["Content-Encoding"] = requestedCompressionType; + responseHeaders[HeaderNames.ContentEncoding] = requestedCompressionType; - responseHeaders["Vary"] = "Accept-Encoding"; + responseHeaders[HeaderNames.Vary] = HeaderNames.AcceptEncoding; var contentLength = content.Length; @@ -537,7 +537,7 @@ namespace Emby.Server.Implementations.HttpServer AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified); AddAgeHeader(responseHeaders, options.DateLastModified); - var rangeHeader = requestContext.Headers["Range"]; + var rangeHeader = requestContext.Headers[HeaderNames.Range]; if (!isHeadRequest && !string.IsNullOrEmpty(options.Path)) { @@ -606,23 +606,23 @@ namespace Emby.Server.Implementations.HttpServer { if (noCache) { - responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate"; - responseHeaders["pragma"] = "no-cache, no-store, must-revalidate"; + responseHeaders[HeaderNames.CacheControl] = "no-cache, no-store, must-revalidate"; + responseHeaders[HeaderNames.Pragma] = "no-cache, no-store, must-revalidate"; return; } if (cacheDuration.HasValue) { - responseHeaders["Cache-Control"] = "public, max-age=" + cacheDuration.Value.TotalSeconds; + responseHeaders[HeaderNames.CacheControl] = "public, max-age=" + cacheDuration.Value.TotalSeconds; } else { - responseHeaders["Cache-Control"] = "public"; + responseHeaders[HeaderNames.CacheControl] = "public"; } if (lastModifiedDate.HasValue) { - responseHeaders["Last-Modified"] = lastModifiedDate.ToString(); + responseHeaders[HeaderNames.LastModified] = lastModifiedDate.ToString(); } } @@ -635,7 +635,7 @@ namespace Emby.Server.Implementations.HttpServer { if (lastDateModified.HasValue) { - responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture); + responseHeaders[HeaderNames.Age] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture); } } @@ -693,9 +693,4 @@ namespace Emby.Server.Implementations.HttpServer } } } - - public interface IBrotliCompressor - { - byte[] Compress(byte[] content); - } } diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 8904e11d30..a0bfe5d8f9 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer { @@ -66,8 +67,8 @@ namespace Emby.Server.Implementations.HttpServer this._logger = logger; ContentType = contentType; - Headers["Content-Type"] = contentType; - Headers["Accept-Ranges"] = "bytes"; + Headers[HeaderNames.ContentType] = contentType; + Headers[HeaderNames.AcceptRanges] = "bytes"; StatusCode = HttpStatusCode.PartialContent; SetRangeValues(contentLength); @@ -96,7 +97,7 @@ namespace Emby.Server.Implementations.HttpServer RangeLength = 1 + RangeEnd - RangeStart; // Content-Length is the length of what we're serving, not the original content - Headers["Content-Range"] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; + Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; if (RangeStart > 0 && SourceStream.CanSeek) { diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index ae6a6576e0..a53d9bf0b9 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Text; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer { @@ -44,13 +45,13 @@ namespace Emby.Server.Implementations.HttpServer if (dto is IHasHeaders hasHeaders) { - if (!hasHeaders.Headers.ContainsKey("Server")) + if (!hasHeaders.Headers.ContainsKey(HeaderNames.Server)) { - hasHeaders.Headers["Server"] = "Microsoft-NetCore/2.0, UPnP/1.0 DLNADOC/1.50"; + hasHeaders.Headers[HeaderNames.Server] = "Microsoft-NetCore/2.0, UPnP/1.0 DLNADOC/1.50"; } // Content length has to be explicitly set on on HttpListenerResponse or it won't be happy - if (hasHeaders.Headers.TryGetValue("Content-Length", out string contentLength) + if (hasHeaders.Headers.TryGetValue(HeaderNames.ContentLength, out string contentLength) && !string.IsNullOrEmpty(contentLength)) { var length = long.Parse(contentLength, UsCulture); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index cab41e65b9..276312a300 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Services; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer.Security { @@ -176,7 +177,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (string.IsNullOrEmpty(auth)) { - auth = httpReq.Headers["Authorization"]; + auth = httpReq.Headers[HeaderNames.Authorization]; } return GetAuthorization(auth); diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 90354385dc..66a13e20df 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer { @@ -52,7 +53,7 @@ namespace Emby.Server.Implementations.HttpServer SourceStream = source; - Headers["Content-Type"] = contentType; + Headers[HeaderNames.ContentType] = contentType; } /// @@ -69,7 +70,7 @@ namespace Emby.Server.Implementations.HttpServer SourceBytes = source; - Headers["Content-Type"] = contentType; + Headers[HeaderNames.ContentType] = contentType; } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 0bbffb824b..4137760d07 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -17,6 +17,7 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.LiveTv.Listings { @@ -638,7 +639,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings #if NETSTANDARD2_0 if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - options.RequestHeaders["Accept-Encoding"] = "deflate"; + options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate"; } #endif @@ -676,7 +677,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings #if NETSTANDARD2_0 if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - options.RequestHeaders["Accept-Encoding"] = "deflate"; + options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate"; } #endif diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index fdaaf0bae7..588dcb843b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -19,6 +19,7 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.LiveTv.TunerHosts { @@ -145,7 +146,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (protocol == MediaProtocol.Http) { // Use user-defined user-agent. If there isn't one, make it look like a browser. - httpHeaders["User-Agent"] = string.IsNullOrWhiteSpace(info.UserAgent) ? + httpHeaders[HeaderNames.UserAgent] = string.IsNullOrWhiteSpace(info.UserAgent) ? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.85 Safari/537.36" : info.UserAgent; } diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index f73adc5ff8..82bcf2f043 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading.Tasks; using MediaBrowser.Model.Services; using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.SocketSharp { @@ -114,9 +115,9 @@ namespace Emby.Server.Implementations.SocketSharp return form; } - public string Accept => StringValues.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"].ToString(); + public string Accept => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Accept]) ? null : request.Headers[HeaderNames.Accept].ToString(); - public string Authorization => StringValues.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"].ToString(); + public string Authorization => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Authorization]) ? null : request.Headers[HeaderNames.Authorization].ToString(); protected bool validate_form { get; set; } protected bool checked_form { get; set; } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 61db7b8d47..c25204bf2d 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -18,6 +18,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Images { @@ -634,7 +635,7 @@ namespace MediaBrowser.Api.Images var imageResult = await _imageProcessor.ProcessImage(options).ConfigureAwait(false); - headers["Vary"] = "Accept"; + headers[HeaderNames.Vary] = HeaderNames.Accept; return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index d44b07256d..8eefbdf2ca 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -29,6 +29,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Library { @@ -827,7 +828,7 @@ namespace MediaBrowser.Api.Library var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); if (!string.IsNullOrWhiteSpace(filename)) { - headers["Content-Disposition"] = "attachment; filename=\"" + filename + "\""; + headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\""; } return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 8fdd726b7b..88ed734569 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -24,6 +24,7 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.LiveTv { @@ -750,9 +751,10 @@ namespace MediaBrowser.Api.LiveTv throw new FileNotFoundException(); } - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - - outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType(path) + }; return new ProgressiveFileCopier(_fileSystem, _streamHelper, path, outputHeaders, Logger, _environment) { @@ -772,9 +774,10 @@ namespace MediaBrowser.Api.LiveTv var directStreamProvider = liveStreamInfo; - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - - outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file." + request.Container) + }; return new ProgressiveFileCopier(directStreamProvider, _streamHelper, outputHeaders, Logger, _environment) { diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 3e74d59db7..fc81e532d8 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -17,6 +17,7 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Playback.Progressive { @@ -154,7 +155,7 @@ namespace MediaBrowser.Api.Playback.Progressive var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); // TODO: Don't hardcode this - outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts"); + outputHeaders[HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file.ts"); return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) { @@ -196,9 +197,11 @@ namespace MediaBrowser.Api.Playback.Progressive { if (state.MediaSource.IsInfiniteStream) { - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = contentType + }; - outputHeaders["Content-Type"] = contentType; return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) { @@ -298,16 +301,16 @@ namespace MediaBrowser.Api.Playback.Progressive if (trySupportSeek) { - if (!string.IsNullOrWhiteSpace(Request.QueryString["Range"])) + if (!string.IsNullOrWhiteSpace(Request.QueryString[HeaderNames.Range])) { - options.RequestHeaders["Range"] = Request.QueryString["Range"]; + options.RequestHeaders[HeaderNames.Range] = Request.QueryString[HeaderNames.Range]; } } var response = await HttpClient.GetResponse(options).ConfigureAwait(false); if (trySupportSeek) { - foreach (var name in new[] { "Content-Range", "Accept-Ranges" }) + foreach (var name in new[] { HeaderNames.ContentRange, HeaderNames.AcceptRanges }) { var val = response.Headers[name]; if (!string.IsNullOrWhiteSpace(val)) @@ -318,14 +321,7 @@ namespace MediaBrowser.Api.Playback.Progressive } else { - responseHeaders["Accept-Ranges"] = "none"; - } - - // Seeing cases of -1 here - if (response.ContentLength.HasValue && response.ContentLength.Value >= 0) - { - // TODO - //responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); + responseHeaders[HeaderNames.AcceptRanges] = "none"; } if (isHeadRequest) @@ -338,7 +334,7 @@ namespace MediaBrowser.Api.Playback.Progressive var result = new StaticRemoteStreamWriter(response); - result.Headers["Content-Type"] = response.ContentType; + result.Headers[HeaderNames.ContentType] = response.ContentType; // Add the response headers to the result object foreach (var header in responseHeaders) @@ -362,42 +358,14 @@ namespace MediaBrowser.Api.Playback.Progressive // Use the command line args with a dummy playlist path var outputPath = state.OutputFilePath; - responseHeaders["Accept-Ranges"] = "none"; + responseHeaders[HeaderNames.AcceptRanges] = "none"; var contentType = state.GetMimeType(outputPath); - // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response - // What we really want to do is hunt that down and remove that - var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null; - - if (contentLength.HasValue) - { - responseHeaders["Content-Length"] = contentLength.Value.ToString(UsCulture); - } - // Headers only if (isHeadRequest) { - var streamResult = ResultFactory.GetResult(null, new byte[] { }, contentType, responseHeaders); - - var hasHeaders = streamResult as IHasHeaders; - if (hasHeaders != null) - { - // TODO - //if (contentLength.HasValue) - //{ - // hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); - //} - //else - //{ - if (hasHeaders.Headers.ContainsKey("Content-Length")) - { - hasHeaders.Headers.Remove("Content-Length"); - } - //} - } - - return streamResult; + return ResultFactory.GetResult(null, new byte[] { }, contentType, responseHeaders); } var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath); @@ -416,9 +384,11 @@ namespace MediaBrowser.Api.Playback.Progressive state.Dispose(); } - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = contentType + }; - outputHeaders["Content-Type"] = contentType; // Add the response headers to the result object foreach (var item in responseHeaders) @@ -433,22 +403,5 @@ namespace MediaBrowser.Api.Playback.Progressive transcodingLock.Release(); } } - - /// - /// Gets the length of the estimated content. - /// - /// The state. - /// System.Nullable{System.Int64}. - private long? GetEstimatedContentLength(StreamState state) - { - var totalBitrate = state.TotalOutputBitrate ?? 0; - - if (totalBitrate > 0 && state.RunTimeTicks.HasValue) - { - return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8); - } - - return null; - } } } diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index dadac5e03d..bea178517b 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Common.Net { @@ -24,8 +25,8 @@ namespace MediaBrowser.Common.Net /// The accept header. public string AcceptHeader { - get => GetHeaderValue("Accept"); - set => RequestHeaders["Accept"] = value; + get => GetHeaderValue(HeaderNames.Accept); + set => RequestHeaders[HeaderNames.Accept] = value; } /// /// Gets or sets the cancellation token. @@ -45,8 +46,8 @@ namespace MediaBrowser.Common.Net /// The user agent. public string UserAgent { - get => GetHeaderValue("User-Agent"); - set => RequestHeaders["User-Agent"] = value; + get => GetHeaderValue(HeaderNames.UserAgent); + set => RequestHeaders[HeaderNames.UserAgent] = value; } /// -- cgit v1.2.3 From 9a4a01fb0e94d8ffbc00f5932f284f7faeb0ee69 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 19:32:22 +0100 Subject: Fix DI in FileWriter.TransmitFile --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../HttpServer/FileWriter.cs | 30 ++++++++++------------ .../HttpServer/HttpResultFactory.cs | 6 +++-- .../SocketSharp/WebSocketSharpResponse.cs | 2 -- 4 files changed, 18 insertions(+), 22 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index deb1d3cee0..93147539c6 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -741,7 +741,7 @@ namespace Emby.Server.Implementations ZipClient = new ZipClient(); serviceCollection.AddSingleton(ZipClient); - HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer); + HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, StreamHelper); serviceCollection.AddSingleton(HttpResultFactory); serviceCollection.AddSingleton(this); diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 303db99801..c4a170eaae 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -15,6 +15,7 @@ namespace Emby.Server.Implementations.HttpServer { public class FileWriter : IHttpResult { + private readonly IStreamHelper _streamHelper; private ILogger Logger { get; set; } public IFileSystem FileSystem { get; } @@ -45,13 +46,15 @@ namespace Emby.Server.Implementations.HttpServer public string Path { get; set; } - public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem) + public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem, IStreamHelper streamHelper) { if (string.IsNullOrEmpty(contentType)) { throw new ArgumentNullException(nameof(contentType)); } + _streamHelper = streamHelper; + Path = path; Logger = logger; FileSystem = fileSystem; @@ -147,8 +150,7 @@ namespace Emby.Server.Implementations.HttpServer } } - private string[] SkipLogExtensions = new string[] - { + private readonly string[] SkipLogExtensions = { ".js", ".html", ".css" @@ -165,8 +167,10 @@ namespace Emby.Server.Implementations.HttpServer } var path = Path; + var offset = RangeStart; + var count = RangeLength; - if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)) + if (string.IsNullOrWhiteSpace(RangeHeader) || RangeStart <= 0 && RangeEnd >= TotalContentLength - 1) { var extension = System.IO.Path.GetExtension(path); @@ -175,20 +179,15 @@ namespace Emby.Server.Implementations.HttpServer Logger.LogDebug("Transmit file {0}", path); } - //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0; - // TODO not DI friendly lol - await response.TransmitFile(path, 0, 0, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); - return; + offset = 0; + count = 0; } - // TODO not DI friendly lol - await response.TransmitFile(path, RangeStart, RangeLength, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); + + await response.TransmitFile(path, offset, count, FileShare, FileSystem, _streamHelper, cancellationToken).ConfigureAwait(false); } finally { - if (OnComplete != null) - { - OnComplete(); - } + OnComplete?.Invoke(); } } @@ -205,8 +204,5 @@ namespace Emby.Server.Implementations.HttpServer get => (HttpStatusCode)Status; set => Status = (int)value; } - - public string StatusDescription { get; set; } - } } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index f4fe6b611a..4632658626 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -34,14 +34,16 @@ namespace Emby.Server.Implementations.HttpServer private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; + private readonly IStreamHelper _streamHelper; /// /// Initializes a new instance of the class. /// - public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer) + public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IStreamHelper streamHelper) { _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; + _streamHelper = streamHelper; _logger = loggerfactory.CreateLogger("HttpResultFactory"); } @@ -541,7 +543,7 @@ namespace Emby.Server.Implementations.HttpServer if (!isHeadRequest && !string.IsNullOrEmpty(options.Path)) { - var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) + var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem, _streamHelper) { OnComplete = options.OnComplete, OnError = options.OnError, diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index 5964107651..a7e3e6c700 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -78,8 +78,6 @@ namespace Emby.Server.Implementations.SocketSharp const int StreamCopyToBufferSize = 81920; public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) { - // TODO - // return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken); var allowAsync = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); //if (count <= 0) -- cgit v1.2.3 From 913e80fd5527775ea71d6f9606f49c5494cb7b63 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 20:35:07 +0100 Subject: Add ProcessWebSocketRequest to IHttpListener --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 5 ++--- Emby.Server.Implementations/HttpServer/IHttpListener.cs | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 293b28e74b..d1b1b5b242 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -756,11 +756,10 @@ namespace Emby.Server.Implementations.HttpServer public Task ProcessWebSocketRequest(HttpContext context) { - // TODO - return ((WebSocketSharpListener)_socketListener).ProcessWebSocketRequest(context); + return _socketListener.ProcessWebSocketRequest(context); } - //TODO Add Jellyfin Route Path Normalizer + //TODO Add Jellyfin Route Path Normalizer private static string NormalizeEmbyRoutePath(string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 1ef65d9d73..005656d2c1 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace Emby.Server.Implementations.HttpServer { @@ -30,5 +31,7 @@ namespace Emby.Server.Implementations.HttpServer /// Stops this instance. /// Task Stop(); + + Task ProcessWebSocketRequest(HttpContext ctx); } } -- cgit v1.2.3 From 446f9bf81fa3ac737acda71d57fdc7289c227cef Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 20:48:04 +0100 Subject: Remove more Content-Length references --- .../HttpServer/FileWriter.cs | 4 +--- .../HttpServer/RangeRequestWriter.cs | 1 - .../Services/QueryParamCollection.cs | 26 ++++------------------ 3 files changed, 5 insertions(+), 26 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index c4a170eaae..78e2015b1e 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -99,12 +99,10 @@ namespace Emby.Server.Implementations.HttpServer RangeStart = requestedRange.Key; RangeLength = 1 + RangeEnd - RangeStart; - // Content-Length is the length of what we're serving, not the original content - var lengthString = RangeLength.ToString(UsCulture); var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; Headers[HeaderNames.ContentRange] = rangeString; - Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); + Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Range: {2}", Path, RangeHeader, rangeString); } /// diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index a0bfe5d8f9..449159834a 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -96,7 +96,6 @@ namespace Emby.Server.Implementations.HttpServer RangeStart = requestedRange.Key; RangeLength = 1 + RangeEnd - RangeStart; - // Content-Length is the length of what we're serving, not the original content Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; if (RangeStart > 0 && SourceStream.CanSeek) diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 4631a3b633..7708db00a8 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -25,16 +25,12 @@ namespace MediaBrowser.Model.Services /// /// Adds a new query parameter. /// - public virtual void Add(string key, string value) + public void Add(string key, string value) { - if (string.Equals(key, "content-length", StringComparison.OrdinalIgnoreCase)) - { - return; - } Add(new NameValuePair(key, value)); } - public virtual void Set(string key, string value) + private void Set(string key, string value) { if (string.IsNullOrEmpty(value)) { @@ -62,7 +58,7 @@ namespace MediaBrowser.Model.Services Add(key, value); } - public string Get(string name) + private string Get(string name) { var stringComparison = GetStringComparison(); @@ -77,7 +73,7 @@ namespace MediaBrowser.Model.Services return null; } - public virtual List GetItems(string name) + private List GetItems(string name) { var stringComparison = GetStringComparison(); @@ -111,20 +107,6 @@ namespace MediaBrowser.Model.Services return list; } - public Dictionary ToDictionary() - { - var stringComparer = GetStringComparer(); - - var headers = new Dictionary(stringComparer); - - foreach (var pair in this) - { - headers[pair.Name] = pair.Value; - } - - return headers; - } - public IEnumerable Keys { get -- cgit v1.2.3 From 20775116f76b12bf77672fa37c4ea5f82b69f157 Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Fri, 8 Feb 2019 13:35:26 +0000 Subject: Reworked FFmpeg path discovery and always display to user 1) Reworked FFmpeg and FFprobe path discovery (CLI switch, Custom xml, system $PATH, UI update trigger). Removed FFMpeg folder from Emby.Server.Implementations. All path discovery now in MediaEncoder. 2) Always display FFmpeg path to user in Transcode page. 3) Allow user to remove a Custome FFmpeg path and return to using system $PATH (or --ffmpeg if available). 4) Remove unused code associated with 'prebuilt' FFmpeg. 5) Much improved logging during path discovery. --- Emby.Server.Implementations/ApplicationHost.cs | 72 +--- Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs | 24 -- .../FFMpeg/FFMpegInstallInfo.cs | 17 - Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs | 132 ------- .../Encoder/EncoderValidator.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 433 ++++++++++----------- .../Configuration/EncodingOptions.cs | 3 +- 7 files changed, 212 insertions(+), 471 deletions(-) delete mode 100644 Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs delete mode 100644 Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs delete mode 100644 Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 94d2cd5daf..2c0d0e7469 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -28,7 +28,6 @@ using Emby.Server.Implementations.Data; using Emby.Server.Implementations.Devices; using Emby.Server.Implementations.Diagnostics; using Emby.Server.Implementations.Dto; -using Emby.Server.Implementations.FFMpeg; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.Security; using Emby.Server.Implementations.IO; @@ -792,7 +791,8 @@ namespace Emby.Server.Implementations ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); serviceCollection.AddSingleton(ChapterManager); - RegisterMediaEncoder(serviceCollection); + MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(LoggerFactory, JsonSerializer, StartupOptions.FFmpegPath, StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, () => SubtitleEncoder, () => MediaSourceManager, ProcessFactory, 5000); + serviceCollection.AddSingleton(MediaEncoder); EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); serviceCollection.AddSingleton(EncodingManager); @@ -908,83 +908,25 @@ namespace Emby.Server.Implementations return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); } - protected virtual FFMpegInstallInfo GetFfmpegInstallInfo() - { - var info = new FFMpegInstallInfo(); - - // Windows builds: http://ffmpeg.zeranoe.com/builds/ - // Linux builds: http://johnvansickle.com/ffmpeg/ - // OS X builds: http://ffmpegmac.net/ - // OS X x64: http://www.evermeet.cx/ffmpeg/ - - if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Linux) - { - info.FFMpegFilename = "ffmpeg"; - info.FFProbeFilename = "ffprobe"; - info.ArchiveType = "7z"; - info.Version = "20170308"; - } - else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) - { - info.FFMpegFilename = "ffmpeg.exe"; - info.FFProbeFilename = "ffprobe.exe"; - info.Version = "20170308"; - info.ArchiveType = "7z"; - } - else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) - { - info.FFMpegFilename = "ffmpeg"; - info.FFProbeFilename = "ffprobe"; - info.ArchiveType = "7z"; - info.Version = "20170308"; - } - - return info; - } - - protected virtual FFMpegInfo GetFFMpegInfo() - { - return new FFMpegLoader(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo()) - .GetFFMpegInfo(StartupOptions); - } - /// /// Registers the media encoder. /// /// Task. - private void RegisterMediaEncoder(IServiceCollection serviceCollection) + private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo) { - string encoderPath = null; - string probePath = null; - - var info = GetFFMpegInfo(); - - encoderPath = info.EncoderPath; - probePath = info.ProbePath; - var hasExternalEncoder = string.Equals(info.Version, "external", StringComparison.OrdinalIgnoreCase); - - var mediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( + MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( LoggerFactory, JsonSerializer, - encoderPath, - probePath, - hasExternalEncoder, + StartupOptions.FFmpegPath, + StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, - LiveTvManager, - IsoManager, - LibraryManager, - ChannelManager, - SessionManager, () => SubtitleEncoder, () => MediaSourceManager, - HttpClient, - ZipClient, ProcessFactory, 5000); - MediaEncoder = mediaEncoder; - serviceCollection.AddSingleton(MediaEncoder); + RegisterSingleInstance(MediaEncoder); } /// diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs b/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs deleted file mode 100644 index 60cd7b3d72..0000000000 --- a/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Emby.Server.Implementations.FFMpeg -{ - /// - /// Class FFMpegInfo - /// - public class FFMpegInfo - { - /// - /// Gets or sets the path. - /// - /// The path. - public string EncoderPath { get; set; } - /// - /// Gets or sets the probe path. - /// - /// The probe path. - public string ProbePath { get; set; } - /// - /// Gets or sets the version. - /// - /// The version. - public string Version { get; set; } - } -} diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs b/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs deleted file mode 100644 index fa9cb5e01b..0000000000 --- a/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Emby.Server.Implementations.FFMpeg -{ - public class FFMpegInstallInfo - { - public string Version { get; set; } - public string FFMpegFilename { get; set; } - public string FFProbeFilename { get; set; } - public string ArchiveType { get; set; } - - public FFMpegInstallInfo() - { - Version = "Path"; - FFMpegFilename = "ffmpeg"; - FFProbeFilename = "ffprobe"; - } - } -} diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs deleted file mode 100644 index bbf51dd246..0000000000 --- a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.FFMpeg -{ - public class FFMpegLoader - { - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - private readonly FFMpegInstallInfo _ffmpegInstallInfo; - - public FFMpegLoader(IApplicationPaths appPaths, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo) - { - _appPaths = appPaths; - _fileSystem = fileSystem; - _ffmpegInstallInfo = ffmpegInstallInfo; - } - - public FFMpegInfo GetFFMpegInfo(IStartupOptions options) - { - var customffMpegPath = options.FFmpegPath; - var customffProbePath = options.FFprobePath; - - if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath)) - { - return new FFMpegInfo - { - ProbePath = customffProbePath, - EncoderPath = customffMpegPath, - Version = "external" - }; - } - - var downloadInfo = _ffmpegInstallInfo; - - var prebuiltFolder = _appPaths.ProgramSystemPath; - var prebuiltffmpeg = Path.Combine(prebuiltFolder, downloadInfo.FFMpegFilename); - var prebuiltffprobe = Path.Combine(prebuiltFolder, downloadInfo.FFProbeFilename); - if (File.Exists(prebuiltffmpeg) && File.Exists(prebuiltffprobe)) - { - return new FFMpegInfo - { - ProbePath = prebuiltffprobe, - EncoderPath = prebuiltffmpeg, - Version = "external" - }; - } - - var version = downloadInfo.Version; - - if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase)) - { - return new FFMpegInfo(); - } - - var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg"); - var versionedDirectoryPath = Path.Combine(rootEncoderPath, version); - - var info = new FFMpegInfo - { - ProbePath = Path.Combine(versionedDirectoryPath, downloadInfo.FFProbeFilename), - EncoderPath = Path.Combine(versionedDirectoryPath, downloadInfo.FFMpegFilename), - Version = version - }; - - Directory.CreateDirectory(versionedDirectoryPath); - - var excludeFromDeletions = new List { versionedDirectoryPath }; - - if (!File.Exists(info.ProbePath) || !File.Exists(info.EncoderPath)) - { - // ffmpeg not present. See if there's an older version we can start with - var existingVersion = GetExistingVersion(info, rootEncoderPath); - - // No older version. Need to download and block until complete - if (existingVersion == null) - { - return new FFMpegInfo(); - } - else - { - info = existingVersion; - versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath); - excludeFromDeletions.Add(versionedDirectoryPath); - } - } - - // Allow just one of these to be overridden, if desired. - if (!string.IsNullOrWhiteSpace(customffMpegPath)) - { - info.EncoderPath = customffMpegPath; - } - if (!string.IsNullOrWhiteSpace(customffProbePath)) - { - info.ProbePath = customffProbePath; - } - - return info; - } - - private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath) - { - var encoderFilename = Path.GetFileName(info.EncoderPath); - var probeFilename = Path.GetFileName(info.ProbePath); - - foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath)) - { - var allFiles = _fileSystem.GetFilePaths(directory, true).ToList(); - - var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase)); - var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase)); - - if (!string.IsNullOrWhiteSpace(encoder) && - !string.IsNullOrWhiteSpace(probe)) - { - return new FFMpegInfo - { - EncoderPath = encoder, - ProbePath = probe, - Version = Path.GetFileName(Path.GetDirectoryName(probe)) - }; - } - } - - return null; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index f725d2c019..1eeea87a06 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _processFactory = processFactory; } - public (IEnumerable decoders, IEnumerable encoders) Validate(string encoderPath) + public (IEnumerable decoders, IEnumerable encoders) GetAvailableCoders(string encoderPath) { _logger.LogInformation("Validating media encoder at {EncoderPath}", encoderPath); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 7f29c06b4c..36d72cad90 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -7,13 +7,9 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Diagnostics; @@ -32,323 +28,288 @@ namespace MediaBrowser.MediaEncoding.Encoder public class MediaEncoder : IMediaEncoder, IDisposable { /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// Gets the json serializer. + /// Gets the encoder path. /// - /// The json serializer. - private readonly IJsonSerializer _jsonSerializer; + /// The encoder path. + public string EncoderPath => FFmpegPath; /// - /// The _thumbnail resource pool + /// External: path supplied via command line + /// Custom: coming from UI or config/encoding.xml file + /// System: FFmpeg found in system $PATH + /// null: No FFmpeg found /// - private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); - - public string FFMpegPath { get; private set; } - - public string FFProbePath { get; private set; } + public string EncoderLocationType { get; private set; } + private readonly ILogger _logger; + private readonly IJsonSerializer _jsonSerializer; + private string FFmpegPath { get; set; } + private string FFprobePath { get; set; } protected readonly IServerConfigurationManager ConfigurationManager; protected readonly IFileSystem FileSystem; - protected readonly ILiveTvManager LiveTvManager; - protected readonly IIsoManager IsoManager; - protected readonly ILibraryManager LibraryManager; - protected readonly IChannelManager ChannelManager; - protected readonly ISessionManager SessionManager; protected readonly Func SubtitleEncoder; protected readonly Func MediaSourceManager; - private readonly IHttpClient _httpClient; - private readonly IZipClient _zipClient; private readonly IProcessFactory _processFactory; + private readonly int DefaultImageExtractionTimeoutMs; + private readonly string StartupOptionFFmpegPath; + private readonly string StartupOptionFFprobePath; + private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); private readonly List _runningProcesses = new List(); - private readonly bool _hasExternalEncoder; - private readonly string _originalFFMpegPath; - private readonly string _originalFFProbePath; - private readonly int DefaultImageExtractionTimeoutMs; public MediaEncoder( ILoggerFactory loggerFactory, IJsonSerializer jsonSerializer, - string ffMpegPath, - string ffProbePath, - bool hasExternalEncoder, + string startupOptionsFFmpegPath, + string startupOptionsFFprobePath, IServerConfigurationManager configurationManager, IFileSystem fileSystem, - ILiveTvManager liveTvManager, - IIsoManager isoManager, - ILibraryManager libraryManager, - IChannelManager channelManager, - ISessionManager sessionManager, Func subtitleEncoder, Func mediaSourceManager, - IHttpClient httpClient, - IZipClient zipClient, IProcessFactory processFactory, int defaultImageExtractionTimeoutMs) { _logger = loggerFactory.CreateLogger(nameof(MediaEncoder)); _jsonSerializer = jsonSerializer; + StartupOptionFFmpegPath = startupOptionsFFmpegPath; + StartupOptionFFprobePath = startupOptionsFFprobePath; ConfigurationManager = configurationManager; FileSystem = fileSystem; - LiveTvManager = liveTvManager; - IsoManager = isoManager; - LibraryManager = libraryManager; - ChannelManager = channelManager; - SessionManager = sessionManager; SubtitleEncoder = subtitleEncoder; - MediaSourceManager = mediaSourceManager; - _httpClient = httpClient; - _zipClient = zipClient; _processFactory = processFactory; DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs; - FFProbePath = ffProbePath; - FFMpegPath = ffMpegPath; - _originalFFProbePath = ffProbePath; - _originalFFMpegPath = ffMpegPath; - _hasExternalEncoder = hasExternalEncoder; } - public string EncoderLocationType + /// + /// Run at startup or if the user removes a Custom path from transcode page. + /// Sets global variables FFmpegPath and EncoderLocationType. + /// If startup options --ffprobe is given then FFprobePath is set too. + /// + public void Init() { - get + // 1) If given, use the --ffmpeg CLI switch + if (ValidatePathFFmpeg("From CLI Switch", StartupOptionFFmpegPath)) { - if (_hasExternalEncoder) - { - return "External"; - } - - if (string.IsNullOrWhiteSpace(FFMpegPath)) - { - return null; - } - - if (IsSystemInstalledPath(FFMpegPath)) - { - return "System"; - } - - return "Custom"; + _logger.LogInformation("FFmpeg: Using path from command line switch --ffmpeg"); + EncoderLocationType = "External"; } - } - private bool IsSystemInstalledPath(string path) - { - if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1) + // 2) Try Custom path stroed in config/encoding xml file under tag + else if (ValidatePathFFmpeg("From Config File", ConfigurationManager.GetConfiguration("encoding").EncoderAppPathCustom)) { - return true; + _logger.LogInformation("FFmpeg: Using path from config/encoding.xml file"); + EncoderLocationType = "Custom"; } - return false; - } - - public void Init() - { - InitPaths(); - - if (!string.IsNullOrWhiteSpace(FFMpegPath)) + // 3) Search system $PATH environment variable for valid FFmpeg + else if (ValidatePathFFmpeg("From $PATH", ExistsOnSystemPath("ffmpeg"))) { - var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath); - - SetAvailableDecoders(result.decoders); - SetAvailableEncoders(result.encoders); + _logger.LogInformation("FFmpeg: Using system $PATH for FFmpeg"); + EncoderLocationType = "System"; + } + else + { + _logger.LogError("FFmpeg: No suitable executable found"); + FFmpegPath = null; + EncoderLocationType = null; } - } - - private void InitPaths() - { - ConfigureEncoderPaths(); - if (_hasExternalEncoder) + // If given, use the --ffprobe CLI switch + if (ValidatePathFFprobe("CLI Switch", StartupOptionFFprobePath)) { - LogPaths(); - return; + _logger.LogInformation("FFprobe: Using path from command line switch --ffprobe"); + } + else + { + // FFprobe path from command line is no good, so set to null and let ReInit() try + // and set using the FFmpeg path. + FFprobePath = null; } - // If the path was passed in, save it into config now. - var encodingOptions = GetEncodingOptions(); - var appPath = encodingOptions.EncoderAppPath; + ReInit(); + } - var valueToSave = FFMpegPath; + /// + /// Writes the currently used FFmpeg to config/encoding.xml file. + /// Sets the FFprobe path if not currently set. + /// Interrogates the FFmpeg tool to identify what encoders/decodres are available. + /// + private void ReInit() + { + // Write the FFmpeg path to the config/encoding.xml file so it appears in UI + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPath = FFmpegPath ?? string.Empty; + ConfigurationManager.SaveConfiguration("encoding", config); - if (!string.IsNullOrWhiteSpace(valueToSave)) + // Only if mpeg path is set, try and set path to probe + if (FFmpegPath != null) { - // if using system variable, don't save this. - if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder) + // Probe would be null here if no valid --ffprobe path was given + // at startup, or we're performing ReInit following mpeg path update from UI + if (FFprobePath == null) { - valueToSave = null; + // Use the mpeg path to create a probe path + if (ValidatePathFFprobe("Copied from FFmpeg:", GetProbePathFromEncoderPath(FFmpegPath))) + { + _logger.LogInformation("FFprobe: Using FFprobe in same folders as FFmpeg"); + } + else + { + _logger.LogError("FFprobe: No suitable executable found"); + } } - } - if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal)) - { - encodingOptions.EncoderAppPath = valueToSave; - ConfigurationManager.SaveConfiguration("encoding", encodingOptions); + // Interrogate to understand what coders it supports + var result = new EncoderValidator(_logger, _processFactory).GetAvailableCoders(FFmpegPath); + + SetAvailableDecoders(result.decoders); + SetAvailableEncoders(result.encoders); } + + // Stamp FFmpeg paths to the log file + LogPaths(); } + /// + /// Triggered from the Settings > Trascoding UI page when users sumits Custom FFmpeg path to use. + /// + /// + /// public void UpdateEncoderPath(string path, string pathType) { - if (_hasExternalEncoder) - { - return; - } - _logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty); - Tuple newPaths; - - if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase)) { - path = "ffmpeg"; - - newPaths = TestForInstalledVersions(); + throw new ArgumentException("Unexpected pathType value"); } - else if (string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase)) + else { if (string.IsNullOrWhiteSpace(path)) { - throw new ArgumentNullException(nameof(path)); - } + // User had cleared the cutom path in UI. Clear the Custom config + // setting and peform full Init to relook any CLI switches and system $PATH + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = string.Empty; + ConfigurationManager.SaveConfiguration("encoding", config); - if (!File.Exists(path) && !Directory.Exists(path)) + Init(); + } + else if (!File.Exists(path) && !Directory.Exists(path)) { + // Given path is neither file or folder throw new ResourceNotFoundException(); } - newPaths = GetEncoderPaths(path); - } - else - { - throw new ArgumentException("Unexpected pathType value"); - } - - if (string.IsNullOrWhiteSpace(newPaths.Item1)) - { - throw new ResourceNotFoundException("ffmpeg not found"); - } - if (string.IsNullOrWhiteSpace(newPaths.Item2)) - { - throw new ResourceNotFoundException("ffprobe not found"); - } - - path = newPaths.Item1; + else + { + // Supplied path could be either file path or folder path. + // Resolve down to file path and validate + path = GetEncoderPath(path); - if (!ValidateVersion(path, true)) - { - throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required."); - } + if (path == null) + { + throw new ResourceNotFoundException("FFmpeg not found"); + } + else if (!ValidatePathFFmpeg("New From UI", path)) + { + throw new ResourceNotFoundException("Failed validation checks. Version 4.0 or greater is required"); + } + else + { + EncoderLocationType = "Custom"; - var config = GetEncodingOptions(); - config.EncoderAppPath = path; - ConfigurationManager.SaveConfiguration("encoding", config); + // Write the validated mpeg path to the xml as + // This ensures its not lost on new startup + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = FFmpegPath; + ConfigurationManager.SaveConfiguration("encoding", config); - Init(); - } + FFprobePath = null; // Clear probe path so it gets relooked in ReInit() - private bool ValidateVersion(string path, bool logOutput) - { - return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput); + ReInit(); + } + } + } } - private void ConfigureEncoderPaths() + private bool ValidatePath(string type, string path) { - if (_hasExternalEncoder) + if (!string.IsNullOrEmpty(path)) { - return; - } - - var appPath = GetEncodingOptions().EncoderAppPath; + if (File.Exists(path)) + { + var valid = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true); - if (string.IsNullOrWhiteSpace(appPath)) - { - appPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg"); + if (valid == true) + { + return true; + } + else + { + _logger.LogError("{0}: Failed validation checks. Version 4.0 or greater is required: {1}", type, path); + } + } + else + { + _logger.LogError("{0}: File not found: {1}", type, path); + } } - var newPaths = GetEncoderPaths(appPath); - if (string.IsNullOrWhiteSpace(newPaths.Item1) || string.IsNullOrWhiteSpace(newPaths.Item2) || IsSystemInstalledPath(appPath)) - { - newPaths = TestForInstalledVersions(); - } + return false; + } - if (!string.IsNullOrWhiteSpace(newPaths.Item1) && !string.IsNullOrWhiteSpace(newPaths.Item2)) + private bool ValidatePathFFmpeg(string comment, string path) + { + if (ValidatePath("FFmpeg: " + comment, path) == true) { - FFMpegPath = newPaths.Item1; - FFProbePath = newPaths.Item2; + FFmpegPath = path; + return true; } - LogPaths(); + return false; } - private Tuple GetEncoderPaths(string configuredPath) + private bool ValidatePathFFprobe(string comment, string path) { - var appPath = configuredPath; - - if (!string.IsNullOrWhiteSpace(appPath)) + if (ValidatePath("FFprobe: " + comment, path) == true) { - if (Directory.Exists(appPath)) - { - return GetPathsFromDirectory(appPath); - } - - if (File.Exists(appPath)) - { - return new Tuple(appPath, GetProbePathFromEncoderPath(appPath)); - } + FFprobePath = path; + return true; } - return new Tuple(null, null); + return false; } - private Tuple TestForInstalledVersions() + private string GetEncoderPath(string path) { - string encoderPath = null; - string probePath = null; - - if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true)) + if (Directory.Exists(path)) { - encoderPath = _originalFFMpegPath; - probePath = _originalFFProbePath; + return GetEncoderPathFromDirectory(path); } - if (string.IsNullOrWhiteSpace(encoderPath)) + if (File.Exists(path)) { - if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false)) - { - encoderPath = "ffmpeg"; - probePath = "ffprobe"; - } + return path; } - return new Tuple(encoderPath, probePath); + return null; } - private Tuple GetPathsFromDirectory(string path) + private string GetEncoderPathFromDirectory(string path) { - // Since we can't predict the file extension, first try directly within the folder - // If that doesn't pan out, then do a recursive search - var files = FileSystem.GetFilePaths(path); - - var excludeExtensions = new[] { ".c" }; - - var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - - if (string.IsNullOrWhiteSpace(ffmpegPath) || !File.Exists(ffmpegPath)) + try { - files = FileSystem.GetFilePaths(path, true); + var files = FileSystem.GetFilePaths(path); - ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); + var excludeExtensions = new[] { ".c" }; - if (!string.IsNullOrWhiteSpace(ffmpegPath)) - { - ffprobePath = GetProbePathFromEncoderPath(ffmpegPath); - } + return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); + } + catch (Exception) + { + // Trap all exceptions, like DirNotExists, and return null + return null; } - - return new Tuple(ffmpegPath, ffprobePath); } private string GetProbePathFromEncoderPath(string appPath) @@ -357,15 +318,31 @@ namespace MediaBrowser.MediaEncoding.Encoder .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); } - private void LogPaths() + /// + /// Search the system $PATH environment variable looking for given filename. + /// + /// + /// + private string ExistsOnSystemPath(string fileName) { - _logger.LogInformation("FFMpeg: {0}", FFMpegPath ?? "not found"); - _logger.LogInformation("FFProbe: {0}", FFProbePath ?? "not found"); + var values = Environment.GetEnvironmentVariable("PATH"); + + foreach (var path in values.Split(Path.PathSeparator)) + { + var candidatePath = GetEncoderPathFromDirectory(path); + + if (ValidatePath("Found on PATH", candidatePath)) + { + return candidatePath; + } + } + return null; } - private EncodingOptions GetEncodingOptions() + private void LogPaths() { - return ConfigurationManager.GetConfiguration("encoding"); + _logger.LogInformation("FFMpeg: {0}", FFmpegPath ?? "not found"); + _logger.LogInformation("FFProbe: {0}", FFprobePath ?? "not found"); } private List _encoders = new List(); @@ -412,12 +389,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return true; } - /// - /// Gets the encoder path. - /// - /// The encoder path. - public string EncoderPath => FFMpegPath; - /// /// Gets the media info. /// @@ -489,7 +460,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // Must consume both or ffmpeg may hang due to deadlocks. See comments below. RedirectStandardOutput = true, - FileName = FFProbePath, + FileName = FFprobePath, Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(), IsHidden = true, @@ -691,7 +662,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { CreateNoWindow = true, UseShellExecute = false, - FileName = FFMpegPath, + FileName = FFmpegPath, Arguments = args, IsHidden = true, ErrorDialog = false, @@ -814,7 +785,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { CreateNoWindow = true, UseShellExecute = false, - FileName = FFMpegPath, + FileName = FFmpegPath, Arguments = args, IsHidden = true, ErrorDialog = false, diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 8584bd3ddb..ff697437ac 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -8,7 +8,8 @@ namespace MediaBrowser.Model.Configuration public bool EnableThrottling { get; set; } public int ThrottleDelaySeconds { get; set; } public string HardwareAccelerationType { get; set; } - public string EncoderAppPath { get; set; } + public string EncoderAppPathCustom { get; set; } // FFmpeg path as set by the user via the UI + public string EncoderAppPath { get; set; } // The current FFmpeg path being used by the system public string VaapiDevice { get; set; } public int H264Crf { get; set; } public string H264Preset { get; set; } -- cgit v1.2.3 From ed69e690b89e6a3e6e22bbf448af08a25c38e71b Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Tue, 12 Feb 2019 22:05:42 +0000 Subject: Review comments Address review comments from JustAMan, Bond-009 and cvium. --- Emby.Server.Implementations/ApplicationHost.cs | 35 +-- .../MediaEncoding/IMediaEncoder.cs | 3 +- .../Encoder/EncoderValidator.cs | 8 + MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 247 ++++++++++----------- MediaBrowser.Model/System/SystemInfo.cs | 17 +- 5 files changed, 150 insertions(+), 160 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 2c0d0e7469..dd29f2adec 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -791,7 +791,17 @@ namespace Emby.Server.Implementations ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); serviceCollection.AddSingleton(ChapterManager); - MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(LoggerFactory, JsonSerializer, StartupOptions.FFmpegPath, StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, () => SubtitleEncoder, () => MediaSourceManager, ProcessFactory, 5000); + MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( + LoggerFactory, + JsonSerializer, + StartupOptions.FFmpegPath, + StartupOptions.FFprobePath, + ServerConfigurationManager, + FileSystemManager, + () => SubtitleEncoder, + () => MediaSourceManager, + ProcessFactory, + 5000); serviceCollection.AddSingleton(MediaEncoder); EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); @@ -908,27 +918,6 @@ namespace Emby.Server.Implementations return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); } - /// - /// Registers the media encoder. - /// - /// Task. - private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo) - { - MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( - LoggerFactory, - JsonSerializer, - StartupOptions.FFmpegPath, - StartupOptions.FFprobePath, - ServerConfigurationManager, - FileSystemManager, - () => SubtitleEncoder, - () => MediaSourceManager, - ProcessFactory, - 5000); - - RegisterSingleInstance(MediaEncoder); - } - /// /// Gets the user repository. /// @@ -1404,7 +1393,7 @@ namespace Emby.Server.Implementations ServerName = FriendlyName, LocalAddress = localAddress, SupportsLibraryMonitor = true, - EncoderLocationType = MediaEncoder.EncoderLocationType, + EncoderLocation = MediaEncoder.EncoderLocation, SystemArchitecture = EnvironmentInfo.SystemArchitecture, SystemUpdateLevel = SystemUpdateLevel, PackageName = StartupOptions.PackageName diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 057e439104..8852dac051 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.System; namespace MediaBrowser.Controller.MediaEncoding { @@ -14,7 +15,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// public interface IMediaEncoder : ITranscoderSupport { - string EncoderLocationType { get; } + FFmpegLocation EncoderLocation { get; } /// /// Gets the encoder path. diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 1eeea87a06..3eed891cb3 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -48,6 +48,10 @@ namespace MediaBrowser.MediaEncoding.Encoder if (string.IsNullOrWhiteSpace(output)) { + if (logOutput) + { + _logger.LogError("FFmpeg validation: The process returned no result"); + } return false; } @@ -55,6 +59,10 @@ namespace MediaBrowser.MediaEncoding.Encoder if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1) { + if (logOutput) + { + _logger.LogError("FFmpeg validation: avconv instead of ffmpeg is not supported"); + } return false; } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 36d72cad90..9aad67ec70 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; @@ -18,6 +19,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace MediaBrowser.MediaEncoding.Encoder @@ -34,17 +36,16 @@ namespace MediaBrowser.MediaEncoding.Encoder public string EncoderPath => FFmpegPath; /// - /// External: path supplied via command line - /// Custom: coming from UI or config/encoding.xml file - /// System: FFmpeg found in system $PATH - /// null: No FFmpeg found + /// The location of the discovered FFmpeg tool. /// - public string EncoderLocationType { get; private set; } + public FFmpegLocation EncoderLocation { get; private set; } + + private FFmpegLocation ProbeLocation; private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; - private string FFmpegPath { get; set; } - private string FFprobePath { get; set; } + private string FFmpegPath; + private string FFprobePath; protected readonly IServerConfigurationManager ConfigurationManager; protected readonly IFileSystem FileSystem; protected readonly Func SubtitleEncoder; @@ -54,6 +55,11 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly string StartupOptionFFmpegPath; private readonly string StartupOptionFFprobePath; + /// + /// Enum to identify the two types of FF utilities of interest. + /// + private enum FFtype { Mpeg, Probe }; + private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); private readonly List _runningProcesses = new List(); @@ -82,48 +88,24 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// Run at startup or if the user removes a Custom path from transcode page. - /// Sets global variables FFmpegPath and EncoderLocationType. - /// If startup options --ffprobe is given then FFprobePath is set too. + /// Sets global variables FFmpegPath. + /// Precedence is: Config > CLI > $PATH /// public void Init() { - // 1) If given, use the --ffmpeg CLI switch - if (ValidatePathFFmpeg("From CLI Switch", StartupOptionFFmpegPath)) - { - _logger.LogInformation("FFmpeg: Using path from command line switch --ffmpeg"); - EncoderLocationType = "External"; - } - - // 2) Try Custom path stroed in config/encoding xml file under tag - else if (ValidatePathFFmpeg("From Config File", ConfigurationManager.GetConfiguration("encoding").EncoderAppPathCustom)) - { - _logger.LogInformation("FFmpeg: Using path from config/encoding.xml file"); - EncoderLocationType = "Custom"; - } - - // 3) Search system $PATH environment variable for valid FFmpeg - else if (ValidatePathFFmpeg("From $PATH", ExistsOnSystemPath("ffmpeg"))) + // 1) Custom path stored in config/encoding xml file under tag takes precedence + if (!ValidatePath(FFtype.Mpeg, ConfigurationManager.GetConfiguration("encoding").EncoderAppPathCustom, FFmpegLocation.Custom)) { - _logger.LogInformation("FFmpeg: Using system $PATH for FFmpeg"); - EncoderLocationType = "System"; - } - else - { - _logger.LogError("FFmpeg: No suitable executable found"); - FFmpegPath = null; - EncoderLocationType = null; - } - - // If given, use the --ffprobe CLI switch - if (ValidatePathFFprobe("CLI Switch", StartupOptionFFprobePath)) - { - _logger.LogInformation("FFprobe: Using path from command line switch --ffprobe"); - } - else - { - // FFprobe path from command line is no good, so set to null and let ReInit() try - // and set using the FFmpeg path. - FFprobePath = null; + // 2) Check if the --ffmpeg CLI switch has been given + if (!ValidatePath(FFtype.Mpeg, StartupOptionFFmpegPath, FFmpegLocation.SetByArgument)) + { + // 3) Search system $PATH environment variable for valid FFmpeg + if (!ValidatePath(FFtype.Mpeg, ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System)) + { + EncoderLocation = FFmpegLocation.NotFound; + FFmpegPath = null; + } + } } ReInit(); @@ -136,27 +118,27 @@ namespace MediaBrowser.MediaEncoding.Encoder /// private void ReInit() { - // Write the FFmpeg path to the config/encoding.xml file so it appears in UI + // Write the FFmpeg path to the config/encoding.xml file as so it appears in UI var config = ConfigurationManager.GetConfiguration("encoding"); config.EncoderAppPath = FFmpegPath ?? string.Empty; ConfigurationManager.SaveConfiguration("encoding", config); + // Clear probe settings in case probe validation fails + ProbeLocation = FFmpegLocation.NotFound; + FFprobePath = null; + // Only if mpeg path is set, try and set path to probe if (FFmpegPath != null) { - // Probe would be null here if no valid --ffprobe path was given - // at startup, or we're performing ReInit following mpeg path update from UI - if (FFprobePath == null) + if (EncoderLocation == FFmpegLocation.Custom || StartupOptionFFprobePath == null) { - // Use the mpeg path to create a probe path - if (ValidatePathFFprobe("Copied from FFmpeg:", GetProbePathFromEncoderPath(FFmpegPath))) - { - _logger.LogInformation("FFprobe: Using FFprobe in same folders as FFmpeg"); - } - else - { - _logger.LogError("FFprobe: No suitable executable found"); - } + // If mpeg was read from config, or CLI switch not given, try and set probe from mpeg path + ValidatePath(FFtype.Probe, GetProbePathFromEncoderPath(FFmpegPath), EncoderLocation); + } + else + { + // Else try and set probe path from CLI switch + ValidatePath(FFtype.Probe, StartupOptionFFmpegPath, FFmpegLocation.SetByArgument); } // Interrogate to understand what coders it supports @@ -183,108 +165,95 @@ namespace MediaBrowser.MediaEncoding.Encoder { throw new ArgumentException("Unexpected pathType value"); } - else + + if (string.IsNullOrWhiteSpace(path)) { - if (string.IsNullOrWhiteSpace(path)) - { - // User had cleared the cutom path in UI. Clear the Custom config - // setting and peform full Init to relook any CLI switches and system $PATH - var config = ConfigurationManager.GetConfiguration("encoding"); - config.EncoderAppPathCustom = string.Empty; - ConfigurationManager.SaveConfiguration("encoding", config); + // User had cleared the custom path in UI. Clear the Custom config + // setting and perform full Init to reinspect any CLI switches and system $PATH + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = string.Empty; + ConfigurationManager.SaveConfiguration("encoding", config); - Init(); - } - else if (!File.Exists(path) && !Directory.Exists(path)) + Init(); + } + else if (!File.Exists(path) && !Directory.Exists(path)) + { + // Given path is neither file or folder + throw new ResourceNotFoundException(); + } + else + { + // Supplied path could be either file path or folder path. + // Resolve down to file path and validate + if (!ValidatePath(FFtype.Mpeg, GetEncoderPath(path), FFmpegLocation.Custom)) { - // Given path is neither file or folder - throw new ResourceNotFoundException(); + throw new ResourceNotFoundException("Failed validation checks."); } else { - // Supplied path could be either file path or folder path. - // Resolve down to file path and validate - path = GetEncoderPath(path); - - if (path == null) - { - throw new ResourceNotFoundException("FFmpeg not found"); - } - else if (!ValidatePathFFmpeg("New From UI", path)) - { - throw new ResourceNotFoundException("Failed validation checks. Version 4.0 or greater is required"); - } - else - { - EncoderLocationType = "Custom"; - - // Write the validated mpeg path to the xml as - // This ensures its not lost on new startup - var config = ConfigurationManager.GetConfiguration("encoding"); - config.EncoderAppPathCustom = FFmpegPath; - ConfigurationManager.SaveConfiguration("encoding", config); - - FFprobePath = null; // Clear probe path so it gets relooked in ReInit() + // Write the validated mpeg path to the xml as + // This ensures its not lost on new startup + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = FFmpegPath; + ConfigurationManager.SaveConfiguration("encoding", config); - ReInit(); - } + ReInit(); } } } - private bool ValidatePath(string type, string path) + /// + /// Validates the supplied FQPN to ensure it is a FFxxx utility. + /// If checks pass, global variable FFmpegPath (or FFprobePath) and + /// EncoderLocation (or ProbeLocation) are updated. + /// + /// Either mpeg or probe + /// FQPN to test + /// Location (External, Custom, System) of tool + /// + private bool ValidatePath(FFtype type, string path, FFmpegLocation location) { + bool rc = false; + if (!string.IsNullOrEmpty(path)) { if (File.Exists(path)) { - var valid = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true); + rc = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, false); - if (valid == true) + // Only update the global variables if the checks passed + if (rc) { - return true; + if (type == FFtype.Mpeg) + { + FFmpegPath = path; + EncoderLocation = location; + } + else + { + FFprobePath = path; + ProbeLocation = location; + } } else { - _logger.LogError("{0}: Failed validation checks. Version 4.0 or greater is required: {1}", type, path); + _logger.LogError("{0}: {1}: Failed version check: {2}", type.ToString(), location.ToString(), path); } } else { - _logger.LogError("{0}: File not found: {1}", type, path); + _logger.LogError("{0}: {1}: File not found: {2}", type.ToString(), location.ToString(), path); } } - return false; - } - - private bool ValidatePathFFmpeg(string comment, string path) - { - if (ValidatePath("FFmpeg: " + comment, path) == true) - { - FFmpegPath = path; - return true; - } - - return false; - } - - private bool ValidatePathFFprobe(string comment, string path) - { - if (ValidatePath("FFprobe: " + comment, path) == true) - { - FFprobePath = path; - return true; - } - - return false; + return rc; } private string GetEncoderPath(string path) { if (Directory.Exists(path)) { - return GetEncoderPathFromDirectory(path); + return GetEncoderPathFromDirectory(path, "ffmpeg"); } if (File.Exists(path)) @@ -295,7 +264,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return null; } - private string GetEncoderPathFromDirectory(string path) + private string GetEncoderPathFromDirectory(string path, string filename) { try { @@ -303,7 +272,8 @@ namespace MediaBrowser.MediaEncoding.Encoder var excludeExtensions = new[] { ".c" }; - return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); + return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), filename, StringComparison.OrdinalIgnoreCase) + && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); } catch (Exception) { @@ -314,8 +284,15 @@ namespace MediaBrowser.MediaEncoding.Encoder private string GetProbePathFromEncoderPath(string appPath) { - return FileSystem.GetFilePaths(Path.GetDirectoryName(appPath)) - .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); + if (!string.IsNullOrEmpty(appPath)) + { + string pattern = @"[^\/\\]+?(\.[^\/\\\n.]+)?$"; + string substitution = @"ffprobe$1"; + + return Regex.Replace(appPath, pattern, substitution); + } + + return null; } /// @@ -323,15 +300,15 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// /// - private string ExistsOnSystemPath(string fileName) + private string ExistsOnSystemPath(string filename) { var values = Environment.GetEnvironmentVariable("PATH"); foreach (var path in values.Split(Path.PathSeparator)) { - var candidatePath = GetEncoderPathFromDirectory(path); + var candidatePath = GetEncoderPathFromDirectory(path, filename); - if (ValidatePath("Found on PATH", candidatePath)) + if (!string.IsNullOrEmpty(candidatePath)) { return candidatePath; } @@ -341,8 +318,8 @@ namespace MediaBrowser.MediaEncoding.Encoder private void LogPaths() { - _logger.LogInformation("FFMpeg: {0}", FFmpegPath ?? "not found"); - _logger.LogInformation("FFProbe: {0}", FFprobePath ?? "not found"); + _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation.ToString(), FFmpegPath ?? string.Empty); + _logger.LogInformation("FFprobe: {0}: {1}", ProbeLocation.ToString(), FFprobePath ?? string.Empty); } private List _encoders = new List(); diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 581a1069cd..6482f2c840 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -4,6 +4,21 @@ using MediaBrowser.Model.Updates; namespace MediaBrowser.Model.System { + /// + /// Enum describing the location of the FFmpeg tool. + /// + public enum FFmpegLocation + { + /// No path to FFmpeg found. + NotFound, + /// Path supplied via command line using switch --ffmpeg. + SetByArgument, + /// User has supplied path via Transcoding UI page. + Custom, + /// FFmpeg tool found on system $PATH. + System + }; + /// /// Class SystemInfo /// @@ -122,7 +137,7 @@ namespace MediaBrowser.Model.System /// true if this instance has update available; otherwise, false. public bool HasUpdateAvailable { get; set; } - public string EncoderLocationType { get; set; } + public FFmpegLocation EncoderLocation { get; set; } public Architecture SystemArchitecture { get; set; } -- cgit v1.2.3 From 2617a49b78c99f72ba36e53a4c97c4e042116a53 Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Thu, 28 Feb 2019 22:47:56 +0000 Subject: Renamed Init() to SetFFmpegPath() --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index dd29f2adec..325df32931 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -534,7 +534,7 @@ namespace Emby.Server.Implementations ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; - MediaEncoder.Init(); + MediaEncoder.SetFFmpegPath(); //if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath)) //{ diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 8852dac051..d4ac3b7c37 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// System.String. string EscapeSubtitleFilterPath(string path); - void Init(); + void SetFFmpegPath(); void UpdateEncoderPath(string path, string pathType); bool SupportsEncoder(string encoder); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 51b4f6e393..2924577888 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -84,7 +84,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// Sets global variables FFmpegPath. /// Precedence is: Config > CLI > $PATH /// - public void Init() + public void SetFFmpegPath() { // ToDo - Finalise removal of the --ffprobe switch if (!string.IsNullOrEmpty(StartupOptionFFprobePath)) @@ -169,8 +169,8 @@ namespace MediaBrowser.MediaEncoding.Encoder config.EncoderAppPath = newPath; ConfigurationManager.SaveConfiguration("encoding", config); - // Trigger Init so we validate the new path and setup probe path - Init(); + // Trigger SetFFmpegPath so we validate the new path and setup probe path + SetFFmpegPath(); } /// -- cgit v1.2.3 From bef665be364ce1477d09ed268f68c19e0099922f Mon Sep 17 00:00:00 2001 From: Phallacy Date: Tue, 5 Mar 2019 23:45:05 -0800 Subject: Minor fixes to address style issues --- .../Library/DefaultAuthenticationProvider.cs | 14 +++++++------- Emby.Server.Implementations/Library/UserManager.cs | 15 +++++---------- MediaBrowser.Model/Cryptography/PasswordHash.cs | 7 ++++--- 3 files changed, 16 insertions(+), 20 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 8f10b5a84e..3ac604b40e 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -50,22 +50,22 @@ namespace Emby.Server.Implementations.Library byte[] passwordbytes = Encoding.UTF8.GetBytes(password); PasswordHash readyHash = new PasswordHash(resolvedUser.Password); - byte[] CalculatedHash; - string CalculatedHashString; + byte[] calculatedHash; + string calculatedHashString; if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) { if (string.IsNullOrEmpty(readyHash.Salt)) { - CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); - CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); + calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); + calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty); } else { - CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); - CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); + calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); + calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty); } - if (CalculatedHashString == readyHash.Hash) + if (calculatedHashString == readyHash.Hash) { success = true; // throw new Exception("Invalid username or password"); diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 57bf16364d..efb1ef4a50 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -475,11 +475,6 @@ namespace Emby.Server.Implementations.Library : user.EasyPassword; } - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.IsNullOrEmpty(passwordHash); - } - /// /// Loads the users from the repository /// @@ -522,14 +517,14 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(user)); } - var hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; - var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); + bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; + bool hasConfiguredEasyPassword = string.IsNullOrEmpty(GetLocalPasswordHash(user)); - var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? + bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : hasConfiguredPassword; - var dto = new UserDto + UserDto dto = new UserDto { Id = user.Id, Name = user.Name, @@ -548,7 +543,7 @@ namespace Emby.Server.Implementations.Library dto.EnableAutoLogin = true; } - var image = user.GetImageInfo(ImageType.Primary, 0); + ItemImageInfo image = user.GetImageInfo(ImageType.Primary, 0); if (image != null) { diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index a528404045..7a1be833db 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -100,13 +100,14 @@ namespace MediaBrowser.Model.Cryptography public static byte[] ConvertFromByteString(string byteString) { - List Bytes = new List(); + List bytes = new List(); for (int i = 0; i < byteString.Length; i += 2) { - Bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); + // TODO: NetStandard2.1 switch this to use a span instead of a substring. + bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); } - return Bytes.ToArray(); + return bytes.ToArray(); } public static string ConvertToByteString(byte[] bytes) -- cgit v1.2.3 From 04db0369d46c4b3961086b1d30946bd990a5767c Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 6 Mar 2019 17:31:52 +0100 Subject: Update LocalizationManager.cs --- Emby.Server.Implementations/Localization/LocalizationManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index d55b258a3d..762649b716 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -93,12 +93,11 @@ namespace Emby.Server.Implementations.Localization #if DEBUG else { - _logger.LogWarning("Misformed line in ratings file for country {CountryCode}", countryCode); + _logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode); } #endif } } - _logger.LogWarning("{t}", countryCode); _allParentalRatings[countryCode] = dict; } -- cgit v1.2.3 From 394d23a73a530b5ce2609d01575e0ced734aecfe Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 6 Mar 2019 19:14:03 +0100 Subject: Review comments --- Emby.Server.Implementations/WebSockets/WebSocketManager.cs | 2 +- .../Playback/Progressive/BaseProgressiveStreamingService.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs index 5db2c8da73..04c73ecea7 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.WebSockets ? Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length) : Encoding.ASCII.GetString(messageBytes, 0, messageBytes.Length); - // All messages are expected to be json + // All messages are expected to be valid JSON objects if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase)) { _logger.LogDebug("Received web socket message that is not a json structure: {Message}", message); diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index fc81e532d8..399ac2be09 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -362,6 +362,7 @@ namespace MediaBrowser.Api.Playback.Progressive var contentType = state.GetMimeType(outputPath); + // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response // Headers only if (isHeadRequest) { -- cgit v1.2.3 From 21c2acc520bcebe74dacc4caf1c244e258a12aec Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 6 Mar 2019 19:27:05 +0100 Subject: Remove public Host property --- Emby.Server.Implementations/ApplicationHost.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 93147539c6..71a112dacf 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -129,7 +129,7 @@ namespace Emby.Server.Implementations /// /// true if this instance can self restart; otherwise, false. public abstract bool CanSelfRestart { get; } - public IWebHost Host { get; set; } + public virtual bool CanLaunchWebBrowser { get @@ -625,7 +625,7 @@ namespace Emby.Server.Implementations contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src"); } - Host = new WebHostBuilder() + var host = new WebHostBuilder() .UseKestrel(options => { options.ListenAnyIP(HttpPort); @@ -652,10 +652,10 @@ namespace Emby.Server.Implementations }) .Build(); - await Host.StartAsync(); + await host.StartAsync(); } - public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) + private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) { if (!context.WebSockets.IsWebSocketRequest) { @@ -665,7 +665,8 @@ namespace Emby.Server.Implementations await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } - public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) + + private async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { if (context.WebSockets.IsWebSocketRequest) { -- cgit v1.2.3 From bba049c987a632375da5e847512efe7ec4a85a31 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 6 Mar 2019 19:29:25 +0100 Subject: Make FileSystem readonly --- Emby.Server.Implementations/HttpServer/FileWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 78e2015b1e..0e989994b1 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.HttpServer { private readonly IStreamHelper _streamHelper; private ILogger Logger { get; set; } - public IFileSystem FileSystem { get; } + private readonly IFileSystem _fileSystem; private string RangeHeader { get; set; } private bool IsHeadRequest { get; set; } @@ -54,10 +54,10 @@ namespace Emby.Server.Implementations.HttpServer } _streamHelper = streamHelper; + _fileSystem = fileSystem; Path = path; Logger = logger; - FileSystem = fileSystem; RangeHeader = rangeHeader; Headers[HeaderNames.ContentType] = contentType; @@ -181,7 +181,7 @@ namespace Emby.Server.Implementations.HttpServer count = 0; } - await response.TransmitFile(path, offset, count, FileShare, FileSystem, _streamHelper, cancellationToken).ConfigureAwait(false); + await response.TransmitFile(path, offset, count, FileShare, _fileSystem, _streamHelper, cancellationToken).ConfigureAwait(false); } finally { -- cgit v1.2.3 From c31b0b311b339475650aa8812eb57152cac32d80 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 02:41:44 -0800 Subject: Apply suggestions from code review more minor fixes before I do larger fixes Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Cryptography/CryptographyProvider.cs | 6 +- .../Library/DefaultAuthenticationProvider.cs | 2 +- MediaBrowser.Model/Cryptography/PasswordHash.cs | 132 ++++++++++----------- 3 files changed, 70 insertions(+), 70 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index cf1ea6efa3..2e882cefed 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.Cryptography { //downgrading for now as we need this library to be dotnetstandard compliant //with this downgrade we'll add a check to make sure we're on the downgrade method at the moment - if(method == DefaultHashMethod) + if (method == DefaultHashMethod) { using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) { @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.Cryptography public byte[] ComputeHash(string hashMethod, byte[] bytes) { - return ComputeHash(hashMethod, bytes, new byte[0]); + return ComputeHash(hashMethod, bytes, Array.Empty()); } public byte[] ComputeHashWithDefaultMethod(byte[] bytes) @@ -106,7 +106,7 @@ namespace Emby.Server.Implementations.Cryptography public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt) { - if(hashMethod == DefaultHashMethod) + if (hashMethod == DefaultHashMethod) { return PBKDF2(hashMethod, bytes, salt, _defaultIterations); } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 3ac604b40e..526509f439 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.Library if (!user.Password.Contains("$")) { string hash = user.Password; - user.Password = String.Format("$SHA1${0}", hash); + user.Password = string.Format("$SHA1${0}", hash); } if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 7a1be833db..72bdc6745e 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -8,34 +8,34 @@ namespace MediaBrowser.Model.Cryptography { // Defined from this hash storage spec // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md - // $[$=(,=)*][$[$]] - // with one slight amendment to ease the transition, we're writing out the bytes in hex + // $[$=(,=)*][$[$]] + // with one slight amendment to ease the transition, we're writing out the bytes in hex // rather than making them a BASE64 string with stripped padding - private string _id; + private string _id; - private Dictionary _parameters = new Dictionary(); + private Dictionary _parameters = new Dictionary(); - private string _salt; + private string _salt; - private byte[] _saltBytes; + private byte[] _saltBytes; - private string _hash; + private string _hash; + + private byte[] _hashBytes; + + public string Id { get => _id; set => _id = value; } + + public Dictionary Parameters { get => _parameters; set => _parameters = value; } + + public string Salt { get => _salt; set => _salt = value; } + + public byte[] SaltBytes { get => _saltBytes; set => _saltBytes = value; } + + public string Hash { get => _hash; set => _hash = value; } + + public byte[] HashBytes { get => _hashBytes; set => _hashBytes = value; } - private byte[] _hashBytes; - - public string Id { get => _id; set => _id = value; } - - public Dictionary Parameters { get => _parameters; set => _parameters = value; } - - public string Salt { get => _salt; set => _salt = value; } - - public byte[] SaltBytes { get => _saltBytes; set => _saltBytes = value; } - - public string Hash { get => _hash; set => _hash = value; } - - public byte[] HashBytes { get => _hashBytes; set => _hashBytes = value; } - public PasswordHash(string storageString) { string[] splitted = storageString.Split('$'); @@ -46,14 +46,14 @@ namespace MediaBrowser.Model.Cryptography { if (!string.IsNullOrEmpty(paramset)) { - string[] fields = paramset.Split('='); - if (fields.Length == 2) - { - _parameters.Add(fields[0], fields[1]); - } - else - { - throw new Exception($"Malformed parameter in password hash string {paramset}"); + string[] fields = paramset.Split('='); + if (fields.Length == 2) + { + _parameters.Add(fields[0], fields[1]); + } + else + { + throw new Exception($"Malformed parameter in password hash string {paramset}"); } } } @@ -89,31 +89,31 @@ namespace MediaBrowser.Model.Cryptography } - } - + } + public PasswordHash(ICryptoProvider cryptoProvider) { _id = cryptoProvider.DefaultHashMethod; _saltBytes = cryptoProvider.GenerateSalt(); - _salt = ConvertToByteString(SaltBytes); - } - - public static byte[] ConvertFromByteString(string byteString) - { - List bytes = new List(); - for (int i = 0; i < byteString.Length; i += 2) - { - // TODO: NetStandard2.1 switch this to use a span instead of a substring. - bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); - } - - return bytes.ToArray(); - } - - public static string ConvertToByteString(byte[] bytes) - { - return BitConverter.ToString(bytes).Replace("-", ""); - } + _salt = ConvertToByteString(SaltBytes); + } + + public static byte[] ConvertFromByteString(string byteString) + { + List bytes = new List(); + for (int i = 0; i < byteString.Length; i += 2) + { + // TODO: NetStandard2.1 switch this to use a span instead of a substring. + bytes.Add(Convert.ToByte(byteString.Substring(i, 2), 16)); + } + + return bytes.ToArray(); + } + + public static string ConvertToByteString(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", ""); + } private string SerializeParameters() { @@ -121,33 +121,33 @@ namespace MediaBrowser.Model.Cryptography foreach (var KVP in _parameters) { ReturnString += $",{KVP.Key}={KVP.Value}"; - } + } if ((!string.IsNullOrEmpty(ReturnString)) && ReturnString[0] == ',') { ReturnString = ReturnString.Remove(0, 1); - } + } return ReturnString; } public override string ToString() - { - string outString = "$" +_id; - string paramstring = SerializeParameters(); - if (!string.IsNullOrEmpty(paramstring)) - { - outString += $"${paramstring}"; - } - - if (!string.IsNullOrEmpty(_salt)) - { - outString += $"${_salt}"; - } - + { + string outString = "$" + _id; + string paramstring = SerializeParameters(); + if (!string.IsNullOrEmpty(paramstring)) + { + outString += $"${paramstring}"; + } + + if (!string.IsNullOrEmpty(_salt)) + { + outString += $"${_salt}"; + } + outString += $"${_hash}"; return outString; } } -} +} -- cgit v1.2.3 From a9302b8b53e8393fbedeefb3be75ae6eba8ae815 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 1 Feb 2019 17:43:31 +0100 Subject: Remove useless abstraction around XmlReaderSettings This removes the amount of stuff that needs to be passed around Also removes some unneeded `ManagedFileSystem` usage --- Emby.Dlna/ConnectionManager/ConnectionManager.cs | 7 +- Emby.Dlna/ConnectionManager/ControlHandler.cs | 4 +- Emby.Dlna/ContentDirectory/ContentDirectory.cs | 11 +- Emby.Dlna/ContentDirectory/ControlHandler.cs | 19 +- Emby.Dlna/Main/DlnaEntryPoint.cs | 15 +- Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs | 5 +- .../MediaReceiverRegistrar.cs | 7 +- Emby.Dlna/Service/BaseControlHandler.cs | 17 +- Emby.Server.Implementations/ApplicationHost.cs | 4 - .../Xml/XmlReaderSettingsFactory.cs | 20 -- .../Parsers/BaseItemXmlParser.cs | 20 +- .../Parsers/BoxSetXmlParser.cs | 5 +- .../Parsers/PlaylistXmlParser.cs | 5 +- .../Providers/BoxSetXmlProvider.cs | 7 +- .../Providers/PlaylistXmlProvider.cs | 7 +- MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 7 +- .../Savers/BoxSetXmlSaver.cs | 4 +- .../Savers/PlaylistXmlSaver.cs | 4 +- .../Xml/IXmlReaderSettingsFactory.cs | 9 - .../Music/MusicBrainzAlbumProvider.cs | 171 +++++---- .../Music/MusicBrainzArtistProvider.cs | 19 +- .../TV/MissingEpisodeProvider.cs | 3 - MediaBrowser.Providers/TV/SeriesMetadataService.cs | 4 - .../TV/TheTVDB/TvdbPrescanTask.cs | 396 +++++++++++++++++++++ .../TV/TheTVDB/TvdbSeriesProvider.cs | 1 - MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 34 +- .../Parsers/EpisodeNfoParser.cs | 5 +- .../Parsers/MovieNfoParser.cs | 19 +- .../Parsers/SeasonNfoParser.cs | 5 +- .../Parsers/SeriesNfoParser.cs | 5 +- .../Providers/AlbumNfoProvider.cs | 7 +- .../Providers/ArtistNfoProvider.cs | 7 +- .../Providers/BaseVideoNfoProvider.cs | 7 +- .../Providers/EpisodeNfoProvider.cs | 7 +- .../Providers/MovieNfoProvider.cs | 10 +- .../Providers/SeasonNfoProvider.cs | 11 +- .../Providers/SeriesNfoProvider.cs | 13 +- MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs | 4 +- MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs | 4 +- MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 81 ++--- .../Savers/EpisodeNfoSaver.cs | 4 +- MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs | 4 +- MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs | 6 +- MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs | 4 +- 44 files changed, 649 insertions(+), 359 deletions(-) delete mode 100644 Emby.Server.Implementations/Xml/XmlReaderSettingsFactory.cs delete mode 100644 MediaBrowser.Model/Xml/IXmlReaderSettingsFactory.cs create mode 100644 MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs index cc427f2a15..679eecc891 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManager.cs @@ -3,7 +3,6 @@ using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.ConnectionManager @@ -13,15 +12,13 @@ namespace Emby.Dlna.ConnectionManager private readonly IDlnaManager _dlna; private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; - public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient) : base(logger, httpClient) { _dlna = dlna; _config = config; _logger = logger; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } public string GetServiceXml(IDictionary headers) @@ -34,7 +31,7 @@ namespace Emby.Dlna.ConnectionManager var profile = _dlna.GetProfile(request.Headers) ?? _dlna.GetDefaultProfile(); - return new ControlHandler(_config, _logger, XmlReaderSettingsFactory, profile).ProcessControlRequest(request); + return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request); } } } diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs index 16211c61f4..2e11047487 100644 --- a/Emby.Dlna/ConnectionManager/ControlHandler.cs +++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs @@ -4,7 +4,6 @@ using Emby.Dlna.Service; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.ConnectionManager @@ -32,7 +31,8 @@ namespace Emby.Dlna.ConnectionManager }; } - public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory, DeviceProfile profile) : base(config, logger, xmlReaderSettingsFactory) + public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile) + : base(config, logger) { _profile = profile; } diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index b0fec90e69..3e2927845e 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -11,7 +11,6 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.ContentDirectory @@ -28,7 +27,6 @@ namespace Emby.Dlna.ContentDirectory private readonly IMediaSourceManager _mediaSourceManager; private readonly IUserViewManager _userViewManager; private readonly IMediaEncoder _mediaEncoder; - protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; private readonly ITVSeriesManager _tvSeriesManager; public ContentDirectory(IDlnaManager dlna, @@ -38,7 +36,12 @@ namespace Emby.Dlna.ContentDirectory IServerConfigurationManager config, IUserManager userManager, ILogger logger, - IHttpClient httpClient, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager) + IHttpClient httpClient, + ILocalizationManager localization, + IMediaSourceManager mediaSourceManager, + IUserViewManager userViewManager, + IMediaEncoder mediaEncoder, + ITVSeriesManager tvSeriesManager) : base(logger, httpClient) { _dlna = dlna; @@ -51,7 +54,6 @@ namespace Emby.Dlna.ContentDirectory _mediaSourceManager = mediaSourceManager; _userViewManager = userViewManager; _mediaEncoder = mediaEncoder; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; _tvSeriesManager = tvSeriesManager; } @@ -94,7 +96,6 @@ namespace Emby.Dlna.ContentDirectory _mediaSourceManager, _userViewManager, _mediaEncoder, - XmlReaderSettingsFactory, _tvSeriesManager) .ProcessControlRequest(request); } diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 84f38ff769..4f8c89e485 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -25,7 +25,6 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.ContentDirectory @@ -51,8 +50,22 @@ namespace Emby.Dlna.ContentDirectory private readonly DeviceProfile _profile; - public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager) - : base(config, logger, xmlReaderSettingsFactory) + public ControlHandler( + ILogger logger, + ILibraryManager libraryManager, + DeviceProfile profile, + string serverAddress, + string accessToken, + IImageProcessor imageProcessor, + IUserDataManager userDataManager, + User user, int systemUpdateId, + IServerConfigurationManager config, + ILocalizationManager localization, + IMediaSourceManager mediaSourceManager, + IUserViewManager userViewManager, + IMediaEncoder mediaEncoder, + ITVSeriesManager tvSeriesManager) + : base(config, logger) { _libraryManager = libraryManager; _userDataManager = userDataManager; diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 57ed0097a0..1339ba9e4c 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; @@ -20,7 +19,6 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; using Rssdp; using Rssdp.Infrastructure; @@ -48,7 +46,7 @@ namespace Emby.Dlna.Main private readonly IDeviceDiscovery _deviceDiscovery; private SsdpDevicePublisher _Publisher; - + private readonly ISocketFactory _socketFactory; private readonly IEnvironmentInfo _environmentInfo; private readonly INetworkManager _networkManager; @@ -79,7 +77,6 @@ namespace Emby.Dlna.Main IEnvironmentInfo environmentInfo, INetworkManager networkManager, IUserViewManager userViewManager, - IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager) { _config = config; @@ -100,7 +97,8 @@ namespace Emby.Dlna.Main _networkManager = networkManager; _logger = loggerFactory.CreateLogger("Dlna"); - ContentDirectory = new ContentDirectory.ContentDirectory(dlnaManager, + ContentDirectory = new ContentDirectory.ContentDirectory( + dlnaManager, userDataManager, imageProcessor, libraryManager, @@ -112,12 +110,11 @@ namespace Emby.Dlna.Main mediaSourceManager, userViewManager, mediaEncoder, - xmlReaderSettingsFactory, tvSeriesManager); - ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient, xmlReaderSettingsFactory); + ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient); - MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config, xmlReaderSettingsFactory); + MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config); Current = this; } @@ -260,7 +257,7 @@ namespace Emby.Dlna.Main var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; - _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); + _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address.ToString()); var descriptorUri = "/dlna/" + udn + "/description.xml"; var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri); diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index ae8175f4a2..7381e52582 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Emby.Dlna.Service; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar @@ -36,8 +35,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar }; } - public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) - : base(config, logger, xmlReaderSettingsFactory) + public ControlHandler(IServerConfigurationManager config, ILogger logger) + : base(config, logger) { } } diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index 2b84528eab..eeae3acb6f 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar @@ -10,13 +9,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar { private readonly IServerConfigurationManager _config; - protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; - public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config) : base(logger, httpClient) { _config = config; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } public string GetServiceXml(IDictionary headers) @@ -28,7 +25,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar { return new ControlHandler( _config, - Logger, XmlReaderSettingsFactory) + Logger) .ProcessControlRequest(request); } } diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 5f78674b88..067d5fa43f 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -7,7 +7,6 @@ using System.Xml; using Emby.Dlna.Didl; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Extensions; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.Service @@ -18,13 +17,11 @@ namespace Emby.Dlna.Service protected readonly IServerConfigurationManager Config; protected readonly ILogger _logger; - protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; - protected BaseControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + protected BaseControlHandler(IServerConfigurationManager config, ILogger logger) { Config = config; _logger = logger; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } public ControlResponse ProcessControlRequest(ControlRequest request) @@ -61,11 +58,13 @@ namespace Emby.Dlna.Service using (var streamReader = new StreamReader(request.InputXml)) { - var readerSettings = XmlReaderSettingsFactory.Create(false); - - readerSettings.CheckCharacters = false; - readerSettings.IgnoreProcessingInstructions = true; - readerSettings.IgnoreComments = true; + var readerSettings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; using (var reader = XmlReader.Create(streamReader, readerSettings)) { diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b3bb4f740e..ec1ad6dbdf 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -44,7 +44,6 @@ using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; -using Emby.Server.Implementations.Xml; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -98,7 +97,6 @@ using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Updates; -using MediaBrowser.Model.Xml; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Subtitles; @@ -691,8 +689,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(new BdInfoExaminer(FileSystemManager)); - serviceCollection.AddSingleton(new XmlReaderSettingsFactory()); - UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager); serviceCollection.AddSingleton(UserDataManager); diff --git a/Emby.Server.Implementations/Xml/XmlReaderSettingsFactory.cs b/Emby.Server.Implementations/Xml/XmlReaderSettingsFactory.cs deleted file mode 100644 index 308922e6d1..0000000000 --- a/Emby.Server.Implementations/Xml/XmlReaderSettingsFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Xml; -using MediaBrowser.Model.Xml; - -namespace Emby.Server.Implementations.Xml -{ - public class XmlReaderSettingsFactory : IXmlReaderSettingsFactory - { - public XmlReaderSettings Create(bool enableValidation) - { - var settings = new XmlReaderSettings(); - - if (!enableValidation) - { - settings.ValidationType = ValidationType.None; - } - - return settings; - } - } -} diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 38458e34c8..23b9745769 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -10,7 +10,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Parsers @@ -30,19 +29,14 @@ namespace MediaBrowser.LocalMetadata.Parsers private Dictionary _validProviderIds; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - protected IFileSystem FileSystem { get; private set; } - /// /// Initializes a new instance of the class. /// /// The logger. - public BaseItemXmlParser(ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory, IFileSystem fileSystem) + public BaseItemXmlParser(ILogger logger, IProviderManager providerManager) { Logger = logger; ProviderManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; - FileSystem = fileSystem; } /// @@ -64,11 +58,13 @@ namespace MediaBrowser.LocalMetadata.Parsers throw new ArgumentException("The metadata file was empty or null.", nameof(metadataFile)); } - var settings = XmlReaderSettingsFactory.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; _validProviderIds = _validProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs index e595e9d068..127334625d 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs @@ -3,8 +3,6 @@ using System.Xml; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Parsers @@ -87,7 +85,8 @@ namespace MediaBrowser.LocalMetadata.Parsers item.Item.LinkedChildren = list.ToArray(); } - public BoxSetXmlParser(ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory, IFileSystem fileSystem) : base(logger, providerManager, xmlReaderSettingsFactory, fileSystem) + public BoxSetXmlParser(ILogger logger, IProviderManager providerManager) + : base(logger, providerManager) { } } diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs index ff69cb0237..5608a0be90 100644 --- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs @@ -3,8 +3,6 @@ using System.Xml; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Parsers @@ -95,7 +93,8 @@ namespace MediaBrowser.LocalMetadata.Parsers item.LinkedChildren = list.ToArray(); } - public PlaylistXmlParser(ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory, IFileSystem fileSystem) : base(logger, providerManager, xmlReaderSettingsFactory, fileSystem) + public PlaylistXmlParser(ILogger logger, IProviderManager providerManager) + : base(logger, providerManager) { } } diff --git a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs index 539f818c65..2e303efab6 100644 --- a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.LocalMetadata.Parsers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Providers @@ -16,19 +15,17 @@ namespace MediaBrowser.LocalMetadata.Providers { private readonly ILogger _logger; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new BoxSetXmlParser(_logger, _providerManager, XmlReaderSettingsFactory, FileSystem).Fetch(result, path, cancellationToken); + new BoxSetXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken); } protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) diff --git a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs index 442a18cb98..d111ae9ba8 100644 --- a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.LocalMetadata.Parsers; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Providers @@ -13,19 +12,17 @@ namespace MediaBrowser.LocalMetadata.Providers { private readonly ILogger _logger; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public PlaylistXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public PlaylistXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new PlaylistXmlParser(_logger, _providerManager, XmlReaderSettingsFactory, FileSystem).Fetch(result, path, cancellationToken); + new PlaylistXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken); } protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 438b842525..30a33b729a 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Savers @@ -23,7 +22,7 @@ namespace MediaBrowser.LocalMetadata.Savers { private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) { FileSystem = fileSystem; ConfigurationManager = configurationManager; @@ -31,7 +30,6 @@ namespace MediaBrowser.LocalMetadata.Savers UserManager = userManager; UserDataManager = userDataManager; Logger = logger; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected IFileSystem FileSystem { get; private set; } @@ -40,9 +38,6 @@ namespace MediaBrowser.LocalMetadata.Savers protected IUserManager UserManager { get; private set; } protected IUserDataManager UserDataManager { get; private set; } protected ILogger Logger { get; private set; } - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - - protected ItemUpdateType MinimumUpdateType => ItemUpdateType.MetadataDownload; public string Name => XmlProviderUtils.Name; diff --git a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs index b4d5440a52..ea939e33b1 100644 --- a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Savers @@ -31,7 +30,8 @@ namespace MediaBrowser.LocalMetadata.Savers return Path.Combine(item.Path, "collection.xml"); } - public BoxSetXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger, xmlReaderSettingsFactory) + public BoxSetXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) + : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } } diff --git a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs index 09bb6d8f72..35a431fa48 100644 --- a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Savers @@ -49,7 +48,8 @@ namespace MediaBrowser.LocalMetadata.Savers return Path.Combine(path, "playlist.xml"); } - public PlaylistXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger, xmlReaderSettingsFactory) + public PlaylistXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) + : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } } diff --git a/MediaBrowser.Model/Xml/IXmlReaderSettingsFactory.cs b/MediaBrowser.Model/Xml/IXmlReaderSettingsFactory.cs deleted file mode 100644 index b39325958b..0000000000 --- a/MediaBrowser.Model/Xml/IXmlReaderSettingsFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Xml; - -namespace MediaBrowser.Model.Xml -{ - public interface IXmlReaderSettingsFactory - { - XmlReaderSettings Create(bool enableValidation); - } -} diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index e4bb52217c..335f2c5395 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -15,7 +15,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Music @@ -28,17 +27,15 @@ namespace MediaBrowser.Providers.Music private readonly IApplicationHost _appHost; private readonly ILogger _logger; private readonly IJsonSerializer _json; - private readonly IXmlReaderSettingsFactory _xmlSettings; public static string MusicBrainzBaseUrl = "https://www.musicbrainz.org"; - public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost, ILogger logger, IJsonSerializer json, IXmlReaderSettingsFactory xmlSettings) + public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost, ILogger logger, IJsonSerializer json) { _httpClient = httpClient; _appHost = appHost; _logger = logger; _json = json; - _xmlSettings = xmlSettings; Current = this; } @@ -101,11 +98,13 @@ namespace MediaBrowser.Providers.Music { using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - var settings = _xmlSettings.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; using (var reader = XmlReader.Create(oReader, settings)) { @@ -240,22 +239,20 @@ namespace MediaBrowser.Providers.Music artistId); using (var response = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - using (var stream = response.Content) + var settings = new XmlReaderSettings() { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = _xmlSettings.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; - using (var reader = XmlReader.Create(oReader, settings)) - { - return ReleaseResult.Parse(reader).FirstOrDefault(); - } - } + using (var reader = XmlReader.Create(oReader, settings)) + { + return ReleaseResult.Parse(reader).FirstOrDefault(); } } } @@ -267,22 +264,20 @@ namespace MediaBrowser.Providers.Music WebUtility.UrlEncode(artistName)); using (var response = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - using (var stream = response.Content) + var settings = new XmlReaderSettings() { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = _xmlSettings.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; - using (var reader = XmlReader.Create(oReader, settings)) - { - return ReleaseResult.Parse(reader).FirstOrDefault(); - } - } + using (var reader = XmlReader.Create(oReader, settings)) + { + return ReleaseResult.Parse(reader).FirstOrDefault(); } } } @@ -590,26 +585,24 @@ namespace MediaBrowser.Providers.Music var url = string.Format("/ws/2/release?release-group={0}", releaseGroupId); using (var response = await GetMusicBrainzResponse(url, true, true, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - using (var stream = response.Content) + var settings = new XmlReaderSettings() { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = _xmlSettings.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; - using (var reader = XmlReader.Create(oReader, settings)) - { - var result = ReleaseResult.Parse(reader).FirstOrDefault(); + using (var reader = XmlReader.Create(oReader, settings)) + { + var result = ReleaseResult.Parse(reader).FirstOrDefault(); - if (result != null) - { - return result.ReleaseId; - } - } + if (result != null) + { + return result.ReleaseId; } } } @@ -628,56 +621,54 @@ namespace MediaBrowser.Providers.Music var url = string.Format("/ws/2/release-group/?query=reid:{0}", releaseEntryId); using (var response = await GetMusicBrainzResponse(url, false, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - using (var stream = response.Content) + var settings = new XmlReaderSettings() { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = _xmlSettings.Create(false); + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + using (var reader = XmlReader.Create(oReader, settings)) + { + reader.MoveToContent(); + reader.Read(); - using (var reader = XmlReader.Create(oReader, settings)) + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) + switch (reader.Name) { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) + case "release-group-list": { - case "release-group-list": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - using (var subReader = reader.ReadSubtree()) - { - return GetFirstReleaseGroupId(subReader); - } - } - default: - { - reader.Skip(); - break; - } + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + using (var subReader = reader.ReadSubtree()) + { + return GetFirstReleaseGroupId(subReader); + } + } + default: + { + reader.Skip(); + break; } - } - else - { - reader.Read(); - } } - return null; + } + else + { + reader.Read(); } } + return null; } } } diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs index 8d10834c32..924b712507 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs @@ -13,17 +13,14 @@ using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Xml; namespace MediaBrowser.Providers.Music { public class MusicBrainzArtistProvider : IRemoteMetadataProvider { - private readonly IXmlReaderSettingsFactory _xmlSettings; - - public MusicBrainzArtistProvider(IXmlReaderSettingsFactory xmlSettings) + public MusicBrainzArtistProvider() { - _xmlSettings = xmlSettings; + } public async Task> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken) @@ -84,11 +81,13 @@ namespace MediaBrowser.Providers.Music { using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - var settings = _xmlSettings.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; using (var reader = XmlReader.Create(oReader, settings)) { diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index 0a2975e0f9..752c0941d0 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Xml; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index afbd838e4b..d5b0b6fd89 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -8,7 +8,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.TV.TheTVDB; using Microsoft.Extensions.Logging; @@ -18,7 +17,6 @@ namespace MediaBrowser.Providers.TV public class SeriesMetadataService : MetadataService { private readonly ILocalizationManager _localization; - private readonly IXmlReaderSettingsFactory _xmlSettings; private readonly TvDbClientManager _tvDbClientManager; public SeriesMetadataService( @@ -29,13 +27,11 @@ namespace MediaBrowser.Providers.TV IUserDataManager userDataManager, ILibraryManager libraryManager, ILocalizationManager localization, - IXmlReaderSettingsFactory xmlSettings, TvDbClientManager tvDbClientManager ) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) { _localization = localization; - _xmlSettings = xmlSettings; _tvDbClientManager = tvDbClientManager; } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs new file mode 100644 index 0000000000..c8ae585740 --- /dev/null +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs @@ -0,0 +1,396 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using MediaBrowser.Providers.TV.TheTVDB; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Providers.TV +{ + /// + /// Class TvdbPrescanTask + /// + public class TvdbPrescanTask : ILibraryPostScanTask + { + public const string TvdbBaseUrl = "https://thetvdb.com/"; + + /// + /// The server time URL + /// + private const string ServerTimeUrl = TvdbBaseUrl + "api/Updates.php?type=none"; + + /// + /// The updates URL + /// + private const string UpdatesUrl = TvdbBaseUrl + "api/Updates.php?type=all&time={0}"; + + /// + /// The _HTTP client + /// + private readonly IHttpClient _httpClient; + /// + /// The _logger + /// + private readonly ILogger _logger; + /// + /// The _config + /// + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly ILibraryManager _libraryManager; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The HTTP client. + /// The config. + public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IFileSystem fileSystem, ILibraryManager libraryManager) + { + _logger = logger; + _httpClient = httpClient; + _config = config; + _fileSystem = fileSystem; + _libraryManager = libraryManager; + } + + protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// + /// Runs the specified progress. + /// + /// The progress. + /// The cancellation token. + /// Task. + public async Task Run(IProgress progress, CancellationToken cancellationToken) + { + var path = TvdbSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths); + + Directory.CreateDirectory(path); + + var timestampFile = Path.Combine(path, "time.txt"); + + var timestampFileInfo = _fileSystem.GetFileInfo(timestampFile); + + // Don't check for tvdb updates anymore frequently than 24 hours + if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1) + { + return; + } + + // Find out the last time we queried tvdb for updates + var lastUpdateTime = timestampFileInfo.Exists ? File.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty; + + string newUpdateTime; + + var existingDirectories = _fileSystem.GetDirectoryPaths(path) + .Select(Path.GetFileName) + .ToList(); + + var seriesList = _libraryManager.GetItemList(new InternalItemsQuery() + { + IncludeItemTypes = new[] { typeof(Series).Name }, + Recursive = true, + GroupByPresentationUniqueKey = false, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } + + }).Cast() + .ToList(); + + var seriesIdsInLibrary = seriesList + .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb))) + .Select(i => i.GetProviderId(MetadataProviders.Tvdb)) + .ToList(); + + var missingSeries = seriesIdsInLibrary.Except(existingDirectories, StringComparer.OrdinalIgnoreCase) + .ToList(); + + var enableInternetProviders = seriesList.Count == 0 ? false : seriesList[0].IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(seriesList[0]), TvdbSeriesProvider.Current.Name); + if (!enableInternetProviders) + { + progress.Report(100); + return; + } + + // If this is our first time, update all series + if (string.IsNullOrEmpty(lastUpdateTime)) + { + // First get tvdb server time + using (var response = await _httpClient.SendAsync(new HttpRequestOptions + { + Url = ServerTimeUrl, + CancellationToken = cancellationToken, + EnableHttpCompression = true, + BufferContent = false + + }, "GET").ConfigureAwait(false)) + { + // First get tvdb server time + using (var stream = response.Content) + { + newUpdateTime = GetUpdateTime(stream); + } + } + + existingDirectories.AddRange(missingSeries); + + await UpdateSeries(existingDirectories, path, null, progress, cancellationToken).ConfigureAwait(false); + } + else + { + var seriesToUpdate = await GetSeriesIdsToUpdate(existingDirectories, lastUpdateTime, cancellationToken).ConfigureAwait(false); + + newUpdateTime = seriesToUpdate.Item2; + + long.TryParse(lastUpdateTime, NumberStyles.Any, UsCulture, out var lastUpdateValue); + + var nullableUpdateValue = lastUpdateValue == 0 ? (long?)null : lastUpdateValue; + + var listToUpdate = seriesToUpdate.Item1.ToList(); + listToUpdate.AddRange(missingSeries); + + await UpdateSeries(listToUpdate, path, nullableUpdateValue, progress, cancellationToken).ConfigureAwait(false); + } + + File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8); + progress.Report(100); + } + + /// + /// Gets the update time. + /// + /// The response. + /// System.String. + private string GetUpdateTime(Stream response) + { + // Use XmlReader for best performance + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var streamReader = new StreamReader(response, Encoding.UTF8)) + using (var reader = XmlReader.Create(streamReader, settings)) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Time": + { + return (reader.ReadElementContentAsString() ?? string.Empty).Trim(); + } + default: + reader.Skip(); + break; + } + } + else + { + reader.Read(); + } + } + } + + return null; + } + + /// + /// Gets the series ids to update. + /// + /// The existing series ids. + /// The last update time. + /// The cancellation token. + /// Task{IEnumerable{System.String}}. + private async Task, string>> GetSeriesIdsToUpdate(IEnumerable existingSeriesIds, string lastUpdateTime, CancellationToken cancellationToken) + { + // First get last time + using (var response = await _httpClient.SendAsync(new HttpRequestOptions + { + Url = string.Format(UpdatesUrl, lastUpdateTime), + CancellationToken = cancellationToken, + EnableHttpCompression = true, + BufferContent = false + + }, "GET").ConfigureAwait(false)) + { + using (var stream = response.Content) + { + var data = GetUpdatedSeriesIdList(stream); + + var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); + + var seriesList = data.ids + .Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i)); + + return new Tuple, string>(seriesList, data.updateTime); + } + } + } + + private (List ids, string updateTime) GetUpdatedSeriesIdList(Stream stream) + { + string updateTime = null; + var idList = new List(); + + // Use XmlReader for best performance + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var streamReader = new StreamReader(stream, Encoding.UTF8)) + using (var reader = XmlReader.Create(streamReader, settings)) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Time": + { + updateTime = (reader.ReadElementContentAsString() ?? string.Empty).Trim(); + break; + } + case "Series": + { + var id = (reader.ReadElementContentAsString() ?? string.Empty).Trim(); + idList.Add(id); + break; + } + default: + reader.Skip(); + break; + } + } + else + { + reader.Read(); + } + } + } + + return (idList, updateTime); + } + + /// + /// Updates the series. + /// + /// The series ids. + /// The series data path. + /// The last tv db update time. + /// The progress. + /// The cancellation token. + /// Task. + private async Task UpdateSeries(List seriesIds, string seriesDataPath, long? lastTvDbUpdateTime, IProgress progress, CancellationToken cancellationToken) + { + var numComplete = 0; + + var seriesList = _libraryManager.GetItemList(new InternalItemsQuery() + { + IncludeItemTypes = new[] { typeof(Series).Name }, + Recursive = true, + GroupByPresentationUniqueKey = false, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } + + }).Cast(); + + // Gather all series into a lookup by tvdb id + var allSeries = seriesList + .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb))) + .ToLookup(i => i.GetProviderId(MetadataProviders.Tvdb)); + + foreach (var seriesId in seriesIds) + { + // Find the preferred language(s) for the movie in the library + var languages = allSeries[seriesId] + .Select(i => i.GetPreferredMetadataLanguage()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + foreach (var language in languages) + { + try + { + await UpdateSeries(seriesId, seriesDataPath, lastTvDbUpdateTime, language, cancellationToken).ConfigureAwait(false); + } + catch (HttpException ex) + { + _logger.LogError(ex, "Error updating tvdb series id {ID}, language {Language}", seriesId, language); + + // Already logged at lower levels, but don't fail the whole operation, unless timed out + // We have to fail this to make it run again otherwise new episode data could potentially be missing + if (ex.IsTimedOut) + { + throw; + } + } + } + + numComplete++; + double percent = numComplete; + percent /= seriesIds.Count; + percent *= 100; + + progress.Report(percent); + } + } + + /// + /// Updates the series. + /// + /// The id. + /// The series data path. + /// The last tv db update time. + /// The preferred metadata language. + /// The cancellation token. + /// Task. + private Task UpdateSeries(string id, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken) + { + _logger.LogInformation("Updating series from tvdb " + id + ", language " + preferredMetadataLanguage); + + seriesDataPath = Path.Combine(seriesDataPath, id); + + Directory.CreateDirectory(seriesDataPath); + + return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), null, null, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken); + } + } +} diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index 9c24e4c987..09b056c0cb 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -23,7 +23,6 @@ namespace MediaBrowser.Providers.TV.TheTVDB { internal static TvdbSeriesProvider Current { get; private set; } private readonly IHttpClient _httpClient; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localizationManager; diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index f20dbbb6ea..aa5313eb72 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -13,8 +13,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.XbmcMetadata.Configuration; using MediaBrowser.XbmcMetadata.Savers; using Microsoft.Extensions.Logging; @@ -28,9 +26,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// The logger /// protected ILogger Logger { get; private set; } - protected IFileSystem FileSystem { get; private set; } protected IProviderManager ProviderManager { get; private set; } - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IConfigurationManager _config; @@ -39,13 +35,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// /// Initializes a new instance of the class. /// - public BaseNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public BaseNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) { Logger = logger; _config = config; ProviderManager = providerManager; - FileSystem = fileSystem; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } /// @@ -68,11 +62,13 @@ namespace MediaBrowser.XbmcMetadata.Parsers throw new ArgumentException("The metadata file was empty or null.", nameof(metadataFile)); } - var settings = XmlReaderSettingsFactory.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; _validProviderIds = _validProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -935,19 +931,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers value = value.Trim().Trim(separator); - return string.IsNullOrWhiteSpace(value) ? Array.Empty() : Split(value, separator, StringSplitOptions.RemoveEmptyEntries); - } - - /// - /// Provides an additional overload for string.split - /// - /// The val. - /// The separators. - /// The options. - /// System.String[][]. - private string[] Split(string val, char[] separators, StringSplitOptions options) - { - return val.Split(separators, options); + return string.IsNullOrWhiteSpace(value) ? Array.Empty() : value.Split(separator, StringSplitOptions.RemoveEmptyEntries); } } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index a8cb6ac40d..7f68ba256e 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -8,8 +8,6 @@ using System.Xml; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers @@ -220,7 +218,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } - public EpisodeNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, config, providerManager, fileSystem, xmlReaderSettingsFactory) + public EpisodeNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(logger, config, providerManager) { } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs index a3e48d30d9..526cc62b0e 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs @@ -7,8 +7,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers @@ -124,14 +122,16 @@ namespace MediaBrowser.XbmcMetadata.Parsers using (var stringReader = new StringReader("" + xml + "")) { // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions - try + var settings = new XmlReaderSettings() { - var settings = XmlReaderSettingsFactory.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + try + { // Use XmlReader for best performance using (var reader = XmlReader.Create(stringReader, settings)) { @@ -167,7 +167,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } - public MovieNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, config, providerManager, fileSystem, xmlReaderSettingsFactory) + public MovieNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(logger, config, providerManager) { } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs index 17f36d82d7..882f3a9d3a 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs @@ -3,8 +3,6 @@ using System.Xml; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers @@ -42,7 +40,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } - public SeasonNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, config, providerManager, fileSystem, xmlReaderSettingsFactory) + public SeasonNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(logger, config, providerManager) { } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs index 700656b652..b0f25ae64e 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs @@ -5,8 +5,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers @@ -94,7 +92,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } - public SeriesNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, config, providerManager, fileSystem, xmlReaderSettingsFactory) + public SeriesNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(logger, config, providerManager) { } } diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs index 1e22bf3582..6e6a22794d 100644 --- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging; @@ -15,20 +14,18 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public AlbumNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public AlbumNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _config = config; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new BaseNfoParser(_logger, _config, _providerManager, FileSystem, XmlReaderSettingsFactory).Fetch(result, path, cancellationToken); + new BaseNfoParser(_logger, _config, _providerManager).Fetch(result, path, cancellationToken); } protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs index ad811b4754..20abfc7f3a 100644 --- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging; @@ -15,20 +14,18 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public ArtistNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public ArtistNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _config = config; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new BaseNfoParser(_logger, _config, _providerManager, FileSystem, XmlReaderSettingsFactory).Fetch(result, path, cancellationToken); + new BaseNfoParser(_logger, _config, _providerManager).Fetch(result, path, cancellationToken); } protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs index 94f104f61b..28a0514d57 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.XbmcMetadata.Parsers; using MediaBrowser.XbmcMetadata.Savers; using Microsoft.Extensions.Logging; @@ -17,15 +16,13 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public BaseVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public BaseVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _config = config; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) @@ -34,7 +31,7 @@ namespace MediaBrowser.XbmcMetadata.Providers { Item = result.Item }; - new MovieNfoParser(_logger, _config, _providerManager, FileSystem, XmlReaderSettingsFactory).Fetch(tmpItem, path, cancellationToken); + new MovieNfoParser(_logger, _config, _providerManager).Fetch(tmpItem, path, cancellationToken); result.Item = (T)tmpItem.Item; result.People = tmpItem.People; diff --git a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs index bf05f0f38b..f90f283cf3 100644 --- a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs @@ -5,7 +5,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging; @@ -16,22 +15,20 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public EpisodeNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public EpisodeNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _config = config; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { var images = new List(); - new EpisodeNfoParser(_logger, _config, _providerManager, FileSystem, XmlReaderSettingsFactory).Fetch(result, images, path, cancellationToken); + new EpisodeNfoParser(_logger, _config, _providerManager).Fetch(result, images, path, cancellationToken); result.Images = images; } diff --git a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs index 77b3b3781b..d21164c022 100644 --- a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs @@ -3,28 +3,30 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Providers { public class MovieNfoProvider : BaseVideoNfoProvider { - public MovieNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(fileSystem, logger, config, providerManager, xmlReaderSettingsFactory) + public MovieNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(fileSystem, logger, config, providerManager) { } } public class MusicVideoNfoProvider : BaseVideoNfoProvider { - public MusicVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(fileSystem, logger, config, providerManager, xmlReaderSettingsFactory) + public MusicVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(fileSystem, logger, config, providerManager) { } } public class VideoNfoProvider : BaseVideoNfoProvider public class TypeMapper { - private readonly IAssemblyInfo _assemblyInfo; - /// /// This holds all the types in the running assemblies so that we can de-serialize properly when we don't have strong types /// private readonly ConcurrentDictionary _typeMap = new ConcurrentDictionary(); - public TypeMapper(IAssemblyInfo assemblyInfo) + public TypeMapper() { - _assemblyInfo = assemblyInfo; } /// @@ -45,8 +41,7 @@ namespace Emby.Server.Implementations.Data /// Type. private Type LookupType(string typeName) { - return _assemblyInfo - .GetCurrentAssemblies() + return AppDomain.CurrentDomain.GetAssemblies() .Select(a => a.GetType(typeName)) .FirstOrDefault(t => t != null); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index fceb82ba19..f4eddb08ba 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -33,7 +33,6 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -58,7 +57,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly IProviderManager _providerManager; private readonly IMediaEncoder _mediaEncoder; private readonly IProcessFactory _processFactory; - private readonly IAssemblyInfo _assemblyInfo; private IMediaSourceManager _mediaSourceManager; public static EmbyTV Current; @@ -74,7 +72,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public EmbyTV(IServerApplicationHost appHost, IStreamHelper streamHelper, IMediaSourceManager mediaSourceManager, - IAssemblyInfo assemblyInfo, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, @@ -101,7 +98,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _processFactory = processFactory; _liveTvManager = (LiveTvManager)liveTvManager; _jsonSerializer = jsonSerializer; - _assemblyInfo = assemblyInfo; _mediaSourceManager = mediaSourceManager; _streamHelper = streamHelper; diff --git a/Emby.Server.Implementations/Reflection/AssemblyInfo.cs b/Emby.Server.Implementations/Reflection/AssemblyInfo.cs deleted file mode 100644 index 9d16fe43f6..0000000000 --- a/Emby.Server.Implementations/Reflection/AssemblyInfo.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using MediaBrowser.Model.Reflection; - -namespace Emby.Server.Implementations.Reflection -{ - public class AssemblyInfo : IAssemblyInfo - { - public Stream GetManifestResourceStream(Type type, string resource) - { - return type.Assembly.GetManifestResourceStream(resource); - } - - public string[] GetManifestResourceNames(Type type) - { - return type.Assembly.GetManifestResourceNames(); - } - - public Assembly[] GetCurrentAssemblies() - { - return AppDomain.CurrentDomain.GetAssemblies(); - } - } -} diff --git a/MediaBrowser.Model/Reflection/IAssemblyInfo.cs b/MediaBrowser.Model/Reflection/IAssemblyInfo.cs deleted file mode 100644 index 5c4536c1c1..0000000000 --- a/MediaBrowser.Model/Reflection/IAssemblyInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.IO; -using System.Reflection; - -namespace MediaBrowser.Model.Reflection -{ - public interface IAssemblyInfo - { - Stream GetManifestResourceStream(Type type, string resource); - string[] GetManifestResourceNames(Type type); - - Assembly[] GetCurrentAssemblies(); - } -} diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 531978e1dd..aa0208495b 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -13,7 +13,6 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; -using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; @@ -117,20 +116,26 @@ namespace MediaBrowser.WebDashboard.Api private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; private readonly IJsonSerializer _jsonSerializer; - private readonly IAssemblyInfo _assemblyInfo; private IResourceFileManager _resourceFileManager; /// /// Initializes a new instance of the class. /// - public DashboardService(IServerApplicationHost appHost, IResourceFileManager resourceFileManager, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, IAssemblyInfo assemblyInfo, ILogger logger, IHttpResultFactory resultFactory) + public DashboardService( + IServerApplicationHost appHost, + IResourceFileManager resourceFileManager, + IServerConfigurationManager serverConfigurationManager, + IFileSystem fileSystem, + ILocalizationManager localization, + IJsonSerializer jsonSerializer, + ILogger logger, + IHttpResultFactory resultFactory) { _appHost = appHost; _serverConfigurationManager = serverConfigurationManager; _fileSystem = fileSystem; _localization = localization; _jsonSerializer = jsonSerializer; - _assemblyInfo = assemblyInfo; _logger = logger; _resultFactory = resultFactory; _resourceFileManager = resourceFileManager; @@ -187,7 +192,7 @@ namespace MediaBrowser.WebDashboard.Api if (altPage != null) { plugin = altPage.Item2; - stream = _assemblyInfo.GetManifestResourceStream(plugin.GetType(), altPage.Item1.EmbeddedResourcePath); + stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath); isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase); isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html"); -- cgit v1.2.3 From f486f5966f2fb9a3cf266ee816b8c247f0de5482 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 09:56:03 -0800 Subject: Update Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 526509f439..3ec1f81d31 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -73,7 +73,7 @@ namespace Emby.Server.Implementations.Library } else { - throw new Exception(String.Format($"Requested crypto method not available in provider: {readyHash.Id}")); + throw new Exception(string.Format($"Requested crypto method not available in provider: {readyHash.Id}")); } // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); -- cgit v1.2.3 From dfff68b2f4ca8d3da59508eb4cbe2e751ed76f49 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 7 Mar 2019 19:05:47 +0100 Subject: Make SkipLogExtensions static --- Emby.Server.Implementations/HttpServer/FileWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 0e989994b1..c4d2a70e23 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -148,7 +148,7 @@ namespace Emby.Server.Implementations.HttpServer } } - private readonly string[] SkipLogExtensions = { + private static readonly string[] SkipLogExtensions = { ".js", ".html", ".css" -- cgit v1.2.3 From c5fce647defd2eace2d2431ccc52ffe1d027c184 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 15:54:30 +0100 Subject: Cleanup/simplification * Removed useless copies/allocations * Reduced unneeded complexity --- .../Collections/CollectionImageProvider.cs | 4 +- .../IO/ManagedFileSystem.cs | 7 +- .../Images/BaseDynamicImageProvider.cs | 58 ++++++-------- .../Playlists/PlaylistImageProvider.cs | 13 +--- .../SocketSharp/RequestMono.cs | 17 ++--- .../SocketSharp/WebSocketSharpRequest.cs | 4 +- .../UserViews/CollectionFolderImageProvider.cs | 9 +-- .../UserViews/DynamicImageProvider.cs | 13 ++-- .../UserViews/FolderImageProvider.cs | 4 +- MediaBrowser.Api/Images/ImageService.cs | 89 ++++++++++++---------- MediaBrowser.Controller/Entities/BaseItem.cs | 26 ++++--- MediaBrowser.Controller/Entities/Folder.cs | 2 +- MediaBrowser.Controller/Entities/ItemImageInfo.cs | 16 +--- MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 2 +- MediaBrowser.Controller/Entities/Movies/Movie.cs | 8 +- MediaBrowser.Controller/Entities/TV/Episode.cs | 2 +- MediaBrowser.Controller/Entities/TV/Series.cs | 3 +- MediaBrowser.Controller/Entities/Video.cs | 2 +- MediaBrowser.Model/IO/IFileSystem.cs | 3 +- 19 files changed, 126 insertions(+), 156 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index cdfb5cadf1..0244c4a684 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Collections return base.Supports(item); } - protected override List GetItemsWithImages(BaseItem item) + protected override IReadOnlyList GetItemsWithImages(BaseItem item) { var playlist = (BoxSet)item; @@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Collections .ToList(); } - protected override string CreateImage(BaseItem item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(BaseItem item, IReadOnlyCollection itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); } diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index a64dfb607b..421592fada 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -7,7 +7,6 @@ using System.Text; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.IO @@ -711,20 +710,20 @@ namespace Emby.Server.Implementations.IO return GetFiles(path, null, false, recursive); } - public virtual IEnumerable GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + public virtual IEnumerable GetFiles(string path, IReadOnlyList extensions, bool enableCaseSensitiveExtensions, bool recursive = false) { var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; // On linux and osx the search pattern is case sensitive // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method - if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Length == 1) + if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Count == 1) { return ToMetadata(new DirectoryInfo(path).EnumerateFiles("*" + extensions[0], searchOption)); } var files = new DirectoryInfo(path).EnumerateFiles("*", searchOption); - if (extensions != null && extensions.Length > 0) + if (extensions != null && extensions.Count > 0) { files = files.Where(i => { diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 109c21f18d..46f209b4b4 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -20,6 +20,9 @@ namespace Emby.Server.Implementations.Images public abstract class BaseDynamicImageProvider : IHasItemChangeMonitor, IForcedProvider, ICustomMetadataProvider, IHasOrder where T : BaseItem { + protected virtual IReadOnlyCollection SupportedImages { get; } + = new ImageType[] { ImageType.Primary }; + protected IFileSystem FileSystem { get; private set; } protected IProviderManager ProviderManager { get; private set; } protected IApplicationPaths ApplicationPaths { get; private set; } @@ -33,18 +36,7 @@ namespace Emby.Server.Implementations.Images ImageProcessor = imageProcessor; } - protected virtual bool Supports(BaseItem item) - { - return true; - } - - public virtual ImageType[] GetSupportedImages(BaseItem item) - { - return new ImageType[] - { - ImageType.Primary - }; - } + protected virtual bool Supports(BaseItem _) => true; public async Task FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) { @@ -54,15 +46,14 @@ namespace Emby.Server.Implementations.Images } var updateType = ItemUpdateType.None; - var supportedImages = GetSupportedImages(item); - if (supportedImages.Contains(ImageType.Primary)) + if (SupportedImages.Contains(ImageType.Primary)) { var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false); updateType = updateType | primaryResult; } - if (supportedImages.Contains(ImageType.Thumb)) + if (SupportedImages.Contains(ImageType.Thumb)) { var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false); updateType = updateType | thumbResult; @@ -94,7 +85,7 @@ namespace Emby.Server.Implementations.Images } protected async Task FetchToFileInternal(BaseItem item, - List itemsWithImages, + IReadOnlyList itemsWithImages, ImageType imageType, CancellationToken cancellationToken) { @@ -119,9 +110,9 @@ namespace Emby.Server.Implementations.Images return ItemUpdateType.ImageUpdate; } - protected abstract List GetItemsWithImages(BaseItem item); + protected abstract IReadOnlyList GetItemsWithImages(BaseItem item); - protected string CreateThumbCollage(BaseItem primaryItem, List items, string outputPath) + protected string CreateThumbCollage(BaseItem primaryItem, IEnumerable items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 640, 360); } @@ -132,38 +123,38 @@ namespace Emby.Server.Implementations.Images .Select(i => { var image = i.GetImageInfo(ImageType.Primary, 0); - if (image != null && image.IsLocalFile) { return image.Path; } - image = i.GetImageInfo(ImageType.Thumb, 0); + image = i.GetImageInfo(ImageType.Thumb, 0); if (image != null && image.IsLocalFile) { return image.Path; } + return null; }) .Where(i => !string.IsNullOrEmpty(i)); } - protected string CreatePosterCollage(BaseItem primaryItem, List items, string outputPath) + protected string CreatePosterCollage(BaseItem primaryItem, IEnumerable items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 400, 600); } - protected string CreateSquareCollage(BaseItem primaryItem, List items, string outputPath) + protected string CreateSquareCollage(BaseItem primaryItem, IEnumerable items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 600, 600); } - protected string CreateThumbCollage(BaseItem primaryItem, List items, string outputPath, int width, int height) + protected string CreateThumbCollage(BaseItem primaryItem, IEnumerable items, string outputPath, int width, int height) { return CreateCollage(primaryItem, items, outputPath, width, height); } - private string CreateCollage(BaseItem primaryItem, List items, string outputPath, int width, int height) + private string CreateCollage(BaseItem primaryItem, IEnumerable items, string outputPath, int width, int height) { Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); @@ -192,7 +183,7 @@ namespace Emby.Server.Implementations.Images public string Name => "Dynamic Image Provider"; protected virtual string CreateImage(BaseItem item, - List itemsWithImages, + IReadOnlyCollection itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) @@ -211,18 +202,15 @@ namespace Emby.Server.Implementations.Images if (imageType == ImageType.Primary) { - if (item is UserView) - { - return CreateSquareCollage(item, itemsWithImages, outputPath); - } - if (item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum) + if (item is UserView || item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum) { return CreateSquareCollage(item, itemsWithImages, outputPath); } + return CreatePosterCollage(item, itemsWithImages, outputPath); } - throw new ArgumentException("Unexpected image type"); + throw new ArgumentException("Unexpected image type", nameof(imageType)); } protected virtual int MaxImageAgeDays => 7; @@ -234,13 +222,11 @@ namespace Emby.Server.Implementations.Images return false; } - var supportedImages = GetSupportedImages(item); - - if (supportedImages.Contains(ImageType.Primary) && HasChanged(item, ImageType.Primary)) + if (SupportedImages.Contains(ImageType.Primary) && HasChanged(item, ImageType.Primary)) { return true; } - if (supportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb)) + if (SupportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb)) { return true; } @@ -285,7 +271,7 @@ namespace Emby.Server.Implementations.Images public int Order => 0; - protected string CreateSingleImage(List itemsWithImages, string outputPathWithoutExtension, ImageType imageType) + protected string CreateSingleImage(IEnumerable itemsWithImages, string outputPathWithoutExtension, ImageType imageType) { var image = itemsWithImages .Where(i => i.HasImage(imageType) && i.GetImageInfo(imageType, 0).IsLocalFile && Path.HasExtension(i.GetImagePath(imageType))) diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index 8a7c1492d4..cad66a80fd 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Playlists { } - protected override List GetItemsWithImages(BaseItem item) + protected override IReadOnlyList GetItemsWithImages(BaseItem item) { var playlist = (Playlist)item; @@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Playlists _libraryManager = libraryManager; } - protected override List GetItemsWithImages(BaseItem item) + protected override IReadOnlyList GetItemsWithImages(BaseItem item) { return _libraryManager.GetItemList(new InternalItemsQuery { @@ -89,7 +89,6 @@ namespace Emby.Server.Implementations.Playlists Recursive = true, ImageTypes = new[] { ImageType.Primary }, DtoOptions = new DtoOptions(false) - }); } } @@ -103,7 +102,7 @@ namespace Emby.Server.Implementations.Playlists _libraryManager = libraryManager; } - protected override List GetItemsWithImages(BaseItem item) + protected override IReadOnlyList GetItemsWithImages(BaseItem item) { return _libraryManager.GetItemList(new InternalItemsQuery { @@ -116,11 +115,5 @@ namespace Emby.Server.Implementations.Playlists DtoOptions = new DtoOptions(false) }); } - - //protected override Task CreateImage(IHasMetadata item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) - //{ - // return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); - //} } - } diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index 5e29e40584..373f6d7580 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.SocketSharp byte[] copy = new byte[e.Length]; input.Position = e.Start; - input.Read(copy, 0, (int)e.Length); + await input.ReadAsync(copy, 0, (int)e.Length).ConfigureAwait(false); form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length)); } @@ -98,11 +98,11 @@ namespace Emby.Server.Implementations.SocketSharp var form = new WebROCollection(); files = new Dictionary(); - if (IsContentType("multipart/form-data", true)) + if (IsContentType("multipart/form-data")) { await LoadMultiPart(form).ConfigureAwait(false); } - else if (IsContentType("application/x-www-form-urlencoded", true)) + else if (IsContentType("application/x-www-form-urlencoded")) { await LoadWwwForm(form).ConfigureAwait(false); } @@ -200,19 +200,14 @@ namespace Emby.Server.Implementations.SocketSharp return false; } - private bool IsContentType(string ct, bool starts_with) + private bool IsContentType(string ct) { - if (ct == null || ContentType == null) + if (ContentType == null) { return false; } - if (starts_with) - { - return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase); - } - - return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase); + return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase); } private async Task LoadWwwForm(WebROCollection form) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index bc002dc4ce..10b7c45149 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.IO; using System.Net; using System.Text; -using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -405,8 +404,7 @@ namespace Emby.Server.Implementations.SocketSharp return null; } - var path = sbPathInfo.ToString(); - return path.Length > 1 ? path.TrimEnd('/') : "/"; + return sbPathInfo.Length > 1 ? sbPathInfo.ToString().TrimEnd('/') : "/"; } public string UserAgent => request.Headers[HeaderNames.UserAgent]; diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index ce6c2cd87d..a3f3f6cb4d 100644 --- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Emby.Server.Implementations.Images; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; @@ -20,7 +19,7 @@ namespace Emby.Server.Implementations.UserViews { } - protected override List GetItemsWithImages(BaseItem item) + protected override IReadOnlyList GetItemsWithImages(BaseItem item) { var view = (CollectionFolder)item; var viewType = view.CollectionType; @@ -56,7 +55,7 @@ namespace Emby.Server.Implementations.UserViews includeItemTypes = new string[] { "Video", "Audio", "Photo", "Movie", "Series" }; } - var recursive = !new[] { CollectionType.Playlists }.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase); return view.GetItemList(new InternalItemsQuery { @@ -71,7 +70,7 @@ namespace Emby.Server.Implementations.UserViews }, IncludeItemTypes = includeItemTypes - }).ToList(); + }); } protected override bool Supports(BaseItem item) @@ -79,7 +78,7 @@ namespace Emby.Server.Implementations.UserViews return item is CollectionFolder; } - protected override string CreateImage(BaseItem item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(BaseItem item, IReadOnlyCollection itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png"); diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs index 4ec68e550d..f485204436 100644 --- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.UserViews _libraryManager = libraryManager; } - protected override List GetItemsWithImages(BaseItem item) + protected override IReadOnlyList GetItemsWithImages(BaseItem item) { var view = (UserView)item; @@ -46,8 +46,7 @@ namespace Emby.Server.Implementations.UserViews var items = result.Select(i => { - var episode = i as Episode; - if (episode != null) + if (i is Episode episode) { var series = episode.Series; if (series != null) @@ -58,8 +57,7 @@ namespace Emby.Server.Implementations.UserViews return episode; } - var season = i as Season; - if (season != null) + if (i is Season season) { var series = season.Series; if (series != null) @@ -70,8 +68,7 @@ namespace Emby.Server.Implementations.UserViews return season; } - var audio = i as Audio; - if (audio != null) + if (i is Audio audio) { var album = audio.AlbumEntity; if (album != null && album.HasImage(ImageType.Primary)) @@ -122,7 +119,7 @@ namespace Emby.Server.Implementations.UserViews return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty); } - protected override string CreateImage(BaseItem item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(BaseItem item, IReadOnlyCollection itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { if (itemsWithImages.Count == 0) { diff --git a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs index c810004ab2..4655cd928a 100644 --- a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.UserViews _libraryManager = libraryManager; } - protected override List GetItemsWithImages(BaseItem item) + protected override IReadOnlyList GetItemsWithImages(BaseItem item) { return _libraryManager.GetItemList(new InternalItemsQuery { @@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.UserViews }); } - protected override string CreateImage(BaseItem item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(BaseItem item, IReadOnlyCollection itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index c25204bf2d..10bbc9e5d8 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -312,35 +312,35 @@ namespace MediaBrowser.Api.Images private ImageInfo GetImageInfo(BaseItem item, ItemImageInfo info, int? imageIndex) { + int? width = null; + int? height = null; + long length = 0; + try { - int? width = null; - int? height = null; - long length = 0; - - try + if (info.IsLocalFile) { - if (info.IsLocalFile) - { - var fileInfo = _fileSystem.GetFileInfo(info.Path); - length = fileInfo.Length; + var fileInfo = _fileSystem.GetFileInfo(info.Path); + length = fileInfo.Length; - ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true); - width = size.Width; - height = size.Height; - - if (width <= 0 || height <= 0) - { - width = null; - height = null; - } + ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true); + width = size.Width; + height = size.Height; + if (width <= 0 || height <= 0) + { + width = null; + height = null; } } - catch - { + } + catch (Exception ex) + { + Logger.LogError(ex, "Error getting image information for {Item}", item.Name); + } - } + try + { return new ImageInfo { Path = info.Path, @@ -354,7 +354,7 @@ namespace MediaBrowser.Api.Images } catch (Exception ex) { - Logger.LogError(ex, "Error getting image information for {path}", info.Path); + Logger.LogError(ex, "Error getting image information for {Path}", info.Path); return null; } @@ -519,16 +519,16 @@ namespace MediaBrowser.Api.Images request.AddPlayedIndicator = true; } } + if (request.PercentPlayed.HasValue) { request.UnplayedCount = null; } - if (request.UnplayedCount.HasValue) + + if (request.UnplayedCount.HasValue + && request.UnplayedCount.Value <= 0) { - if (request.UnplayedCount.Value <= 0) - { - request.UnplayedCount = null; - } + request.UnplayedCount = null; } if (item == null) @@ -542,7 +542,6 @@ namespace MediaBrowser.Api.Images } var imageInfo = GetImageInfo(request, item); - if (imageInfo == null) { var displayText = item == null ? itemId.ToString() : item.Name; @@ -550,7 +549,6 @@ namespace MediaBrowser.Api.Images } IImageEnhancer[] supportedImageEnhancers; - if (_imageProcessor.ImageEnhancers.Length > 0) { if (item == null) @@ -565,13 +563,15 @@ namespace MediaBrowser.Api.Images supportedImageEnhancers = Array.Empty(); } - var cropwhitespace = request.Type == ImageType.Logo || - request.Type == ImageType.Art; - + bool cropwhitespace; if (request.CropWhitespace.HasValue) { cropwhitespace = request.CropWhitespace.Value; } + else + { + cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art; + } var outputFormats = GetOutputFormats(request); @@ -653,12 +653,10 @@ namespace MediaBrowser.Api.Images private ImageFormat[] GetOutputFormats(ImageRequest request) { - if (!string.IsNullOrWhiteSpace(request.Format)) + if (!string.IsNullOrWhiteSpace(request.Format) + && Enum.TryParse(request.Format, true, out ImageFormat format)) { - if (Enum.TryParse(request.Format, true, out ImageFormat format)) - { - return new ImageFormat[] { format }; - } + return new ImageFormat[] { format }; } return GetClientSupportedFormats(); @@ -666,8 +664,19 @@ namespace MediaBrowser.Api.Images private ImageFormat[] GetClientSupportedFormats() { - //logger.LogDebug("Request types: {0}", string.Join(",", Request.AcceptTypes ?? Array.Empty())); - var supportedFormats = (Request.AcceptTypes ?? Array.Empty()).Select(i => i.Split(';')[0]).ToArray(); + var supportedFormats = Request.AcceptTypes ?? Array.Empty(); + if (supportedFormats.Length > 0) + { + for (int i = 0; i < supportedFormats.Length; i++) + { + int index = supportedFormats[i].IndexOf(';'); + if (index != -1) + { + supportedFormats[i] = supportedFormats[i].Substring(0, index); + } + } + } + var acceptParam = Request.QueryString["accept"]; var supportsWebP = SupportsFormat(supportedFormats, acceptParam, "webp", false); @@ -700,7 +709,7 @@ namespace MediaBrowser.Api.Images return formats.ToArray(); } - private bool SupportsFormat(string[] requestAcceptTypes, string acceptParam, string format, bool acceptAll) + private bool SupportsFormat(IEnumerable requestAcceptTypes, string acceptParam, string format, bool acceptAll) { var mimeType = "image/" + format; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 43fee79a10..39b6eedfeb 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -36,10 +36,20 @@ namespace MediaBrowser.Controller.Entities /// public abstract class BaseItem : IHasProviderIds, IHasLookupInfo { - protected static MetadataFields[] EmptyMetadataFieldsArray = Array.Empty(); - protected static MediaUrl[] EmptyMediaUrlArray = Array.Empty(); - protected static ItemImageInfo[] EmptyItemImageInfoArray = Array.Empty(); - public static readonly LinkedChild[] EmptyLinkedChildArray = Array.Empty(); + private static readonly List _supportedExtensions = new List(SupportedImageExtensions) + { + ".nfo", + ".xml", + ".srt", + ".vtt", + ".sub", + ".idx", + ".txt", + ".edl", + ".bif", + ".smi", + ".ttml" + }; protected BaseItem() { @@ -49,8 +59,8 @@ namespace MediaBrowser.Controller.Entities Genres = Array.Empty(); Studios = Array.Empty(); ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - LockedFields = EmptyMetadataFieldsArray; - ImageInfos = EmptyItemImageInfoArray; + LockedFields = Array.Empty(); + ImageInfos = Array.Empty(); ProductionLocations = Array.Empty(); RemoteTrailers = Array.Empty(); ExtraIds = Array.Empty(); @@ -2452,10 +2462,8 @@ namespace MediaBrowser.Controller.Entities } var filename = System.IO.Path.GetFileNameWithoutExtension(Path); - var extensions = new List { ".nfo", ".xml", ".srt", ".vtt", ".sub", ".idx", ".txt", ".edl", ".bif", ".smi", ".ttml" }; - extensions.AddRange(SupportedImageExtensions); - return FileSystem.GetFiles(System.IO.Path.GetDirectoryName(Path), extensions.ToArray(), false, false) + return FileSystem.GetFiles(System.IO.Path.GetDirectoryName(Path), _supportedExtensions, false, false) .Where(i => System.IO.Path.GetFileNameWithoutExtension(i.FullName).StartsWith(filename, StringComparison.OrdinalIgnoreCase)) .ToList(); } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index e49ff20baa..c056bc0b4e 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.Controller.Entities public Folder() { - LinkedChildren = EmptyLinkedChildArray; + LinkedChildren = Array.Empty(); } [IgnoreDataMember] diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index ff6b133982..8484938642 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -25,22 +25,10 @@ namespace MediaBrowser.Controller.Entities public DateTime DateModified { get; set; } public int Width { get; set; } + public int Height { get; set; } [IgnoreDataMember] - public bool IsLocalFile - { - get - { - if (Path != null) - { - if (Path.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - return true; - } - } + public bool IsLocalFile => Path == null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase); } } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 124a943ef8..a532b5ee9e 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Entities.Movies { public BoxSet() { - RemoteTrailers = EmptyMediaUrlArray; + RemoteTrailers = Array.Empty(); LocalTrailerIds = Array.Empty(); RemoteTrailerIds = Array.Empty(); diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 232d116241..20c5b35219 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -21,10 +21,10 @@ namespace MediaBrowser.Controller.Entities.Movies public Movie() { - SpecialFeatureIds = new Guid[] { }; - RemoteTrailers = EmptyMediaUrlArray; - LocalTrailerIds = new Guid[] { }; - RemoteTrailerIds = new Guid[] { }; + SpecialFeatureIds = Array.Empty(); + RemoteTrailers = Array.Empty(); + LocalTrailerIds = Array.Empty(); + RemoteTrailerIds = Array.Empty(); } public Guid[] LocalTrailerIds { get; set; } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 072b1d89af..fb29c07b0d 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Entities.TV { public Episode() { - RemoteTrailers = EmptyMediaUrlArray; + RemoteTrailers = Array.Empty(); LocalTrailerIds = Array.Empty(); RemoteTrailerIds = Array.Empty(); } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 570e9389e9..eae834f6f0 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -7,7 +7,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; @@ -22,7 +21,7 @@ namespace MediaBrowser.Controller.Entities.TV { public Series() { - RemoteTrailers = EmptyMediaUrlArray; + RemoteTrailers = Array.Empty(); LocalTrailerIds = Array.Empty(); RemoteTrailerIds = Array.Empty(); AirDays = Array.Empty(); diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 31cd429756..8379dcc090 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -167,7 +167,7 @@ namespace MediaBrowser.Controller.Entities AdditionalParts = Array.Empty(); LocalAlternateVersions = Array.Empty(); SubtitleFiles = Array.Empty(); - LinkedAlternateVersions = EmptyLinkedChildArray; + LinkedAlternateVersions = Array.Empty(); } public override bool CanDownload() diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index e0771245fd..ca99b28ca4 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text; namespace MediaBrowser.Model.IO { @@ -177,7 +176,7 @@ namespace MediaBrowser.Model.IO /// IEnumerable GetFiles(string path, bool recursive = false); - IEnumerable GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive); + IEnumerable GetFiles(string path, IReadOnlyList extensions, bool enableCaseSensitiveExtensions, bool recursive); /// /// Gets the file system entries. -- cgit v1.2.3 From 37ea50a572e8bd00aad39c193087228a5d3a3433 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 24 Feb 2019 15:47:59 +0100 Subject: Reduce the amount of exceptions thrown --- Emby.Dlna/PlayTo/Device.cs | 9 +- .../Diagnostics/CommonProcess.cs | 2 +- .../Library/LibraryManager.cs | 27 +- .../LiveTv/EmbyTV/EmbyTV.cs | 4 +- .../LiveTv/EmbyTV/ItemDataProvider.cs | 26 +- .../MediaEncoder/EncodingManager.cs | 4 + MediaBrowser.Api/ApiEntryPoint.cs | 5 + .../MediaEncoding/IMediaEncoder.cs | 2 +- .../Images/InternalMetadataFolderImageProvider.cs | 16 +- .../Encoder/EncodingUtils.cs | 6 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 64 +-- .../MediaInfo/SubtitleResolver.cs | 13 +- SocketHttpListener/Net/HttpConnection.cs | 532 +++++++++++++++++++++ 13 files changed, 630 insertions(+), 80 deletions(-) create mode 100644 SocketHttpListener/Net/HttpConnection.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index b62c5e1d4c..0c5ddee654 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -1126,6 +1126,11 @@ namespace Emby.Dlna.PlayTo private void OnPlaybackStart(uBaseObject mediaInfo) { + if (string.IsNullOrWhiteSpace(mediaInfo.Url)) + { + return; + } + PlaybackStart?.Invoke(this, new PlaybackStartEventArgs { MediaInfo = mediaInfo @@ -1134,8 +1139,7 @@ namespace Emby.Dlna.PlayTo private void OnPlaybackProgress(uBaseObject mediaInfo) { - var mediaUrl = mediaInfo.Url; - if (string.IsNullOrWhiteSpace(mediaUrl)) + if (string.IsNullOrWhiteSpace(mediaInfo.Url)) { return; } @@ -1148,7 +1152,6 @@ namespace Emby.Dlna.PlayTo private void OnPlaybackStop(uBaseObject mediaInfo) { - PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs { MediaInfo = mediaInfo diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index 78b22bda3f..2c4ef170dc 100644 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs @@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.Diagnostics public void Dispose() { - _process.Dispose(); + _process?.Dispose(); } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 3c2272b566..6591d54c56 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -278,6 +278,7 @@ namespace Emby.Server.Implementations.Library { throw new ArgumentNullException(nameof(item)); } + if (item is IItemByName) { if (!(item is MusicArtist)) @@ -285,18 +286,7 @@ namespace Emby.Server.Implementations.Library return; } } - - else if (item.IsFolder) - { - //if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel) && !(item is AggregateFolder)) - //{ - // if (item.SourceType != SourceType.Library) - // { - // return; - // } - //} - } - else + else if (!item.IsFolder) { if (!(item is Video) && !(item is LiveTvChannel)) { @@ -371,19 +361,20 @@ namespace Emby.Server.Implementations.Library foreach (var metadataPath in GetMetadataPaths(item, children)) { - _logger.LogDebug("Deleting path {0}", metadataPath); + if (!Directory.Exists(metadataPath)) + { + continue; + } + + _logger.LogDebug("Deleting path {MetadataPath}", metadataPath); try { Directory.Delete(metadataPath, true); - } - catch (IOException) - { - } catch (Exception ex) { - _logger.LogError(ex, "Error deleting {metadataPath}", metadataPath); + _logger.LogError(ex, "Error deleting {MetadataPath}", metadataPath); } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index fceb82ba19..f424bdf5c4 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -105,8 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _mediaSourceManager = mediaSourceManager; _streamHelper = streamHelper; - _seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers")); - _timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger); + _seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers.json")); + _timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers.json"), _logger); _timerProvider.TimerFired += _timerProvider_TimerFired; _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index a2ac60b319..9c45ee36a2 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -32,32 +31,28 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { if (_items == null) { + if (!File.Exists(_dataPath)) + { + return new List(); + } + Logger.LogInformation("Loading live tv data from {0}", _dataPath); _items = GetItemsFromFile(_dataPath); } + return _items.ToList(); } } private List GetItemsFromFile(string path) { - var jsonFile = path + ".json"; - - if (!File.Exists(jsonFile)) - { - return new List(); - } - try { - return _jsonSerializer.DeserializeFromFile>(jsonFile) ?? new List(); - } - catch (IOException) - { + return _jsonSerializer.DeserializeFromFile>(path); } catch (Exception ex) { - Logger.LogError(ex, "Error deserializing {jsonFile}", jsonFile); + Logger.LogError(ex, "Error deserializing {Path}", path); } return new List(); @@ -70,12 +65,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV throw new ArgumentNullException(nameof(newList)); } - var file = _dataPath + ".json"; - Directory.CreateDirectory(Path.GetDirectoryName(file)); + Directory.CreateDirectory(Path.GetDirectoryName(_dataPath)); lock (_fileDataLock) { - _jsonSerializer.SerializeToFile(newList, file); + _jsonSerializer.SerializeToFile(newList, _dataPath); _items = newList; } } diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index e68046f6d7..52d07d784d 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -202,6 +202,10 @@ namespace Emby.Server.Implementations.MediaEncoder private static List GetSavedChapterImages(Video video, IDirectoryService directoryService) { var path = GetChapterImagesPath(video); + if (!Directory.Exists(path)) + { + return new List(); + } try { diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index ceff6b02e5..700cbb9439 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -172,6 +172,11 @@ namespace MediaBrowser.Api { var path = _config.ApplicationPaths.GetTranscodingTempPath(); + if (!Directory.Exists(path)) + { + return; + } + foreach (var file in _fileSystem.GetFilePaths(path, true)) { _fileSystem.DeleteFile(file); diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index d4ac3b7c37..d032a849e7 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -74,7 +74,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// The input files. /// The protocol. /// System.String. - string GetInputArgument(string[] inputFiles, MediaProtocol protocol); + string GetInputArgument(IReadOnlyList inputFiles, MediaProtocol protocol); /// /// Gets the time parameter. diff --git a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs index c0706ceeb0..25a8ad5966 100644 --- a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Images { @@ -12,11 +13,16 @@ namespace MediaBrowser.LocalMetadata.Images { private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; - public InternalMetadataFolderImageProvider(IServerConfigurationManager config, IFileSystem fileSystem) + public InternalMetadataFolderImageProvider( + IServerConfigurationManager config, + IFileSystem fileSystem, + ILogger logger) { _config = config; _fileSystem = fileSystem; + _logger = logger; } public string Name => "Internal Images"; @@ -53,12 +59,18 @@ namespace MediaBrowser.LocalMetadata.Images { var path = item.GetInternalMetadataPath(); + if (!Directory.Exists(path)) + { + return new List(); + } + try { return new LocalImageProvider(_fileSystem).GetImages(item, path, false, directoryService); } - catch (IOException) + catch (IOException ex) { + _logger.LogError(ex, "Error while getting images for {Library}", item.Name); return new List(); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs index 44e62446b2..d4aede572b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs @@ -6,11 +6,11 @@ namespace MediaBrowser.MediaEncoding.Encoder { public static class EncodingUtils { - public static string GetInputArgument(List inputFiles, MediaProtocol protocol) + public static string GetInputArgument(IReadOnlyList inputFiles, MediaProtocol protocol) { if (protocol != MediaProtocol.File) { - var url = inputFiles.First(); + var url = inputFiles[0]; return string.Format("\"{0}\"", url); } @@ -29,7 +29,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // If there's more than one we'll need to use the concat command if (inputFiles.Count > 1) { - var files = string.Join("|", inputFiles.Select(NormalizePath).ToArray()); + var files = string.Join("|", inputFiles.Select(NormalizePath)); return string.Format("concat:\"{0}\"", files); } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 2924577888..f6ff719d58 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -334,10 +334,8 @@ namespace MediaBrowser.MediaEncoding.Encoder /// The protocol. /// System.String. /// Unrecognized InputType - public string GetInputArgument(string[] inputFiles, MediaProtocol protocol) - { - return EncodingUtils.GetInputArgument(inputFiles.ToList(), protocol); - } + public string GetInputArgument(IReadOnlyList inputFiles, MediaProtocol protocol) + => EncodingUtils.GetInputArgument(inputFiles, protocol); /// /// Gets the media info internal. @@ -354,8 +352,9 @@ namespace MediaBrowser.MediaEncoding.Encoder CancellationToken cancellationToken) { var args = extractChapters - ? "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_chapters -show_format" - : "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format"; + ? "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_chapters -show_format" + : "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format"; + args = string.Format(args, probeSizeArgument, inputPath).Trim(); var process = _processFactory.Create(new ProcessOptions { @@ -364,8 +363,10 @@ namespace MediaBrowser.MediaEncoding.Encoder // Must consume both or ffmpeg may hang due to deadlocks. See comments below. RedirectStandardOutput = true, + FileName = FFprobePath, - Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(), + Arguments = args, + IsHidden = true, ErrorDialog = false, @@ -383,43 +384,44 @@ namespace MediaBrowser.MediaEncoding.Encoder using (var processWrapper = new ProcessWrapper(process, this, _logger)) { + _logger.LogDebug("Starting ffprobe with args {Args}", args); StartProcess(processWrapper); + InternalMediaInfoResult result; try { - //process.BeginErrorReadLine(); + result = await _jsonSerializer.DeserializeFromStreamAsync(process.StandardOutput.BaseStream).ConfigureAwait(false); + } + catch + { + StopProcess(processWrapper, 100); - var result = await _jsonSerializer.DeserializeFromStreamAsync(process.StandardOutput.BaseStream).ConfigureAwait(false); + throw; + } - if (result == null || (result.streams == null && result.format == null)) - { - throw new Exception("ffprobe failed - streams and format are both null."); - } + if (result == null || (result.streams == null && result.format == null)) + { + throw new Exception("ffprobe failed - streams and format are both null."); + } - if (result.streams != null) + if (result.streams != null) + { + // Normalize aspect ratio if invalid + foreach (var stream in result.streams) { - // Normalize aspect ratio if invalid - foreach (var stream in result.streams) + if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) - { - stream.display_aspect_ratio = string.Empty; - } - if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) - { - stream.sample_aspect_ratio = string.Empty; - } + stream.display_aspect_ratio = string.Empty; } - } - return new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); + if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) + { + stream.sample_aspect_ratio = string.Empty; + } + } } - catch - { - StopProcess(processWrapper, 100); - throw; - } + return new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); } } diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index cd026b39b8..8195591e17 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ILocalizationManager _localization; private readonly IFileSystem _fileSystem; - private string[] SubtitleExtensions = new[] + private static readonly HashSet SubtitleExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { ".srt", ".ssa", @@ -49,9 +49,16 @@ namespace MediaBrowser.Providers.MediaInfo startIndex += streams.Count; + string folder = video.GetInternalMetadataPath(); + + if (!Directory.Exists(folder)) + { + return streams; + } + try { - AddExternalSubtitleStreams(streams, video.GetInternalMetadataPath(), video.Path, startIndex, directoryService, clearCache); + AddExternalSubtitleStreams(streams, folder, video.Path, startIndex, directoryService, clearCache); } catch (IOException) { @@ -105,7 +112,7 @@ namespace MediaBrowser.Providers.MediaInfo { var extension = Path.GetExtension(fullName); - if (!SubtitleExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!SubtitleExtensions.Contains(extension)) { continue; } diff --git a/SocketHttpListener/Net/HttpConnection.cs b/SocketHttpListener/Net/HttpConnection.cs new file mode 100644 index 0000000000..5beea5f221 --- /dev/null +++ b/SocketHttpListener/Net/HttpConnection.cs @@ -0,0 +1,532 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.System; +using Microsoft.Extensions.Logging; +namespace SocketHttpListener.Net +{ + sealed class HttpConnection + { + private static AsyncCallback s_onreadCallback = new AsyncCallback(OnRead); + const int BufferSize = 8192; + Socket _socket; + Stream _stream; + HttpEndPointListener _epl; + MemoryStream _memoryStream; + byte[] _buffer; + HttpListenerContext _context; + StringBuilder _currentLine; + ListenerPrefix _prefix; + HttpRequestStream _requestStream; + HttpResponseStream _responseStream; + bool _chunked; + int _reuses; + bool _contextBound; + bool secure; + IPEndPoint local_ep; + HttpListener _lastListener; + X509Certificate cert; + SslStream ssl_stream; + + private readonly ILogger _logger; + private readonly ICryptoProvider _cryptoProvider; + private readonly IStreamHelper _streamHelper; + private readonly IFileSystem _fileSystem; + private readonly IEnvironmentInfo _environment; + + public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure, + X509Certificate cert, ICryptoProvider cryptoProvider, IStreamHelper streamHelper, IFileSystem fileSystem, + IEnvironmentInfo environment) + { + _logger = logger; + this._socket = socket; + this._epl = epl; + this.secure = secure; + this.cert = cert; + _cryptoProvider = cryptoProvider; + _streamHelper = streamHelper; + _fileSystem = fileSystem; + _environment = environment; + + if (secure == false) + { + _stream = new SocketStream(_socket, false); + } + else + { + ssl_stream = new SslStream(new SocketStream(_socket, false), false, (t, c, ch, e) => + { + if (c == null) + { + return true; + } + + //var c2 = c as X509Certificate2; + //if (c2 == null) + //{ + // c2 = new X509Certificate2(c.GetRawCertData()); + //} + + //_clientCert = c2; + //_clientCertErrors = new int[] { (int)e }; + return true; + }); + + _stream = ssl_stream; + } + } + + public Stream Stream => _stream; + + public async Task Init() + { + if (ssl_stream != null) + { + var enableAsync = true; + if (enableAsync) + { + await ssl_stream.AuthenticateAsServerAsync(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false).ConfigureAwait(false); + } + else + { + ssl_stream.AuthenticateAsServer(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false); + } + } + + InitInternal(); + } + + private void InitInternal() + { + _contextBound = false; + _requestStream = null; + _responseStream = null; + _prefix = null; + _chunked = false; + _memoryStream = new MemoryStream(); + _position = 0; + _inputState = InputState.RequestLine; + _lineState = LineState.None; + _context = new HttpListenerContext(this); + } + + public bool IsClosed => (_socket == null); + + public int Reuses => _reuses; + + public IPEndPoint LocalEndPoint + { + get + { + if (local_ep != null) + return local_ep; + + local_ep = (IPEndPoint)_socket.LocalEndPoint; + return local_ep; + } + } + + public IPEndPoint RemoteEndPoint => _socket.RemoteEndPoint as IPEndPoint; + + public bool IsSecure => secure; + + public ListenerPrefix Prefix + { + get => _prefix; + set => _prefix = value; + } + + private void OnTimeout(object unused) + { + //_logger.LogInformation("HttpConnection timer fired"); + CloseSocket(); + Unbind(); + } + + public void BeginReadRequest() + { + if (_buffer == null) + { + _buffer = new byte[BufferSize]; + } + + try + { + _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); + } + catch + { + CloseSocket(); + Unbind(); + } + } + + public HttpRequestStream GetRequestStream(bool chunked, long contentlength) + { + if (_requestStream == null) + { + byte[] buffer = _memoryStream.GetBuffer(); + int length = (int)_memoryStream.Length; + _memoryStream = null; + if (chunked) + { + _chunked = true; + //_context.Response.SendChunked = true; + _requestStream = new ChunkedInputStream(_context, _stream, buffer, _position, length - _position); + } + else + { + _requestStream = new HttpRequestStream(_stream, buffer, _position, length - _position, contentlength); + } + } + return _requestStream; + } + + public HttpResponseStream GetResponseStream(bool isExpect100Continue = false) + { + // TODO: can we get this _stream before reading the input? + if (_responseStream == null) + { + var supportsDirectSocketAccess = !_context.Response.SendChunked && !isExpect100Continue && !secure; + + _responseStream = new HttpResponseStream(_stream, _context.Response, false, _streamHelper, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger); + } + return _responseStream; + } + + private static void OnRead(IAsyncResult ares) + { + var cnc = (HttpConnection)ares.AsyncState; + cnc.OnReadInternal(ares); + } + + private void OnReadInternal(IAsyncResult ares) + { + int nread = -1; + try + { + nread = _stream.EndRead(ares); + _memoryStream.Write(_buffer, 0, nread); + if (_memoryStream.Length > 32768) + { + SendError("Bad Request", 400); + Close(true); + return; + } + } + catch + { + if (_memoryStream != null && _memoryStream.Length > 0) + { + SendError(); + } + + if (_socket != null) + { + CloseSocket(); + Unbind(); + } + return; + } + + if (nread == 0) + { + CloseSocket(); + Unbind(); + return; + } + + if (ProcessInput(_memoryStream)) + { + if (!_context.HaveError) + _context.Request.FinishInitialization(); + + if (_context.HaveError) + { + SendError(); + Close(true); + return; + } + + if (!_epl.BindContext(_context)) + { + const int NotFoundErrorCode = 404; + SendError(HttpStatusDescription.Get(NotFoundErrorCode), NotFoundErrorCode); + Close(true); + return; + } + HttpListener listener = _epl.Listener; + if (_lastListener != listener) + { + RemoveConnection(); + listener.AddConnection(this); + _lastListener = listener; + } + + _contextBound = true; + listener.RegisterContext(_context); + return; + } + _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); + } + + private void RemoveConnection() + { + if (_lastListener == null) + _epl.RemoveConnection(this); + else + _lastListener.RemoveConnection(this); + } + + private enum InputState + { + RequestLine, + Headers + } + + private enum LineState + { + None, + CR, + LF + } + + InputState _inputState = InputState.RequestLine; + LineState _lineState = LineState.None; + int _position; + + // true -> done processing + // false -> need more input + private bool ProcessInput(MemoryStream ms) + { + byte[] buffer = ms.GetBuffer(); + int len = (int)ms.Length; + int used = 0; + string line; + + while (true) + { + if (_context.HaveError) + return true; + + if (_position >= len) + break; + + try + { + line = ReadLine(buffer, _position, len - _position, ref used); + _position += used; + } + catch + { + _context.ErrorMessage = "Bad request"; + _context.ErrorStatus = 400; + return true; + } + + if (line == null) + break; + + if (line == "") + { + if (_inputState == InputState.RequestLine) + continue; + _currentLine = null; + ms = null; + return true; + } + + if (_inputState == InputState.RequestLine) + { + _context.Request.SetRequestLine(line); + _inputState = InputState.Headers; + } + else + { + try + { + _context.Request.AddHeader(line); + } + catch (Exception e) + { + _context.ErrorMessage = e.Message; + _context.ErrorStatus = 400; + return true; + } + } + } + + if (used == len) + { + ms.SetLength(0); + _position = 0; + } + return false; + } + + private string ReadLine(byte[] buffer, int offset, int len, ref int used) + { + if (_currentLine == null) + _currentLine = new StringBuilder(128); + int last = offset + len; + used = 0; + for (int i = offset; i < last && _lineState != LineState.LF; i++) + { + used++; + byte b = buffer[i]; + if (b == 13) + { + _lineState = LineState.CR; + } + else if (b == 10) + { + _lineState = LineState.LF; + } + else + { + _currentLine.Append((char)b); + } + } + + string result = null; + if (_lineState == LineState.LF) + { + _lineState = LineState.None; + result = _currentLine.ToString(); + _currentLine.Length = 0; + } + + return result; + } + + public void SendError(string msg, int status) + { + try + { + HttpListenerResponse response = _context.Response; + response.StatusCode = status; + response.ContentType = "text/html"; + string description = HttpStatusDescription.Get(status); + string str; + if (msg != null) + str = string.Format("

{0} ({1})

", description, msg); + else + str = string.Format("

{0}

", description); + + byte[] error = Encoding.UTF8.GetBytes(str); + response.Close(error, false); + } + catch + { + // response was already closed + } + } + + public void SendError() + { + SendError(_context.ErrorMessage, _context.ErrorStatus); + } + + private void Unbind() + { + if (_contextBound) + { + _epl.UnbindContext(_context); + _contextBound = false; + } + } + + public void Close() + { + Close(false); + } + + private void CloseSocket() + { + if (_socket == null) + return; + + try + { + _socket.Close(); + } + catch { } + finally + { + _socket = null; + } + + RemoveConnection(); + } + + internal void Close(bool force) + { + if (_socket != null) + { + Stream st = GetResponseStream(); + if (st != null) + st.Close(); + + _responseStream = null; + } + + if (_socket != null) + { + force |= !_context.Request.KeepAlive; + if (!force) + { + force = string.Equals(_context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase); + } + + if (!force && _context.Request.FlushInput()) + { + if (_chunked && _context.Response.ForceCloseChunked == false) + { + // Don't close. Keep working. + _reuses++; + Unbind(); + InitInternal(); + BeginReadRequest(); + return; + } + + _reuses++; + Unbind(); + InitInternal(); + BeginReadRequest(); + return; + } + + Socket s = _socket; + _socket = null; + try + { + s?.Shutdown(SocketShutdown.Both); + } + catch + { + } + finally + { + try + { + s?.Close(); + } + catch { } + } + Unbind(); + RemoveConnection(); + return; + } + } + } +} -- cgit v1.2.3 From 620d7b560d0b72f532d96a5b459f14fd4ad3044a Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 28 Feb 2019 22:33:20 +0100 Subject: Fail on warnings for Jellyfin.Server --- .../Activity/ActivityLogEntryPoint.cs | 181 +++++++++------------ .../Emby.Server.Implementations.csproj | 15 ++ .../SocketSharp/HttpPostedFile.cs | 44 ++--- Jellyfin.Server/Jellyfin.Server.csproj | 7 +- jellyfin.ruleset | 5 + 5 files changed, 118 insertions(+), 134 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 739f687678..98cd97c318 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -30,13 +30,10 @@ namespace Emby.Server.Implementations.Activity public class ActivityLogEntryPoint : IServerEntryPoint { private readonly IInstallationManager _installationManager; - - //private readonly ILogger _logger; private readonly ISessionManager _sessionManager; private readonly ITaskManager _taskManager; private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; - private readonly ILibraryManager _libraryManager; private readonly ISubtitleManager _subManager; private readonly IUserManager _userManager; @@ -61,41 +58,37 @@ namespace Emby.Server.Implementations.Activity public Task RunAsync() { - _taskManager.TaskCompleted += _taskManager_TaskCompleted; - - _installationManager.PluginInstalled += _installationManager_PluginInstalled; - _installationManager.PluginUninstalled += _installationManager_PluginUninstalled; - _installationManager.PluginUpdated += _installationManager_PluginUpdated; - _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; + _taskManager.TaskCompleted += OnTaskCompleted; - _sessionManager.SessionStarted += _sessionManager_SessionStarted; - _sessionManager.AuthenticationFailed += _sessionManager_AuthenticationFailed; - _sessionManager.AuthenticationSucceeded += _sessionManager_AuthenticationSucceeded; - _sessionManager.SessionEnded += _sessionManager_SessionEnded; + _installationManager.PluginInstalled += OnPluginInstalled; + _installationManager.PluginUninstalled += OnPluginUninstalled; + _installationManager.PluginUpdated += OnPluginUpdated; + _installationManager.PackageInstallationFailed += OnPackageInstallationFailed; - _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + _sessionManager.SessionStarted += OnSessionStarted; + _sessionManager.AuthenticationFailed += OnAuthenticationFailed; + _sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded; + _sessionManager.SessionEnded += OnSessionEnded; - //_subManager.SubtitlesDownloaded += _subManager_SubtitlesDownloaded; - _subManager.SubtitleDownloadFailure += _subManager_SubtitleDownloadFailure; + _sessionManager.PlaybackStart += OnPlaybackStart; + _sessionManager.PlaybackStopped += OnPlaybackStopped; - _userManager.UserCreated += _userManager_UserCreated; - _userManager.UserPasswordChanged += _userManager_UserPasswordChanged; - _userManager.UserDeleted += _userManager_UserDeleted; - _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; - _userManager.UserLockedOut += _userManager_UserLockedOut; + _subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure; - //_config.ConfigurationUpdated += _config_ConfigurationUpdated; - //_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; + _userManager.UserCreated += OnUserCreated; + _userManager.UserPasswordChanged += OnUserPasswordChanged; + _userManager.UserDeleted += OnUserDeleted; + _userManager.UserPolicyUpdated += OnUserPolicyUpdated; + _userManager.UserLockedOut += OnUserLockedOut; - _deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded; + _deviceManager.CameraImageUploaded += OnCameraImageUploaded; - _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; + _appHost.ApplicationUpdated += OnApplicationUpdated; return Task.CompletedTask; } - void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs e) + private void OnCameraImageUploaded(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -104,7 +97,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _userManager_UserLockedOut(object sender, GenericEventArgs e) + private void OnUserLockedOut(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -114,7 +107,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) + private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -125,7 +118,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e) { var item = e.MediaInfo; @@ -146,7 +139,7 @@ namespace Emby.Server.Implementations.Activity return; } - var user = e.Users.First(); + var user = e.Users[0]; CreateLogEntry(new ActivityLogEntry { @@ -156,7 +149,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) + private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e) { var item = e.MediaInfo; @@ -232,7 +225,7 @@ namespace Emby.Server.Implementations.Activity return null; } - void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + private void OnSessionEnded(object sender, SessionEventArgs e) { string name; var session = e.SessionInfo; @@ -258,7 +251,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs e) + private void OnAuthenticationSucceeded(object sender, GenericEventArgs e) { var user = e.Argument.User; @@ -271,7 +264,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs e) + private void OnAuthenticationFailed(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -282,7 +275,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _appHost_ApplicationUpdated(object sender, GenericEventArgs e) + private void OnApplicationUpdated(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -292,25 +285,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) - { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key), - Type = "NamedConfigurationUpdated" - }); - } - - void _config_ConfigurationUpdated(object sender, EventArgs e) - { - CreateLogEntry(new ActivityLogEntry - { - Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"), - Type = "ServerConfigurationUpdated" - }); - } - - void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) + private void OnUserPolicyUpdated(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -320,7 +295,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _userManager_UserDeleted(object sender, GenericEventArgs e) + private void OnUserDeleted(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -329,7 +304,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _userManager_UserPasswordChanged(object sender, GenericEventArgs e) + private void OnUserPasswordChanged(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -339,7 +314,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _userManager_UserCreated(object sender, GenericEventArgs e) + private void OnUserCreated(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -349,18 +324,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e) - { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)), - Type = "SubtitlesDownloaded", - ItemId = e.Item.Id.ToString("N"), - ShortOverview = string.Format(_localization.GetLocalizedString("ProviderValue"), e.Provider) - }); - } - - void _sessionManager_SessionStarted(object sender, SessionEventArgs e) + private void OnSessionStarted(object sender, SessionEventArgs e) { string name; var session = e.SessionInfo; @@ -386,7 +350,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _installationManager_PluginUpdated(object sender, GenericEventArgs> e) + private void OnPluginUpdated(object sender, GenericEventArgs> e) { CreateLogEntry(new ActivityLogEntry { @@ -397,7 +361,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _installationManager_PluginUninstalled(object sender, GenericEventArgs e) + private void OnPluginUninstalled(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -406,7 +370,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _installationManager_PluginInstalled(object sender, GenericEventArgs e) + private void OnPluginInstalled(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -416,7 +380,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e) + private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) { var installationInfo = e.InstallationInfo; @@ -429,7 +393,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) + private void OnTaskCompleted(object sender, TaskCompletionEventArgs e) { var result = e.Result; var task = e.Task; @@ -468,48 +432,36 @@ namespace Emby.Server.Implementations.Activity } private void CreateLogEntry(ActivityLogEntry entry) - { - try - { - _activityManager.Create(entry); - } - catch - { - // Logged at lower levels - } - } + => _activityManager.Create(entry); public void Dispose() { - _taskManager.TaskCompleted -= _taskManager_TaskCompleted; + _taskManager.TaskCompleted -= OnTaskCompleted; - _installationManager.PluginInstalled -= _installationManager_PluginInstalled; - _installationManager.PluginUninstalled -= _installationManager_PluginUninstalled; - _installationManager.PluginUpdated -= _installationManager_PluginUpdated; - _installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed; + _installationManager.PluginInstalled -= OnPluginInstalled; + _installationManager.PluginUninstalled -= OnPluginUninstalled; + _installationManager.PluginUpdated -= OnPluginUpdated; + _installationManager.PackageInstallationFailed -= OnPackageInstallationFailed; - _sessionManager.SessionStarted -= _sessionManager_SessionStarted; - _sessionManager.AuthenticationFailed -= _sessionManager_AuthenticationFailed; - _sessionManager.AuthenticationSucceeded -= _sessionManager_AuthenticationSucceeded; - _sessionManager.SessionEnded -= _sessionManager_SessionEnded; + _sessionManager.SessionStarted -= OnSessionStarted; + _sessionManager.AuthenticationFailed -= OnAuthenticationFailed; + _sessionManager.AuthenticationSucceeded -= OnAuthenticationSucceeded; + _sessionManager.SessionEnded -= OnSessionEnded; - _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + _sessionManager.PlaybackStart -= OnPlaybackStart; + _sessionManager.PlaybackStopped -= OnPlaybackStopped; - _subManager.SubtitleDownloadFailure -= _subManager_SubtitleDownloadFailure; + _subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure; - _userManager.UserCreated -= _userManager_UserCreated; - _userManager.UserPasswordChanged -= _userManager_UserPasswordChanged; - _userManager.UserDeleted -= _userManager_UserDeleted; - _userManager.UserPolicyUpdated -= _userManager_UserPolicyUpdated; - _userManager.UserLockedOut -= _userManager_UserLockedOut; + _userManager.UserCreated -= OnUserCreated; + _userManager.UserPasswordChanged -= OnUserPasswordChanged; + _userManager.UserDeleted -= OnUserDeleted; + _userManager.UserPolicyUpdated -= OnUserPolicyUpdated; + _userManager.UserLockedOut -= OnUserLockedOut; - _config.ConfigurationUpdated -= _config_ConfigurationUpdated; - _config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated; + _deviceManager.CameraImageUploaded -= OnCameraImageUploaded; - _deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded; - - _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated; + _appHost.ApplicationUpdated -= OnApplicationUpdated; } /// @@ -531,6 +483,7 @@ namespace Emby.Server.Implementations.Activity values.Add(CreateValueString(years, "year")); days = days % DaysInYear; } + // Number of months if (days >= DaysInMonth) { @@ -538,25 +491,39 @@ namespace Emby.Server.Implementations.Activity values.Add(CreateValueString(months, "month")); days = days % DaysInMonth; } + // Number of days if (days >= 1) + { values.Add(CreateValueString(days, "day")); + } + // Number of hours if (span.Hours >= 1) + { values.Add(CreateValueString(span.Hours, "hour")); + } // Number of minutes if (span.Minutes >= 1) + { values.Add(CreateValueString(span.Minutes, "minute")); + } + // Number of seconds (include when 0 if no other components included) if (span.Seconds >= 1 || values.Count == 0) + { values.Add(CreateValueString(span.Seconds, "second")); + } // Combine values into string var builder = new StringBuilder(); for (int i = 0; i < values.Count; i++) { if (builder.Length > 0) + { builder.Append(i == values.Count - 1 ? " and " : ", "); + } + builder.Append(values[i]); } // Return result diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 39e9ed3756..3cbd2792d9 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -47,6 +47,21 @@ false + + true + + + + + + + + + + + ../jellyfin.ruleset + + diff --git a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs index f38ed848ee..95b7912fbb 100644 --- a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs +++ b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs @@ -63,6 +63,28 @@ public sealed class HttpPostedFile : IDisposable _position = offset; } + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => _end - _offset; + + public override long Position + { + get => _position - _offset; + set + { + if (value > Length) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _position = Seek(value, SeekOrigin.Begin); + } + } + public override void Flush() { } @@ -178,27 +200,5 @@ public sealed class HttpPostedFile : IDisposable { throw new NotSupportedException(); } - - public override bool CanRead => true; - - public override bool CanSeek => true; - - public override bool CanWrite => false; - - public override long Length => _end - _offset; - - public override long Position - { - get => _position - _offset; - set - { - if (value > Length) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _position = Seek(value, SeekOrigin.Begin); - } - } } } diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index bd670df527..9346a2d254 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -12,7 +12,8 @@ latest - SA1600;CS1591 + SA1600;SA1601;CS1591 + true @@ -23,10 +24,6 @@ - - true - - diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 4381349ca6..0a04b4c557 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -1,6 +1,11 @@ + + + + + -- cgit v1.2.3 From 3fa43a1e08a719e65ed38a57b556be0c0edacaef Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 7 Mar 2019 22:26:23 +0100 Subject: Don't set status code if response is closed --- .../HttpServer/HttpListenerHost.cs | 4 +-- .../Services/ServiceExec.cs | 2 +- .../SocketSharp/WebSocketSharpListener.cs | 5 ++- .../SocketSharp/WebSocketSharpRequest.cs | 7 ++-- .../SocketSharp/WebSocketSharpResponse.cs | 37 ++++++---------------- MediaBrowser.Model/Services/IRequest.cs | 14 +------- 6 files changed, 19 insertions(+), 50 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index d1b1b5b242..eab755cefb 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -225,7 +225,7 @@ namespace Emby.Server.Implementations.HttpServer var httpRes = httpReq.Response; - if (httpRes.IsClosed) + if (httpRes.OriginalResponse.HasStarted) { return; } @@ -595,8 +595,6 @@ namespace Emby.Server.Implementations.HttpServer } finally { - // TODO response closes automatically after the handler is done, but some functions rely on knowing if it's closed or not - httpRes.IsClosed = true; stopWatch.Stop(); var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index 79f5c59e65..38952628d8 100644 --- a/Emby.Server.Implementations/Services/ServiceExec.cs +++ b/Emby.Server.Implementations/Services/ServiceExec.cs @@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Services foreach (var requestFilter in actionContext.RequestFilters) { requestFilter.RequestFilter(request, request.Response, requestDto); - if (request.Response.IsClosed) + if (request.Response.OriginalResponse.HasStarted) { Task.FromResult(null); } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 2df826957a..dd313b3363 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -90,7 +90,10 @@ namespace Emby.Server.Implementations.SocketSharp catch (Exception ex) { _logger.LogError(ex, "AcceptWebSocketAsync error"); - ctx.Response.StatusCode = 500; + if (!ctx.Response.HasStarted) + { + ctx.Response.StatusCode = 500; + } } } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index bc002dc4ce..2d3ec3c8ea 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -20,22 +20,19 @@ namespace Emby.Server.Implementations.SocketSharp public partial class WebSocketSharpRequest : IHttpRequest { private readonly HttpRequest request; - private readonly IResponse response; public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger) { this.OperationName = operationName; this.request = httpContext; - this.response = new WebSocketSharpResponse(logger, response, this); + this.Response = new WebSocketSharpResponse(logger, response); // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); } public HttpRequest HttpRequest => request; - public IResponse Response => response; - - public IResponse HttpResponse => response; + public IResponse Response { get; } public string OperationName { get; set; } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index a7e3e6c700..0f67eaa622 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -16,38 +16,28 @@ namespace Emby.Server.Implementations.SocketSharp { private readonly ILogger _logger; - private readonly HttpResponse _response; - - public WebSocketSharpResponse(ILogger logger, HttpResponse response, IRequest request) + public WebSocketSharpResponse(ILogger logger, HttpResponse response) { _logger = logger; - this._response = response; - Items = new Dictionary(); - Request = request; + OriginalResponse = response; } - public IRequest Request { get; private set; } - - public Dictionary Items { get; private set; } - - public object OriginalResponse => _response; + public HttpResponse OriginalResponse { get; } public int StatusCode { - get => this._response.StatusCode; - set => this._response.StatusCode = value; + get => OriginalResponse.StatusCode; + set => OriginalResponse.StatusCode = value; } public string StatusDescription { get; set; } public string ContentType { - get => _response.ContentType; - set => _response.ContentType = value; + get => OriginalResponse.ContentType; + set => OriginalResponse.ContentType = value; } - public IHeaderDictionary Headers => _response.Headers; - public void AddHeader(string name, string value) { if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) @@ -56,22 +46,15 @@ namespace Emby.Server.Implementations.SocketSharp return; } - _response.Headers.Add(name, value); - } - - public string GetHeader(string name) - { - return _response.Headers[name]; + OriginalResponse.Headers.Add(name, value); } public void Redirect(string url) { - _response.Redirect(url); + OriginalResponse.Redirect(url); } - public Stream OutputStream => _response.Body; - - public bool IsClosed { get; set; } + public Stream OutputStream => OriginalResponse.Body; public bool SendChunked { get; set; } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index edb5a2509b..4f6ddb476e 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -101,7 +101,7 @@ namespace MediaBrowser.Model.Services public interface IResponse { - IRequest Request { get; } + HttpResponse OriginalResponse { get; } int StatusCode { get; set; } @@ -111,22 +111,10 @@ namespace MediaBrowser.Model.Services void AddHeader(string name, string value); - string GetHeader(string name); - void Redirect(string url); Stream OutputStream { get; } - /// - /// Gets a value indicating whether this instance is closed. - /// - bool IsClosed { get; set; } - - //Add Metadata to Response - Dictionary Items { get; } - - IHeaderDictionary Headers { get; } - Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken); bool SendChunked { get; set; } -- cgit v1.2.3 From decaffed86cbc5db99c3f79d266fdfcdd262c12d Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 17:39:40 +0100 Subject: Remove EnvironmentInfo This moved the last bit of usefulness of EnvironmentInfo into a static class. --- Emby.Dlna/Main/DlnaEntryPoint.cs | 13 ++-- Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs | 10 ++- Emby.Server.Implementations/ApplicationHost.cs | 47 +++++--------- .../EnvironmentInfo/EnvironmentInfo.cs | 36 ----------- Emby.Server.Implementations/IO/LibraryMonitor.cs | 15 +---- .../IO/ManagedFileSystem.cs | 16 +++-- .../Networking/NetworkManager.cs | 8 +-- Jellyfin.Server/CoreAppHost.cs | 3 - Jellyfin.Server/Program.cs | 51 ++------------- MediaBrowser.Api/LiveTv/LiveTvService.cs | 8 +-- MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs | 8 +-- .../Playback/Progressive/AudioService.cs | 8 +-- .../Progressive/BaseProgressiveStreamingService.cs | 15 ++--- .../Progressive/ProgressiveStreamWriter.cs | 10 ++- .../Playback/Progressive/VideoService.cs | 8 +-- MediaBrowser.Api/Playback/UniversalAudioService.cs | 7 +-- MediaBrowser.Common/IApplicationHost.cs | 3 +- MediaBrowser.Common/System/OperatingSystem.cs | 72 ++++++++++++++++++++++ MediaBrowser.Model/System/IEnvironmentInfo.cs | 21 ------- MediaBrowser.Model/System/OperatingSystemId.cs | 10 +++ 20 files changed, 144 insertions(+), 225 deletions(-) delete mode 100644 Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs create mode 100644 MediaBrowser.Common/System/OperatingSystem.cs delete mode 100644 MediaBrowser.Model/System/IEnvironmentInfo.cs create mode 100644 MediaBrowser.Model/System/OperatingSystemId.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 57ed0097a0..919a8f46f8 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; @@ -24,6 +23,7 @@ using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; using Rssdp; using Rssdp.Infrastructure; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Dlna.Main { @@ -48,9 +48,8 @@ namespace Emby.Dlna.Main private readonly IDeviceDiscovery _deviceDiscovery; private SsdpDevicePublisher _Publisher; - + private readonly ISocketFactory _socketFactory; - private readonly IEnvironmentInfo _environmentInfo; private readonly INetworkManager _networkManager; private ISsdpCommunicationsServer _communicationsServer; @@ -76,7 +75,6 @@ namespace Emby.Dlna.Main IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, - IEnvironmentInfo environmentInfo, INetworkManager networkManager, IUserViewManager userViewManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory, @@ -96,7 +94,6 @@ namespace Emby.Dlna.Main _deviceDiscovery = deviceDiscovery; _mediaEncoder = mediaEncoder; _socketFactory = socketFactory; - _environmentInfo = environmentInfo; _networkManager = networkManager; _logger = loggerFactory.CreateLogger("Dlna"); @@ -169,8 +166,8 @@ namespace Emby.Dlna.Main { if (_communicationsServer == null) { - var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows || - _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Linux; + var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows || + OperatingSystem.Id == OperatingSystemId.Linux; _communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding) { @@ -230,7 +227,7 @@ namespace Emby.Dlna.Main try { - _Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion, _config.GetDlnaConfiguration().SendOnlyMatchedHost); + _Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost); _Publisher.LogFunction = LogMessage; _Publisher.SupportPnpRootDevice = false; diff --git a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs index 943caa3e62..2f0003be88 100644 --- a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs +++ b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs @@ -7,6 +7,7 @@ using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace IsoMounter { @@ -17,7 +18,6 @@ namespace IsoMounter #region Private Fields - private readonly IEnvironmentInfo EnvironmentInfo; private readonly bool ExecutablesAvailable; private readonly ILogger _logger; private readonly string MountCommand; @@ -30,10 +30,8 @@ namespace IsoMounter #region Constructor(s) - public LinuxIsoManager(ILogger logger, IEnvironmentInfo environment, IProcessFactory processFactory) + public LinuxIsoManager(ILogger logger, IProcessFactory processFactory) { - - EnvironmentInfo = environment; _logger = logger; ProcessFactory = processFactory; @@ -109,7 +107,7 @@ namespace IsoMounter public bool CanMount(string path) { - if (EnvironmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Linux) + if (OperatingSystem.Id != OperatingSystemId.Linux) { return false; } @@ -118,7 +116,7 @@ namespace IsoMounter Name, path, Path.GetExtension(path), - EnvironmentInfo.OperatingSystem, + OperatingSystem.Name, ExecutablesAvailable ); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a581214c71..964d173421 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -115,6 +115,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using ServiceStack; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations { @@ -143,12 +144,8 @@ namespace Emby.Server.Implementations return false; } - if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) - { - return true; - } - - if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) + if (OperatingSystem.Id == OperatingSystemId.Windows + || OperatingSystem.Id == OperatingSystemId.Darwin) { return true; } @@ -218,8 +215,6 @@ namespace Emby.Server.Implementations public IFileSystem FileSystemManager { get; set; } - protected IEnvironmentInfo EnvironmentInfo { get; set; } - public PackageVersionClass SystemUpdateLevel { get @@ -239,15 +234,6 @@ namespace Emby.Server.Implementations /// The server configuration manager. public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager; - /// - /// Gets the configuration manager. - /// - /// IConfigurationManager. - protected IConfigurationManager GetConfigurationManager() - { - return new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager); - } - protected virtual IResourceFileManager CreateResourceFileManager() { return new ResourceFileManager(HttpResultFactory, LoggerFactory, FileSystemManager); @@ -356,7 +342,6 @@ namespace Emby.Server.Implementations ILoggerFactory loggerFactory, IStartupOptions options, IFileSystem fileSystem, - IEnvironmentInfo environmentInfo, IImageEncoder imageEncoder, INetworkManager networkManager, IConfiguration configuration) @@ -370,13 +355,12 @@ namespace Emby.Server.Implementations NetworkManager = networkManager; networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets; - EnvironmentInfo = environmentInfo; ApplicationPaths = applicationPaths; LoggerFactory = loggerFactory; FileSystemManager = fileSystem; - ConfigurationManager = GetConfigurationManager(); + ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager); Logger = LoggerFactory.CreateLogger("App"); @@ -532,7 +516,7 @@ namespace Emby.Server.Implementations /// /// Runs the startup tasks. /// - public async Task RunStartupTasks() + public async Task RunStartupTasksAsync() { Logger.LogInformation("Running startup tasks"); @@ -584,7 +568,7 @@ namespace Emby.Server.Implementations } } - public async Task Init(IServiceCollection serviceCollection) + public async Task InitAsync(IServiceCollection serviceCollection) { HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; @@ -706,8 +690,6 @@ namespace Emby.Server.Implementations serviceCollection.AddLogging(); serviceCollection.AddSingleton(Logger); - serviceCollection.AddSingleton(EnvironmentInfo); - serviceCollection.AddSingleton(FileSystemManager); serviceCollection.AddSingleton(); @@ -786,7 +768,7 @@ namespace Emby.Server.Implementations var musicManager = new MusicManager(LibraryManager); serviceCollection.AddSingleton(new MusicManager(LibraryManager)); - LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo); + LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager); serviceCollection.AddSingleton(LibraryMonitor); serviceCollection.AddSingleton(new SearchEngine(LoggerFactory, LibraryManager, UserManager)); @@ -907,7 +889,7 @@ namespace Emby.Server.Implementations public virtual string PackageRuntime => "netcore"; - public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo) + public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) { // Distinct these to prevent users from reporting problems that aren't actually problems var commandLineArgs = Environment @@ -915,8 +897,9 @@ namespace Emby.Server.Implementations .Distinct(); logger.LogInformation("Arguments: {Args}", commandLineArgs); - logger.LogInformation("Operating system: {OS} {OSVersion}", environmentInfo.OperatingSystemName, environmentInfo.OperatingSystemVersion); - logger.LogInformation("Architecture: {Architecture}", environmentInfo.SystemArchitecture); + // FIXME: @bond this logs the kernel version, not the OS version + logger.LogInformation("Operating system: {OS} {OSVersion}", OperatingSystem.Name, Environment.OSVersion.Version); + logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture); logger.LogInformation("64-Bit Process: {Is64Bit}", Environment.Is64BitProcess); logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive); logger.LogInformation("Processor count: {ProcessorCount}", Environment.ProcessorCount); @@ -1400,8 +1383,8 @@ namespace Emby.Server.Implementations HttpServerPortNumber = HttpPort, SupportsHttps = SupportsHttps, HttpsPortNumber = HttpsPort, - OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(), - OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName, + OperatingSystem = OperatingSystem.Id.ToString(), + OperatingSystemDisplayName = OperatingSystem.Name, CanSelfRestart = CanSelfRestart, CanLaunchWebBrowser = CanLaunchWebBrowser, WanAddress = wanAddress, @@ -1411,7 +1394,7 @@ namespace Emby.Server.Implementations LocalAddress = localAddress, SupportsLibraryMonitor = true, EncoderLocation = MediaEncoder.EncoderLocation, - SystemArchitecture = EnvironmentInfo.SystemArchitecture, + SystemArchitecture = RuntimeInformation.OSArchitecture, SystemUpdateLevel = SystemUpdateLevel, PackageName = StartupOptions.PackageName }; @@ -1435,7 +1418,7 @@ namespace Emby.Server.Implementations { Version = ApplicationVersion, Id = SystemId, - OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(), + OperatingSystem = OperatingSystem.Id.ToString(), WanAddress = wanAddress, ServerName = FriendlyName, LocalAddress = localAddress diff --git a/Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs b/Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs deleted file mode 100644 index c8104150d6..0000000000 --- a/Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using MediaBrowser.Model.System; - -namespace Emby.Server.Implementations.EnvironmentInfo -{ - public class EnvironmentInfo : IEnvironmentInfo - { - public EnvironmentInfo(MediaBrowser.Model.System.OperatingSystem operatingSystem) - { - OperatingSystem = operatingSystem; - } - - public MediaBrowser.Model.System.OperatingSystem OperatingSystem { get; private set; } - - public string OperatingSystemName - { - get - { - switch (OperatingSystem) - { - case MediaBrowser.Model.System.OperatingSystem.Android: return "Android"; - case MediaBrowser.Model.System.OperatingSystem.BSD: return "BSD"; - case MediaBrowser.Model.System.OperatingSystem.Linux: return "Linux"; - case MediaBrowser.Model.System.OperatingSystem.OSX: return "macOS"; - case MediaBrowser.Model.System.OperatingSystem.Windows: return "Windows"; - default: throw new Exception($"Unknown OS {OperatingSystem}"); - } - } - } - - public string OperatingSystemVersion => Environment.OSVersion.Version.ToString() + " " + Environment.OSVersion.ServicePack.ToString(); - - public Architecture SystemArchitecture => RuntimeInformation.OSArchitecture; - } -} diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index d473425115..df4dc41b99 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -10,8 +10,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; -using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations.IO { @@ -127,7 +127,6 @@ namespace Emby.Server.Implementations.IO private IServerConfigurationManager ConfigurationManager { get; set; } private readonly IFileSystem _fileSystem; - private readonly IEnvironmentInfo _environmentInfo; /// /// Initializes a new instance of the class. @@ -136,14 +135,12 @@ namespace Emby.Server.Implementations.IO ILoggerFactory loggerFactory, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, - IFileSystem fileSystem, - IEnvironmentInfo environmentInfo) + IFileSystem fileSystem) { LibraryManager = libraryManager; Logger = loggerFactory.CreateLogger(GetType().Name); ConfigurationManager = configurationManager; _fileSystem = fileSystem; - _environmentInfo = environmentInfo; } private bool IsLibraryMonitorEnabled(BaseItem item) @@ -267,7 +264,7 @@ namespace Emby.Server.Implementations.IO return; } - if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) + if (OperatingSystem.Id != OperatingSystemId.Windows) { if (path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase) || path.StartsWith("smb://", StringComparison.OrdinalIgnoreCase)) { @@ -276,12 +273,6 @@ namespace Emby.Server.Implementations.IO } } - if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Android) - { - // causing crashing - return; - } - // Already being watched if (_fileSystemWatchers.ContainsKey(path)) { diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 421592fada..47cea7269d 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -8,6 +8,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations.IO { @@ -24,22 +25,19 @@ namespace Emby.Server.Implementations.IO private readonly string _tempPath; - private readonly IEnvironmentInfo _environmentInfo; private readonly bool _isEnvironmentCaseInsensitive; public ManagedFileSystem( ILoggerFactory loggerFactory, - IEnvironmentInfo environmentInfo, IApplicationPaths applicationPaths) { Logger = loggerFactory.CreateLogger("FileSystem"); _supportsAsyncFileStreams = true; _tempPath = applicationPaths.TempDirectory; - _environmentInfo = environmentInfo; - SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows); + SetInvalidFileNameChars(OperatingSystem.Id == OperatingSystemId.Windows); - _isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows; + _isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows; } public virtual void AddShortcutHandler(IShortcutHandler handler) @@ -468,7 +466,7 @@ namespace Emby.Server.Implementations.IO public virtual void SetHidden(string path, bool isHidden) { - if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) + if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows) { return; } @@ -492,7 +490,7 @@ namespace Emby.Server.Implementations.IO public virtual void SetReadOnly(string path, bool isReadOnly) { - if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) + if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows) { return; } @@ -516,7 +514,7 @@ namespace Emby.Server.Implementations.IO public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly) { - if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) + if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows) { return; } @@ -801,7 +799,7 @@ namespace Emby.Server.Implementations.IO public virtual void SetExecutable(string path) { - if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) + if (OperatingSystem.Id == MediaBrowser.Model.System.OperatingSystemId.Darwin) { RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path)); } diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index ace93ebdee..c102f9eb55 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -7,11 +7,11 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading.Tasks; using MediaBrowser.Common.Net; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations.Networking { @@ -22,14 +22,12 @@ namespace Emby.Server.Implementations.Networking public event EventHandler NetworkChanged; public Func LocalSubnetsFn { get; set; } - public NetworkManager( - ILoggerFactory loggerFactory, - IEnvironmentInfo environment) + public NetworkManager(ILoggerFactory loggerFactory) { Logger = loggerFactory.CreateLogger(nameof(NetworkManager)); // In FreeBSD these events cause a crash - if (environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.BSD) + if (OperatingSystem.Id != OperatingSystemId.BSD) { try { diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 17259c7370..8e6ed7a7e5 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -3,7 +3,6 @@ using System.Reflection; using Emby.Server.Implementations; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -16,7 +15,6 @@ namespace Jellyfin.Server ILoggerFactory loggerFactory, StartupOptions options, IFileSystem fileSystem, - IEnvironmentInfo environmentInfo, MediaBrowser.Controller.Drawing.IImageEncoder imageEncoder, MediaBrowser.Common.Net.INetworkManager networkManager, IConfiguration configuration) @@ -25,7 +23,6 @@ namespace Jellyfin.Server loggerFactory, options, fileSystem, - environmentInfo, imageEncoder, networkManager, configuration) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 0ef1711d4c..a60f2a4f61 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -12,7 +12,6 @@ using System.Threading.Tasks; using CommandLine; using Emby.Drawing; using Emby.Server.Implementations; -using Emby.Server.Implementations.EnvironmentInfo; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Networking; using Jellyfin.Drawing.Skia; @@ -46,7 +45,6 @@ namespace Jellyfin.Server const string pattern = @"^(-[^-\s]{2})"; // Match -xx, not -x, not --xx, not xx const string substitution = @"-$1"; // Prepend with additional single-hyphen var regex = new Regex(pattern); - for (var i = 0; i < args.Length; i++) { args[i] = regex.Replace(args[i], substitution); @@ -54,9 +52,7 @@ namespace Jellyfin.Server // Parse the command line arguments and either start the app or exit indicating error await Parser.Default.ParseArguments(args) - .MapResult( - options => StartApp(options), - errs => Task.FromResult(0)).ConfigureAwait(false); + .MapResult(StartApp, _ => Task.CompletedTask).ConfigureAwait(false); } public static void Shutdown() @@ -119,31 +115,29 @@ namespace Jellyfin.Server _logger.LogInformation("Jellyfin version: {Version}", Assembly.GetEntryAssembly().GetName().Version); - EnvironmentInfo environmentInfo = new EnvironmentInfo(GetOperatingSystem()); - ApplicationHost.LogEnvironmentInfo(_logger, appPaths, environmentInfo); + ApplicationHost.LogEnvironmentInfo(_logger, appPaths); SQLitePCL.Batteries_V2.Init(); // Allow all https requests ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); - var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, appPaths); + var fileSystem = new ManagedFileSystem(_loggerFactory, appPaths); using (var appHost = new CoreAppHost( appPaths, _loggerFactory, options, fileSystem, - environmentInfo, new NullImageEncoder(), - new NetworkManager(_loggerFactory, environmentInfo), + new NetworkManager(_loggerFactory), appConfig)) { - await appHost.Init(new ServiceCollection()).ConfigureAwait(false); + await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false); appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); - await appHost.RunStartupTasks().ConfigureAwait(false); + await appHost.RunStartupTasksAsync().ConfigureAwait(false); try { @@ -179,7 +173,6 @@ namespace Jellyfin.Server // ELSE IF $XDG_DATA_HOME then use $XDG_DATA_HOME/jellyfin // ELSE use $HOME/.local/share/jellyfin var dataDir = options.DataDir; - if (string.IsNullOrEmpty(dataDir)) { dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH"); @@ -236,7 +229,6 @@ namespace Jellyfin.Server // ELSE IF XDG_CACHE_HOME, use $XDG_CACHE_HOME/jellyfin // ELSE HOME/.cache/jellyfin var cacheDir = options.CacheDir; - if (string.IsNullOrEmpty(cacheDir)) { cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR"); @@ -270,7 +262,6 @@ namespace Jellyfin.Server // ELSE IF --datadir, use /log (assume portable run) // ELSE /log var logDir = options.LogDir; - if (string.IsNullOrEmpty(logDir)) { logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR"); @@ -364,36 +355,6 @@ namespace Jellyfin.Server return new NullImageEncoder(); } - private static MediaBrowser.Model.System.OperatingSystem GetOperatingSystem() - { - switch (Environment.OSVersion.Platform) - { - case PlatformID.MacOSX: - return MediaBrowser.Model.System.OperatingSystem.OSX; - case PlatformID.Win32NT: - return MediaBrowser.Model.System.OperatingSystem.Windows; - case PlatformID.Unix: - default: - { - string osDescription = RuntimeInformation.OSDescription; - if (osDescription.Contains("linux", StringComparison.OrdinalIgnoreCase)) - { - return MediaBrowser.Model.System.OperatingSystem.Linux; - } - else if (osDescription.Contains("darwin", StringComparison.OrdinalIgnoreCase)) - { - return MediaBrowser.Model.System.OperatingSystem.OSX; - } - else if (osDescription.Contains("bsd", StringComparison.OrdinalIgnoreCase)) - { - return MediaBrowser.Model.System.OperatingSystem.BSD; - } - - throw new Exception($"Can't resolve OS with description: '{osDescription}'"); - } - } - } - private static void StartNewInstance(StartupOptions options) { _logger.LogInformation("Starting new instance"); diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 88ed734569..486d5e8a71 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -698,12 +698,11 @@ namespace MediaBrowser.Api.LiveTv private readonly IFileSystem _fileSystem; private readonly IAuthorizationContext _authContext; private readonly ISessionContext _sessionContext; - private readonly IEnvironmentInfo _environment; private ICryptoProvider _cryptographyProvider; private IStreamHelper _streamHelper; private IMediaSourceManager _mediaSourceManager; - public LiveTvService(ICryptoProvider crypto, IMediaSourceManager mediaSourceManager, IStreamHelper streamHelper, ILiveTvManager liveTvManager, IUserManager userManager, IServerConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService, IFileSystem fileSystem, IAuthorizationContext authContext, ISessionContext sessionContext, IEnvironmentInfo environment) + public LiveTvService(ICryptoProvider crypto, IMediaSourceManager mediaSourceManager, IStreamHelper streamHelper, ILiveTvManager liveTvManager, IUserManager userManager, IServerConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService, IFileSystem fileSystem, IAuthorizationContext authContext, ISessionContext sessionContext) { _liveTvManager = liveTvManager; _userManager = userManager; @@ -714,7 +713,6 @@ namespace MediaBrowser.Api.LiveTv _fileSystem = fileSystem; _authContext = authContext; _sessionContext = sessionContext; - _environment = environment; _cryptographyProvider = crypto; _streamHelper = streamHelper; _mediaSourceManager = mediaSourceManager; @@ -756,7 +754,7 @@ namespace MediaBrowser.Api.LiveTv [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType(path) }; - return new ProgressiveFileCopier(_fileSystem, _streamHelper, path, outputHeaders, Logger, _environment) + return new ProgressiveFileCopier(_fileSystem, _streamHelper, path, outputHeaders, Logger) { AllowEndOfFile = false }; @@ -779,7 +777,7 @@ namespace MediaBrowser.Api.LiveTv [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file." + request.Container) }; - return new ProgressiveFileCopier(directStreamProvider, _streamHelper, outputHeaders, Logger, _environment) + return new ProgressiveFileCopier(directStreamProvider, _streamHelper, outputHeaders, Logger) { AllowEndOfFile = false }; diff --git a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs index 8412bf66b9..51552d928b 100644 --- a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs +++ b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.LiveTv @@ -23,25 +22,22 @@ namespace MediaBrowser.Api.LiveTv public bool AllowEndOfFile = true; private readonly IDirectStreamProvider _directStreamProvider; - private readonly IEnvironmentInfo _environment; private IStreamHelper _streamHelper; - public ProgressiveFileCopier(IFileSystem fileSystem, IStreamHelper streamHelper, string path, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment) + public ProgressiveFileCopier(IFileSystem fileSystem, IStreamHelper streamHelper, string path, Dictionary outputHeaders, ILogger logger) { _fileSystem = fileSystem; _path = path; _outputHeaders = outputHeaders; _logger = logger; - _environment = environment; _streamHelper = streamHelper; } - public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, IStreamHelper streamHelper, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment) + public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, IStreamHelper streamHelper, Dictionary outputHeaders, ILogger logger) { _directStreamProvider = directStreamProvider; _outputHeaders = outputHeaders; _logger = logger; - _environment = environment; _streamHelper = streamHelper; } diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 48b4e2f24e..dfe4b2b8e9 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -3,7 +3,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; @@ -11,7 +10,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; namespace MediaBrowser.Api.Playback.Progressive { @@ -46,8 +44,7 @@ namespace MediaBrowser.Api.Playback.Progressive IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - IEnvironmentInfo environmentInfo) + IAuthorizationContext authorizationContext) : base(httpClient, serverConfig, userManager, @@ -60,8 +57,7 @@ namespace MediaBrowser.Api.Playback.Progressive deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext, - environmentInfo) + authorizationContext) { } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index f43f26b2f3..a2c20e38fd 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -8,15 +7,12 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Playback.Progressive @@ -26,7 +22,6 @@ namespace MediaBrowser.Api.Playback.Progressive /// public abstract class BaseProgressiveStreamingService : BaseStreamingService { - protected readonly IEnvironmentInfo EnvironmentInfo; protected IHttpClient HttpClient { get; private set; } public BaseProgressiveStreamingService( @@ -42,8 +37,7 @@ namespace MediaBrowser.Api.Playback.Progressive IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - IEnvironmentInfo environmentInfo) + IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, @@ -57,7 +51,6 @@ namespace MediaBrowser.Api.Playback.Progressive jsonSerializer, authorizationContext) { - EnvironmentInfo = environmentInfo; HttpClient = httpClient; } @@ -157,7 +150,7 @@ namespace MediaBrowser.Api.Playback.Progressive // TODO: Don't hardcode this outputHeaders[HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file.ts"); - return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) + return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, CancellationToken.None) { AllowEndOfFile = false }; @@ -203,7 +196,7 @@ namespace MediaBrowser.Api.Playback.Progressive }; - return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) + return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, CancellationToken.None) { AllowEndOfFile = false }; @@ -397,7 +390,7 @@ namespace MediaBrowser.Api.Playback.Progressive outputHeaders[item.Key] = item.Value; } - return new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, EnvironmentInfo, CancellationToken.None); + return new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, CancellationToken.None); } finally { diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index 3dd9de2a1b..6609120655 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -8,6 +8,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace MediaBrowser.Api.Playback.Progressive { @@ -27,9 +28,8 @@ namespace MediaBrowser.Api.Playback.Progressive public bool AllowEndOfFile = true; private readonly IDirectStreamProvider _directStreamProvider; - private readonly IEnvironmentInfo _environment; - public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary outputHeaders, TranscodingJob job, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) + public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _fileSystem = fileSystem; _path = path; @@ -37,17 +37,15 @@ namespace MediaBrowser.Api.Playback.Progressive _job = job; _logger = logger; _cancellationToken = cancellationToken; - _environment = environment; } - public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary outputHeaders, TranscodingJob job, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) + public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _directStreamProvider = directStreamProvider; _outputHeaders = outputHeaders; _job = job; _logger = logger; _cancellationToken = cancellationToken; - _environment = environment; } public IDictionary Headers => _outputHeaders; @@ -79,7 +77,7 @@ namespace MediaBrowser.Api.Playback.Progressive var eofCount = 0; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - var allowAsyncFileRead = _environment.OperatingSystem != Model.System.OperatingSystem.Windows; + var allowAsyncFileRead = OperatingSystem.Id != OperatingSystemId.Windows; using (var inputStream = GetInputStream(allowAsyncFileRead)) { diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index bf15cc756c..ab19fdc261 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -3,7 +3,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; @@ -11,7 +10,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; namespace MediaBrowser.Api.Playback.Progressive { @@ -83,8 +81,7 @@ namespace MediaBrowser.Api.Playback.Progressive IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - IEnvironmentInfo environmentInfo) + IAuthorizationContext authorizationContext) : base(httpClient, serverConfig, userManager, @@ -97,8 +94,7 @@ namespace MediaBrowser.Api.Playback.Progressive deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext, - environmentInfo) + authorizationContext) { } diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs index f97e88e986..b3d8bfe59f 100644 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs @@ -18,7 +18,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Playback @@ -93,7 +92,6 @@ namespace MediaBrowser.Api.Playback IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, INetworkManager networkManager, - IEnvironmentInfo environmentInfo, ILoggerFactory loggerFactory) { HttpClient = httpClient; @@ -112,7 +110,6 @@ namespace MediaBrowser.Api.Playback AuthorizationContext = authorizationContext; ImageProcessor = imageProcessor; NetworkManager = networkManager; - EnvironmentInfo = environmentInfo; _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(nameof(UniversalAudioService)); } @@ -133,7 +130,6 @@ namespace MediaBrowser.Api.Playback protected IAuthorizationContext AuthorizationContext { get; private set; } protected IImageProcessor ImageProcessor { get; private set; } protected INetworkManager NetworkManager { get; private set; } - protected IEnvironmentInfo EnvironmentInfo { get; private set; } private ILoggerFactory _loggerFactory; private ILogger _logger; @@ -338,8 +334,7 @@ namespace MediaBrowser.Api.Playback DeviceManager, MediaSourceManager, JsonSerializer, - AuthorizationContext, - EnvironmentInfo) + AuthorizationContext) { Request = Request }; diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 3a4098612d..966448d630 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Events; @@ -107,7 +106,7 @@ namespace MediaBrowser.Common /// /// Inits this instance. /// - Task Init(IServiceCollection serviceCollection); + Task InitAsync(IServiceCollection serviceCollection); /// /// Creates the instance. diff --git a/MediaBrowser.Common/System/OperatingSystem.cs b/MediaBrowser.Common/System/OperatingSystem.cs new file mode 100644 index 0000000000..640821d4d7 --- /dev/null +++ b/MediaBrowser.Common/System/OperatingSystem.cs @@ -0,0 +1,72 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using MediaBrowser.Model.System; + +namespace MediaBrowser.Common.System +{ + public static class OperatingSystem + { + // We can't use Interlocked.CompareExchange for enums + private static int _id = int.MaxValue; + + public static OperatingSystemId Id + { + get + { + if (_id == int.MaxValue) + { + Interlocked.CompareExchange(ref _id, (int)GetId(), int.MaxValue); + } + + return (OperatingSystemId)_id; + } + } + + public static string Name + { + get + { + switch (Id) + { + case OperatingSystemId.BSD: return "BSD"; + case OperatingSystemId.Linux: return "Linux"; + case OperatingSystemId.Darwin: return "macOS"; + case OperatingSystemId.Windows: return "Windows"; + default: throw new Exception($"Unknown OS {Id}"); + } + } + } + + private static OperatingSystemId GetId() + { + switch (Environment.OSVersion.Platform) + { + // On .NET Core `MacOSX` got replaced by `Unix`, this case should never be hit. + case PlatformID.MacOSX: + return OperatingSystemId.Darwin; + case PlatformID.Win32NT: + return OperatingSystemId.Windows; + case PlatformID.Unix: + default: + { + string osDescription = RuntimeInformation.OSDescription; + if (osDescription.IndexOf("linux", StringComparison.OrdinalIgnoreCase) != -1) + { + return OperatingSystemId.Linux; + } + else if (osDescription.IndexOf("darwin", StringComparison.OrdinalIgnoreCase) != -1) + { + return OperatingSystemId.Darwin; + } + else if (osDescription.IndexOf("bsd", StringComparison.OrdinalIgnoreCase) != -1) + { + return OperatingSystemId.BSD; + } + + throw new Exception($"Can't resolve OS with description: '{osDescription}'"); + } + } + } + } +} diff --git a/MediaBrowser.Model/System/IEnvironmentInfo.cs b/MediaBrowser.Model/System/IEnvironmentInfo.cs deleted file mode 100644 index 3ffcc7de14..0000000000 --- a/MediaBrowser.Model/System/IEnvironmentInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.InteropServices; - -namespace MediaBrowser.Model.System -{ - public interface IEnvironmentInfo - { - OperatingSystem OperatingSystem { get; } - string OperatingSystemName { get; } - string OperatingSystemVersion { get; } - Architecture SystemArchitecture { get; } - } - - public enum OperatingSystem - { - Windows, - Linux, - OSX, - BSD, - Android - } -} diff --git a/MediaBrowser.Model/System/OperatingSystemId.cs b/MediaBrowser.Model/System/OperatingSystemId.cs new file mode 100644 index 0000000000..e81dd4213f --- /dev/null +++ b/MediaBrowser.Model/System/OperatingSystemId.cs @@ -0,0 +1,10 @@ +namespace MediaBrowser.Model.System +{ + public enum OperatingSystemId + { + Windows, + Linux, + Darwin, + BSD + } +} -- cgit v1.2.3 From e3b844b5aa34ef6a8f4a23c053115f4eabadcbf7 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 7 Mar 2019 22:49:41 +0100 Subject: Add urlprefixes during init --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 6 ++++-- MediaBrowser.Controller/Net/IHttpServer.cs | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a581214c71..f7d9bad1b0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1056,7 +1056,7 @@ namespace Emby.Server.Implementations .Where(i => i != null) .ToArray(); - HttpServer.Init(GetExports(false), GetExports()); + HttpServer.Init(GetExports(false), GetExports(), GetUrlPrefixes()); LibraryManager.AddParts(GetExports(), GetExports(), diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index eab755cefb..e8d47cad52 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -676,10 +676,12 @@ namespace Emby.Server.Implementations.HttpServer /// Adds the rest handlers. /// /// The services. - public void Init(IEnumerable services, IEnumerable listeners) + /// + /// + public void Init(IEnumerable services, IEnumerable listeners, IEnumerable urlPrefixes) { _webSocketListeners = listeners.ToArray(); - + UrlPrefixes = urlPrefixes.ToArray(); ServiceController = new ServiceController(); Logger.LogInformation("Calling ServiceStack AppHost.Init"); diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index cede9a98fb..46933c0465 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Net /// /// Inits this instance. /// - void Init(IEnumerable services, IEnumerable listener); + void Init(IEnumerable services, IEnumerable listener, IEnumerable urlPrefixes); /// /// If set, all requests will respond with this message -- cgit v1.2.3 From 58061a729528e3969b07b35fdaac534f5948eb89 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 8 Mar 2019 17:15:52 +0100 Subject: Make MusicBrainz base url configurable --- Emby.Server.Implementations/ConfigurationOptions.cs | 3 ++- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 1 + MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs | 12 ++++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 30bfd87498..9bc60972a1 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -6,7 +6,8 @@ namespace Emby.Server.Implementations { public static readonly Dictionary Configuration = new Dictionary { - {"HttpListenerHost:DefaultRedirectPath", "web/index.html"} + {"HttpListenerHost:DefaultRedirectPath", "web/index.html"}, + {"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"} }; } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 52a52efdc0..cfbb85ea6b 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -11,6 +11,7 @@ + diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index e4bb52217c..7e47c0b2a5 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -16,6 +16,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Xml; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Music @@ -30,15 +31,22 @@ namespace MediaBrowser.Providers.Music private readonly IJsonSerializer _json; private readonly IXmlReaderSettingsFactory _xmlSettings; - public static string MusicBrainzBaseUrl = "https://www.musicbrainz.org"; + public readonly string MusicBrainzBaseUrl; - public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost, ILogger logger, IJsonSerializer json, IXmlReaderSettingsFactory xmlSettings) + public MusicBrainzAlbumProvider( + IHttpClient httpClient, + IApplicationHost appHost, + ILogger logger, + IJsonSerializer json, + IXmlReaderSettingsFactory xmlSettings, + IConfiguration configuration) { _httpClient = httpClient; _appHost = appHost; _logger = logger; _json = json; _xmlSettings = xmlSettings; + MusicBrainzBaseUrl = configuration["MusicBrainz:BaseUrl"]; Current = this; } -- cgit v1.2.3 From 369785c184a8e19250c6e3b16b3609c222399552 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 8 Mar 2019 20:17:17 +0100 Subject: Remove usage of depricated 'WebRequest' Ref: https://docs.microsoft.com/en-us/dotnet/api/system.net.webrequest?view=netframework-4.7.2 --- .../HttpClientManager/HttpClientInfo.cs | 18 - .../HttpClientManager/HttpClientManager.cs | 432 +++++++-------------- MediaBrowser.Common/Net/HttpRequestOptions.cs | 1 - MediaBrowser.Common/Net/IHttpClient.cs | 11 + 4 files changed, 161 insertions(+), 301 deletions(-) delete mode 100644 Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs deleted file mode 100644 index f747b01b93..0000000000 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Net.Http; - -namespace Emby.Server.Implementations.HttpClientManager -{ - /// - /// Class HttpClientInfo - /// - public class HttpClientInfo - { - /// - /// Gets or sets the last timeout. - /// - /// The last timeout. - public DateTime LastTimeout { get; set; } - public HttpClient HttpClient { get; set; } - } -} diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 1bebdd1637..581d6bafd1 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Net; -using System.Net.Cache; +using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -55,12 +54,13 @@ namespace Emby.Server.Implementations.HttpClientManager { throw new ArgumentNullException(nameof(appPaths)); } + if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); } - _logger = loggerFactory.CreateLogger("HttpClient"); + _logger = loggerFactory.CreateLogger(nameof(HttpClientManager)); _fileSystem = fileSystem; _appPaths = appPaths; _defaultUserAgentFn = defaultUserAgentFn; @@ -74,27 +74,26 @@ namespace Emby.Server.Implementations.HttpClientManager /// DON'T dispose it after use. /// /// The HTTP clients. - private readonly ConcurrentDictionary _httpClients = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _httpClients = new ConcurrentDictionary(); /// /// Gets /// - /// The host. + /// The host. /// if set to true [enable HTTP compression]. /// HttpClient. /// host - private HttpClientInfo GetHttpClient(string host, bool enableHttpCompression) + private HttpClient GetHttpClient(string url, bool enableHttpCompression) { - if (string.IsNullOrEmpty(host)) - { - throw new ArgumentNullException(nameof(host)); - } - - var key = host + enableHttpCompression; + var key = GetHostFromUrl(url) + enableHttpCompression; if (!_httpClients.TryGetValue(key, out var client)) { - client = new HttpClientInfo(); + + client = new HttpClient() + { + BaseAddress = new Uri(url) + }; _httpClients.TryAdd(key, client); } @@ -102,110 +101,87 @@ namespace Emby.Server.Implementations.HttpClientManager return client; } - private WebRequest GetRequest(HttpRequestOptions options, string method) + private HttpRequestMessage GetRequestMessage(HttpRequestOptions options, HttpMethod method) { string url = options.Url; - var uriAddress = new Uri(url); string userInfo = uriAddress.UserInfo; if (!string.IsNullOrWhiteSpace(userInfo)) { - _logger.LogInformation("Found userInfo in url: {0} ... url: {1}", userInfo, url); + _logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url); url = url.Replace(userInfo + "@", string.Empty); } - var request = WebRequest.Create(url); + var request = new HttpRequestMessage(method, url); - if (request is HttpWebRequest httpWebRequest) - { - AddRequestHeaders(httpWebRequest, options); + AddRequestHeaders(request, options); - if (options.EnableHttpCompression) + if (options.EnableHttpCompression) + { + if (options.DecompressionMethod.HasValue + && options.DecompressionMethod.Value == CompressionMethod.Gzip) { - httpWebRequest.AutomaticDecompression = DecompressionMethods.Deflate; - if (options.DecompressionMethod.HasValue - && options.DecompressionMethod.Value == CompressionMethod.Gzip) - { - httpWebRequest.AutomaticDecompression = DecompressionMethods.GZip; - } + request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" }); } else { - httpWebRequest.AutomaticDecompression = DecompressionMethods.None; + request.Headers.Add(HeaderNames.AcceptEncoding, "deflate"); } + } - httpWebRequest.KeepAlive = options.EnableKeepAlive; + if (options.EnableKeepAlive) + { + request.Headers.Add(HeaderNames.Connection, "Keep-Alive"); + } - if (!string.IsNullOrEmpty(options.Host)) - { - httpWebRequest.Host = options.Host; - } + if (!string.IsNullOrEmpty(options.Host)) + { + request.Headers.Add(HeaderNames.Host, options.Host); + } - if (!string.IsNullOrEmpty(options.Referer)) - { - httpWebRequest.Referer = options.Referer; - } + if (!string.IsNullOrEmpty(options.Referer)) + { + request.Headers.Add(HeaderNames.Referer, options.Referer); } - request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); + //request.Headers.Add(HeaderNames.CacheControl, "no-cache"); - request.Method = method; - request.Timeout = options.TimeoutMs; + //request.Headers.Add(HeaderNames., options.TimeoutMs; + /* if (!string.IsNullOrWhiteSpace(userInfo)) { var parts = userInfo.Split(':'); if (parts.Length == 2) { - request.Credentials = GetCredential(url, parts[0], parts[1]); - // TODO: .net core ?? - request.PreAuthenticate = true; + request.Headers.Add(HeaderNames., GetCredential(url, parts[0], parts[1]); } } + */ return request; } - private static CredentialCache GetCredential(string url, string username, string password) - { - //ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3; - var credentialCache = new CredentialCache(); - credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password)); - return credentialCache; - } - - private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options) + private void AddRequestHeaders(HttpRequestMessage request, HttpRequestOptions options) { var hasUserAgent = false; foreach (var header in options.RequestHeaders) { - if (string.Equals(header.Key, HeaderNames.Accept, StringComparison.OrdinalIgnoreCase)) - { - request.Accept = header.Value; - } - else if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) { - SetUserAgent(request, header.Value); hasUserAgent = true; } - else - { - request.Headers.Set(header.Key, header.Value); - } + + request.Headers.Add(header.Key, header.Value); } if (!hasUserAgent && options.EnableDefaultUserAgent) { - SetUserAgent(request, _defaultUserAgentFn()); + request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn()); } } - private static void SetUserAgent(HttpWebRequest request, string userAgent) - { - request.UserAgent = userAgent; - } - /// /// Gets the response internal. /// @@ -213,7 +189,7 @@ namespace Emby.Server.Implementations.HttpClientManager /// Task{HttpResponseInfo}. public Task GetResponse(HttpRequestOptions options) { - return SendAsync(options, "GET"); + return SendAsync(options, HttpMethod.Get); } /// @@ -235,7 +211,21 @@ namespace Emby.Server.Implementations.HttpClientManager /// Task{HttpResponseInfo}. /// /// - public async Task SendAsync(HttpRequestOptions options, string httpMethod) + public Task SendAsync(HttpRequestOptions options, string httpMethod) + { + var httpMethod2 = GetHttpMethod(httpMethod); + return SendAsync(options, httpMethod2); + } + + /// + /// send as an asynchronous operation. + /// + /// The options. + /// The HTTP method. + /// Task{HttpResponseInfo}. + /// + /// + public async Task SendAsync(HttpRequestOptions options, HttpMethod httpMethod) { if (options.CacheMode == CacheMode.None) { @@ -263,6 +253,40 @@ namespace Emby.Server.Implementations.HttpClientManager return response; } + private HttpMethod GetHttpMethod(string httpMethod) + { + if (httpMethod.Equals("DELETE", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Delete; + } + else if (httpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Get; + } + else if (httpMethod.Equals("HEAD", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Head; + } + else if (httpMethod.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Options; + } + else if (httpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Post; + } + else if (httpMethod.Equals("PUT", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Put; + } + else if (httpMethod.Equals("TRACE", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Trace; + } + + throw new ArgumentException("Invalid HTTP method", nameof(httpMethod)); + } + private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url) { if (File.Exists(responseCachePath) @@ -294,31 +318,23 @@ namespace Emby.Server.Implementations.HttpClientManager } } - private async Task SendAsyncInternal(HttpRequestOptions options, string httpMethod) + private async Task SendAsyncInternal(HttpRequestOptions options, HttpMethod httpMethod) { ValidateParams(options); options.CancellationToken.ThrowIfCancellationRequested(); - var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); - - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds) - { - throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url)) - { - IsTimedOut = true - }; - } + var client = GetHttpClient(options.Url, options.EnableHttpCompression); - var httpWebRequest = GetRequest(options, httpMethod); + var httpWebRequest = GetRequestMessage(options, httpMethod); if (options.RequestContentBytes != null || !string.IsNullOrEmpty(options.RequestContent) || - string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase)) + httpMethod == HttpMethod.Post) { try { - var bytes = options.RequestContentBytes ?? Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty); + httpWebRequest.Content = new StringContent(Encoding.UTF8.GetString(options.RequestContentBytes) ?? options.RequestContent ?? string.Empty); var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded"; @@ -327,8 +343,8 @@ namespace Emby.Server.Implementations.HttpClientManager contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\""; } - httpWebRequest.ContentType = contentType; - (await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length); + httpWebRequest.Headers.Add(HeaderNames.ContentType, contentType); + await client.SendAsync(httpWebRequest).ConfigureAwait(false); } catch (Exception ex) { @@ -341,68 +357,45 @@ namespace Emby.Server.Implementations.HttpClientManager await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); } - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds) - { - options.ResourcePool?.Release(); - - throw new HttpException($"Connection to {options.Url} timed out") { IsTimedOut = true }; - } - if (options.LogRequest) { - if (options.LogRequestAsDebug) - { - _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); - } - else - { - _logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); - } + _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToString(), options.Url); } try { options.CancellationToken.ThrowIfCancellationRequested(); - if (!options.BufferContent) + /*if (!options.BufferContent) { - var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false); - - var httpResponse = (HttpWebResponse)response; + var response = await client.HttpClient.SendAsync(httpWebRequest).ConfigureAwait(false); - EnsureSuccessStatusCode(client, httpResponse, options); + await EnsureSuccessStatusCode(client, response, options).ConfigureAwait(false); options.CancellationToken.ThrowIfCancellationRequested(); - return GetResponseInfo(httpResponse, httpResponse.GetResponseStream(), GetContentLength(httpResponse), httpResponse); - } + return GetResponseInfo(response, await response.Content.ReadAsStreamAsync().ConfigureAwait(false), response.Content.Headers.ContentLength, response); + }*/ - using (var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false)) + using (var response = await client.SendAsync(httpWebRequest).ConfigureAwait(false)) { - var httpResponse = (HttpWebResponse)response; - - EnsureSuccessStatusCode(client, httpResponse, options); + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); options.CancellationToken.ThrowIfCancellationRequested(); - using (var stream = httpResponse.GetResponseStream()) + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; - return GetResponseInfo(httpResponse, memoryStream, memoryStream.Length, null); + return GetResponseInfo(response, memoryStream, memoryStream.Length, null); } } } catch (OperationCanceledException ex) { - throw GetCancellationException(options, client, options.CancellationToken, ex); - } - catch (Exception ex) - { - throw GetException(ex, options, client); + throw GetCancellationException(options, options.CancellationToken, ex); } finally { @@ -410,69 +403,54 @@ namespace Emby.Server.Implementations.HttpClientManager } } - private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, Stream content, long? contentLength, IDisposable disposable) + private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, Stream content, long? contentLength, IDisposable disposable) { var responseInfo = new HttpResponseInfo(disposable) { Content = content, StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.ContentType, + ContentType = httpResponse.Content.Headers.ContentType?.MediaType, ContentLength = contentLength, - ResponseUrl = httpResponse.ResponseUri.ToString() + ResponseUrl = httpResponse.Content.Headers.ContentLocation?.ToString() }; if (httpResponse.Headers != null) { - SetHeaders(httpResponse.Headers, responseInfo); + SetHeaders(httpResponse.Content.Headers, responseInfo); } return responseInfo; } - private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, string tempFile, long? contentLength) + private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, string tempFile, long? contentLength) { var responseInfo = new HttpResponseInfo { TempFilePath = tempFile, StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.ContentType, + ContentType = httpResponse.Content.Headers.ContentType?.MediaType, ContentLength = contentLength }; if (httpResponse.Headers != null) { - SetHeaders(httpResponse.Headers, responseInfo); + SetHeaders(httpResponse.Content.Headers, responseInfo); } return responseInfo; } - private static void SetHeaders(WebHeaderCollection headers, HttpResponseInfo responseInfo) + private static void SetHeaders(HttpContentHeaders headers, HttpResponseInfo responseInfo) { - foreach (var key in headers.AllKeys) + foreach (var key in headers) { - responseInfo.Headers[key] = headers[key]; + responseInfo.Headers[key.Key] = string.Join(", ", key.Value); } } public Task Post(HttpRequestOptions options) { - return SendAsync(options, "POST"); - } - - /// - /// Performs a POST request - /// - /// The options. - /// Params to add to the POST data. - /// stream on success, null on failure - public async Task Post(HttpRequestOptions options, Dictionary postData) - { - options.SetPostData(postData); - - var response = await Post(options).ConfigureAwait(false); - - return response.Content; + return SendAsync(options, HttpMethod.Post); } /// @@ -482,9 +460,10 @@ namespace Emby.Server.Implementations.HttpClientManager /// Task{System.String}. public async Task GetTempFile(HttpRequestOptions options) { - var response = await GetTempFileResponse(options).ConfigureAwait(false); - - return response.TempFilePath; + using (var response = await GetTempFileResponse(options).ConfigureAwait(false)) + { + return response.TempFilePath; + } } public async Task GetTempFileResponse(HttpRequestOptions options) @@ -502,7 +481,7 @@ namespace Emby.Server.Implementations.HttpClientManager options.CancellationToken.ThrowIfCancellationRequested(); - var httpWebRequest = GetRequest(options, "GET"); + var httpWebRequest = GetRequestMessage(options, HttpMethod.Get); if (options.ResourcePool != null) { @@ -513,33 +492,22 @@ namespace Emby.Server.Implementations.HttpClientManager if (options.LogRequest) { - if (options.LogRequestAsDebug) - { - _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url); - } - else - { - _logger.LogInformation("HttpClientManager.GetTempFileResponse url: {0}", options.Url); - } + _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url); } - var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); + var client = GetHttpClient(options.Url, options.EnableHttpCompression); try { options.CancellationToken.ThrowIfCancellationRequested(); - using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) + using (var response = (await client.SendAsync(httpWebRequest).ConfigureAwait(false))) { - var httpResponse = (HttpWebResponse)response; - - EnsureSuccessStatusCode(client, httpResponse, options); + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); options.CancellationToken.ThrowIfCancellationRequested(); - var contentLength = GetContentLength(httpResponse); - - using (var stream = httpResponse.GetResponseStream()) + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) { await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); @@ -547,13 +515,18 @@ namespace Emby.Server.Implementations.HttpClientManager options.Progress.Report(100); - return GetResponseInfo(httpResponse, tempFile, contentLength); + var contentLength = response.Content.Headers.ContentLength; + return GetResponseInfo(response, tempFile, contentLength); } } catch (Exception ex) { - DeleteTempFile(tempFile); - throw GetException(ex, options, client); + if (File.Exists(tempFile)) + { + File.Delete(tempFile); + } + + throw GetException(ex, options); } finally { @@ -561,21 +534,7 @@ namespace Emby.Server.Implementations.HttpClientManager } } - private static long? GetContentLength(HttpWebResponse response) - { - var length = response.ContentLength; - - if (length == 0) - { - return null; - } - - return length; - } - - protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - private Exception GetException(Exception ex, HttpRequestOptions options, HttpClientInfo client) + private Exception GetException(Exception ex, HttpRequestOptions options) { if (ex is HttpException) { @@ -599,11 +558,6 @@ namespace Emby.Server.Implementations.HttpClientManager if (response != null) { exception.StatusCode = response.StatusCode; - - if ((int)response.StatusCode == 429) - { - client.LastTimeout = DateTime.UtcNow; - } } } @@ -624,7 +578,7 @@ namespace Emby.Server.Implementations.HttpClientManager if (operationCanceledException != null) { - return GetCancellationException(options, client, options.CancellationToken, operationCanceledException); + return GetCancellationException(options, options.CancellationToken, operationCanceledException); } if (options.LogErrors) @@ -635,18 +589,6 @@ namespace Emby.Server.Implementations.HttpClientManager return ex; } - private void DeleteTempFile(string file) - { - try - { - _fileSystem.DeleteFile(file); - } - catch (IOException) - { - // Might not have been created at all. No need to worry. - } - } - private void ValidateParams(HttpRequestOptions options) { if (string.IsNullOrEmpty(options.Url)) @@ -682,11 +624,10 @@ namespace Emby.Server.Implementations.HttpClientManager /// Throws the cancellation exception. /// /// The options. - /// The client. /// The cancellation token. /// The exception. /// Exception. - private Exception GetCancellationException(HttpRequestOptions options, HttpClientInfo client, CancellationToken cancellationToken, OperationCanceledException exception) + private Exception GetCancellationException(HttpRequestOptions options, CancellationToken cancellationToken, OperationCanceledException exception) { // If the HttpClient's timeout is reached, it will cancel the Task internally if (!cancellationToken.IsCancellationRequested) @@ -698,8 +639,6 @@ namespace Emby.Server.Implementations.HttpClientManager _logger.LogError(msg); } - client.LastTimeout = DateTime.UtcNow; - // Throw an HttpException so that the caller doesn't think it was cancelled by user code return new HttpException(msg, exception) { @@ -710,91 +649,20 @@ namespace Emby.Server.Implementations.HttpClientManager return exception; } - private void EnsureSuccessStatusCode(HttpClientInfo client, HttpWebResponse response, HttpRequestOptions options) + private async Task EnsureSuccessStatusCode(HttpResponseMessage response, HttpRequestOptions options) { - var statusCode = response.StatusCode; - - var isSuccessful = statusCode >= HttpStatusCode.OK && statusCode <= (HttpStatusCode)299; - - if (isSuccessful) + if (response.IsSuccessStatusCode) { return; } - if (options.LogErrorResponseBody) - { - try - { - using (var stream = response.GetResponseStream()) - { - if (stream != null) - { - using (var reader = new StreamReader(stream)) - { - var msg = reader.ReadToEnd(); - - _logger.LogError(msg); - } - } - } - } - catch - { + var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + _logger.LogError(msg); - } - } - - throw new HttpException(response.StatusDescription) + throw new HttpException(response.ReasonPhrase) { StatusCode = response.StatusCode }; } - - private static Task GetResponseAsync(WebRequest request, TimeSpan timeout) - { - var taskCompletion = new TaskCompletionSource(); - - var asyncTask = Task.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null); - - ThreadPool.RegisterWaitForSingleObject((asyncTask as IAsyncResult).AsyncWaitHandle, TimeoutCallback, request, timeout, true); - var callback = new TaskCallback { taskCompletion = taskCompletion }; - asyncTask.ContinueWith(callback.OnSuccess, TaskContinuationOptions.NotOnFaulted); - - // Handle errors - asyncTask.ContinueWith(callback.OnError, TaskContinuationOptions.OnlyOnFaulted); - - return taskCompletion.Task; - } - - private static void TimeoutCallback(object state, bool timedOut) - { - if (timedOut && state != null) - { - var request = (WebRequest)state; - request.Abort(); - } - } - - private class TaskCallback - { - public TaskCompletionSource taskCompletion; - - public void OnSuccess(Task task) - { - taskCompletion.TrySetResult(task.Result); - } - - public void OnError(Task task) - { - if (task.Exception == null) - { - taskCompletion.TrySetException(Enumerable.Empty()); - } - else - { - taskCompletion.TrySetException(task.Exception); - } - } - } } } diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index bea178517b..874383fedf 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -99,7 +99,6 @@ namespace MediaBrowser.Common.Net public bool EnableDefaultUserAgent { get; set; } public bool AppendCharsetToMimeType { get; set; } - public string DownloadFilePath { get; set; } private string GetHeaderValue(string name) { diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs index 5aaf7e0be6..d332ab2000 100644 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ b/MediaBrowser.Common/Net/IHttpClient.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading.Tasks; +using System.Net.Http; namespace MediaBrowser.Common.Net { @@ -23,6 +24,8 @@ namespace MediaBrowser.Common.Net Task Get(HttpRequestOptions options); /// + /// Warning: Depricated function, + /// use 'Task SendAsync(HttpRequestOptions options, HttpMethod httpMethod);' instead /// Sends the asynchronous. /// /// The options. @@ -30,6 +33,14 @@ namespace MediaBrowser.Common.Net /// Task{HttpResponseInfo}. Task SendAsync(HttpRequestOptions options, string httpMethod); + /// + /// Sends the asynchronous. + /// + /// The options. + /// The HTTP method. + /// Task{HttpResponseInfo}. + Task SendAsync(HttpRequestOptions options, HttpMethod httpMethod); + /// /// Posts the specified options. /// -- cgit v1.2.3 From 7f42dcc60fd3aaf30f2408f6bddeb2b8bf921cdf Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 8 Mar 2019 20:32:14 +0100 Subject: Remove more unused stuff --- Emby.Server.Implementations/ApplicationHost.cs | 11 +++++------ .../HttpClientManager/HttpClientManager.cs | 18 ------------------ .../LiveTv/Listings/SchedulesDirect.cs | 10 +--------- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 2 -- .../LiveTv/TunerHosts/SharedHttpStream.cs | 6 ------ MediaBrowser.Common/Net/HttpRequestOptions.cs | 21 +-------------------- 6 files changed, 7 insertions(+), 61 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f7d9bad1b0..2f396f8146 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; @@ -34,7 +35,6 @@ using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Library; using Emby.Server.Implementations.LiveTv; using Emby.Server.Implementations.Localization; -using Emby.Server.Implementations.Middleware; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Reflection; @@ -1479,12 +1479,12 @@ namespace Emby.Server.Implementations LogErrorResponseBody = false, LogErrors = false, LogRequest = false, - TimeoutMs = 10000, BufferContent = false, CancellationToken = cancellationToken })) { - return GetLocalApiUrl(response.ReadToEnd().Trim()); + string res = await response.ReadToEndAsync().ConfigureAwait(false); + return GetLocalApiUrl(res.Trim()); } } catch (Exception ex) @@ -1604,16 +1604,15 @@ namespace Emby.Server.Implementations LogErrorResponseBody = false, LogErrors = logPing, LogRequest = logPing, - TimeoutMs = 5000, BufferContent = false, CancellationToken = cancellationToken - }, "POST").ConfigureAwait(false)) + }, HttpMethod.Post).ConfigureAwait(false)) { using (var reader = new StreamReader(response.Content)) { - var result = reader.ReadToEnd(); + var result = await reader.ReadToEndAsync().ConfigureAwait(false); var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase); _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid); diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 581d6bafd1..b82d55d0e6 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -352,11 +352,6 @@ namespace Emby.Server.Implementations.HttpClientManager } } - if (options.ResourcePool != null) - { - await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); - } - if (options.LogRequest) { _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToString(), options.Url); @@ -397,10 +392,6 @@ namespace Emby.Server.Implementations.HttpClientManager { throw GetCancellationException(options, options.CancellationToken, ex); } - finally - { - options.ResourcePool?.Release(); - } } private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, Stream content, long? contentLength, IDisposable disposable) @@ -483,11 +474,6 @@ namespace Emby.Server.Implementations.HttpClientManager var httpWebRequest = GetRequestMessage(options, HttpMethod.Get); - if (options.ResourcePool != null) - { - await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); - } - options.Progress.Report(0); if (options.LogRequest) @@ -528,10 +514,6 @@ namespace Emby.Server.Implementations.HttpClientManager throw GetException(ex, options); } - finally - { - options.ResourcePool?.Release(); - } } private Exception GetException(Exception ex, HttpRequestOptions options) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 4137760d07..f3f7477180 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -96,8 +96,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings Url = ApiUrl + "/schedules", UserAgent = UserAgent, CancellationToken = cancellationToken, - // The data can be large so give it some extra time - TimeoutMs = 60000, LogErrorResponseBody = true, RequestContent = requestString }; @@ -115,9 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings Url = ApiUrl + "/programs", UserAgent = UserAgent, CancellationToken = cancellationToken, - LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 + LogErrorResponseBody = true }; httpOptions.RequestHeaders["token"] = token; @@ -483,8 +479,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings CancellationToken = cancellationToken, RequestContent = imageIdString, LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 }; try @@ -871,8 +865,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings UserAgent = UserAgent, CancellationToken = cancellationToken, LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 }; httpOptions.RequestHeaders["token"] = token; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 24b100eddc..761275f8f3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -138,7 +138,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { Url = string.Format("{0}/discover.json", GetApiUrl(info)), CancellationToken = cancellationToken, - TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(10).TotalMilliseconds), BufferContent = false }, "GET").ConfigureAwait(false)) @@ -191,7 +190,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { Url = string.Format("{0}/tuners.html", GetApiUrl(info)), CancellationToken = cancellationToken, - TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds), BufferContent = false })) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index d74cf3be2d..e8b34da0ca 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -47,13 +47,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts CancellationToken = CancellationToken.None, BufferContent = false, - // Increase a little bit - TimeoutMs = 30000, - EnableHttpCompression = false, - - LogResponse = true, - LogResponseHeaders = true }; foreach (var header in mediaSource.RequiredHttpHeaders) diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index 874383fedf..38e0ff0f55 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -28,18 +28,13 @@ namespace MediaBrowser.Common.Net get => GetHeaderValue(HeaderNames.Accept); set => RequestHeaders[HeaderNames.Accept] = value; } + /// /// Gets or sets the cancellation token. /// /// The cancellation token. public CancellationToken CancellationToken { get; set; } - /// - /// Gets or sets the resource pool. - /// - /// The resource pool. - public SemaphoreSlim ResourcePool { get; set; } - /// /// Gets or sets the user agent. /// @@ -86,8 +81,6 @@ namespace MediaBrowser.Common.Net public bool LogRequest { get; set; } public bool LogRequestAsDebug { get; set; } public bool LogErrors { get; set; } - public bool LogResponse { get; set; } - public bool LogResponseHeaders { get; set; } public bool LogErrorResponseBody { get; set; } public bool EnableKeepAlive { get; set; } @@ -95,7 +88,6 @@ namespace MediaBrowser.Common.Net public CacheMode CacheMode { get; set; } public TimeSpan CacheLength { get; set; } - public int TimeoutMs { get; set; } public bool EnableDefaultUserAgent { get; set; } public bool AppendCharsetToMimeType { get; set; } @@ -119,17 +111,6 @@ namespace MediaBrowser.Common.Net LogRequest = true; LogErrors = true; CacheMode = CacheMode.None; - - TimeoutMs = 20000; - } - - public void SetPostData(IDictionary values) - { - var strings = values.Keys.Select(key => string.Format("{0}={1}", key, values[key])); - var postContent = string.Join("&", strings.ToArray()); - - RequestContent = postContent; - RequestContentType = "application/x-www-form-urlencoded"; } } -- cgit v1.2.3 From f2062ba19bb38a2dfa6b10ab778718730fe0f14a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 8 Mar 2019 22:25:45 +0100 Subject: Disable HTTPS in Kestrel if Certificate is null --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f7d9bad1b0..ae54606747 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -629,7 +629,7 @@ namespace Emby.Server.Implementations { options.ListenAnyIP(HttpPort); - if (EnableHttps) + if (EnableHttps && Certificate != null) { options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); } -- cgit v1.2.3 From 93fe7957fb4ed4f85b14bc4560f8e595540e91bd Mon Sep 17 00:00:00 2001 From: Matsuri Date: Fri, 1 Mar 2019 05:50:39 +0000 Subject: Translated using Weblate (Chinese (Simplified)) Currently translated at 97.8% (92 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 8910a6bce8..6f7d362d3b 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -34,14 +34,14 @@ "LabelRunningTimeValue": "运行时间:{0}", "Latest": "最新", "MessageApplicationUpdated": "Jellyfin 服务器已更新", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "Jellyfin Server 的版本已更新为 {0}", "MessageNamedServerConfigurationUpdatedWithValue": "服务器配置 {0} 部分已更新", "MessageServerConfigurationUpdated": "服务器配置已更新", "MixedContent": "混合内容", "Movies": "电影", "Music": "音乐", "MusicVideos": "音乐视频", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "{0} 安装失败", "NameSeasonNumber": "季 {0}", "NameSeasonUnknown": "未知季", "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", @@ -70,7 +70,7 @@ "ProviderValue": "提供商:{0}", "ScheduledTaskFailedWithName": "{0} 已失败", "ScheduledTaskStartedWithName": "{0} 已开始", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "{0} 需要重新启动", "Shows": "节目", "Songs": "歌曲", "StartupEmbyServerIsLoading": "Jellyfin 服务器加载中。请稍后再试。", -- cgit v1.2.3 From 16adaa64c9c94ea35ffd99df114a0b8de9249cf6 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Mon, 4 Mar 2019 12:00:32 +0000 Subject: Translated using Weblate (Kazakh) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/ --- Emby.Server.Implementations/Localization/Core/kk.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 658d168e9f..23841f37d6 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -3,15 +3,15 @@ "AppDeviceValues": "Qoldanba: {0}, Qurylǵy: {1}", "Application": "Qoldanba", "Artists": "Oryndaýshylar", - "AuthenticationSucceededWithUserName": "{0} túpnusqalyǵyn rastalýy sátti", + "AuthenticationSucceededWithUserName": "{0} túpnusqalyq rastalýy sátti aıaqtaldy", "Books": "Kitaptar", - "CameraImageUploadedFrom": "Jańa sýret {0} kamerasynan júktep alyndy", + "CameraImageUploadedFrom": "{0} kamerasynan jańa sýret júktep alyndy", "Channels": "Arnalar", "ChapterNameValue": "{0}-sahna", "Collections": "Jıyntyqtar", "DeviceOfflineWithName": "{0} ajyratylǵan", "DeviceOnlineWithName": "{0} qosylǵan", - "FailedLoginAttemptWithUserName": "{0} tarapynan kirý áreketi sátsiz", + "FailedLoginAttemptWithUserName": "{0} tarapynan kirý áreketi sátsiz aıaqtaldy", "Favorites": "Tańdaýlylar", "Folders": "Qaltalar", "Genres": "Janrlar", @@ -28,13 +28,13 @@ "HeaderRecordingGroups": "Jazba toptary", "HomeVideos": "Úılik beıneler", "Inherit": "Muraǵa ıelený", - "ItemAddedWithName": "{0} tasyǵyshhanaǵa ústelindi", + "ItemAddedWithName": "{0} tasyǵyshhanaǵa ústeldi", "ItemRemovedWithName": "{0} tasyǵyshhanadan alastaldy", "LabelIpAddressValue": "IP-mekenjaıy: {0}", "LabelRunningTimeValue": "Oınatý ýaqyty: {0}", "Latest": "Eń keıingi", "MessageApplicationUpdated": "Jellyfin Serveri jańartyldy", - "MessageApplicationUpdatedTo": "Jellyfin Serveri {0} deńgeıge jańartyldy", + "MessageApplicationUpdatedTo": "Jellyfin Serveri {0} nusqasyna jańartyldy", "MessageNamedServerConfigurationUpdatedWithValue": "Server teńsheliminiń {0} bólimi jańartyldy", "MessageServerConfigurationUpdated": "Server teńshelimi jańartyldy", "MixedContent": "Aralas mazmun", -- cgit v1.2.3 From 93d15cd96972588bbbc29a9398c9d4b050d30411 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 16:17:48 -0400 Subject: Add configuration flag for Web directory --- .../AppBase/BaseApplicationPaths.cs | 4 +++- Emby.Server.Implementations/ServerApplicationPaths.cs | 6 ++++-- Jellyfin.Server/Program.cs | 19 ++++++++++++++++++- Jellyfin.Server/StartupOptions.cs | 3 +++ 4 files changed, 28 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 65cdccfa5d..3ee09c9c04 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -17,12 +17,14 @@ namespace Emby.Server.Implementations.AppBase string programDataPath, string logDirectoryPath, string configurationDirectoryPath, - string cacheDirectoryPath) + string cacheDirectoryPath, + string webDirectoryPath) { ProgramDataPath = programDataPath; LogDirectoryPath = logDirectoryPath; ConfigurationDirectoryPath = configurationDirectoryPath; CachePath = cacheDirectoryPath; + WebPath = webDirectoryPath; DataPath = Path.Combine(ProgramDataPath, "data"); } diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index 05f6469ece..adaf23234f 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -17,11 +17,13 @@ namespace Emby.Server.Implementations string programDataPath, string logDirectoryPath, string configurationDirectoryPath, - string cacheDirectoryPath) + string cacheDirectoryPath, + string webDirectoryPath) : base(programDataPath, logDirectoryPath, configurationDirectoryPath, - cacheDirectoryPath) + cacheDirectoryPath, + webDirectoryPath) { } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 0ef1711d4c..3d81ade35c 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -264,6 +264,23 @@ namespace Jellyfin.Server } } + // webDir + // IF --webdir + // ELSE IF $JELLYFIN_WEB_DIR + // ELSE use /jellyfin-web + var webDir = options.WebDir; + + if (string.IsNullOrEmpty(webDir)) + { + webDir = Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR"); + + if (string.IsNullOrEmpty(webDir)) + { + // Use default location under ResourcesPath + webDir = Path.Combine(AppContext.BaseDirectory, "jellyfin-web") + } + } + // logDir // IF --logdir // ELSE IF $JELLYFIN_LOG_DIR @@ -296,7 +313,7 @@ namespace Jellyfin.Server Environment.Exit(1); } - return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir); + return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir, webDir); } private static async Task CreateConfiguration(IApplicationPaths appPaths) diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index c8cdb984db..2b01c7bbdf 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -11,6 +11,9 @@ namespace Jellyfin.Server [Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")] public string DataDir { get; set; } + [Option('w', "webdir", Required = false, HelpText = "Path to the Jellyfin web resources.")] + public string WebDir { get; set; } + [Option('C', "cachedir", Required = false, HelpText = "Path to use for caching.")] public string CacheDir { get; set; } -- cgit v1.2.3 From 25deca95793581df85dab8c454d53a1d07a6ab53 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 16:20:46 -0400 Subject: Make use of WebPath --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ae54606747..05ede0ddab 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -621,7 +621,7 @@ namespace Emby.Server.Implementations string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; if (string.IsNullOrEmpty(contentRoot)) { - contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src"); + contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths,WebPath, "src"); } var host = new WebHostBuilder() diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 531978e1dd..05df54b115 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.WebDashboard.Api return _serverConfigurationManager.Configuration.DashboardSourcePath; } - return Path.Combine(_serverConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src"); + return Path.Combine(_serverConfigurationManager.ApplicationPaths.WebPath, "src"); } } -- cgit v1.2.3 From 132ce3ece14ff7ca756c81e0a12172744c5c50aa Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 17:04:18 -0400 Subject: Add further resources to complete WebPath --- Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs | 6 ++++++ Emby.Server.Implementations/ApplicationHost.cs | 4 +++- Emby.Server.Implementations/ServerApplicationPaths.cs | 6 ++++++ Jellyfin.Server/Program.cs | 2 +- MediaBrowser.Common/Configuration/IApplicationPaths.cs | 6 ++++++ MediaBrowser.Model/System/SystemInfo.cs | 6 ++++++ 6 files changed, 28 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 3ee09c9c04..fd9ce3a364 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -35,6 +35,12 @@ namespace Emby.Server.Implementations.AppBase /// The program data path. public string ProgramDataPath { get; private set; } + /// + /// Gets the path to the web resources folder + /// + /// The web resources path. + public string WebPath { get; set; } + /// /// Gets the path to the system folder /// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 05ede0ddab..f240ef871a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -621,7 +621,7 @@ namespace Emby.Server.Implementations string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; if (string.IsNullOrEmpty(contentRoot)) { - contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths,WebPath, "src"); + contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.WebPath, "src"); } var host = new WebHostBuilder() @@ -921,6 +921,7 @@ namespace Emby.Server.Implementations logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive); logger.LogInformation("Processor count: {ProcessorCount}", Environment.ProcessorCount); logger.LogInformation("Program data path: {ProgramDataPath}", appPaths.ProgramDataPath); + logger.LogInformation("Web resources path: {WebPath}", appPaths.WebPath); logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath); } @@ -1393,6 +1394,7 @@ namespace Emby.Server.Implementations CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(), Id = SystemId, ProgramDataPath = ApplicationPaths.ProgramDataPath, + WebPath = ApplicationPaths.WebPath, LogPath = ApplicationPaths.LogDirectoryPath, ItemsByNamePath = ApplicationPaths.InternalMetadataPath, InternalMetadataPath = ApplicationPaths.InternalMetadataPath, diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index adaf23234f..68d5881220 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -35,6 +35,12 @@ namespace Emby.Server.Implementations /// The root folder path. public string RootFolderPath => Path.Combine(ProgramDataPath, "root"); +/* /// + /// Gets the path to the Web resources + /// + /// The web folder path. + public string WebPath => WebPath; */ + /// /// Gets the path to the default user view directory. Used if no specific user view is defined. /// diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 3d81ade35c..8f1498b13b 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -277,7 +277,7 @@ namespace Jellyfin.Server if (string.IsNullOrEmpty(webDir)) { // Use default location under ResourcesPath - webDir = Path.Combine(AppContext.BaseDirectory, "jellyfin-web") + webDir = Path.Combine(AppContext.BaseDirectory, "jellyfin-web"); } } diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index cb4e8bf5f0..1f56c98fd8 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -11,6 +11,12 @@ namespace MediaBrowser.Common.Configuration /// The program data path. string ProgramDataPath { get; } + /// + /// Gets the path to the web resources folder + /// + /// The web resources path. + string WebPath { get; } + /// /// Gets the path to the program system folder /// diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 6482f2c840..fa72624819 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -83,6 +83,12 @@ namespace MediaBrowser.Model.System /// The program data path. public string ProgramDataPath { get; set; } + /// + /// Gets or sets the web resources path. + /// + /// The web resources path. + public string WebPath { get; set; } + /// /// Gets or sets the items by name path. /// -- cgit v1.2.3 From 5f7524aca26797a3dc7acbed8cd50e113e2f9a4d Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 17:21:10 -0400 Subject: Remove unneccessary string --- Emby.Server.Implementations/ServerApplicationPaths.cs | 6 ------ 1 file changed, 6 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index 68d5881220..adaf23234f 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -35,12 +35,6 @@ namespace Emby.Server.Implementations /// The root folder path. public string RootFolderPath => Path.Combine(ProgramDataPath, "root"); -/* /// - /// Gets the path to the Web resources - /// - /// The web folder path. - public string WebPath => WebPath; */ - /// /// Gets the path to the default user view directory. Used if no specific user view is defined. /// -- cgit v1.2.3 From 0ff038f0a2c90ba0ad671eae1ac3c74a7602b855 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 11 Mar 2019 23:13:01 +0100 Subject: Fix nullref --- Emby.Server.Implementations/ApplicationHost.cs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ae54606747..d7696393db 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -700,6 +700,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ApplicationPaths); + serviceCollection.AddSingleton(_configuration); + serviceCollection.AddSingleton(JsonSerializer); serviceCollection.AddSingleton(LoggerFactory); -- cgit v1.2.3 From 715ddbb3b08cab2ff64919f577d5f72d9af5ea22 Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 11 Mar 2019 18:10:31 -0700 Subject: remove open subtitles from the server --- .../Emby.Server.Implementations.csproj | 1 - .../MediaBrowser.MediaEncoding.csproj | 1 - .../Subtitles/ConfigurationExtension.cs | 29 - .../Subtitles/OpenSubtitleDownloader.cs | 347 --- MediaBrowser.sln | 2 - OpenSubtitlesHandler/Console/OSHConsole.cs | 92 - OpenSubtitlesHandler/Interfaces/IMethodResponse.cs | 71 - .../Interfaces/MethodResponseAttr.cs | 40 - .../Languages/DetectLanguageResult.cs | 31 - .../MethodResponses/MethodResponseAddComment.cs | 32 - .../MethodResponses/MethodResponseAddRequest.cs | 35 - .../MethodResponses/MethodResponseAutoUpdate.cs | 59 - .../MethodResponseCheckMovieHash.cs | 37 - .../MethodResponseCheckMovieHash2.cs | 37 - .../MethodResponses/MethodResponseCheckSubHash.cs | 38 - .../MethodResponseDetectLanguage.cs | 37 - .../MethodResponses/MethodResponseError.cs | 36 - .../MethodResponseGetAvailableTranslations.cs | 37 - .../MethodResponses/MethodResponseGetComments.cs | 39 - .../MethodResponseGetSubLanguages.cs | 39 - .../MethodResponseGetTranslation.cs | 36 - .../MethodResponses/MethodResponseInsertMovie.cs | 36 - .../MethodResponseInsertMovieHash.cs | 38 - .../MethodResponses/MethodResponseLogIn.cs | 39 - .../MethodResponses/MethodResponseMovieDetails.cs | 73 - .../MethodResponses/MethodResponseMovieSearch.cs | 43 - .../MethodResponses/MethodResponseNoOperation.cs | 52 - .../MethodResponseReportWrongImdbMovie.cs | 33 - .../MethodResponseReportWrongMovieHash.cs | 33 - .../MethodResponses/MethodResponseSearchToMail.cs | 32 - .../MethodResponses/MethodResponseServerInfo.cs | 131 - .../MethodResponseSubtitleDownload.cs | 44 - .../MethodResponseSubtitleSearch.cs | 46 - .../MethodResponses/MethodResponseSubtitlesVote.cs | 43 - .../MethodResponseTryUploadSubtitles.cs | 40 - .../MethodResponseUploadSubtitles.cs | 38 - OpenSubtitlesHandler/Methods Implemeted.txt | 39 - OpenSubtitlesHandler/MovieHasher.cs | 48 - OpenSubtitlesHandler/Movies/CheckMovieHash2Data.cs | 42 - .../Movies/CheckMovieHash2Result.cs | 37 - .../Movies/CheckMovieHashResult.cs | 41 - .../Movies/InsertMovieHashParameters.cs | 38 - OpenSubtitlesHandler/Movies/MovieSearchResult.cs | 40 - .../Movies/SearchToMailMovieParameter.cs | 30 - OpenSubtitlesHandler/OpenSubtitles.cs | 2744 -------------------- OpenSubtitlesHandler/OpenSubtitlesHandler.csproj | 13 - .../OtherTypes/GetAvailableTranslationsResult.cs | 39 - .../OtherTypes/GetCommentsResult.cs | 36 - OpenSubtitlesHandler/Properties/AssemblyInfo.cs | 24 - OpenSubtitlesHandler/Readme.txt | 20 - .../SubtitleTypes/CheckSubHashResult.cs | 30 - .../SubtitleTypes/SubtitleDownloadResult.cs | 43 - .../SubtitleTypes/SubtitleLanguage.cs | 39 - .../SubtitleTypes/SubtitleSearchParameters.cs | 82 - .../SubtitleTypes/SubtitleSearchResult.cs | 136 - .../SubtitleTypes/TryUploadSubtitlesParameters.cs | 47 - .../SubtitleTypes/UploadSubtitleInfoParameter.cs | 46 - .../SubtitleTypes/UploadSubtitleParameters.cs | 52 - OpenSubtitlesHandler/Utilities.cs | 174 -- OpenSubtitlesHandler/XML-RPC/Docs/XML-RPC.txt | 225 -- .../XML-RPC/Enums/XmlRpcValueType.cs | 25 - .../XML-RPC/Types/XmlRpcMethodCall.cs | 63 - .../XML-RPC/Values/IXmlRpcValue.cs | 36 - .../XML-RPC/Values/XmlRpcStructMember.cs | 43 - .../XML-RPC/Values/XmlRpcValueArray.cs | 121 - .../XML-RPC/Values/XmlRpcValueBasic.cs | 99 - .../XML-RPC/Values/XmlRpcValueStruct.cs | 43 - OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs | 350 --- 68 files changed, 6642 deletions(-) delete mode 100644 MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs delete mode 100644 MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs delete mode 100644 OpenSubtitlesHandler/Console/OSHConsole.cs delete mode 100644 OpenSubtitlesHandler/Interfaces/IMethodResponse.cs delete mode 100644 OpenSubtitlesHandler/Interfaces/MethodResponseAttr.cs delete mode 100644 OpenSubtitlesHandler/Languages/DetectLanguageResult.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseAddComment.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseAddRequest.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseAutoUpdate.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash2.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseCheckSubHash.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseDetectLanguage.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseError.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseGetAvailableTranslations.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseGetComments.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseGetSubLanguages.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseGetTranslation.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovie.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovieHash.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseLogIn.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseMovieDetails.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseMovieSearch.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseNoOperation.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongImdbMovie.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongMovieHash.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseSearchToMail.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseServerInfo.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleDownload.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleSearch.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitlesVote.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseTryUploadSubtitles.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseUploadSubtitles.cs delete mode 100644 OpenSubtitlesHandler/Methods Implemeted.txt delete mode 100644 OpenSubtitlesHandler/MovieHasher.cs delete mode 100644 OpenSubtitlesHandler/Movies/CheckMovieHash2Data.cs delete mode 100644 OpenSubtitlesHandler/Movies/CheckMovieHash2Result.cs delete mode 100644 OpenSubtitlesHandler/Movies/CheckMovieHashResult.cs delete mode 100644 OpenSubtitlesHandler/Movies/InsertMovieHashParameters.cs delete mode 100644 OpenSubtitlesHandler/Movies/MovieSearchResult.cs delete mode 100644 OpenSubtitlesHandler/Movies/SearchToMailMovieParameter.cs delete mode 100644 OpenSubtitlesHandler/OpenSubtitles.cs delete mode 100644 OpenSubtitlesHandler/OpenSubtitlesHandler.csproj delete mode 100644 OpenSubtitlesHandler/OtherTypes/GetAvailableTranslationsResult.cs delete mode 100644 OpenSubtitlesHandler/OtherTypes/GetCommentsResult.cs delete mode 100644 OpenSubtitlesHandler/Properties/AssemblyInfo.cs delete mode 100644 OpenSubtitlesHandler/Readme.txt delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/CheckSubHashResult.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/SubtitleDownloadResult.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/SubtitleLanguage.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchParameters.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchResult.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/TryUploadSubtitlesParameters.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleInfoParameter.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleParameters.cs delete mode 100644 OpenSubtitlesHandler/Utilities.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Docs/XML-RPC.txt delete mode 100644 OpenSubtitlesHandler/XML-RPC/Enums/XmlRpcValueType.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Types/XmlRpcMethodCall.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Values/IXmlRpcValue.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Values/XmlRpcStructMember.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueArray.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueBasic.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueStruct.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 39e9ed3756..0b0dab2139 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -13,7 +13,6 @@ - diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 68b8bd4fae..e4757543ea 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -14,7 +14,6 @@ - diff --git a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs b/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs deleted file mode 100644 index 92544f4f62..0000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public static class ConfigurationExtension - { - public static SubtitleOptions GetSubtitleConfiguration(this IConfigurationManager manager) - { - return manager.GetConfiguration("subtitles"); - } - } - - public class SubtitleConfigurationFactory : IConfigurationFactory - { - public IEnumerable GetConfigurations() - { - return new List - { - new ConfigurationStore - { - Key = "subtitles", - ConfigurationType = typeof (SubtitleOptions) - } - }; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs deleted file mode 100644 index a7e3f61972..0000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs +++ /dev/null @@ -1,347 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -using OpenSubtitlesHandler; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class OpenSubtitleDownloader : ISubtitleProvider, IDisposable - { - private readonly ILogger _logger; - private readonly IHttpClient _httpClient; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - private readonly IServerConfigurationManager _config; - - private readonly IJsonSerializer _json; - private readonly IFileSystem _fileSystem; - - public OpenSubtitleDownloader(ILoggerFactory loggerFactory, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem) - { - _logger = loggerFactory.CreateLogger(GetType().Name); - _httpClient = httpClient; - _config = config; - _json = json; - _fileSystem = fileSystem; - - _config.NamedConfigurationUpdating += _config_NamedConfigurationUpdating; - - Utilities.HttpClient = httpClient; - OpenSubtitles.SetUserAgent("jellyfin"); - } - - private const string PasswordHashPrefix = "h:"; - void _config_NamedConfigurationUpdating(object sender, ConfigurationUpdateEventArgs e) - { - if (!string.Equals(e.Key, "subtitles", StringComparison.OrdinalIgnoreCase)) - { - return; - } - - var options = (SubtitleOptions)e.NewConfiguration; - - if (options != null && - !string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash) && - !options.OpenSubtitlesPasswordHash.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) - { - options.OpenSubtitlesPasswordHash = EncodePassword(options.OpenSubtitlesPasswordHash); - } - } - - private static string EncodePassword(string password) - { - var bytes = Encoding.UTF8.GetBytes(password); - return PasswordHashPrefix + Convert.ToBase64String(bytes); - } - - private static string DecodePassword(string password) - { - if (password == null || - !password.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) - { - return string.Empty; - } - - var bytes = Convert.FromBase64String(password.Substring(2)); - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } - - public string Name => "Open Subtitles"; - - private SubtitleOptions GetOptions() - { - return _config.GetSubtitleConfiguration(); - } - - public IEnumerable SupportedMediaTypes - { - get - { - var options = GetOptions(); - - if (string.IsNullOrWhiteSpace(options.OpenSubtitlesUsername) || - string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash)) - { - return new VideoContentType[] { }; - } - - return new[] { VideoContentType.Episode, VideoContentType.Movie }; - } - } - - public Task GetSubtitles(string id, CancellationToken cancellationToken) - { - return GetSubtitlesInternal(id, GetOptions(), cancellationToken); - } - - private DateTime _lastRateLimitException; - private async Task GetSubtitlesInternal(string id, - SubtitleOptions options, - CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException(nameof(id)); - } - - var idParts = id.Split(new[] { '-' }, 3); - - var format = idParts[0]; - var language = idParts[1]; - var ossId = idParts[2]; - - var downloadsList = new[] { int.Parse(ossId, _usCulture) }; - - await Login(cancellationToken).ConfigureAwait(false); - - if ((DateTime.UtcNow - _lastRateLimitException).TotalHours < 1) - { - throw new RateLimitExceededException("OpenSubtitles rate limit reached"); - } - - var resultDownLoad = await OpenSubtitles.DownloadSubtitlesAsync(downloadsList, cancellationToken).ConfigureAwait(false); - - if ((resultDownLoad.Status ?? string.Empty).IndexOf("407", StringComparison.OrdinalIgnoreCase) != -1) - { - _lastRateLimitException = DateTime.UtcNow; - throw new RateLimitExceededException("OpenSubtitles rate limit reached"); - } - - if (!(resultDownLoad is MethodResponseSubtitleDownload)) - { - throw new Exception("Invalid response type"); - } - - var results = ((MethodResponseSubtitleDownload)resultDownLoad).Results; - - _lastRateLimitException = DateTime.MinValue; - - if (results.Count == 0) - { - var msg = string.Format("Subtitle with Id {0} was not found. Name: {1}. Status: {2}. Message: {3}", - ossId, - resultDownLoad.Name ?? string.Empty, - resultDownLoad.Status ?? string.Empty, - resultDownLoad.Message ?? string.Empty); - - throw new ResourceNotFoundException(msg); - } - - var data = Convert.FromBase64String(results.First().Data); - - return new SubtitleResponse - { - Format = format, - Language = language, - - Stream = new MemoryStream(Utilities.Decompress(new MemoryStream(data))) - }; - } - - private DateTime _lastLogin; - private async Task Login(CancellationToken cancellationToken) - { - if ((DateTime.UtcNow - _lastLogin).TotalSeconds < 60) - { - return; - } - - var options = GetOptions(); - - var user = options.OpenSubtitlesUsername ?? string.Empty; - var password = DecodePassword(options.OpenSubtitlesPasswordHash); - - var loginResponse = await OpenSubtitles.LogInAsync(user, password, "en", cancellationToken).ConfigureAwait(false); - - if (!(loginResponse is MethodResponseLogIn)) - { - throw new Exception("Authentication to OpenSubtitles failed."); - } - - _lastLogin = DateTime.UtcNow; - } - - public async Task> GetSupportedLanguages(CancellationToken cancellationToken) - { - await Login(cancellationToken).ConfigureAwait(false); - - var result = OpenSubtitles.GetSubLanguages("en"); - if (!(result is MethodResponseGetSubLanguages)) - { - _logger.LogError("Invalid response type"); - return new List(); - } - - var results = ((MethodResponseGetSubLanguages)result).Languages; - - return results.Select(i => new NameIdPair - { - Name = i.LanguageName, - Id = i.SubLanguageID - }); - } - - private string NormalizeLanguage(string language) - { - // Problem with Greek subtitle download #1349 - if (string.Equals(language, "gre", StringComparison.OrdinalIgnoreCase)) - { - - return "ell"; - } - - return language; - } - - public async Task> Search(SubtitleSearchRequest request, CancellationToken cancellationToken) - { - var imdbIdText = request.GetProviderId(MetadataProviders.Imdb); - long imdbId = 0; - - switch (request.ContentType) - { - case VideoContentType.Episode: - if (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName)) - { - _logger.LogDebug("Episode information missing"); - return new List(); - } - break; - case VideoContentType.Movie: - if (string.IsNullOrEmpty(request.Name)) - { - _logger.LogDebug("Movie name missing"); - return new List(); - } - if (string.IsNullOrWhiteSpace(imdbIdText) || !long.TryParse(imdbIdText.TrimStart('t'), NumberStyles.Any, _usCulture, out imdbId)) - { - _logger.LogDebug("Imdb id missing"); - return new List(); - } - break; - } - - if (string.IsNullOrEmpty(request.MediaPath)) - { - _logger.LogDebug("Path Missing"); - return new List(); - } - - await Login(cancellationToken).ConfigureAwait(false); - - var subLanguageId = NormalizeLanguage(request.Language); - string hash; - - using (var fileStream = File.OpenRead(request.MediaPath)) - { - hash = Utilities.ComputeHash(fileStream); - } - var fileInfo = _fileSystem.GetFileInfo(request.MediaPath); - var movieByteSize = fileInfo.Length; - var searchImdbId = request.ContentType == VideoContentType.Movie ? imdbId.ToString(_usCulture) : ""; - var subtitleSearchParameters = request.ContentType == VideoContentType.Episode - ? new List { - new SubtitleSearchParameters(subLanguageId, - query: request.SeriesName, - season: request.ParentIndexNumber.Value.ToString(_usCulture), - episode: request.IndexNumber.Value.ToString(_usCulture)) - } - : new List { - new SubtitleSearchParameters(subLanguageId, imdbid: searchImdbId), - new SubtitleSearchParameters(subLanguageId, query: request.Name, imdbid: searchImdbId) - }; - var parms = new List { - new SubtitleSearchParameters( subLanguageId, - movieHash: hash, - movieByteSize: movieByteSize, - imdbid: searchImdbId ), - }; - parms.AddRange(subtitleSearchParameters); - var result = await OpenSubtitles.SearchSubtitlesAsync(parms.ToArray(), cancellationToken).ConfigureAwait(false); - if (!(result is MethodResponseSubtitleSearch)) - { - _logger.LogError("Invalid response type"); - return new List(); - } - - Predicate mediaFilter = - x => - request.ContentType == VideoContentType.Episode - ? !string.IsNullOrEmpty(x.SeriesSeason) && !string.IsNullOrEmpty(x.SeriesEpisode) && - int.Parse(x.SeriesSeason, _usCulture) == request.ParentIndexNumber && - int.Parse(x.SeriesEpisode, _usCulture) == request.IndexNumber - : !string.IsNullOrEmpty(x.IDMovieImdb) && long.Parse(x.IDMovieImdb, _usCulture) == imdbId; - - var results = ((MethodResponseSubtitleSearch)result).Results; - - // Avoid implicitly captured closure - var hasCopy = hash; - - return results.Where(x => x.SubBad == "0" && mediaFilter(x) && (!request.IsPerfectMatch || string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase))) - .OrderBy(x => (string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase) ? 0 : 1)) - .ThenBy(x => Math.Abs(long.Parse(x.MovieByteSize, _usCulture) - movieByteSize)) - .ThenByDescending(x => int.Parse(x.SubDownloadsCnt, _usCulture)) - .ThenByDescending(x => double.Parse(x.SubRating, _usCulture)) - .Select(i => new RemoteSubtitleInfo - { - Author = i.UserNickName, - Comment = i.SubAuthorComment, - CommunityRating = float.Parse(i.SubRating, _usCulture), - DownloadCount = int.Parse(i.SubDownloadsCnt, _usCulture), - Format = i.SubFormat, - ProviderName = Name, - ThreeLetterISOLanguageName = i.SubLanguageID, - - Id = i.SubFormat + "-" + i.SubLanguageID + "-" + i.IDSubtitleFile, - - Name = i.SubFileName, - DateCreated = DateTime.Parse(i.SubAddDate, _usCulture), - IsHashMatch = i.MovieHash == hasCopy - - }).Where(i => !string.Equals(i.Format, "sub", StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Format, "idx", StringComparison.OrdinalIgnoreCase)); - } - - public void Dispose() - { - _config.NamedConfigurationUpdating -= _config_NamedConfigurationUpdating; - } - } -} diff --git a/MediaBrowser.sln b/MediaBrowser.sln index a3c0569bad..3ed86d65c7 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" diff --git a/OpenSubtitlesHandler/Console/OSHConsole.cs b/OpenSubtitlesHandler/Console/OSHConsole.cs deleted file mode 100644 index 396b28cbc6..0000000000 --- a/OpenSubtitlesHandler/Console/OSHConsole.cs +++ /dev/null @@ -1,92 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System; - -namespace OpenSubtitlesHandler.Console -{ - public class OSHConsole - { - /// - /// Write line to the console and raise the "LineWritten" event - /// - /// - /// The debug line - /// The status - public static void WriteLine(string text, DebugCode code = DebugCode.None) - { - if (LineWritten != null) - LineWritten(null, new DebugEventArgs(text, code)); - } - /// - /// Update the last written line - /// - /// The debug line - /// The status - public static void UpdateLine(string text, DebugCode code = DebugCode.None) - { - if (UpdateLastLine != null) - UpdateLastLine(null, new DebugEventArgs(text, code)); - } - - public static event EventHandler LineWritten; - public static event EventHandler UpdateLastLine; - } - public enum DebugCode - { - None, - Good, - Warning, - Error - } - /// - /// Console Debug Args - /// - public class DebugEventArgs : EventArgs - { - public DebugCode Code { get; private set; } - public string Text { get; private set; } - - /// - /// Console Debug Args - /// - /// The debug line - /// The status - public DebugEventArgs(string text, DebugCode code) - { - this.Text = text; - this.Code = code; - } - } - public struct DebugLine - { - public DebugLine(string debugLine, DebugCode status) - { - this.debugLine = debugLine; - this.status = status; - } - string debugLine; - DebugCode status; - - public string Text - { get { return debugLine; } set { debugLine = value; } } - public DebugCode Code - { get { return status; } set { status = value; } } - } -} diff --git a/OpenSubtitlesHandler/Interfaces/IMethodResponse.cs b/OpenSubtitlesHandler/Interfaces/IMethodResponse.cs deleted file mode 100644 index 3450beff59..0000000000 --- a/OpenSubtitlesHandler/Interfaces/IMethodResponse.cs +++ /dev/null @@ -1,71 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; - -namespace OpenSubtitlesHandler -{ - /// - /// When you call a method to communicate with OpenSubtitles server, that method should return this response with the reuired information. - /// - public abstract class IMethodResponse - { - public IMethodResponse() { LoadAttributes(); } - public IMethodResponse(string name, string message) - { - this.name = name; - this.message = message; - } - protected string name; - protected string message; - protected double seconds; - protected string status; - - protected void LoadAttributes() - { - //foreach (Attribute attr in Attribute.GetCustomAttributes(this.GetType())) - //{ - // if (attr.GetType() == typeof(MethodResponseDescription)) - // { - // this.name = ((MethodResponseDescription)attr).Name; - // this.message = ((MethodResponseDescription)attr).Message; - // break; - // } - //} - } - - [Description("The name of this response"), Category("MethodResponse")] - public virtual string Name { get { return name; } set { name = value; } } - [Description("The message about this response"), Category("MethodResponse")] - public virtual string Message { get { return message; } set { message = value; } } - [Description("Time taken to execute this command on server"), Category("MethodResponse")] - public double Seconds { get { return seconds; } set { seconds = value; } } - [Description("The status"), Category("MethodResponse")] - public string Status { get { return status; } set { status = value; } } - } - - public class DescriptionAttribute : Attribute - { - public DescriptionAttribute(string text) { } - } - - public class CategoryAttribute : Attribute - { - public CategoryAttribute(string text) { } - } -} diff --git a/OpenSubtitlesHandler/Interfaces/MethodResponseAttr.cs b/OpenSubtitlesHandler/Interfaces/MethodResponseAttr.cs deleted file mode 100644 index a7e49032de..0000000000 --- a/OpenSubtitlesHandler/Interfaces/MethodResponseAttr.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; - -namespace OpenSubtitlesHandler -{ - /// - /// Attributes to describe a method response object. - /// - public class MethodResponseDescription : Attribute - { - public MethodResponseDescription(string name, string message) - { - this.name = name; - this.message = message; - } - - private string name; - private string message; - - public string Name { get { return name; } set { name = value; } } - public string Message { get { return message; } set { message = value; } } - } -} diff --git a/OpenSubtitlesHandler/Languages/DetectLanguageResult.cs b/OpenSubtitlesHandler/Languages/DetectLanguageResult.cs deleted file mode 100644 index 3e72dc65c4..0000000000 --- a/OpenSubtitlesHandler/Languages/DetectLanguageResult.cs +++ /dev/null @@ -1,31 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - public struct DetectLanguageResult - { - private string inputSample; - private string languageID; - - public string LanguageID - { get { return languageID; } set { languageID = value; } } - public string InputSample - { get { return inputSample; } set { inputSample = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseAddComment.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseAddComment.cs deleted file mode 100644 index 03d0337d4c..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseAddComment.cs +++ /dev/null @@ -1,32 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("AddComment method response", - "AddComment method response hold all expected values from server.")] - public class MethodResponseAddComment : IMethodResponse - { - public MethodResponseAddComment() - : base() - { } - public MethodResponseAddComment(string name, string message) - : base(name, message) - { } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseAddRequest.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseAddRequest.cs deleted file mode 100644 index b996043c28..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseAddRequest.cs +++ /dev/null @@ -1,35 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("AddRequest method response", - "AddRequest method response hold all expected values from server.")] - public class MethodResponseAddRequest : IMethodResponse - { - public MethodResponseAddRequest() - : base() - { } - public MethodResponseAddRequest(string name, string message) - : base(name, message) - { } - private string _request_url; - - public string request_url { get { return _request_url; } set { _request_url = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseAutoUpdate.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseAutoUpdate.cs deleted file mode 100644 index 6ee14f1f8f..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseAutoUpdate.cs +++ /dev/null @@ -1,59 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("AutoUpdate method response", - "AutoUpdate method response hold all expected values from server.")] - public class MethodResponseAutoUpdate : IMethodResponse - { - public MethodResponseAutoUpdate() - : base() - { } - public MethodResponseAutoUpdate(string name, string message) - : base(name, message) - { } - - private string _version; - private string _url_windows; - private string _comments; - private string _url_linux; - /// - /// Latest application version - /// - [Description("Latest application version"), Category("AutoUpdate")] - public string version { get { return _version; } set { _version = value; } } - /// - /// Download URL for Windows version - /// - [Description("Download URL for Windows version"), Category("AutoUpdate")] - public string url_windows { get { return _url_windows; } set { _url_windows = value; } } - /// - /// Application changelog and other comments - /// - [Description("Application changelog and other comments"), Category("AutoUpdate")] - public string comments { get { return _comments; } set { _comments = value; } } - /// - /// Download URL for Linux version - /// - [Description("Download URL for Linux version"), Category("AutoUpdate")] - public string url_linux { get { return _url_linux; } set { _url_linux = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash.cs deleted file mode 100644 index 4bb3262442..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("CheckMovieHash method response", - "CheckMovieHash method response hold all expected values from server.")] - public class MethodResponseCheckMovieHash : IMethodResponse - { - public MethodResponseCheckMovieHash() - : base() - { } - public MethodResponseCheckMovieHash(string name, string message) - : base(name, message) - { } - private List results = new List(); - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash2.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash2.cs deleted file mode 100644 index 72b4c3b152..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash2.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("CheckMovieHash2 method response", - "CheckMovieHash2 method response hold all expected values from server.")] - public class MethodResponseCheckMovieHash2 : IMethodResponse - { - public MethodResponseCheckMovieHash2() - : base() - { } - public MethodResponseCheckMovieHash2(string name, string message) - : base(name, message) - { } - private List results = new List(); - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckSubHash.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckSubHash.cs deleted file mode 100644 index 04e287ea74..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckSubHash.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("CheckSubHash method response", - "CheckSubHash method response hold all expected values from server.")] - public class MethodResponseCheckSubHash : IMethodResponse - { - public MethodResponseCheckSubHash() - : base() - { } - public MethodResponseCheckSubHash(string name, string message) - : base(name, message) - { } - private List results = new List(); - - public List Results { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseDetectLanguage.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseDetectLanguage.cs deleted file mode 100644 index 6bf21d50e8..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseDetectLanguage.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("DetectLanguage method response", - "DetectLanguage method response hold all expected values from server.")] - public class MethodResponseDetectLanguage : IMethodResponse - { - public MethodResponseDetectLanguage() - : base() - { } - public MethodResponseDetectLanguage(string name, string message) - : base(name, message) - { } - private List results = new List(); - - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseError.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseError.cs deleted file mode 100644 index 7ed807067e..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseError.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - /// - /// Response that can be used for general error like internet connection fail. - /// - [MethodResponseDescription("Error method response", - "Error method response that describes error that occured")] - public class MethodResponseError : IMethodResponse - { - public MethodResponseError() - : base() - { } - public MethodResponseError(string name, string message) - : base(name, message) - { } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetAvailableTranslations.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseGetAvailableTranslations.cs deleted file mode 100644 index 91803f4b3d..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetAvailableTranslations.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("GetAvailableTranslations method response", - "GetAvailableTranslations method response hold all expected values from server.")] - public class MethodResponseGetAvailableTranslations : IMethodResponse - { - public MethodResponseGetAvailableTranslations() - : base() - { } - public MethodResponseGetAvailableTranslations(string name, string message) - : base(name, message) - { } - private List results = new List(); - public List Results { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetComments.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseGetComments.cs deleted file mode 100644 index 421e1783bc..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetComments.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("GetComments method response", - "GetComments method response hold all expected values from server.")] - public class MethodResponseGetComments : IMethodResponse - { - public MethodResponseGetComments() - : base() - { } - public MethodResponseGetComments(string name, string message) - : base(name, message) - { } - private List results = new List(); - - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetSubLanguages.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseGetSubLanguages.cs deleted file mode 100644 index 905b87c916..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetSubLanguages.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("GetSubLanguages method response", - "GetSubLanguages method response hold all expected values from server.")] - public class MethodResponseGetSubLanguages : IMethodResponse - { - public MethodResponseGetSubLanguages() - : base() - { } - public MethodResponseGetSubLanguages(string name, string message) - : base(name, message) - { } - private List languages = new List(); - - public List Languages - { get { return languages; } set { languages = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetTranslation.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseGetTranslation.cs deleted file mode 100644 index f008f7711d..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetTranslation.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("GetTranslation method response", - "GetTranslation method response hold all expected values from server.")] - public class MethodResponseGetTranslation : IMethodResponse - { - public MethodResponseGetTranslation() - : base() - { } - public MethodResponseGetTranslation(string name, string message) - : base(name, message) - { } - private string data; - - public string ContentData { get { return data; } set { data = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovie.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovie.cs deleted file mode 100644 index 1148b5f479..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovie.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("InsertMovie method response", - "InsertMovie method response hold all expected values from server.")] - public class MethodResponseInsertMovie : IMethodResponse - { - public MethodResponseInsertMovie() - : base() - { } - public MethodResponseInsertMovie(string name, string message) - : base(name, message) - { } - private string _ID; - public string ID - { get { return _ID; } set { _ID = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovieHash.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovieHash.cs deleted file mode 100644 index 74f52837c9..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovieHash.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("InsertMovieHash method response", - "InsertMovieHash method response hold all expected values from server.")] - public class MethodResponseInsertMovieHash : IMethodResponse - { - public MethodResponseInsertMovieHash() - : base() - { } - public MethodResponseInsertMovieHash(string name, string message) - : base(name, message) - { } - private List _accepted_moviehashes = new List(); - private List _new_imdbs = new List(); - - public List accepted_moviehashes { get { return _accepted_moviehashes; } set { _accepted_moviehashes = value; } } - public List new_imdbs { get { return _new_imdbs; } set { _new_imdbs = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseLogIn.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseLogIn.cs deleted file mode 100644 index 44d2943824..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseLogIn.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - /// - /// Response can be used for log in/out operation. - /// - [MethodResponseDescription("LogIn method response", - "LogIn method response hold all expected values from server.")] - public class MethodResponseLogIn : IMethodResponse - { - public MethodResponseLogIn() - : base() - { } - public MethodResponseLogIn(string name, string message) - : base(name, message) - { } - private string token; - - public string Token { get { return token; } set { token = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieDetails.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieDetails.cs deleted file mode 100644 index 6126c0053a..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieDetails.cs +++ /dev/null @@ -1,73 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("MovieDetails method response", - "MovieDetails method response hold all expected values from server.")] - public class MethodResponseMovieDetails : IMethodResponse - { - public MethodResponseMovieDetails() - : base() - { } - public MethodResponseMovieDetails(string name, string message) - : base(name, message) - { } - // Details - private string id; - private string title; - private string year; - private string coverLink; - - private string duration; - private string tagline; - private string plot; - private string goofs; - private string trivia; - private List cast = new List(); - private List directors = new List(); - private List writers = new List(); - private List awards = new List(); - private List genres = new List(); - private List country = new List(); - private List language = new List(); - private List certification = new List(); - - // Details - public string ID { get { return id; } set { id = value; } } - public string Title { get { return title; } set { title = value; } } - public string Year { get { return year; } set { year = value; } } - public string CoverLink { get { return coverLink; } set { coverLink = value; } } - public string Duration { get { return duration; } set { duration = value; } } - public string Tagline { get { return tagline; } set { tagline = value; } } - public string Plot { get { return plot; } set { plot = value; } } - public string Goofs { get { return goofs; } set { goofs = value; } } - public string Trivia { get { return trivia; } set { trivia = value; } } - public List Cast { get { return cast; } set { cast = value; } } - public List Directors { get { return directors; } set { directors = value; } } - public List Writers { get { return writers; } set { writers = value; } } - public List Genres { get { return genres; } set { genres = value; } } - public List Awards { get { return awards; } set { awards = value; } } - public List Country { get { return country; } set { country = value; } } - public List Language { get { return language; } set { language = value; } } - public List Certification { get { return certification; } set { certification = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieSearch.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieSearch.cs deleted file mode 100644 index 93cd703462..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieSearch.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("MovieSearch method response", - "MovieSearch method response hold all expected values from server.")] - public class MethodResponseMovieSearch : IMethodResponse - { - public MethodResponseMovieSearch() - : base() - { - results = new List(); - } - public MethodResponseMovieSearch(string name, string message) - : base(name, message) - { - results = new List(); - } - private List results; - - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseNoOperation.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseNoOperation.cs deleted file mode 100644 index 02a9993cb1..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseNoOperation.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("NoOperation method response", - "NoOperation method response hold all expected values from server.")] - public class MethodResponseNoOperation : IMethodResponse - { - public MethodResponseNoOperation() - : base() { } - public MethodResponseNoOperation(string name, string message) - : base(name, message) - { } - - private string _global_wrh_download_limit; - private string _client_ip; - private string _limit_check_by; - private string _client_24h_download_count; - private string _client_downlaod_quota; - private string _client_24h_download_limit; - - public string global_wrh_download_limit - { get { return _global_wrh_download_limit; } set { _global_wrh_download_limit = value; } } - public string client_ip - { get { return _client_ip; } set { _client_ip = value; } } - public string limit_check_by - { get { return _limit_check_by; } set { _limit_check_by = value; } } - public string client_24h_download_count - { get { return _client_24h_download_count; } set { _client_24h_download_count = value; } } - public string client_downlaod_quota - { get { return _client_downlaod_quota; } set { _client_downlaod_quota = value; } } - public string client_24h_download_limit - { get { return _client_24h_download_limit; } set { _client_24h_download_limit = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongImdbMovie.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongImdbMovie.cs deleted file mode 100644 index 391fec58a0..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongImdbMovie.cs +++ /dev/null @@ -1,33 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("ReportWrongImdbMovie method response", - "ReportWrongImdbMovie method response hold all expected values from server.")] - public class MethodResponseReportWrongImdbMovie : IMethodResponse - { - public MethodResponseReportWrongImdbMovie() - : base() - { } - public MethodResponseReportWrongImdbMovie(string name, string message) - : base(name, message) - { } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongMovieHash.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongMovieHash.cs deleted file mode 100644 index 5696e7084c..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongMovieHash.cs +++ /dev/null @@ -1,33 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("ReportWrongMovieHash method response", - "ReportWrongMovieHash method response hold all expected values from server.")] - public class MethodResponseReportWrongMovieHash : IMethodResponse - { - public MethodResponseReportWrongMovieHash() - : base() - { } - public MethodResponseReportWrongMovieHash(string name, string message) - : base(name, message) - { } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseSearchToMail.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseSearchToMail.cs deleted file mode 100644 index ea248bc22c..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseSearchToMail.cs +++ /dev/null @@ -1,32 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("SearchToMail method response", - "SearchToMail method response hold all expected values from server.")] - public class MethodResponseSearchToMail : IMethodResponse - { - public MethodResponseSearchToMail() - : base() - { } - public MethodResponseSearchToMail(string name, string message) - : base(name, message) - { } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseServerInfo.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseServerInfo.cs deleted file mode 100644 index 973550e9f0..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseServerInfo.cs +++ /dev/null @@ -1,131 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("ServerInfo method response", - "ServerInfo method response hold all expected values from server.")] - public class MethodResponseServerInfo : IMethodResponse - { - public MethodResponseServerInfo() - : base() - { } - public MethodResponseServerInfo(string name, string message) - : base(name, message) - { } - private string _xmlrpc_version; - private string _xmlrpc_url; - private string _application; - private string _contact; - private string _website_url; - private int _users_online_total; - private int _users_online_program; - private int _users_loggedin; - private string _users_max_alltime; - private string _users_registered; - private string _subs_downloads; - private string _subs_subtitle_files; - private string _movies_total; - private string _movies_aka; - private string _total_subtitles_languages; - private List _last_update_strings = new List(); - - /// - /// Version of server's XML-RPC API implementation - /// - [Description("Version of server's XML-RPC API implementation"), Category("OS")] - public string xmlrpc_version { get { return _xmlrpc_version; } set { _xmlrpc_version = value; } } - /// - /// XML-RPC interface URL - /// - [Description("XML-RPC interface URL"), Category("OS")] - public string xmlrpc_url { get { return _xmlrpc_url; } set { _xmlrpc_url = value; } } - /// - /// Server's application name and version - /// - [Description("Server's application name and version"), Category("OS")] - public string application { get { return _application; } set { _application = value; } } - /// - /// Contact e-mail address for server related quuestions and problems - /// - [Description("Contact e-mail address for server related quuestions and problems"), Category("OS")] - public string contact { get { return _contact; } set { _contact = value; } } - /// - /// Main server URL - /// - [Description("Main server URL"), Category("OS")] - public string website_url { get { return _website_url; } set { _website_url = value; } } - /// - /// Number of users currently online - /// - [Description("Number of users currently online"), Category("OS")] - public int users_online_total { get { return _users_online_total; } set { _users_online_total = value; } } - /// - /// Number of users currently online using a client application (XML-RPC API) - /// - [Description("Number of users currently online using a client application (XML-RPC API)"), Category("OS")] - public int users_online_program { get { return _users_online_program; } set { _users_online_program = value; } } - /// - /// Number of currently logged-in users - /// - [Description("Number of currently logged-in users"), Category("OS")] - public int users_loggedin { get { return _users_loggedin; } set { _users_loggedin = value; } } - /// - /// Maximum number of users throughout the history - /// - [Description("Maximum number of users throughout the history"), Category("OS")] - public string users_max_alltime { get { return _users_max_alltime; } set { _users_max_alltime = value; } } - /// - /// Number of registered users - /// - [Description("Number of registered users"), Category("OS")] - public string users_registered { get { return _users_registered; } set { _users_registered = value; } } - /// - /// Total number of subtitle downloads - /// - [Description("Total number of subtitle downloads"), Category("OS")] - public string subs_downloads { get { return _subs_downloads; } set { _subs_downloads = value; } } - /// - /// Total number of subtitle files stored on the server - /// - [Description("Total number of subtitle files stored on the server"), Category("OS")] - public string subs_subtitle_files { get { return _subs_subtitle_files; } set { _subs_subtitle_files = value; } } - /// - /// Total number of movies in the database - /// - [Description("Total number of movies in the database"), Category("OS")] - public string movies_total { get { return _movies_total; } set { _movies_total = value; } } - /// - /// Total number of movie A.K.A. titles in the database - /// - [Description("Total number of movie A.K.A. titles in the database"), Category("OS")] - public string movies_aka { get { return _movies_aka; } set { _movies_aka = value; } } - /// - /// Total number of subtitle languages supported - /// - [Description("Total number of subtitle languages supported"), Category("OS")] - public string total_subtitles_languages { get { return _total_subtitles_languages; } set { _total_subtitles_languages = value; } } - /// - /// Structure containing information about last updates of translations. - /// - [Description("Structure containing information about last updates of translations"), Category("OS")] - public List last_update_strings { get { return _last_update_strings; } set { _last_update_strings = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleDownload.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleDownload.cs deleted file mode 100644 index 6a5d57d19a..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleDownload.cs +++ /dev/null @@ -1,44 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - /// - /// The response that should be used by subtitle download methods. - /// - [MethodResponseDescription("SubtitleDownload method response", - "SubtitleDownload method response hold all expected values from server.")] - public class MethodResponseSubtitleDownload : IMethodResponse - { - public MethodResponseSubtitleDownload() - : base() - { - results = new List(); - } - public MethodResponseSubtitleDownload(string name, string message) - : base(name, message) - { - results = new List(); - } - private List results; - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleSearch.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleSearch.cs deleted file mode 100644 index 0dce203495..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleSearch.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - /// - /// Response to SearchSubtitle successed call. - /// - [MethodResponseDescription("SubtitleSearch method response", - "SubtitleSearch method response hold all expected values from server.")] - public class MethodResponseSubtitleSearch : IMethodResponse - { - public MethodResponseSubtitleSearch() - : base() - { - results = new List(); - } - public MethodResponseSubtitleSearch(string name, string message) - : base(name, message) - { - results = new List(); - } - - private List results; - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitlesVote.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitlesVote.cs deleted file mode 100644 index f02f822f05..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitlesVote.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("SubtitlesVote method response", - "SubtitlesVote method response hold all expected values from server.")] - public class MethodResponseSubtitlesVote : IMethodResponse - { - public MethodResponseSubtitlesVote() - : base() - { } - public MethodResponseSubtitlesVote(string name, string message) - : base(name, message) - { } - private string _SubRating; - private string _SubSumVotes; - private string _IDSubtitle; - - public string SubRating - { get { return _SubRating; } set { _SubRating = value; } } - public string SubSumVotes - { get { return _SubSumVotes; } set { _SubSumVotes = value; } } - public string IDSubtitle - { get { return _IDSubtitle; } set { _IDSubtitle = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseTryUploadSubtitles.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseTryUploadSubtitles.cs deleted file mode 100644 index cb3866a628..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseTryUploadSubtitles.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("TryUploadSubtitles method response", - "TryUploadSubtitles method response hold all expected values from server.")] - public class MethodResponseTryUploadSubtitles : IMethodResponse - { - public MethodResponseTryUploadSubtitles() - : base() - { } - public MethodResponseTryUploadSubtitles(string name, string message) - : base(name, message) - { } - private int alreadyindb; - private List results = new List(); - - public int AlreadyInDB { get { return alreadyindb; } set { alreadyindb = value; } } - public List Results { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseUploadSubtitles.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseUploadSubtitles.cs deleted file mode 100644 index bda950befc..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseUploadSubtitles.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("UploadSubtitles method response", - "UploadSubtitles method response hold all expected values from server.")] - public class MethodResponseUploadSubtitles : IMethodResponse - { - public MethodResponseUploadSubtitles() - : base() - { } - public MethodResponseUploadSubtitles(string name, string message) - : base(name, message) - { } - private string _data; - private bool _subtitles; - - public string Data { get { return _data; } set { _data = value; } } - public bool SubTitles { get { return _subtitles; } set { _subtitles = value; } } - } -} diff --git a/OpenSubtitlesHandler/Methods Implemeted.txt b/OpenSubtitlesHandler/Methods Implemeted.txt deleted file mode 100644 index e3493d9a2c..0000000000 --- a/OpenSubtitlesHandler/Methods Implemeted.txt +++ /dev/null @@ -1,39 +0,0 @@ -List of available OpenSubtitles.org server XML-RPC methods. -========================================================== -Legends: -* OK: this method is fully implemented, tested and works fine. -* TODO: this method is in the plan to be added. -* NOT TESTED: this method added and expected to work fine but never tested. -* NOT WORK (x): this method added but not work. x= Description of the error. - --------------------------------------------- -Method name | Status --------------------------|------------------ -LogIn | OK -LogOut | OK -NoOperation | OK -SearchSubtitles | OK -DownloadSubtitles | OK -SearchToMail | OK -TryUploadSubtitles | OK -UploadSubtitles | OK -SearchMoviesOnIMDB | OK -GetIMDBMovieDetails | OK -InsertMovie | OK -InsertMovieHash | OK -ServerInfo | OK -ReportWrongMovieHash | OK -ReportWrongImdbMovie | OK -SubtitlesVote | OK -AddComment | OK -AddRequest | OK -GetComments | OK -GetSubLanguages | OK -DetectLanguage | OK -GetAvailableTranslations | OK -GetTranslation | NOT WORK (Returns status of error 410 'Other or unknown error') -AutoUpdate | NOT WORK (Returns status: 'parse error. not well formed') -CheckMovieHash | OK -CheckMovieHash2 | OK -CheckSubHash | OK --------------------------------------------- diff --git a/OpenSubtitlesHandler/MovieHasher.cs b/OpenSubtitlesHandler/MovieHasher.cs deleted file mode 100644 index 25d91c1aca..0000000000 --- a/OpenSubtitlesHandler/MovieHasher.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.IO; -using System.Text; - -namespace OpenSubtitlesHandler -{ - public class MovieHasher - { - public static byte[] ComputeMovieHash(Stream input) - { - using (input) - { - long lhash, streamsize; - streamsize = input.Length; - lhash = streamsize; - - long i = 0; - byte[] buffer = new byte[sizeof(long)]; - while (i < 65536 / sizeof(long) && (input.Read(buffer, 0, sizeof(long)) > 0)) - { - i++; - lhash += BitConverter.ToInt64(buffer, 0); - } - - input.Position = Math.Max(0, streamsize - 65536); - i = 0; - while (i < 65536 / sizeof(long) && (input.Read(buffer, 0, sizeof(long)) > 0)) - { - i++; - lhash += BitConverter.ToInt64(buffer, 0); - } - byte[] result = BitConverter.GetBytes(lhash); - Array.Reverse(result); - return result; - } - } - - public static string ToHexadecimal(byte[] bytes) - { - var hexBuilder = new StringBuilder(); - for (int i = 0; i < bytes.Length; i++) - { - hexBuilder.Append(bytes[i].ToString("x2")); - } - return hexBuilder.ToString(); - } - } -} diff --git a/OpenSubtitlesHandler/Movies/CheckMovieHash2Data.cs b/OpenSubtitlesHandler/Movies/CheckMovieHash2Data.cs deleted file mode 100644 index 9535901375..0000000000 --- a/OpenSubtitlesHandler/Movies/CheckMovieHash2Data.cs +++ /dev/null @@ -1,42 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct CheckMovieHash2Data - { - private string _MovieHash; - private string _MovieImdbID; - private string _MovieName; - private string _MovieYear; - private string _MovieKind; - private string _SeriesSeason; - private string _SeriesEpisode; - private string _SeenCount; - - public string MovieHash { get { return _MovieHash; } set { _MovieHash = value; } } - public string MovieImdbID { get { return _MovieImdbID; } set { _MovieImdbID = value; } } - public string MovieName { get { return _MovieName; } set { _MovieName = value; } } - public string MovieYear { get { return _MovieYear; } set { _MovieYear = value; } } - public string MovieKind { get { return _MovieKind; } set { _MovieKind = value; } } - public string SeriesSeason { get { return _SeriesSeason; } set { _SeriesSeason = value; } } - public string SeriesEpisode { get { return _SeriesEpisode; } set { _SeriesEpisode = value; } } - public string SeenCount { get { return _SeenCount; } set { _SeenCount = value; } } - } -} diff --git a/OpenSubtitlesHandler/Movies/CheckMovieHash2Result.cs b/OpenSubtitlesHandler/Movies/CheckMovieHash2Result.cs deleted file mode 100644 index 96652fae74..0000000000 --- a/OpenSubtitlesHandler/Movies/CheckMovieHash2Result.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - public class CheckMovieHash2Result - { - private string name; - private List data = new List(); - - public string Name { get { return name; } set { name = value; } } - public List Items { get { return data; } set { data = value; } } - - public override string ToString() - { - return name; - } - } -} diff --git a/OpenSubtitlesHandler/Movies/CheckMovieHashResult.cs b/OpenSubtitlesHandler/Movies/CheckMovieHashResult.cs deleted file mode 100644 index 0d6c79f80b..0000000000 --- a/OpenSubtitlesHandler/Movies/CheckMovieHashResult.cs +++ /dev/null @@ -1,41 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct CheckMovieHashResult - { - private string name; - private string _MovieHash; - private string _MovieImdbID; - private string _MovieName; - private string _MovieYear; - - public string MovieHash { get { return _MovieHash; } set { _MovieHash = value; } } - public string MovieImdbID { get { return _MovieImdbID; } set { _MovieImdbID = value; } } - public string MovieName { get { return _MovieName; } set { _MovieName = value; } } - public string MovieYear { get { return _MovieYear; } set { _MovieYear = value; } } - public string Name { get { return name; } set { name = value; } } - - public override string ToString() - { - return name; - } - } -} diff --git a/OpenSubtitlesHandler/Movies/InsertMovieHashParameters.cs b/OpenSubtitlesHandler/Movies/InsertMovieHashParameters.cs deleted file mode 100644 index d0de7f8c64..0000000000 --- a/OpenSubtitlesHandler/Movies/InsertMovieHashParameters.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public class InsertMovieHashParameters - { - private string _moviehash = ""; - private string _moviebytesize = ""; - private string _imdbid = ""; - private string _movietimems = ""; - private string _moviefps = ""; - private string _moviefilename = ""; - - public string moviehash { get { return _moviehash; } set { _moviehash = value; } } - public string moviebytesize { get { return _moviebytesize; } set { _moviebytesize = value; } } - public string imdbid { get { return _imdbid; } set { _imdbid = value; } } - public string movietimems { get { return _movietimems; } set { _movietimems = value; } } - public string moviefps { get { return _moviefps; } set { _moviefps = value; } } - public string moviefilename { get { return _moviefilename; } set { _moviefilename = value; } } - } -} diff --git a/OpenSubtitlesHandler/Movies/MovieSearchResult.cs b/OpenSubtitlesHandler/Movies/MovieSearchResult.cs deleted file mode 100644 index d771165830..0000000000 --- a/OpenSubtitlesHandler/Movies/MovieSearchResult.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct MovieSearchResult - { - private string id; - private string title; - - public string ID - { get { return id; } set { id = value; } } - public string Title - { get { return title; } set { title = value; } } - /// - /// Title - /// - /// - public override string ToString() - { - return title; - } - } -} diff --git a/OpenSubtitlesHandler/Movies/SearchToMailMovieParameter.cs b/OpenSubtitlesHandler/Movies/SearchToMailMovieParameter.cs deleted file mode 100644 index a0ecc87f8a..0000000000 --- a/OpenSubtitlesHandler/Movies/SearchToMailMovieParameter.cs +++ /dev/null @@ -1,30 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct SearchToMailMovieParameter - { - private string _moviehash; - private double _moviesize; - - public string moviehash { get { return _moviehash; } set { _moviehash = value; } } - public double moviesize { get { return _moviesize; } set { _moviesize = value; } } - } -} diff --git a/OpenSubtitlesHandler/OpenSubtitles.cs b/OpenSubtitlesHandler/OpenSubtitles.cs deleted file mode 100644 index 76f70dc070..0000000000 --- a/OpenSubtitlesHandler/OpenSubtitles.cs +++ /dev/null @@ -1,2744 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using OpenSubtitlesHandler.Console; -using XmlRpcHandler; - -namespace OpenSubtitlesHandler -{ - /// - /// The core of the OpenSubtitles Handler library. All members are static. - /// - public sealed class OpenSubtitles - { - private static string XML_PRC_USERAGENT = ""; - // This is session id after log in, important value and MUST be set by LogIn before any other call. - private static string TOKEN = ""; - - /// - /// Set the useragent value. This must be called before doing anything else. - /// - /// The useragent value - public static void SetUserAgent(string agent) - { - XML_PRC_USERAGENT = agent; - } - - /*Session handling*/ - /// - /// Send a LogIn request, this must be called before anything else in this class. - /// - /// The user name of OpenSubtitles - /// The password - /// The language, usally en - /// Status of the login operation - public static IMethodResponse LogIn(string userName, string password, string language) - { - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(userName)); - parms.Add(new XmlRpcValueBasic(password)); - parms.Add(new XmlRpcValueBasic(language)); - parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT)); - var call = new XmlRpcMethodCall("LogIn", parms); - OSHConsole.WriteLine("Sending LogIn request to the server ...", DebugCode.Good); - - //File.WriteAllText(".\\request.txt", Encoding.UTF8.GetString(XmlRpcGenerator.Generate(call))); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. We expect Struct here. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - var re = new MethodResponseLogIn("Success", "Log in successful."); - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "token": re.Token = TOKEN = MEMBER.Data.Data.ToString(); OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": re.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "status": re.Status = MEMBER.Data.Data.ToString(); OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - return re; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "Log in failed !"); - } - - public static async Task LogInAsync(string userName, string password, string language, CancellationToken cancellationToken) - { - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(userName)); - parms.Add(new XmlRpcValueBasic(password)); - parms.Add(new XmlRpcValueBasic(language)); - parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT)); - var call = new XmlRpcMethodCall("LogIn", parms); - OSHConsole.WriteLine("Sending LogIn request to the server ...", DebugCode.Good); - - //File.WriteAllText(".\\request.txt", Encoding.UTF8.GetString(XmlRpcGenerator.Generate(call))); - // Send the request to the server - var stream = await Utilities.SendRequestAsync(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT, cancellationToken) - .ConfigureAwait(false); - - string response = Utilities.GetStreamString(stream); - - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. We expect Struct here. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - var re = new MethodResponseLogIn("Success", "Log in successful."); - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "token": - re.Token = TOKEN = MEMBER.Data.Data.ToString(); - OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "seconds": - re.Seconds = double.Parse(MEMBER.Data.Data.ToString(), CultureInfo.InvariantCulture); - OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "status": - re.Status = MEMBER.Data.Data.ToString(); - OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - } - } - return re; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "Log in failed !"); - } - - /// - /// Log out from the server. Call this to terminate the session. - /// - /// - public static IMethodResponse LogOut() - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var call = new XmlRpcMethodCall("LogOut", parms); - - OSHConsole.WriteLine("Sending LogOut request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. We expect Struct here. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var strct = (XmlRpcValueStruct)calls[0].Parameters[0]; - OSHConsole.WriteLine("STATUS=" + ((XmlRpcValueBasic)strct.Members[0].Data).Data.ToString()); - OSHConsole.WriteLine("SECONDS=" + ((XmlRpcValueBasic)strct.Members[1].Data).Data.ToString()); - var re = new MethodResponseLogIn("Success", "Log out successful."); - re.Status = ((XmlRpcValueBasic)strct.Members[0].Data).Data.ToString(); - re.Seconds = (double)((XmlRpcValueBasic)strct.Members[1].Data).Data; - return re; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "Log out failed !"); - } - /// - /// keep-alive user's session, verify token/session validity - /// - /// Status of the call operation - public static IMethodResponse NoOperation() - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT, XmlRpcBasicValueType.String)); - var call = new XmlRpcMethodCall("NoOperation", parms); - - OSHConsole.WriteLine("Sending NoOperation request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. We expect Struct here. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var R = new MethodResponseNoOperation(); - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data); break; - case "download_limits": - var dlStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dlmember in dlStruct.Members) - { - OSHConsole.WriteLine(" >" + dlmember.Name + "= " + dlmember.Data.Data.ToString()); - switch (dlmember.Name) - { - case "global_wrh_download_limit": R.global_wrh_download_limit = dlmember.Data.Data.ToString(); break; - case "client_ip": R.client_ip = dlmember.Data.Data.ToString(); break; - case "limit_check_by": R.limit_check_by = dlmember.Data.Data.ToString(); break; - case "client_24h_download_count": R.client_24h_download_count = dlmember.Data.Data.ToString(); break; - case "client_downlaod_quota": R.client_downlaod_quota = dlmember.Data.Data.ToString(); break; - case "client_24h_download_limit": R.client_24h_download_limit = dlmember.Data.Data.ToString(); break; - } - } - break; - } - } - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "NoOperation call failed !"); - } - /*Search and download*/ - /// - /// Search for subtitle files matching your videos using either video file hashes or IMDb IDs. - /// - /// List of search subtitle parameters which each one represents 'struct parameter' as descriped at http://trac.opensubtitles.org/projects/opensubtitles/wiki/XmlRpcSearchSubtitles - /// Status of the call operation. If the call success the response will be 'MethodResponseSubtitleSearch' - public static IMethodResponse SearchSubtitles(SubtitleSearchParameters[] parameters) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - if (parameters == null) - { - OSHConsole.UpdateLine("No subtitle search parameter passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle search parameter passed"); - } - if (parameters.Length == 0) - { - OSHConsole.UpdateLine("No subtitle search parameter passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle search parameter passed"); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add subtitle search parameters. Each one will be like 'array' of structs. - var array = new XmlRpcValueArray(); - foreach (SubtitleSearchParameters param in parameters) - { - var strct = new XmlRpcValueStruct(new List()); - // sublanguageid member - var member = new XmlRpcStructMember("sublanguageid", - new XmlRpcValueBasic(param.SubLangaugeID, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - // moviehash member - if (param.MovieHash.Length > 0 && param.MovieByteSize > 0) - { - member = new XmlRpcStructMember("moviehash", - new XmlRpcValueBasic(param.MovieHash, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - // moviehash member - member = new XmlRpcStructMember("moviebytesize", - new XmlRpcValueBasic(param.MovieByteSize, XmlRpcBasicValueType.Int)); - strct.Members.Add(member); - } - if (param.Query.Length > 0) - { - member = new XmlRpcStructMember("query", - new XmlRpcValueBasic(param.Query, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - - if (param.Episode.Length > 0 && param.Season.Length > 0) - { - member = new XmlRpcStructMember("season", - new XmlRpcValueBasic(param.Season, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - member = new XmlRpcStructMember("episode", - new XmlRpcValueBasic(param.Episode, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - - // imdbid member - if (param.IMDbID.Length > 0) - { - member = new XmlRpcStructMember("imdbid", - new XmlRpcValueBasic(param.IMDbID, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - // Add the struct to the array - array.Values.Add(strct); - } - // Add the array to the parameters - parms.Add(array); - // Call ! - var call = new XmlRpcMethodCall("SearchSubtitles", parms); - OSHConsole.WriteLine("Sending SearchSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - // We expect Struct of 3 members: - //* the first is status - //* the second is [array of structs, each one includes subtitle file]. - //* the third is [double basic value] represent seconds token by server. - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSubtitleSearch(); - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Search results: "); - - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new SubtitleSearchResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "IDMovie": result.IDMovie = submember.Data.Data.ToString(); break; - case "IDMovieImdb": result.IDMovieImdb = submember.Data.Data.ToString(); break; - case "IDSubMovieFile": result.IDSubMovieFile = submember.Data.Data.ToString(); break; - case "IDSubtitle": result.IDSubtitle = submember.Data.Data.ToString(); break; - case "IDSubtitleFile": result.IDSubtitleFile = submember.Data.Data.ToString(); break; - case "ISO639": result.ISO639 = submember.Data.Data.ToString(); break; - case "LanguageName": result.LanguageName = submember.Data.Data.ToString(); break; - case "MovieByteSize": result.MovieByteSize = submember.Data.Data.ToString(); break; - case "MovieHash": result.MovieHash = submember.Data.Data.ToString(); break; - case "MovieImdbRating": result.MovieImdbRating = submember.Data.Data.ToString(); break; - case "MovieName": result.MovieName = submember.Data.Data.ToString(); break; - case "MovieNameEng": result.MovieNameEng = submember.Data.Data.ToString(); break; - case "MovieReleaseName": result.MovieReleaseName = submember.Data.Data.ToString(); break; - case "MovieTimeMS": result.MovieTimeMS = submember.Data.Data.ToString(); break; - case "MovieYear": result.MovieYear = submember.Data.Data.ToString(); break; - case "SubActualCD": result.SubActualCD = submember.Data.Data.ToString(); break; - case "SubAddDate": result.SubAddDate = submember.Data.Data.ToString(); break; - case "SubAuthorComment": result.SubAuthorComment = submember.Data.Data.ToString(); break; - case "SubBad": result.SubBad = submember.Data.Data.ToString(); break; - case "SubDownloadLink": result.SubDownloadLink = submember.Data.Data.ToString(); break; - case "SubDownloadsCnt": result.SubDownloadsCnt = submember.Data.Data.ToString(); break; - case "SeriesEpisode": result.SeriesEpisode = submember.Data.Data.ToString(); break; - case "SeriesSeason": result.SeriesSeason = submember.Data.Data.ToString(); break; - case "SubFileName": result.SubFileName = submember.Data.Data.ToString(); break; - case "SubFormat": result.SubFormat = submember.Data.Data.ToString(); break; - case "SubHash": result.SubHash = submember.Data.Data.ToString(); break; - case "SubLanguageID": result.SubLanguageID = submember.Data.Data.ToString(); break; - case "SubRating": result.SubRating = submember.Data.Data.ToString(); break; - case "SubSize": result.SubSize = submember.Data.Data.ToString(); break; - case "SubSumCD": result.SubSumCD = submember.Data.Data.ToString(); break; - case "UserID": result.UserID = submember.Data.Data.ToString(); break; - case "UserNickName": result.UserNickName = submember.Data.Data.ToString(); break; - case "ZipDownloadLink": result.ZipDownloadLink = submember.Data.Data.ToString(); break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine(">" + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "Search Subtitles call failed !"); - } - - public static async Task SearchSubtitlesAsync(SubtitleSearchParameters[] parameters, CancellationToken cancellationToken) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - if (parameters == null) - { - OSHConsole.UpdateLine("No subtitle search parameter passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle search parameter passed"); - } - if (parameters.Length == 0) - { - OSHConsole.UpdateLine("No subtitle search parameter passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle search parameter passed"); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add subtitle search parameters. Each one will be like 'array' of structs. - var array = new XmlRpcValueArray(); - foreach (SubtitleSearchParameters param in parameters) - { - var strct = new XmlRpcValueStruct(new List()); - // sublanguageid member - var member = new XmlRpcStructMember("sublanguageid", - new XmlRpcValueBasic(param.SubLangaugeID, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - // moviehash member - if (param.MovieHash.Length > 0 && param.MovieByteSize > 0) - { - member = new XmlRpcStructMember("moviehash", - new XmlRpcValueBasic(param.MovieHash, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - // moviehash member - member = new XmlRpcStructMember("moviebytesize", - new XmlRpcValueBasic(param.MovieByteSize, XmlRpcBasicValueType.Int)); - strct.Members.Add(member); - } - if (param.Query.Length > 0) - { - member = new XmlRpcStructMember("query", - new XmlRpcValueBasic(param.Query, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - - if (param.Episode.Length > 0 && param.Season.Length > 0) - { - member = new XmlRpcStructMember("season", - new XmlRpcValueBasic(param.Season, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - member = new XmlRpcStructMember("episode", - new XmlRpcValueBasic(param.Episode, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - - // imdbid member - if (param.IMDbID.Length > 0) - { - member = new XmlRpcStructMember("imdbid", - new XmlRpcValueBasic(param.IMDbID, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - // Add the struct to the array - array.Values.Add(strct); - } - // Add the array to the parameters - parms.Add(array); - // Call ! - var call = new XmlRpcMethodCall("SearchSubtitles", parms); - OSHConsole.WriteLine("Sending SearchSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(await Utilities.SendRequestAsync(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT, cancellationToken).ConfigureAwait(false)); - - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - // We expect Struct of 3 members: - //* the first is status - //* the second is [array of structs, each one includes subtitle file]. - //* the third is [double basic value] represent seconds token by server. - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSubtitleSearch(); - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Search results: "); - - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new SubtitleSearchResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "IDMovie": result.IDMovie = submember.Data.Data.ToString(); break; - case "IDMovieImdb": result.IDMovieImdb = submember.Data.Data.ToString(); break; - case "IDSubMovieFile": result.IDSubMovieFile = submember.Data.Data.ToString(); break; - case "IDSubtitle": result.IDSubtitle = submember.Data.Data.ToString(); break; - case "IDSubtitleFile": result.IDSubtitleFile = submember.Data.Data.ToString(); break; - case "ISO639": result.ISO639 = submember.Data.Data.ToString(); break; - case "LanguageName": result.LanguageName = submember.Data.Data.ToString(); break; - case "MovieByteSize": result.MovieByteSize = submember.Data.Data.ToString(); break; - case "MovieHash": result.MovieHash = submember.Data.Data.ToString(); break; - case "MovieImdbRating": result.MovieImdbRating = submember.Data.Data.ToString(); break; - case "MovieName": result.MovieName = submember.Data.Data.ToString(); break; - case "MovieNameEng": result.MovieNameEng = submember.Data.Data.ToString(); break; - case "MovieReleaseName": result.MovieReleaseName = submember.Data.Data.ToString(); break; - case "MovieTimeMS": result.MovieTimeMS = submember.Data.Data.ToString(); break; - case "MovieYear": result.MovieYear = submember.Data.Data.ToString(); break; - case "SubActualCD": result.SubActualCD = submember.Data.Data.ToString(); break; - case "SubAddDate": result.SubAddDate = submember.Data.Data.ToString(); break; - case "SubAuthorComment": result.SubAuthorComment = submember.Data.Data.ToString(); break; - case "SubBad": result.SubBad = submember.Data.Data.ToString(); break; - case "SubDownloadLink": result.SubDownloadLink = submember.Data.Data.ToString(); break; - case "SubDownloadsCnt": result.SubDownloadsCnt = submember.Data.Data.ToString(); break; - case "SeriesEpisode": result.SeriesEpisode = submember.Data.Data.ToString(); break; - case "SeriesSeason": result.SeriesSeason = submember.Data.Data.ToString(); break; - case "SubFileName": result.SubFileName = submember.Data.Data.ToString(); break; - case "SubFormat": result.SubFormat = submember.Data.Data.ToString(); break; - case "SubHash": result.SubHash = submember.Data.Data.ToString(); break; - case "SubLanguageID": result.SubLanguageID = submember.Data.Data.ToString(); break; - case "SubRating": result.SubRating = submember.Data.Data.ToString(); break; - case "SubSize": result.SubSize = submember.Data.Data.ToString(); break; - case "SubSumCD": result.SubSumCD = submember.Data.Data.ToString(); break; - case "UserID": result.UserID = submember.Data.Data.ToString(); break; - case "UserNickName": result.UserNickName = submember.Data.Data.ToString(); break; - case "ZipDownloadLink": result.ZipDownloadLink = submember.Data.Data.ToString(); break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine(">" + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "Search Subtitles call failed !"); - } - - /// - /// Download subtitle file(s) - /// - /// The subtitle IDS (an array of IDSubtitleFile value that given by server as SearchSubtiles results) - /// Status of the call operation. If the call success the response will be 'MethodResponseSubtitleDownload' which will hold downloaded subtitles - public static IMethodResponse DownloadSubtitles(int[] subIDS) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - if (subIDS == null) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - if (subIDS.Length == 0) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add subtitle search parameters. Each one will be like 'array' of structs. - var array = new XmlRpcValueArray(); - foreach (int id in subIDS) - { - array.Values.Add(new XmlRpcValueBasic(id, XmlRpcBasicValueType.Int)); - } - // Add the array to the parameters - parms.Add(array); - // Call ! - var call = new XmlRpcMethodCall("DownloadSubtitles", parms); - OSHConsole.WriteLine("Sending DownloadSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - // We expect Struct of 3 members: - //* the first is status - //* the second is [array of structs, each one includes subtitle file]. - //* the third is [double basic value] represent seconds token by server. - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSubtitleDownload(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Download results:"); - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new SubtitleDownloadResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "idsubtitlefile": result.IdSubtitleFile = (string)submember.Data.Data; break; - case "data": result.Data = (string)submember.Data.Data; break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine("> IDSubtilteFile= " + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "DownloadSubtitles call failed !"); - } - - public static async Task DownloadSubtitlesAsync(int[] subIDS, CancellationToken cancellationToken) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - if (subIDS == null) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - if (subIDS.Length == 0) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add subtitle search parameters. Each one will be like 'array' of structs. - var array = new XmlRpcValueArray(); - foreach (int id in subIDS) - { - array.Values.Add(new XmlRpcValueBasic(id, XmlRpcBasicValueType.Int)); - } - // Add the array to the parameters - parms.Add(array); - // Call ! - var call = new XmlRpcMethodCall("DownloadSubtitles", parms); - OSHConsole.WriteLine("Sending DownloadSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - - var httpResponse = await Utilities.SendRequestAsync(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT, cancellationToken).ConfigureAwait(false); - - string response = Utilities.GetStreamString(httpResponse); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - // We expect Struct of 3 members: - //* the first is status - //* the second is [array of structs, each one includes subtitle file]. - //* the third is [double basic value] represent seconds token by server. - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSubtitleDownload(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Download results:"); - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new SubtitleDownloadResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "idsubtitlefile": result.IdSubtitleFile = (string)submember.Data.Data; break; - case "data": result.Data = (string)submember.Data.Data; break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine("> IDSubtilteFile= " + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "DownloadSubtitles call failed !"); - } - - /// - /// Returns comments for subtitles - /// - /// The subtitle IDS (an array of IDSubtitleFile value that given by server as SearchSubtiles results) - /// Status of the call operation. If the call success the response will be 'MethodResponseGetComments' - public static IMethodResponse GetComments(int[] subIDS) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - if (subIDS == null) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - if (subIDS.Length == 0) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN)); - // Add subtitle search parameters. Each one will be like 'array' of structs. - var array = new XmlRpcValueArray(subIDS); - // Add the array to the parameters - parms.Add(array); - // Call ! - var call = new XmlRpcMethodCall("GetComments", parms); - OSHConsole.WriteLine("Sending GetComments request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseGetComments(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Comments results:"); - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue commentStruct in rarray.Values) - { - if (commentStruct == null) continue; - if (!(commentStruct is XmlRpcValueStruct)) continue; - - var result = new GetCommentsResult(); - foreach (XmlRpcStructMember commentmember in ((XmlRpcValueStruct)commentStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (commentmember.Name) - { - case "IDSubtitle": result.IDSubtitle = (string)commentmember.Data.Data; break; - case "UserID": result.UserID = (string)commentmember.Data.Data; break; - case "UserNickName": result.UserNickName = (string)commentmember.Data.Data; break; - case "Comment": result.Comment = (string)commentmember.Data.Data; break; - case "Created": result.Created = (string)commentmember.Data.Data; break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine("> IDSubtitle= " + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "GetComments call failed !"); - } - - /// - /// Schedule a periodical search for subtitles matching given video files, send results to user's e-mail address. - /// - /// The language 3 lenght ids array - /// The movies parameters - /// Status of the call operation. If the call success the response will be 'MethodResponseSearchToMail' - public static IMethodResponse SearchToMail(string[] languageIDS, SearchToMailMovieParameter[] movies) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Array of sub langs - var a = new XmlRpcValueArray(languageIDS); - parms.Add(a); - // Array of video parameters - a = new XmlRpcValueArray(); - foreach (SearchToMailMovieParameter p in movies) - { - var str = new XmlRpcValueStruct(new List()); - str.Members.Add(new XmlRpcStructMember("moviehash", new XmlRpcValueBasic(p.moviehash))); - str.Members.Add(new XmlRpcStructMember("moviesize", new XmlRpcValueBasic(p.moviesize))); - a.Values.Add(str); - } - parms.Add(a); - var call = new XmlRpcMethodCall("SearchToMail", parms); - - OSHConsole.WriteLine("Sending SearchToMail request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSearchToMail(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "SearchToMail call failed !"); - } - /*Movies*/ - /// - /// Search for a movie (using movie title) - /// - /// Movie title user is searching for, this is cleaned-up a bit (remove dvdrip, etc.) before searching - /// Status of the call operation. If the call success the response will be 'MethodResponseSubtitleSearch' - public static IMethodResponse SearchMoviesOnIMDB(string query) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add query param - parms.Add(new XmlRpcValueBasic(query, XmlRpcBasicValueType.String)); - // Call ! - var call = new XmlRpcMethodCall("SearchMoviesOnIMDB", parms); - OSHConsole.WriteLine("Sending SearchMoviesOnIMDB request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseMovieSearch(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Search results:"); - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new MovieSearchResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "id": result.ID = (string)submember.Data.Data; break; - case "title": result.Title = (string)submember.Data.Data; break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine(">" + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "SearchMoviesOnIMDB call failed !"); - } - /// - /// Get movie details for given IMDb ID - /// - /// http://www.imdb.com/ - /// Status of the call operation. If the call success the response will be 'MethodResponseMovieDetails' - public static IMethodResponse GetIMDBMovieDetails(string imdbid) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN)); - // Add query param - parms.Add(new XmlRpcValueBasic(imdbid)); - // Call ! - var call = new XmlRpcMethodCall("GetIMDBMovieDetails", parms); - OSHConsole.WriteLine("Sending GetIMDBMovieDetails request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseMovieDetails(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - // We expect struct with details... - if (MEMBER.Data is XmlRpcValueStruct) - { - OSHConsole.WriteLine("Details result:"); - var detailsStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dmem in detailsStruct.Members) - { - switch (dmem.Name) - { - case "id": R.ID = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "title": R.Title = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "year": R.Year = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "cover": R.CoverLink = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "duration": R.Duration = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "tagline": R.Tagline = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "plot": R.Plot = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "goofs": R.Goofs = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "trivia": R.Trivia = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "cast": - // this is another struct with cast members... - OSHConsole.WriteLine(">" + dmem.Name + "= "); - var castStruct = (XmlRpcValueStruct)dmem.Data; - foreach (XmlRpcStructMember castMemeber in castStruct.Members) - { - R.Cast.Add(castMemeber.Data.Data.ToString()); - OSHConsole.WriteLine(" >" + castMemeber.Data.Data.ToString()); - } - break; - case "directors": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is another struct with directors members... - var directorsStruct = (XmlRpcValueStruct)dmem.Data; - foreach (XmlRpcStructMember directorsMember in directorsStruct.Members) - { - R.Directors.Add(directorsMember.Data.Data.ToString()); - OSHConsole.WriteLine(" >" + directorsMember.Data.Data.ToString()); - } - break; - case "writers": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is another struct with writers members... - var writersStruct = (XmlRpcValueStruct)dmem.Data; - foreach (XmlRpcStructMember writersMember in writersStruct.Members) - { - R.Writers.Add(writersMember.Data.Data.ToString()); - OSHConsole.WriteLine("+->" + writersMember.Data.Data.ToString()); - } - break; - case "awards": - // this is an array of genres... - var awardsArray = (XmlRpcValueArray)dmem.Data; - foreach (XmlRpcValueBasic award in awardsArray.Values) - { - R.Awards.Add(award.Data.ToString()); - OSHConsole.WriteLine(" >" + award.Data.ToString()); - } - break; - case "genres": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is an array of genres... - var genresArray = (XmlRpcValueArray)dmem.Data; - foreach (XmlRpcValueBasic genre in genresArray.Values) - { - R.Genres.Add(genre.Data.ToString()); - OSHConsole.WriteLine(" >" + genre.Data.ToString()); - } - break; - case "country": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is an array of country... - var countryArray = (XmlRpcValueArray)dmem.Data; - foreach (XmlRpcValueBasic country in countryArray.Values) - { - R.Country.Add(country.Data.ToString()); - OSHConsole.WriteLine(" >" + country.Data.ToString()); - } - break; - case "language": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is an array of language... - var languageArray = (XmlRpcValueArray)dmem.Data; - foreach (XmlRpcValueBasic language in languageArray.Values) - { - R.Language.Add(language.Data.ToString()); - OSHConsole.WriteLine(" >" + language.Data.ToString()); - } - break; - case "certification": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is an array of certification... - var certificationArray = (XmlRpcValueArray)dmem.Data; - foreach (XmlRpcValueBasic certification in certificationArray.Values) - { - R.Certification.Add(certification.Data.ToString()); - OSHConsole.WriteLine(" >" + certification.Data.ToString()); - } - break; - } - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "GetIMDBMovieDetails call failed !"); - } - /// - /// Allows registered users to insert new movies (not stored in IMDb) to the database. - /// - /// Movie title - /// Release year - /// Status of the call operation. If the call success the response will be 'MethodResponseInsertMovie' - public static IMethodResponse InsertMovie(string movieName, string movieyear) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add movieinfo struct - var movieinfo = new XmlRpcValueStruct(new List()); - movieinfo.Members.Add(new XmlRpcStructMember("moviename", new XmlRpcValueBasic(movieName))); - movieinfo.Members.Add(new XmlRpcStructMember("movieyear", new XmlRpcValueBasic(movieyear))); - parms.Add(movieinfo); - // Call ! - var call = new XmlRpcMethodCall("InsertMovie", parms); - OSHConsole.WriteLine("Sending InsertMovie request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseInsertMovie(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "id") - { - R.ID = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("ID= " + R.Seconds); - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "InsertMovie call failed !"); - } - /// - /// Inserts or updates data to tables, which are used for CheckMovieHash() and !CheckMovieHash2(). - /// - /// The parameters - /// Status of the call operation. If the call success the response will be 'MethodResponseInsertMovieHash' - public static IMethodResponse InsertMovieHash(InsertMovieHashParameters[] parameters) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - foreach (InsertMovieHashParameters p in parameters) - { - var pstruct = new XmlRpcValueStruct(new List()); - pstruct.Members.Add(new XmlRpcStructMember("moviehash", new XmlRpcValueBasic(p.moviehash))); - pstruct.Members.Add(new XmlRpcStructMember("moviebytesize", new XmlRpcValueBasic(p.moviebytesize))); - pstruct.Members.Add(new XmlRpcStructMember("imdbid", new XmlRpcValueBasic(p.imdbid))); - pstruct.Members.Add(new XmlRpcStructMember("movietimems", new XmlRpcValueBasic(p.movietimems))); - pstruct.Members.Add(new XmlRpcStructMember("moviefps", new XmlRpcValueBasic(p.moviefps))); - pstruct.Members.Add(new XmlRpcStructMember("moviefilename", new XmlRpcValueBasic(p.moviefilename))); - parms.Add(pstruct); - } - var call = new XmlRpcMethodCall("InsertMovieHash", parms); - - OSHConsole.WriteLine("Sending InsertMovieHash request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseInsertMovieHash(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "seconds": - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - switch (dataMember.Name) - { - case "accepted_moviehashes": - var mh = (XmlRpcValueArray)dataMember.Data; - foreach (IXmlRpcValue val in mh.Values) - { - if (val is XmlRpcValueBasic) - { - R.accepted_moviehashes.Add(val.Data.ToString()); - } - } - break; - case "new_imdbs": - var mi = (XmlRpcValueArray)dataMember.Data; - foreach (IXmlRpcValue val in mi.Values) - { - if (val is XmlRpcValueBasic) - { - R.new_imdbs.Add(val.Data.ToString()); - } - } - break; - } - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "InsertMovieHash call failed !"); - } - /*Reporting and rating*/ - /// - /// Get basic server information and statistics - /// - /// Status of the call operation. If the call success the response will be 'MethodResponseServerInfo' - public static IMethodResponse ServerInfo() - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT, XmlRpcBasicValueType.String)); - var call = new XmlRpcMethodCall("ServerInfo", parms); - - OSHConsole.WriteLine("Sending ServerInfo request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseServerInfo(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "seconds": - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "xmlrpc_version": - R.xmlrpc_version = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "xmlrpc_url": - R.xmlrpc_url = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "application": - R.application = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "contact": - R.contact = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "website_url": - R.website_url = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "users_online_total": - R.users_online_total = (int)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "users_online_program": - R.users_online_program = (int)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "users_loggedin": - R.users_loggedin = (int)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "users_max_alltime": - R.users_max_alltime = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "users_registered": - R.users_registered = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "subs_downloads": - R.subs_downloads = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "subs_subtitle_files": - R.subs_subtitle_files = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "movies_total": - R.movies_total = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "movies_aka": - R.movies_aka = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "total_subtitles_languages": - R.total_subtitles_languages = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "last_update_strings": - //R.total_subtitles_languages = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + ":"); - var luStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember luMemeber in luStruct.Members) - { - R.last_update_strings.Add(luMemeber.Name + " [" + luMemeber.Data.Data.ToString() + "]"); - OSHConsole.WriteLine(" >" + luMemeber.Name + "= " + luMemeber.Data.Data.ToString()); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "ServerInfo call failed !"); - } - /// - /// Report wrong subtitle file <--> video file combination - /// - /// Identifier of the subtitle file <--> video file combination - /// Status of the call operation. If the call success the response will be 'MethodResponseReportWrongMovieHash' - public static IMethodResponse ReportWrongMovieHash(string IDSubMovieFile) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - parms.Add(new XmlRpcValueBasic(IDSubMovieFile, XmlRpcBasicValueType.String)); - var call = new XmlRpcMethodCall("ReportWrongMovieHash", parms); - - OSHConsole.WriteLine("Sending ReportWrongMovieHash request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseReportWrongMovieHash(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "seconds": - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "ReportWrongMovieHash call failed !"); - } - /// - /// This method is needed to report bad movie hash for imdbid. This method should be used for correcting wrong entries, - /// when using CheckMovieHash2. Pass moviehash and moviebytesize for file, and imdbid as new, corrected one IMDBID - /// (id number without trailing zeroes). After some reports, moviehash will be linked to new imdbid. - /// - /// The movie hash - /// The movie size in bytes - /// The movie imbid - /// Status of the call operation. If the call success the response will be 'MethodResponseReportWrongImdbMovie' - public static IMethodResponse ReportWrongImdbMovie(string moviehash, string moviebytesize, string imdbid) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var s = new XmlRpcValueStruct(new List()); - s.Members.Add(new XmlRpcStructMember("moviehash", new XmlRpcValueBasic(moviehash))); - s.Members.Add(new XmlRpcStructMember("moviebytesize", new XmlRpcValueBasic(moviebytesize))); - s.Members.Add(new XmlRpcStructMember("imdbid", new XmlRpcValueBasic(imdbid))); - parms.Add(s); - var call = new XmlRpcMethodCall("ReportWrongImdbMovie", parms); - - OSHConsole.WriteLine("Sending ReportWrongImdbMovie request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseAddComment(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "ReportWrongImdbMovie call failed !"); - } - /// - /// Rate subtitles - /// - /// Id of subtitle (NOT subtitle file) user wants to rate - /// Subtitle rating, must be in interval 1 (worst) to 10 (best). - /// Status of the call operation. If the call success the response will be 'MethodResponseSubtitlesVote' - public static IMethodResponse SubtitlesVote(int idsubtitle, int score) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var s = new XmlRpcValueStruct(new List()); - s.Members.Add(new XmlRpcStructMember("idsubtitle", new XmlRpcValueBasic(idsubtitle))); - s.Members.Add(new XmlRpcStructMember("score", new XmlRpcValueBasic(score))); - parms.Add(s); - var call = new XmlRpcMethodCall("SubtitlesVote", parms); - - OSHConsole.WriteLine("Sending SubtitlesVote request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSubtitlesVote(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dataMemeber in dataStruct.Members) - { - OSHConsole.WriteLine(" >" + dataMemeber.Name + "= " + dataMemeber.Data.Data.ToString()); - switch (dataMemeber.Name) - { - case "SubRating": R.SubRating = dataMemeber.Data.Data.ToString(); break; - case "SubSumVotes": R.SubSumVotes = dataMemeber.Data.Data.ToString(); break; - case "IDSubtitle": R.IDSubtitle = dataMemeber.Data.Data.ToString(); break; - } - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "SubtitlesVote call failed !"); - } - /// - /// Add comment to a subtitle - /// - /// Subtitle identifier (BEWARE! this is not the ID of subtitle file but of the whole subtitle (a subtitle can contain multiple subtitle files)) - /// User's comment - /// Optional parameter. If set to 1, subtitles are marked as bad. - /// Status of the call operation. If the call success the response will be 'MethodResponseAddComment' - public static IMethodResponse AddComment(int idsubtitle, string comment, int badsubtitle) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var s = new XmlRpcValueStruct(new List()); - s.Members.Add(new XmlRpcStructMember("idsubtitle", new XmlRpcValueBasic(idsubtitle))); - s.Members.Add(new XmlRpcStructMember("comment", new XmlRpcValueBasic(comment))); - s.Members.Add(new XmlRpcStructMember("badsubtitle", new XmlRpcValueBasic(badsubtitle))); - parms.Add(s); - var call = new XmlRpcMethodCall("AddComment", parms); - - OSHConsole.WriteLine("Sending AddComment request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseAddComment(); - - // To make sure response is not currepted by server, do it in loop - foreach (var MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "AddComment call failed !"); - } - /// - /// Add new request for subtitles, user must be logged in - /// - /// The subtitle language id 3 length - /// http://www.imdb.com/ - /// The comment - /// Status of the call operation. If the call success the response will be 'MethodResponseAddRequest' - public static IMethodResponse AddRequest(string sublanguageid, string idmovieimdb, string comment) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var s = new XmlRpcValueStruct(new List()); - s.Members.Add(new XmlRpcStructMember("sublanguageid", new XmlRpcValueBasic(sublanguageid))); - s.Members.Add(new XmlRpcStructMember("idmovieimdb", new XmlRpcValueBasic(idmovieimdb))); - s.Members.Add(new XmlRpcStructMember("comment", new XmlRpcValueBasic(comment))); - parms.Add(s); - var call = new XmlRpcMethodCall("AddRequest", parms); - - OSHConsole.WriteLine("Sending AddRequest request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseAddRequest(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dataMemeber in dataStruct.Members) - { - switch (dataMemeber.Name) - { - case "request_url": R.request_url = dataMemeber.Data.Data.ToString(); OSHConsole.WriteLine(">" + dataMemeber.Name + "= " + dataMemeber.Data.Data.ToString()); break; - } - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "AddRequest call failed !"); - } - /*User interface*/ - /// - /// Get list of supported subtitle languages - /// - /// ISO639-1 2-letter language code of user's interface language. - /// Status of the call operation. If the call success the response will be 'MethodResponseGetSubLanguages' - public static IMethodResponse GetSubLanguages(string language) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueBasic(language)); - var call = new XmlRpcMethodCall("GetSubLanguages", parms); - - OSHConsole.WriteLine("Sending GetSubLanguages request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseGetSubLanguages(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data":// array of structs - var array = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue value in array.Values) - { - if (value is XmlRpcValueStruct) - { - var valueStruct = (XmlRpcValueStruct)value; - var lang = new SubtitleLanguage(); - OSHConsole.WriteLine(">SubLanguage:"); - foreach (XmlRpcStructMember langMemeber in valueStruct.Members) - { - OSHConsole.WriteLine(" >" + langMemeber.Name + "= " + langMemeber.Data.Data.ToString()); - switch (langMemeber.Name) - { - case "SubLanguageID": lang.SubLanguageID = langMemeber.Data.Data.ToString(); break; - case "LanguageName": lang.LanguageName = langMemeber.Data.Data.ToString(); break; - case "ISO639": lang.ISO639 = langMemeber.Data.Data.ToString(); break; - } - } - R.Languages.Add(lang); - } - else - { - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + - MEMBER.Data.Data.ToString() + " [Struct expected !]", DebugCode.Warning); - } - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "GetSubLanguages call failed !"); - } - /// - /// Detect language for given strings - /// - /// Array of strings you want language detected for - /// The encoding that will be used to get buffer of given strings. (this is not OS official parameter) - /// Status of the call operation. If the call success the response will be 'MethodResponseDetectLanguage' - public static IMethodResponse DetectLanguage(string[] texts, Encoding encodingUsed) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // We need to gzip texts then code them with base 24 - var decodedTexts = new List(); - foreach (string text in texts) - { - // compress - Stream str = new MemoryStream(); - byte[] stringData = encodingUsed.GetBytes(text); - str.Write(stringData, 0, stringData.Length); - str.Position = 0; - byte[] data = Utilities.Compress(str); - //base 64 - decodedTexts.Add(Convert.ToBase64String(data)); - } - parms.Add(new XmlRpcValueArray(decodedTexts.ToArray())); - var call = new XmlRpcMethodCall("DetectLanguage", parms); - - OSHConsole.WriteLine("Sending DetectLanguage request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseDetectLanguage(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - if (MEMBER.Data is XmlRpcValueStruct) - { - OSHConsole.WriteLine(">Languages:"); - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - var lang = new DetectLanguageResult(); - lang.InputSample = dataMember.Name; - lang.LanguageID = dataMember.Data.Data.ToString(); - R.Results.Add(lang); - OSHConsole.WriteLine(" >" + dataMember.Name + " (" + dataMember.Data.Data.ToString() + ")"); - } - } - else - { - OSHConsole.WriteLine(">Languages ?? Struct expected but server return another type!!", DebugCode.Warning); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "DetectLanguage call failed !"); - } - /// - /// Get available translations for given program - /// - /// Name of the program/client application you want translations for. Currently supported values: subdownloader, oscar - /// Status of the call operation. If the call success the response will be 'MethodResponseGetAvailableTranslations' - public static IMethodResponse GetAvailableTranslations(string program) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueBasic(program)); - var call = new XmlRpcMethodCall("GetAvailableTranslations", parms); - - OSHConsole.WriteLine("Sending GetAvailableTranslations request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseGetAvailableTranslations(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - OSHConsole.WriteLine(">data:"); - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - if (dataMember.Data is XmlRpcValueStruct) - { - var resStruct = (XmlRpcValueStruct)dataMember.Data; - var res = new GetAvailableTranslationsResult(); - res.LanguageID = dataMember.Name; - OSHConsole.WriteLine(" >LanguageID: " + dataMember.Name); - foreach (XmlRpcStructMember resMember in resStruct.Members) - { - switch (resMember.Name) - { - case "LastCreated": res.LastCreated = resMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + resMember.Name + "= " + resMember.Data.Data.ToString()); break; - case "StringsNo": res.StringsNo = resMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + resMember.Name + "= " + resMember.Data.Data.ToString()); break; - } - R.Results.Add(res); - } - } - else - { - OSHConsole.WriteLine(" >Struct expected !!", DebugCode.Warning); - } - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "GetAvailableTranslations call failed !"); - } - /// - /// Get a translation for given program and language - /// - /// language ​ISO639-1 2-letter code - /// available formats: [gnugettext compatible: mo, po] and [additional: txt, xml] - /// Name of the program/client application you want translations for. (currently supported values: subdownloader, oscar) - /// Status of the call operation. If the call success the response will be 'MethodResponseGetTranslation' - public static IMethodResponse GetTranslation(string iso639, string format, string program) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueBasic(iso639)); - parms.Add(new XmlRpcValueBasic(format)); - parms.Add(new XmlRpcValueBasic(program)); - var call = new XmlRpcMethodCall("GetTranslation", parms); - - OSHConsole.WriteLine("Sending GetTranslation request to the server ...", DebugCode.Good); - // Send the request to the server - //File.WriteAllText(".\\REQUEST_GetTranslation.xml", Encoding.ASCII.GetString(XmlRpcGenerator.Generate(call))); - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseGetTranslation(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": R.ContentData = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "GetTranslation call failed !"); - } - /// - /// Check for the latest version of given application - /// - /// name of the program/client application you want to check. (Currently supported values: subdownloader, oscar) - /// Status of the call operation. If the call success the response will be 'MethodResponseAutoUpdate' - public static IMethodResponse AutoUpdate(string program) - { - /*if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - }*/ - // Method call .. - var parms = new List(); - // parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueBasic(program)); - // parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT)); - - var call = new XmlRpcMethodCall("AutoUpdate", parms); - OSHConsole.WriteLine("Sending AutoUpdate request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseAutoUpdate(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "version": R.version = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "url_windows": R.url_windows = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "url_linux": R.url_linux = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "comments": R.comments = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "AutoUpdate call failed !"); - } - /*Checking*/ - /// - /// Check if video file hashes are already stored in the database - /// - /// Array of video file hashes - /// Status of the call operation. If the call success the response will be 'MethodResponseCheckMovieHash' - public static IMethodResponse CheckMovieHash(string[] hashes) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueArray(hashes)); - var call = new XmlRpcMethodCall("CheckMovieHash", parms); - - OSHConsole.WriteLine("Sending CheckMovieHash request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseCheckMovieHash(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - OSHConsole.WriteLine(">Data:"); - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - var res = new CheckMovieHashResult(); - res.Name = dataMember.Name; - OSHConsole.WriteLine(" >" + res.Name + ":"); - var movieStruct = (XmlRpcValueStruct)dataMember.Data; - foreach (XmlRpcStructMember movieMember in movieStruct.Members) - { - switch (movieMember.Name) - { - case "MovieHash": res.MovieHash = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieImdbID": res.MovieImdbID = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieName": res.MovieName = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieYear": res.MovieYear = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - } - } - R.Results.Add(res); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "CheckMovieHash call failed !"); - } - /// - /// Check if video file hashes are already stored in the database. This method returns matching !MovieImdbID, MovieName, MovieYear, SeriesSeason, SeriesEpisode, - /// MovieKind if available for each $moviehash, always sorted by SeenCount DESC. - /// - /// Array of video file hashes - /// Status of the call operation. If the call success the response will be 'MethodResponseCheckMovieHash2' - public static IMethodResponse CheckMovieHash2(string[] hashes) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueArray(hashes)); - var call = new XmlRpcMethodCall("CheckMovieHash2", parms); - - OSHConsole.WriteLine("Sending CheckMovieHash2 request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseCheckMovieHash2(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - OSHConsole.WriteLine(">Data:"); - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - var res = new CheckMovieHash2Result(); - res.Name = dataMember.Name; - OSHConsole.WriteLine(" >" + res.Name + ":"); - - var dataArray = (XmlRpcValueArray)dataMember.Data; - foreach (XmlRpcValueStruct movieStruct in dataArray.Values) - { - var d = new CheckMovieHash2Data(); - foreach (XmlRpcStructMember movieMember in movieStruct.Members) - { - switch (movieMember.Name) - { - case "MovieHash": d.MovieHash = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieImdbID": d.MovieImdbID = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieName": d.MovieName = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieYear": d.MovieYear = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieKind": d.MovieKind = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "SeriesSeason": d.SeriesSeason = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "SeriesEpisode": d.SeriesEpisode = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "SeenCount": d.MovieYear = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - } - } - res.Items.Add(d); - } - R.Results.Add(res); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "CheckMovieHash2 call failed !"); - } - /// - /// Check if given subtitle files are already stored in the database - /// - /// Array of subtitle file hashes (MD5 hashes of subtitle file contents) - /// Status of the call operation. If the call success the response will be 'MethodResponseCheckSubHash' - public static IMethodResponse CheckSubHash(string[] hashes) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueArray(hashes)); - var call = new XmlRpcMethodCall("CheckSubHash", parms); - - OSHConsole.WriteLine("Sending CheckSubHash request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseCheckSubHash(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - OSHConsole.WriteLine(">Data:"); - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - OSHConsole.WriteLine(" >" + dataMember.Name + "= " + dataMember.Data.Data.ToString()); - var r = new CheckSubHashResult(); - r.Hash = dataMember.Name; - r.SubID = dataMember.Data.Data.ToString(); - R.Results.Add(r); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "CheckSubHash call failed !"); - } - /*Upload*/ - /// - /// Try to upload subtitles, perform pre-upload checking (i.e. check if subtitles already exist on server) - /// - /// The subtitle parameters collection to try to upload - /// Status of the call operation. If the call success the response will be 'MethodResponseTryUploadSubtitles' - public static IMethodResponse TryUploadSubtitles(TryUploadSubtitlesParameters[] subs) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var s = new XmlRpcValueStruct(new List()); - int i = 1; - foreach (var cd in subs) - { - var member = new XmlRpcStructMember("cd" + i, null); - var memberStruct = new XmlRpcValueStruct(new List()); - memberStruct.Members.Add(new XmlRpcStructMember("subhash", new XmlRpcValueBasic(cd.subhash))); - memberStruct.Members.Add(new XmlRpcStructMember("subfilename", new XmlRpcValueBasic(cd.subfilename))); - memberStruct.Members.Add(new XmlRpcStructMember("moviehash", new XmlRpcValueBasic(cd.moviehash))); - memberStruct.Members.Add(new XmlRpcStructMember("moviebytesize", new XmlRpcValueBasic(cd.moviebytesize))); - memberStruct.Members.Add(new XmlRpcStructMember("moviefps", new XmlRpcValueBasic(cd.moviefps))); - memberStruct.Members.Add(new XmlRpcStructMember("movietimems", new XmlRpcValueBasic(cd.movietimems))); - memberStruct.Members.Add(new XmlRpcStructMember("movieframes", new XmlRpcValueBasic(cd.movieframes))); - memberStruct.Members.Add(new XmlRpcStructMember("moviefilename", new XmlRpcValueBasic(cd.moviefilename))); - member.Data = memberStruct; - s.Members.Add(member); - i++; - } - parms.Add(s); - var call = new XmlRpcMethodCall("TryUploadSubtitles", parms); - - OSHConsole.WriteLine("Sending TryUploadSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseTryUploadSubtitles(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "alreadyindb": R.AlreadyInDB = (int)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Results: "); - - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new SubtitleSearchResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "IDMovie": result.IDMovie = submember.Data.Data.ToString(); break; - case "IDMovieImdb": result.IDMovieImdb = submember.Data.Data.ToString(); break; - case "IDSubMovieFile": result.IDSubMovieFile = submember.Data.Data.ToString(); break; - case "IDSubtitle": result.IDSubtitle = submember.Data.Data.ToString(); break; - case "IDSubtitleFile": result.IDSubtitleFile = submember.Data.Data.ToString(); break; - case "ISO639": result.ISO639 = submember.Data.Data.ToString(); break; - case "LanguageName": result.LanguageName = submember.Data.Data.ToString(); break; - case "MovieByteSize": result.MovieByteSize = submember.Data.Data.ToString(); break; - case "MovieHash": result.MovieHash = submember.Data.Data.ToString(); break; - case "MovieImdbRating": result.MovieImdbRating = submember.Data.Data.ToString(); break; - case "MovieName": result.MovieName = submember.Data.Data.ToString(); break; - case "MovieNameEng": result.MovieNameEng = submember.Data.Data.ToString(); break; - case "MovieReleaseName": result.MovieReleaseName = submember.Data.Data.ToString(); break; - case "MovieTimeMS": result.MovieTimeMS = submember.Data.Data.ToString(); break; - case "MovieYear": result.MovieYear = submember.Data.Data.ToString(); break; - case "SubActualCD": result.SubActualCD = submember.Data.Data.ToString(); break; - case "SubAddDate": result.SubAddDate = submember.Data.Data.ToString(); break; - case "SubAuthorComment": result.SubAuthorComment = submember.Data.Data.ToString(); break; - case "SubBad": result.SubBad = submember.Data.Data.ToString(); break; - case "SubDownloadLink": result.SubDownloadLink = submember.Data.Data.ToString(); break; - case "SubDownloadsCnt": result.SubDownloadsCnt = submember.Data.Data.ToString(); break; - case "SubFileName": result.SubFileName = submember.Data.Data.ToString(); break; - case "SubFormat": result.SubFormat = submember.Data.Data.ToString(); break; - case "SubHash": result.SubHash = submember.Data.Data.ToString(); break; - case "SubLanguageID": result.SubLanguageID = submember.Data.Data.ToString(); break; - case "SubRating": result.SubRating = submember.Data.Data.ToString(); break; - case "SubSize": result.SubSize = submember.Data.Data.ToString(); break; - case "SubSumCD": result.SubSumCD = submember.Data.Data.ToString(); break; - case "UserID": result.UserID = submember.Data.Data.ToString(); break; - case "UserNickName": result.UserNickName = submember.Data.Data.ToString(); break; - case "ZipDownloadLink": result.ZipDownloadLink = submember.Data.Data.ToString(); break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine(">" + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "TryUploadSubtitles call failed !"); - } - /// - /// Upload given subtitles to OSDb server - /// - /// The pamaters of upload method - /// Status of the call operation. If the call success the response will be 'MethodResponseUploadSubtitles' - public static IMethodResponse UploadSubtitles(UploadSubtitleInfoParameters info) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - // Main struct - var s = new XmlRpcValueStruct(new List()); - - // Base info member as struct - var member = new XmlRpcStructMember("baseinfo", null); - var memberStruct = new XmlRpcValueStruct(new List()); - memberStruct.Members.Add(new XmlRpcStructMember("idmovieimdb", new XmlRpcValueBasic(info.idmovieimdb))); - memberStruct.Members.Add(new XmlRpcStructMember("sublanguageid", new XmlRpcValueBasic(info.sublanguageid))); - memberStruct.Members.Add(new XmlRpcStructMember("moviereleasename", new XmlRpcValueBasic(info.moviereleasename))); - memberStruct.Members.Add(new XmlRpcStructMember("movieaka", new XmlRpcValueBasic(info.movieaka))); - memberStruct.Members.Add(new XmlRpcStructMember("subauthorcomment", new XmlRpcValueBasic(info.subauthorcomment))); - // memberStruct.Members.Add(new XmlRpcStructMember("hearingimpaired", new XmlRpcValueBasic(info.hearingimpaired))); - // memberStruct.Members.Add(new XmlRpcStructMember("highdefinition", new XmlRpcValueBasic(info.highdefinition))); - // memberStruct.Members.Add(new XmlRpcStructMember("automatictranslation", new XmlRpcValueBasic(info.automatictranslation))); - member.Data = memberStruct; - s.Members.Add(member); - - // CDS members - int i = 1; - foreach (UploadSubtitleParameters cd in info.CDS) - { - var member2 = new XmlRpcStructMember("cd" + i, null); - var memberStruct2 = new XmlRpcValueStruct(new List()); - memberStruct2.Members.Add(new XmlRpcStructMember("subhash", new XmlRpcValueBasic(cd.subhash))); - memberStruct2.Members.Add(new XmlRpcStructMember("subfilename", new XmlRpcValueBasic(cd.subfilename))); - memberStruct2.Members.Add(new XmlRpcStructMember("moviehash", new XmlRpcValueBasic(cd.moviehash))); - memberStruct2.Members.Add(new XmlRpcStructMember("moviebytesize", new XmlRpcValueBasic(cd.moviebytesize))); - memberStruct2.Members.Add(new XmlRpcStructMember("moviefps", new XmlRpcValueBasic(cd.moviefps))); - memberStruct2.Members.Add(new XmlRpcStructMember("movietimems", new XmlRpcValueBasic(cd.movietimems))); - memberStruct2.Members.Add(new XmlRpcStructMember("movieframes", new XmlRpcValueBasic(cd.movieframes))); - memberStruct2.Members.Add(new XmlRpcStructMember("moviefilename", new XmlRpcValueBasic(cd.moviefilename))); - memberStruct2.Members.Add(new XmlRpcStructMember("subcontent", new XmlRpcValueBasic(cd.subcontent))); - member2.Data = memberStruct2; - s.Members.Add(member2); - i++; - } - - // add main struct to parameters - parms.Add(s); - // add user agent - //parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT)); - var call = new XmlRpcMethodCall("UploadSubtitles", parms); - OSHConsole.WriteLine("Sending UploadSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseUploadSubtitles(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": R.Data = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "subtitles": R.SubTitles = (bool)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "UploadSubtitles call failed !"); - } - } -} diff --git a/OpenSubtitlesHandler/OpenSubtitlesHandler.csproj b/OpenSubtitlesHandler/OpenSubtitlesHandler.csproj deleted file mode 100644 index eabd3e070c..0000000000 --- a/OpenSubtitlesHandler/OpenSubtitlesHandler.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - netstandard2.0 - false - - - diff --git a/OpenSubtitlesHandler/OtherTypes/GetAvailableTranslationsResult.cs b/OpenSubtitlesHandler/OtherTypes/GetAvailableTranslationsResult.cs deleted file mode 100644 index 39d0485459..0000000000 --- a/OpenSubtitlesHandler/OtherTypes/GetAvailableTranslationsResult.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - public struct GetAvailableTranslationsResult - { - private string _language; - private string _LastCreated; - private string _StringsNo; - - public string LanguageID { get { return _language; } set { _language = value; } } - public string LastCreated { get { return _LastCreated; } set { _LastCreated = value; } } - public string StringsNo { get { return _StringsNo; } set { _StringsNo = value; } } - /// - /// LanguageID (LastCreated) - /// - /// - public override string ToString() - { - return _language + " (" + _LastCreated + ")"; - } - } -} diff --git a/OpenSubtitlesHandler/OtherTypes/GetCommentsResult.cs b/OpenSubtitlesHandler/OtherTypes/GetCommentsResult.cs deleted file mode 100644 index 8f4aa9db42..0000000000 --- a/OpenSubtitlesHandler/OtherTypes/GetCommentsResult.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct GetCommentsResult - { - private string _IDSubtitle; - private string _UserID; - private string _UserNickName; - private string _Comment; - private string _Created; - - public string IDSubtitle { get { return _IDSubtitle; } set { _IDSubtitle = value; } } - public string UserID { get { return _UserID; } set { _UserID = value; } } - public string UserNickName { get { return _UserNickName; } set { _UserNickName = value; } } - public string Comment { get { return _Comment; } set { _Comment = value; } } - public string Created { get { return _Created; } set { _Created = value; } } - } -} diff --git a/OpenSubtitlesHandler/Properties/AssemblyInfo.cs b/OpenSubtitlesHandler/Properties/AssemblyInfo.cs deleted file mode 100644 index b5ae23021c..0000000000 --- a/OpenSubtitlesHandler/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("OpenSubtitlesHandler")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] -[assembly: AssemblyCopyright("Copyright © 2013 Ala Ibrahim Hadid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -[assembly: AssemblyVersion("1.0.3.0")] -[assembly: AssemblyFileVersion("2019.1.20.3")] diff --git a/OpenSubtitlesHandler/Readme.txt b/OpenSubtitlesHandler/Readme.txt deleted file mode 100644 index d5814aec16..0000000000 --- a/OpenSubtitlesHandler/Readme.txt +++ /dev/null @@ -1,20 +0,0 @@ -OpenSubtitlesHandler -==================== -This project is for OpenSubtitles.org integration‏. The point is to allow user to access OpenSubtitles.org database directly -within ASM without the need to open internet browser. -The plan: Implement the "OSDb protocol" http://trac.opensubtitles.org/projects/opensubtitles/wiki/OSDb - -Copyright: -========= -This library ann all its content are written by Ala Ibrahim Hadid. -Copyright © Ala Ibrahim Hadid 2013 -mailto:ahdsoftwares@hotmail.com - -Resources: -========== -* GetHash.dll: this dll is used to compute hash for movie. - For more information please visit http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes#C2 - -XML_RPC: -======== -This class is created to generate XML-RPC requests as XML String. All you need is to call XML_RPC.Generate() method. diff --git a/OpenSubtitlesHandler/SubtitleTypes/CheckSubHashResult.cs b/OpenSubtitlesHandler/SubtitleTypes/CheckSubHashResult.cs deleted file mode 100644 index a2fbe87736..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/CheckSubHashResult.cs +++ /dev/null @@ -1,30 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct CheckSubHashResult - { - private string _hash; - private string _id; - - public string Hash { get { return _hash; } set { _hash = value; } } - public string SubID { get { return _id; } set { _id = value; } } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/SubtitleDownloadResult.cs b/OpenSubtitlesHandler/SubtitleTypes/SubtitleDownloadResult.cs deleted file mode 100644 index e4194994c3..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/SubtitleDownloadResult.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct SubtitleDownloadResult - { - private string idsubtitlefile; - private string data; - - public string IdSubtitleFile - { get { return idsubtitlefile; } set { idsubtitlefile = value; } } - /// - /// Get or set the data of subtitle file. To decode, decode the string to base64 and then decompress with GZIP. - /// - public string Data - { get { return data; } set { data = value; } } - /// - /// IdSubtitleFile - /// - /// - public override string ToString() - { - return idsubtitlefile.ToString(); - } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/SubtitleLanguage.cs b/OpenSubtitlesHandler/SubtitleTypes/SubtitleLanguage.cs deleted file mode 100644 index b2dc154133..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/SubtitleLanguage.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - public struct SubtitleLanguage - { - private string _SubLanguageID; - private string _LanguageName; - private string _ISO639; - - public string SubLanguageID { get { return _SubLanguageID; } set { _SubLanguageID = value; } } - public string LanguageName { get { return _LanguageName; } set { _LanguageName = value; } } - public string ISO639 { get { return _ISO639; } set { _ISO639 = value; } } - /// - /// LanguageName [SubLanguageID] - /// - /// LanguageName [SubLanguageID] - public override string ToString() - { - return _LanguageName + " [" + _SubLanguageID + "]"; - } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchParameters.cs b/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchParameters.cs deleted file mode 100644 index 5c8f8c01a4..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchParameters.cs +++ /dev/null @@ -1,82 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - /// - /// Paramaters for subtitle search call - /// - public struct SubtitleSearchParameters - { - public SubtitleSearchParameters(string subLanguageId, string query = "", string season = "", string episode = "", string movieHash = "", long movieByteSize = 0, string imdbid = "") - { - this.subLanguageId = subLanguageId; - this.movieHash = movieHash; - this.movieByteSize = movieByteSize; - this.imdbid = imdbid; - this._episode = episode; - this._season = season; - this._query = query; - } - - private string subLanguageId; - private string movieHash; - private long movieByteSize; - private string imdbid; - private string _query; - private string _episode; - - public string Episode - { - get { return _episode; } - set { _episode = value; } - } - - public string Season - { - get { return _season; } - set { _season = value; } - } - - private string _season; - - public string Query - { - get { return _query; } - set { _query = value; } - } - - /// - /// List of language ISO639-3 language codes to search for, divided by ',' (e.g. 'cze,eng,slo') - /// - public string SubLangaugeID { get { return subLanguageId; } set { subLanguageId = value; } } - /// - /// Video file hash as calculated by one of the implementation functions as seen on http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes - /// - public string MovieHash { get { return movieHash; } set { movieHash = value; } } - /// - /// Size of video file in bytes - /// - public long MovieByteSize { get { return movieByteSize; } set { movieByteSize = value; } } - /// - /// ​IMDb ID of movie this video is part of, belongs to. - /// - public string IMDbID { get { return imdbid; } set { imdbid = value; } } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchResult.cs b/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchResult.cs deleted file mode 100644 index a4a8dd3e69..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchResult.cs +++ /dev/null @@ -1,136 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - /// - /// The subtitle search result that comes with server response on SearchSubtitles successed call - /// - public struct SubtitleSearchResult - { - private string _IDSubMovieFile; - private string _MovieHash; - private string _MovieByteSize; - private string _MovieTimeMS; - private string _IDSubtitleFile; - private string _SubFileName; - private string _SubActualCD; - private string _SubSize; - private string _SubHash; - private string _IDSubtitle; - private string _UserID; - private string _SubLanguageID; - private string _SubFormat; - private string _SeriesSeason; - private string _SeriesEpisode; - private string _SubSumCD; - private string _SubAuthorComment; - private string _SubAddDate; - private string _SubBad; - private string _SubRating; - private string _SubDownloadsCnt; - private string _MovieReleaseName; - private string _IDMovie; - private string _IDMovieImdb; - private string _MovieName; - private string _MovieNameEng; - private string _MovieYear; - private string _MovieImdbRating; - private string _UserNickName; - private string _ISO639; - private string _LanguageName; - private string _SubDownloadLink; - private string _ZipDownloadLink; - - public string IDSubMovieFile - { get { return _IDSubMovieFile; } set { _IDSubMovieFile = value; } } - public string MovieHash - { get { return _MovieHash; } set { _MovieHash = value; } } - public string MovieByteSize - { get { return _MovieByteSize; } set { _MovieByteSize = value; } } - public string MovieTimeMS - { get { return _MovieTimeMS; } set { _MovieTimeMS = value; } } - public string IDSubtitleFile - { get { return _IDSubtitleFile; } set { _IDSubtitleFile = value; } } - public string SubFileName - { get { return _SubFileName; } set { _SubFileName = value; } } - public string SubActualCD - { get { return _SubActualCD; } set { _SubActualCD = value; } } - public string SubSize - { get { return _SubSize; } set { _SubSize = value; } } - public string SubHash - { get { return _SubHash; } set { _SubHash = value; } } - public string IDSubtitle - { get { return _IDSubtitle; } set { _IDSubtitle = value; } } - public string UserID - { get { return _UserID; } set { _UserID = value; } } - public string SubLanguageID - { get { return _SubLanguageID; } set { _SubLanguageID = value; } } - public string SubFormat - { get { return _SubFormat; } set { _SubFormat = value; } } - public string SubSumCD - { get { return _SubSumCD; } set { _SubSumCD = value; } } - public string SubAuthorComment - { get { return _SubAuthorComment; } set { _SubAuthorComment = value; } } - public string SubAddDate - { get { return _SubAddDate; } set { _SubAddDate = value; } } - public string SubBad - { get { return _SubBad; } set { _SubBad = value; } } - public string SubRating - { get { return _SubRating; } set { _SubRating = value; } } - public string SubDownloadsCnt - { get { return _SubDownloadsCnt; } set { _SubDownloadsCnt = value; } } - public string MovieReleaseName - { get { return _MovieReleaseName; } set { _MovieReleaseName = value; } } - public string IDMovie - { get { return _IDMovie; } set { _IDMovie = value; } } - public string IDMovieImdb - { get { return _IDMovieImdb; } set { _IDMovieImdb = value; } } - public string MovieName - { get { return _MovieName; } set { _MovieName = value; } } - public string MovieNameEng - { get { return _MovieNameEng; } set { _MovieNameEng = value; } } - public string MovieYear - { get { return _MovieYear; } set { _MovieYear = value; } } - public string MovieImdbRating - { get { return _MovieImdbRating; } set { _MovieImdbRating = value; } } - public string UserNickName - { get { return _UserNickName; } set { _UserNickName = value; } } - public string ISO639 - { get { return _ISO639; } set { _ISO639 = value; } } - public string LanguageName - { get { return _LanguageName; } set { _LanguageName = value; } } - public string SubDownloadLink - { get { return _SubDownloadLink; } set { _SubDownloadLink = value; } } - public string ZipDownloadLink - { get { return _ZipDownloadLink; } set { _ZipDownloadLink = value; } } - public string SeriesSeason - { get { return _SeriesSeason; } set { _SeriesSeason = value; } } - public string SeriesEpisode - { get { return _SeriesEpisode; } set { _SeriesEpisode = value; } } - /// - /// SubFileName + " (" + SubFormat + ")" - /// - /// - public override string ToString() - { - return _SubFileName + " (" + _SubFormat + ")"; - } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/TryUploadSubtitlesParameters.cs b/OpenSubtitlesHandler/SubtitleTypes/TryUploadSubtitlesParameters.cs deleted file mode 100644 index 11f3a706c1..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/TryUploadSubtitlesParameters.cs +++ /dev/null @@ -1,47 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public class TryUploadSubtitlesParameters - { - private string _subhash = ""; - private string _subfilename = ""; - private string _moviehash = ""; - private string _moviebytesize = ""; - private int _movietimems = 0; - private int _movieframes = 0; - private double _moviefps = 0; - private string _moviefilename = ""; - - public string subhash { get { return _subhash; } set { _subhash = value; } } - public string subfilename { get { return _subfilename; } set { _subfilename = value; } } - public string moviehash { get { return _moviehash; } set { _moviehash = value; } } - public string moviebytesize { get { return _moviebytesize; } set { _moviebytesize = value; } } - public int movietimems { get { return _movietimems; } set { _movietimems = value; } } - public int movieframes { get { return _movieframes; } set { _movieframes = value; } } - public double moviefps { get { return _moviefps; } set { _moviefps = value; } } - public string moviefilename { get { return _moviefilename; } set { _moviefilename = value; } } - - public override string ToString() - { - return _subfilename + " (" + _subhash + ")"; - } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleInfoParameter.cs b/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleInfoParameter.cs deleted file mode 100644 index 133cc1d238..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleInfoParameter.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - public class UploadSubtitleInfoParameters - { - private string _idmovieimdb; - private string _moviereleasename; - private string _movieaka; - private string _sublanguageid; - private string _subauthorcomment; - private bool _hearingimpaired; - private bool _highdefinition; - private bool _automatictranslation; - private List cds; - - public string idmovieimdb { get { return _idmovieimdb; } set { _idmovieimdb = value; } } - public string moviereleasename { get { return _moviereleasename; } set { _moviereleasename = value; } } - public string movieaka { get { return _movieaka; } set { _movieaka = value; } } - public string sublanguageid { get { return _sublanguageid; } set { _sublanguageid = value; } } - public string subauthorcomment { get { return _subauthorcomment; } set { _subauthorcomment = value; } } - public bool hearingimpaired { get { return _hearingimpaired; } set { _hearingimpaired = value; } } - public bool highdefinition { get { return _highdefinition; } set { _highdefinition = value; } } - public bool automatictranslation { get { return _automatictranslation; } set { _automatictranslation = value; } } - public List CDS { get { return cds; } set { cds = value; } } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleParameters.cs b/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleParameters.cs deleted file mode 100644 index 9910dadc56..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleParameters.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct UploadSubtitleParameters - { - private string _subhash; - private string _subfilename; - private string _moviehash; - private double _moviebytesize; - private int _movietimems; - private int _movieframes; - private double _moviefps; - private string _moviefilename; - private string _subcontent; - - public string subhash { get { return _subhash; } set { _subhash = value; } } - public string subfilename { get { return _subfilename; } set { _subfilename = value; } } - public string moviehash { get { return _moviehash; } set { _moviehash = value; } } - public double moviebytesize { get { return _moviebytesize; } set { _moviebytesize = value; } } - public int movietimems { get { return _movietimems; } set { _movietimems = value; } } - public int movieframes { get { return _movieframes; } set { _movieframes = value; } } - public double moviefps { get { return _moviefps; } set { _moviefps = value; } } - public string moviefilename { get { return _moviefilename; } set { _moviefilename = value; } } - /// - /// Sub content. Note: this value must be the subtitle file gziped and then base64 decoded. - /// - public string subcontent { get { return _subcontent; } set { _subcontent = value; } } - - public override string ToString() - { - return _subfilename + " (" + _subhash + ")"; - } - } -} diff --git a/OpenSubtitlesHandler/Utilities.cs b/OpenSubtitlesHandler/Utilities.cs deleted file mode 100644 index 429e28a8f4..0000000000 --- a/OpenSubtitlesHandler/Utilities.cs +++ /dev/null @@ -1,174 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Cryptography; - -namespace OpenSubtitlesHandler -{ - /// - /// Include helper methods. All member are statics. - /// - public sealed class Utilities - { - public static ICryptoProvider CryptographyProvider { get; set; } - public static IHttpClient HttpClient { get; set; } - private static string XML_RPC_SERVER = "https://api.opensubtitles.org/xml-rpc"; - //private static string XML_RPC_SERVER = "https://92.240.234.122/xml-rpc"; - private static string HostHeader = "api.opensubtitles.org:443"; - - /// - /// Compute movie hash - /// - /// The hash as Hexadecimal string - public static string ComputeHash(Stream stream) - { - byte[] hash = MovieHasher.ComputeMovieHash(stream); - return MovieHasher.ToHexadecimal(hash); - } - /// - /// Decompress data using GZip - /// - /// The stream that hold the data - /// Bytes array of decompressed data - public static byte[] Decompress(Stream dataToDecompress) - { - using (var target = new MemoryStream()) - { - using (var decompressionStream = new System.IO.Compression.GZipStream(dataToDecompress, System.IO.Compression.CompressionMode.Decompress)) - { - decompressionStream.CopyTo(target); - } - return target.ToArray(); - } - } - - /// - /// Compress data using GZip (the retunred buffer will be WITHOUT HEADER) - /// - /// The stream that hold the data - /// Bytes array of compressed data WITHOUT HEADER bytes - public static byte[] Compress(Stream dataToCompress) - { - /*using (var compressed = new MemoryStream()) - { - using (var compressor = new System.IO.Compression.GZipStream(compressed, - System.IO.Compression.CompressionMode.Compress)) - { - dataToCompress.CopyTo(compressor); - } - // Get the compressed bytes only after closing the GZipStream - return compressed.ToArray(); - }*/ - //using (var compressedOutput = new MemoryStream()) - //{ - // using (var compressedStream = new ZlibStream(compressedOutput, - // Ionic.Zlib.CompressionMode.Compress, - // CompressionLevel.Default, false)) - // { - // var buffer = new byte[4096]; - // int byteCount; - // do - // { - // byteCount = dataToCompress.Read(buffer, 0, buffer.Length); - - // if (byteCount > 0) - // { - // compressedStream.Write(buffer, 0, byteCount); - // } - // } while (byteCount > 0); - // } - // return compressedOutput.ToArray(); - //} - - throw new NotImplementedException(); - } - - /// - /// Handle server response stream and decode it as given encoding string. - /// - /// The string of the stream after decode using given encoding - public static string GetStreamString(Stream responseStream) - { - using (responseStream) - { - // Handle response, should be XML text. - var data = new List(); - while (true) - { - int r = responseStream.ReadByte(); - if (r < 0) - break; - data.Add((byte)r); - } - var bytes = data.ToArray(); - return Encoding.ASCII.GetString(bytes, 0, bytes.Length); - } - } - - public static byte[] GetASCIIBytes(string text) - { - return Encoding.ASCII.GetBytes(text); - } - - /// - /// Send a request to the server - /// - /// The request buffer to send as bytes array. - /// The user agent value. - /// Response of the server or stream of error message as string started with 'ERROR:' keyword. - public static Stream SendRequest(byte[] request, string userAgent) - { - return SendRequestAsync(request, userAgent, CancellationToken.None).Result; - } - - public static async Task SendRequestAsync(byte[] request, string userAgent, CancellationToken cancellationToken) - { - var options = new HttpRequestOptions - { - RequestContentBytes = request, - RequestContentType = "text/xml", - UserAgent = userAgent, - Host = HostHeader, - Url = XML_RPC_SERVER, - - // Response parsing will fail with this enabled - EnableHttpCompression = false, - - CancellationToken = cancellationToken, - BufferContent = false - }; - - if (string.IsNullOrEmpty(options.UserAgent)) - { - options.UserAgent = "xmlrpc-epi-php/0.2 (PHP)"; - } - - var result = await HttpClient.Post(options).ConfigureAwait(false); - - return result.Content; - } - - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Docs/XML-RPC.txt b/OpenSubtitlesHandler/XML-RPC/Docs/XML-RPC.txt deleted file mode 100644 index a4de38cdea..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Docs/XML-RPC.txt +++ /dev/null @@ -1,225 +0,0 @@ -XML-RPC Specification - -Tue, Jun 15, 1999; by Dave Winer. - -Updated 6/30/03 DW - -Updated 10/16/99 DW - -Updated 1/21/99 DW - -This specification documents the XML-RPC protocol implemented in UserLand Frontier 5.1. - -For a non-technical explanation, see XML-RPC for Newbies. - -This page provides all the information that an implementor needs. - -Overview - -XML-RPC is a Remote Procedure Calling protocol that works over the Internet. - -An XML-RPC message is an HTTP-POST request. The body of the request is in XML. A procedure executes on the server and the value it returns is also formatted in XML. - -Procedure parameters can be scalars, numbers, strings, dates, etc.; and can also be complex record and list structures. - -Request example - -Here's an example of an XML-RPC request: - -POST /RPC2 HTTP/1.0 -User-Agent: Frontier/5.1.2 (WinNT) -Host: betty.userland.com -Content-Type: text/xml -Content-length: 181 - - - - examples.getStateName - - - - 41 - - - - - -Header requirements - -The format of the URI in the first line of the header is not specified. For example, it could be empty, a single slash, if the server is only handling XML-RPC calls. However, if the server is handling a mix of incoming HTTP requests, we allow the URI to help route the request to the code that handles XML-RPC requests. (In the example, the URI is /RPC2, telling the server to route the request to the "RPC2" responder.) - -A User-Agent and Host must be specified. - -The Content-Type is text/xml. - -The Content-Length must be specified and must be correct. - -Payload format - -The payload is in XML, a single structure. - -The must contain a sub-item, a string, containing the name of the method to be called. The string may only contain identifier characters, upper and lower-case A-Z, the numeric characters, 0-9, underscore, dot, colon and slash. It's entirely up to the server to decide how to interpret the characters in a methodName. - -For example, the methodName could be the name of a file containing a script that executes on an incoming request. It could be the name of a cell in a database table. Or it could be a path to a file contained within a hierarchy of folders and files. - -If the procedure call has parameters, the must contain a sub-item. The sub-item can contain any number of s, each of which has a . - -Scalar s - -s can be scalars, type is indicated by nesting the value inside one of the tags listed in this table: - -Tag Type Example - or four-byte signed integer -12 - 0 (false) or 1 (true) 1 - string hello world - double-precision signed floating point number -12.214 - date/time 19980717T14:08:55 - base64-encoded binary eW91IGNhbid0IHJlYWQgdGhpcyE= - -If no type is indicated, the type is string. - -s - -A value can also be of type . - -A contains s and each contains a and a . - -Here's an example of a two-element : - - - - lowerBound - 18 - - - upperBound - 139 - - - -s can be recursive, any may contain a or any other type, including an , described below. - -s - -A value can also be of type . - -An contains a single element, which can contain any number of s. - -Here's an example of a four-element array: - - - - 12 - Egypt - 0 - -31 - - - - elements do not have names. - -You can mix types as the example above illustrates. - -s can be recursive, any value may contain an or any other type, including a , described above. - -Response example - -Here's an example of a response to an XML-RPC request: - -HTTP/1.1 200 OK -Connection: close -Content-Length: 158 -Content-Type: text/xml -Date: Fri, 17 Jul 1998 19:55:08 GMT -Server: UserLand Frontier/5.1.2-WinNT - - South Dakota - -Response format - -Unless there's a lower-level error, always return 200 OK. - -The Content-Type is text/xml. Content-Length must be present and correct. - -The body of the response is a single XML structure, a , which can contain a single which contains a single which contains a single . - -The could also contain a which contains a which is a containing two elements, one named , an and one named , a . - -A can not contain both a and a . - -Fault example - -HTTP/1.1 200 OK -Connection: close -Content-Length: 426 -Content-Type: text/xml -Date: Fri, 17 Jul 1998 19:55:02 GMT -Server: UserLand Frontier/5.1.2-WinNT - - faultCode 4 faultString Too many parameters. - -Strategies/Goals - -Firewalls. The goal of this protocol is to lay a compatible foundation across different environments, no new power is provided beyond the capabilities of the CGI interface. Firewall software can watch for POSTs whose Content-Type is text/xml. - -Discoverability. We wanted a clean, extensible format that's very simple. It should be possible for an HTML coder to be able to look at a file containing an XML-RPC procedure call, understand what it's doing, and be able to modify it and have it work on the first or second try. - -Easy to implement. We also wanted it to be an easy to implement protocol that could quickly be adapted to run in other environments or on other operating systems. - -Updated 1/21/99 DW - -The following questions came up on the UserLand discussion group as XML-RPC was being implemented in Python. - - The Response Format section says "The body of the response is a single XML structure, a , which can contain a single ..." This is confusing. Can we leave out the ? - - No you cannot leave it out if the procedure executed successfully. There are only two options, either a response contains a structure or it contains a structure. That's why we used the word "can" in that sentence. - - Is "boolean" a distinct data type, or can boolean values be interchanged with integers (e.g. zero=false, non-zero=true)? - - Yes, boolean is a distinct data type. Some languages/environments allow for an easy coercion from zero to false and one to true, but if you mean true, send a boolean type with the value true, so your intent can't possibly be misunderstood. - - What is the legal syntax (and range) for integers? How to deal with leading zeros? Is a leading plus sign allowed? How to deal with whitespace? - - An integer is a 32-bit signed number. You can include a plus or minus at the beginning of a string of numeric characters. Leading zeros are collapsed. Whitespace is not permitted. Just numeric characters preceeded by a plus or minus. - - What is the legal syntax (and range) for floating point values (doubles)? How is the exponent represented? How to deal with whitespace? Can infinity and "not a number" be represented? - - There is no representation for infinity or negative infinity or "not a number". At this time, only decimal point notation is allowed, a plus or a minus, followed by any number of numeric characters, followed by a period and any number of numeric characters. Whitespace is not allowed. The range of allowable values is implementation-dependent, is not specified. - - What characters are allowed in strings? Non-printable characters? Null characters? Can a "string" be used to hold an arbitrary chunk of binary data? - - Any characters are allowed in a string except < and &, which are encoded as < and &. A string can be used to encode binary data. - - Does the "struct" element keep the order of keys. Or in other words, is the struct "foo=1, bar=2" equivalent to "bar=2, foo=1" or not? - - The struct element does not preserve the order of the keys. The two structs are equivalent. - - Can the struct contain other members than and ? Is there a global list of faultCodes? (so they can be mapped to distinct exceptions for languages like Python and Java)? - - A struct may not contain members other than those specified. This is true for all other structures. We believe the specification is flexible enough so that all reasonable data-transfer needs can be accomodated within the specified structures. If you believe strongly that this is not true, please post a message on the discussion group. - - There is no global list of fault codes. It is up to the server implementer, or higher-level standards to specify fault codes. - - What timezone should be assumed for the dateTime.iso8601 type? UTC? localtime? - - Don't assume a timezone. It should be specified by the server in its documentation what assumptions it makes about timezones. - -Additions - - type. 1/21/99 DW. - -Updated 6/30/03 DW - -Removed "ASCII" from definition of string. - -Changed copyright dates, below, to 1999-2003 from 1998-99. - -Copyright and disclaimer - -© Copyright 1998-2003 UserLand Software. All Rights Reserved. - -This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and these paragraphs are included on all such copies and derivative works. - -This document may not be modified in any way, such as by removing the copyright notice or references to UserLand or other organizations. Further, while these copyright restrictions apply to the written XML-RPC specification, no claim of ownership is made by UserLand to the protocol it describes. Any party may, for commercial or non-commercial purposes, implement this protocol without royalty or license fee to UserLand. The limited permissions granted herein are perpetual and will not be revoked by UserLand or its successors or assigns. - -This document and the information contained herein is provided on an "AS IS" basis and USERLAND DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. diff --git a/OpenSubtitlesHandler/XML-RPC/Enums/XmlRpcValueType.cs b/OpenSubtitlesHandler/XML-RPC/Enums/XmlRpcValueType.cs deleted file mode 100644 index 51e0ee98f8..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Enums/XmlRpcValueType.cs +++ /dev/null @@ -1,25 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace XmlRpcHandler -{ - public enum XmlRpcBasicValueType - { - String, Int, Boolean, Double, dateTime_iso8601, base64 - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Types/XmlRpcMethodCall.cs b/OpenSubtitlesHandler/XML-RPC/Types/XmlRpcMethodCall.cs deleted file mode 100644 index 7cc1a164f6..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Types/XmlRpcMethodCall.cs +++ /dev/null @@ -1,63 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace XmlRpcHandler -{ - /// - /// A method call - /// - public struct XmlRpcMethodCall - { - /// - /// A method call - /// - /// The name of this method - public XmlRpcMethodCall(string name) - { - this.name = name; - this.parameters = new List(); - } - /// - /// A method call - /// - /// The name of this method - /// A list of parameters - public XmlRpcMethodCall(string name, List parameters) - { - this.name = name; - this.parameters = parameters; - } - - private string name; - private List parameters; - - /// - /// Get or set the name of this method - /// - public string Name - { get { return name; } set { name = value; } } - /// - /// Get or set the parameters to be sent - /// - public List Parameters - { get { return parameters; } set { parameters = value; } } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Values/IXmlRpcValue.cs b/OpenSubtitlesHandler/XML-RPC/Values/IXmlRpcValue.cs deleted file mode 100644 index c918790cba..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Values/IXmlRpcValue.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace XmlRpcHandler -{ - public abstract class IXmlRpcValue - { - public IXmlRpcValue() - { } - public IXmlRpcValue(object data) - { - this.data = data; - } - private object data; - /// - /// Get or set the data of this value - /// - public virtual object Data { get { return data; } set { data = value; } } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcStructMember.cs b/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcStructMember.cs deleted file mode 100644 index 2aad7ebffe..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcStructMember.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace XmlRpcHandler -{ - public class XmlRpcStructMember - { - public XmlRpcStructMember(string name, IXmlRpcValue data) - { - this.name = name; - this.data = data; - } - private string name; - private IXmlRpcValue data; - - /// - /// Get or set the name of this member - /// - public string Name - { get { return name; } set { name = value; } } - /// - /// Get or set the data of this member - /// - public IXmlRpcValue Data - { get { return data; } set { data = value; } } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueArray.cs b/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueArray.cs deleted file mode 100644 index d10a80175b..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueArray.cs +++ /dev/null @@ -1,121 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; -using System.Collections.Generic; - -namespace XmlRpcHandler -{ - public class XmlRpcValueArray : IXmlRpcValue - { - public XmlRpcValueArray() : - base() - { - values = new List(); - } - public XmlRpcValueArray(object data) : - base(data) - { - values = new List(); - } - public XmlRpcValueArray(string[] texts) : - base() - { - values = new List(); - foreach (string val in texts) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(int[] ints) : - base() - { - values = new List(); - foreach (int val in ints) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(double[] doubles) : - base() - { - values = new List(); - foreach (double val in doubles) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(bool[] bools) : - base() - { - values = new List(); - foreach (bool val in bools) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(long[] base24s) : - base() - { - values = new List(); - foreach (long val in base24s) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(DateTime[] dates) : - base() - { - values = new List(); - foreach (var val in dates) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(XmlRpcValueBasic[] basicValues) : - base() - { - values = new List(); - foreach (var val in basicValues) - { - values.Add(val); - } - } - public XmlRpcValueArray(XmlRpcValueStruct[] structs) : - base() - { - values = new List(); - foreach (var val in structs) - { - values.Add(val); - } - } - public XmlRpcValueArray(XmlRpcValueArray[] arrays) : - base() - { - values = new List(); - foreach (var val in arrays) - { - values.Add(val); - } - } - private List values; - - public List Values { get { return values; } set { values = value; } } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueBasic.cs b/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueBasic.cs deleted file mode 100644 index f2811b9889..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueBasic.cs +++ /dev/null @@ -1,99 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; - -namespace XmlRpcHandler -{ - public class XmlRpcValueBasic : IXmlRpcValue - { - public XmlRpcValueBasic() - : base() - { } - public XmlRpcValueBasic(string data) - : base(data) - { this.type = XmlRpcBasicValueType.String; } - public XmlRpcValueBasic(int data) - : base(data) - { this.type = XmlRpcBasicValueType.Int; } - public XmlRpcValueBasic(double data) - : base(data) - { this.type = XmlRpcBasicValueType.Double; } - public XmlRpcValueBasic(DateTime data) - : base(data) - { this.type = XmlRpcBasicValueType.dateTime_iso8601; } - public XmlRpcValueBasic(bool data) - : base(data) - { this.type = XmlRpcBasicValueType.Boolean; } - public XmlRpcValueBasic(long data) - : base(data) - { this.type = XmlRpcBasicValueType.base64; } - public XmlRpcValueBasic(object data, XmlRpcBasicValueType type) - : base(data) - { this.type = type; } - - private XmlRpcBasicValueType type = XmlRpcBasicValueType.String; - /// - /// Get or set the type of this basic value - /// - public XmlRpcBasicValueType ValueType { get { return type; } set { type = value; } } - /*Oprators. help a lot.*/ - public static implicit operator string(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return f.Data.ToString(); - else - throw new Exception("Unable to convert, this value is not string type."); - } - public static implicit operator int(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return (int)f.Data; - else - throw new Exception("Unable to convert, this value is not int type."); - } - public static implicit operator double(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return (double)f.Data; - else - throw new Exception("Unable to convert, this value is not double type."); - } - public static implicit operator bool(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return (bool)f.Data; - else - throw new Exception("Unable to convert, this value is not bool type."); - } - public static implicit operator long(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return (long)f.Data; - else - throw new Exception("Unable to convert, this value is not long type."); - } - public static implicit operator DateTime(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return (DateTime)f.Data; - else - throw new Exception("Unable to convert, this value is not DateTime type."); - } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueStruct.cs b/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueStruct.cs deleted file mode 100644 index 4863e38e84..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueStruct.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace XmlRpcHandler -{ - /// - /// Represents XML-RPC struct - /// - public class XmlRpcValueStruct : IXmlRpcValue - { - /// - /// Represents XML-RPC struct - /// - /// - public XmlRpcValueStruct(List members) - { this.members = members; } - - private List members; - /// - /// Get or set the members collection - /// - public List Members - { get { return members; } set { members = value; } } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs b/OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs deleted file mode 100644 index a79a278fa1..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs +++ /dev/null @@ -1,350 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; -using System.Xml; -using OpenSubtitlesHandler; - -namespace XmlRpcHandler -{ - /// - /// A class for making XML-RPC requests. The requests then can be sent directly as http request. - /// - public class XmlRpcGenerator - { - /// - /// Generate XML-RPC request using method call. - /// - /// The method call - /// The request in xml. - public static byte[] Generate(XmlRpcMethodCall method) - { return Generate(new XmlRpcMethodCall[] { method }); } - /// - /// Generate XML-RPC request using method calls array. - /// - /// The method calls array - /// The request in utf8 xml string as buffer - public static byte[] Generate(XmlRpcMethodCall[] methods) - { - if (methods == null) - throw new Exception("No method to write !"); - if (methods.Length == 0) - throw new Exception("No method to write !"); - // Create xml - var sett = new XmlWriterSettings(); - sett.Indent = true; - - sett.Encoding = Encoding.UTF8; - - using (var ms = new MemoryStream()) - { - using (var XMLwrt = XmlWriter.Create(ms, sett)) - { - // Let's write the methods - foreach (XmlRpcMethodCall method in methods) - { - XMLwrt.WriteStartElement("methodCall");//methodCall - XMLwrt.WriteStartElement("methodName");//methodName - XMLwrt.WriteString(method.Name); - XMLwrt.WriteEndElement();//methodName - XMLwrt.WriteStartElement("params");//params - // Write values - foreach (IXmlRpcValue p in method.Parameters) - { - XMLwrt.WriteStartElement("param");//param - if (p is XmlRpcValueBasic) - { - WriteBasicValue(XMLwrt, (XmlRpcValueBasic)p); - } - else if (p is XmlRpcValueStruct) - { - WriteStructValue(XMLwrt, (XmlRpcValueStruct)p); - } - else if (p is XmlRpcValueArray) - { - WriteArrayValue(XMLwrt, (XmlRpcValueArray)p); - } - XMLwrt.WriteEndElement();//param - } - - XMLwrt.WriteEndElement();//params - XMLwrt.WriteEndElement();//methodCall - } - XMLwrt.Flush(); - return ms.ToArray(); - } - } - } - /// - /// Decode response then return the values - /// - /// The response xml string as provided by server as methodResponse - /// - public static XmlRpcMethodCall[] DecodeMethodResponse(string xmlResponse) - { - var methods = new List(); - var sett = new XmlReaderSettings(); - sett.DtdProcessing = DtdProcessing.Ignore; - sett.IgnoreWhitespace = true; - MemoryStream str; - if (xmlResponse.Contains(@"encoding=""utf-8""")) - { - str = new MemoryStream(Encoding.UTF8.GetBytes(xmlResponse)); - } - else - { - str = new MemoryStream(Utilities.GetASCIIBytes(xmlResponse)); - } - using (str) - { - using (var XMLread = XmlReader.Create(str, sett)) - { - var call = new XmlRpcMethodCall("methodResponse"); - // Read parameters - while (XMLread.Read()) - { - if (XMLread.Name == "param" && XMLread.IsStartElement()) - { - IXmlRpcValue val = ReadValue(XMLread); - if (val != null) - call.Parameters.Add(val); - } - } - methods.Add(call); - return methods.ToArray(); - } - } - } - - private static void WriteBasicValue(XmlWriter XMLwrt, XmlRpcValueBasic val) - { - XMLwrt.WriteStartElement("value");//value - switch (val.ValueType) - { - case XmlRpcBasicValueType.String: - XMLwrt.WriteStartElement("string"); - if (val.Data != null) - XMLwrt.WriteString(val.Data.ToString()); - XMLwrt.WriteEndElement(); - break; - case XmlRpcBasicValueType.Int: - XMLwrt.WriteStartElement("int"); - if (val.Data != null) - XMLwrt.WriteString(val.Data.ToString()); - XMLwrt.WriteEndElement(); - break; - case XmlRpcBasicValueType.Boolean: - XMLwrt.WriteStartElement("boolean"); - if (val.Data != null) - XMLwrt.WriteString(((bool)val.Data) ? "1" : "0"); - XMLwrt.WriteEndElement(); - break; - case XmlRpcBasicValueType.Double: - XMLwrt.WriteStartElement("double"); - if (val.Data != null) - XMLwrt.WriteString(val.Data.ToString()); - XMLwrt.WriteEndElement(); - break; - case XmlRpcBasicValueType.dateTime_iso8601: - XMLwrt.WriteStartElement("dateTime.iso8601"); - // Get date time format - if (val.Data != null) - { - var time = (DateTime)val.Data; - string dt = time.Year + time.Month.ToString("D2") + time.Day.ToString("D2") + - "T" + time.Hour.ToString("D2") + ":" + time.Minute.ToString("D2") + ":" + - time.Second.ToString("D2"); - XMLwrt.WriteString(dt); - } - XMLwrt.WriteEndElement(); - break; - case XmlRpcBasicValueType.base64: - XMLwrt.WriteStartElement("base64"); - if (val.Data != null) - XMLwrt.WriteString(Convert.ToBase64String(BitConverter.GetBytes((long)val.Data))); - XMLwrt.WriteEndElement(); - break; - } - XMLwrt.WriteEndElement();//value - } - private static void WriteStructValue(XmlWriter XMLwrt, XmlRpcValueStruct val) - { - XMLwrt.WriteStartElement("value");//value - XMLwrt.WriteStartElement("struct");//struct - foreach (XmlRpcStructMember member in val.Members) - { - XMLwrt.WriteStartElement("member");//member - - XMLwrt.WriteStartElement("name");//name - XMLwrt.WriteString(member.Name); - XMLwrt.WriteEndElement();//name - - if (member.Data is XmlRpcValueBasic) - { - WriteBasicValue(XMLwrt, (XmlRpcValueBasic)member.Data); - } - else if (member.Data is XmlRpcValueStruct) - { - WriteStructValue(XMLwrt, (XmlRpcValueStruct)member.Data); - } - else if (member.Data is XmlRpcValueArray) - { - WriteArrayValue(XMLwrt, (XmlRpcValueArray)member.Data); - } - - XMLwrt.WriteEndElement();//member - } - XMLwrt.WriteEndElement();//struct - XMLwrt.WriteEndElement();//value - } - private static void WriteArrayValue(XmlWriter XMLwrt, XmlRpcValueArray val) - { - XMLwrt.WriteStartElement("value");//value - XMLwrt.WriteStartElement("array");//array - XMLwrt.WriteStartElement("data");//data - foreach (IXmlRpcValue o in val.Values) - { - if (o is XmlRpcValueBasic) - { - WriteBasicValue(XMLwrt, (XmlRpcValueBasic)o); - } - else if (o is XmlRpcValueStruct) - { - WriteStructValue(XMLwrt, (XmlRpcValueStruct)o); - } - else if (o is XmlRpcValueArray) - { - WriteArrayValue(XMLwrt, (XmlRpcValueArray)o); - } - } - XMLwrt.WriteEndElement();//data - XMLwrt.WriteEndElement();//array - XMLwrt.WriteEndElement();//value - } - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - private static string ReadString(XmlReader reader) - { - if (reader.NodeType == XmlNodeType.Element) - { - return reader.ReadElementContentAsString(); - } - return reader.ReadContentAsString(); - } - - private static IXmlRpcValue ReadValue(XmlReader xmlReader, bool skipRead = false) - { - while (skipRead || xmlReader.Read()) - { - if (xmlReader.Name == "value" && xmlReader.IsStartElement()) - { - xmlReader.Read(); - if (xmlReader.Name == "string" && xmlReader.IsStartElement()) - { - return new XmlRpcValueBasic(ReadString(xmlReader), XmlRpcBasicValueType.String); - } - else if (xmlReader.Name == "int" && xmlReader.IsStartElement()) - { - return new XmlRpcValueBasic(int.Parse(ReadString(xmlReader), UsCulture), XmlRpcBasicValueType.Int); - } - else if (xmlReader.Name == "boolean" && xmlReader.IsStartElement()) - { - return new XmlRpcValueBasic(ReadString(xmlReader) == "1", XmlRpcBasicValueType.Boolean); - } - else if (xmlReader.Name == "double" && xmlReader.IsStartElement()) - { - return new XmlRpcValueBasic(double.Parse(ReadString(xmlReader), UsCulture), XmlRpcBasicValueType.Double); - } - else if (xmlReader.Name == "dateTime.iso8601" && xmlReader.IsStartElement()) - { - string date = ReadString(xmlReader); - int year = int.Parse(date.Substring(0, 4), UsCulture); - int month = int.Parse(date.Substring(4, 2), UsCulture); - int day = int.Parse(date.Substring(6, 2), UsCulture); - int hour = int.Parse(date.Substring(9, 2), UsCulture); - int minute = int.Parse(date.Substring(12, 2), UsCulture);//19980717T14:08:55 - int sec = int.Parse(date.Substring(15, 2), UsCulture); - var time = new DateTime(year, month, day, hour, minute, sec); - return new XmlRpcValueBasic(time, XmlRpcBasicValueType.dateTime_iso8601); - } - else if (xmlReader.Name == "base64" && xmlReader.IsStartElement()) - { - return new XmlRpcValueBasic(BitConverter.ToInt64(Convert.FromBase64String(ReadString(xmlReader)), 0) - , XmlRpcBasicValueType.Double); - } - else if (xmlReader.Name == "struct" && xmlReader.IsStartElement()) - { - var strct = new XmlRpcValueStruct(new List()); - // Read members... - while (xmlReader.Read()) - { - if (xmlReader.Name == "member" && xmlReader.IsStartElement()) - { - var member = new XmlRpcStructMember("", null); - xmlReader.Read();// read name - member.Name = ReadString(xmlReader); - - IXmlRpcValue val = ReadValue(xmlReader, true); - if (val != null) - { - member.Data = val; - strct.Members.Add(member); - } - } - else if (xmlReader.Name == "struct" && !xmlReader.IsStartElement()) - { - return strct; - } - } - return strct; - } - else if (xmlReader.Name == "array" && xmlReader.IsStartElement()) - { - var array = new XmlRpcValueArray(); - // Read members... - while (xmlReader.Read()) - { - if (xmlReader.Name == "array" && !xmlReader.IsStartElement()) - { - return array; - } - else - { - IXmlRpcValue val = ReadValue(xmlReader); - if (val != null) - array.Values.Add(val); - } - } - return array; - } - } - else break; - - if (skipRead) - { - return null; - } - } - return null; - } - } -} -- cgit v1.2.3 From 3c4043199accbfe7995dd6060c89fc837300884a Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 12 Mar 2019 09:18:45 -0400 Subject: Implement review feedback --- Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs | 4 ++-- Emby.Server.Implementations/ApplicationHost.cs | 2 +- Jellyfin.Server/Program.cs | 2 +- Jellyfin.Server/StartupOptions.cs | 2 +- MediaBrowser.Common/Configuration/IApplicationPaths.cs | 4 ++-- MediaBrowser.Model/System/SystemInfo.cs | 4 ++-- MediaBrowser.WebDashboard/Api/DashboardService.cs | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index fd9ce3a364..00cfa0c9a9 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -36,9 +36,9 @@ namespace Emby.Server.Implementations.AppBase public string ProgramDataPath { get; private set; } /// - /// Gets the path to the web resources folder + /// Gets the path to the web UI resources folder /// - /// The web resources path. + /// The web UI resources path. public string WebPath { get; set; } /// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f240ef871a..14c700cc5d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -621,7 +621,7 @@ namespace Emby.Server.Implementations string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; if (string.IsNullOrEmpty(contentRoot)) { - contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.WebPath, "src"); + contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; } var host = new WebHostBuilder() diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 8f1498b13b..7534a6398f 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -277,7 +277,7 @@ namespace Jellyfin.Server if (string.IsNullOrEmpty(webDir)) { // Use default location under ResourcesPath - webDir = Path.Combine(AppContext.BaseDirectory, "jellyfin-web"); + webDir = Path.Combine(AppContext.BaseDirectory, "jellyfin-web", "src"); } } diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 2b01c7bbdf..345a8a731f 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Server [Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")] public string DataDir { get; set; } - [Option('w', "webdir", Required = false, HelpText = "Path to the Jellyfin web resources.")] + [Option('w', "webdir", Required = false, HelpText = "Path to the Jellyfin web UI resources.")] public string WebDir { get; set; } [Option('C', "cachedir", Required = false, HelpText = "Path to use for caching.")] diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index 1f56c98fd8..fd11bf904f 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -12,9 +12,9 @@ namespace MediaBrowser.Common.Configuration string ProgramDataPath { get; } /// - /// Gets the path to the web resources folder + /// Gets the path to the web UI resources folder /// - /// The web resources path. + /// The web UI resources path. string WebPath { get; } /// diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index fa72624819..222c10798a 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -84,9 +84,9 @@ namespace MediaBrowser.Model.System public string ProgramDataPath { get; set; } /// - /// Gets or sets the web resources path. + /// Gets or sets the web UI resources path. /// - /// The web resources path. + /// The web UI resources path. public string WebPath { get; set; } /// diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 05df54b115..01cbe2c314 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.WebDashboard.Api return _serverConfigurationManager.Configuration.DashboardSourcePath; } - return Path.Combine(_serverConfigurationManager.ApplicationPaths.WebPath, "src"); + return _serverConfigurationManager.ApplicationPaths.WebPath; } } -- cgit v1.2.3 From b864e9da2a512250eff48baae28251db5a508f8f Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Tue, 12 Mar 2019 22:09:18 +0000 Subject: Finalise removal of --ffprobe switch Removed --ffprobe from src files and server/docker scripts. --- Dockerfile | 3 +-- Dockerfile.arm | 3 +-- Dockerfile.arm64 | 3 +-- Emby.Server.Implementations/ApplicationHost.cs | 1 - Emby.Server.Implementations/IStartupOptions.cs | 5 ----- Jellyfin.Server/StartupOptions.cs | 3 --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 9 --------- deployment/debian-package-x64/pkg-src/conf/jellyfin | 3 +-- deployment/debian-package-x64/pkg-src/jellyfin.service | 2 +- deployment/fedora-package-x64/pkg-src/jellyfin.env | 1 - deployment/fedora-package-x64/pkg-src/jellyfin.service | 2 +- 11 files changed, 6 insertions(+), 29 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Dockerfile b/Dockerfile index 91a4f5a2d2..72c0cdbf3a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,5 +31,4 @@ VOLUME /cache /config /media ENTRYPOINT dotnet /jellyfin/jellyfin.dll \ --datadir /config \ --cachedir /cache \ - --ffmpeg /usr/local/bin/ffmpeg \ - --ffprobe /usr/local/bin/ffprobe + --ffmpeg /usr/local/bin/ffmpeg diff --git a/Dockerfile.arm b/Dockerfile.arm index 42f0354a32..238e90ccd8 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -39,5 +39,4 @@ VOLUME /cache /config /media ENTRYPOINT dotnet /jellyfin/jellyfin.dll \ --datadir /config \ --cachedir /cache \ - --ffmpeg /usr/bin/ffmpeg \ - --ffprobe /usr/bin/ffprobe + --ffmpeg /usr/bin/ffmpeg diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index d3103d3893..2927d4e658 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -40,5 +40,4 @@ VOLUME /cache /config /media ENTRYPOINT dotnet /jellyfin/jellyfin.dll \ --datadir /config \ --cachedir /cache \ - --ffmpeg /usr/bin/ffmpeg \ - --ffprobe /usr/bin/ffprobe + --ffmpeg /usr/bin/ffmpeg diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 98f6ab6bc7..1810064e69 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -837,7 +837,6 @@ namespace Emby.Server.Implementations LoggerFactory, JsonSerializer, StartupOptions.FFmpegPath, - StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, () => SubtitleEncoder, diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 24aaa76c02..6e915de3d1 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -7,11 +7,6 @@ namespace Emby.Server.Implementations /// string FFmpegPath { get; } - /// - /// --ffprobe - /// - string FFprobePath { get; } - /// /// --service /// diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 345a8a731f..8296d414ef 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -26,9 +26,6 @@ namespace Jellyfin.Server [Option("ffmpeg", Required = false, HelpText = "Path to external FFmpeg executable to use in place of default found in PATH.")] public string FFmpegPath { get; set; } - [Option("ffprobe", Required = false, HelpText = "(deprecated) Option has no effect and shall be removed in next release.")] - public string FFprobePath { get; set; } - [Option("service", Required = false, HelpText = "Run as headless service.")] public bool IsService { get; set; } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 94beda3dbb..f454c27235 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -51,7 +51,6 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly IProcessFactory _processFactory; private readonly int DefaultImageExtractionTimeoutMs; private readonly string StartupOptionFFmpegPath; - private readonly string StartupOptionFFprobePath; private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); private readonly List _runningProcesses = new List(); @@ -60,7 +59,6 @@ namespace MediaBrowser.MediaEncoding.Encoder ILoggerFactory loggerFactory, IJsonSerializer jsonSerializer, string startupOptionsFFmpegPath, - string startupOptionsFFprobePath, IServerConfigurationManager configurationManager, IFileSystem fileSystem, Func subtitleEncoder, @@ -71,7 +69,6 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger = loggerFactory.CreateLogger(nameof(MediaEncoder)); _jsonSerializer = jsonSerializer; StartupOptionFFmpegPath = startupOptionsFFmpegPath; - StartupOptionFFprobePath = startupOptionsFFprobePath; ConfigurationManager = configurationManager; FileSystem = fileSystem; SubtitleEncoder = subtitleEncoder; @@ -86,12 +83,6 @@ namespace MediaBrowser.MediaEncoding.Encoder /// public void SetFFmpegPath() { - // ToDo - Finalise removal of the --ffprobe switch - if (!string.IsNullOrEmpty(StartupOptionFFprobePath)) - { - _logger.LogWarning("--ffprobe switch is deprecated and shall be removed in the next release"); - } - // 1) Custom path stored in config/encoding xml file under tag takes precedence if (!ValidatePath(ConfigurationManager.GetConfiguration("encoding").EncoderAppPath, FFmpegLocation.Custom)) { diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin b/deployment/debian-package-x64/pkg-src/conf/jellyfin index 7db482d428..bc00c37e20 100644 --- a/deployment/debian-package-x64/pkg-src/conf/jellyfin +++ b/deployment/debian-package-x64/pkg-src/conf/jellyfin @@ -23,7 +23,6 @@ JELLYFIN_RESTART_OPT="--restartpath=/usr/lib/jellyfin/restart.sh" # ffmpeg binary paths, overriding the system values JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/share/jellyfin-ffmpeg/ffmpeg" -JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/share/jellyfin-ffmpeg/ffprobe" # [OPTIONAL] run Jellyfin as a headless service #JELLYFIN_SERVICE_OPT="--service" @@ -38,4 +37,4 @@ JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/share/jellyfin-ffmpeg/ffprobe" # Application username JELLYFIN_USER="jellyfin" # Full application command -JELLYFIN_ARGS="$JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_FFPROBE_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT" +JELLYFIN_ARGS="$JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT" diff --git a/deployment/debian-package-x64/pkg-src/jellyfin.service b/deployment/debian-package-x64/pkg-src/jellyfin.service index b4da3a945b..1305e238b0 100644 --- a/deployment/debian-package-x64/pkg-src/jellyfin.service +++ b/deployment/debian-package-x64/pkg-src/jellyfin.service @@ -6,7 +6,7 @@ After = network.target Type = simple EnvironmentFile = /etc/default/jellyfin User = jellyfin -ExecStart = /usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} +ExecStart = /usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} Restart = on-failure TimeoutSec = 15 diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.env b/deployment/fedora-package-x64/pkg-src/jellyfin.env index 143a317c43..de48f13af5 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.env +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.env @@ -25,7 +25,6 @@ JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh" # [OPTIONAL] ffmpeg binary paths, overriding the UI-configured values #JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/bin/ffmpeg" -#JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/bin/ffprobe" # [OPTIONAL] run Jellyfin as a headless service #JELLYFIN_SERVICE_OPT="--service" diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.service b/deployment/fedora-package-x64/pkg-src/jellyfin.service index 1f83d3d386..f3dc594b1c 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.service +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.service @@ -5,7 +5,7 @@ Description=Jellyfin is a free software media system that puts you in control of [Service] EnvironmentFile=/etc/sysconfig/jellyfin WorkingDirectory=/var/lib/jellyfin -ExecStart=/usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} +ExecStart=/usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} TimeoutSec=15 Restart=on-failure User=jellyfin -- cgit v1.2.3 From e64aaebbacfa7a720c99ca2ab1aa11f7fcd63868 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Mar 2019 17:51:33 +0100 Subject: Improvements around streams * Use ArrayPool instead of allocating new buffers each time * Remove NetworkStream copy * Remove some dead code --- .../HttpServer/StreamWriter.cs | 1 - Emby.Server.Implementations/IO/StreamHelper.cs | 226 +++++++++++++-------- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 3 +- .../LiveTv/TunerHosts/LiveStream.cs | 58 ++---- .../Net/DisposableManagedObjectBase.cs | 66 ------ Emby.Server.Implementations/Net/SocketFactory.cs | 134 ++++-------- Emby.Server.Implementations/Net/UdpSocket.cs | 72 +++---- MediaBrowser.Api/LiveTv/LiveTvService.cs | 34 ++-- MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs | 39 +--- MediaBrowser.Model/Net/HttpResponse.cs | 64 ------ MediaBrowser.Model/Net/IAcceptSocket.cs | 15 -- 11 files changed, 265 insertions(+), 447 deletions(-) delete mode 100644 Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs delete mode 100644 MediaBrowser.Model/Net/HttpResponse.cs delete mode 100644 MediaBrowser.Model/Net/IAcceptSocket.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 66a13e20df..cf30bbc326 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -5,7 +5,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs index 09cf4d4a3e..d02cd84a03 100644 --- a/Emby.Server.Implementations/IO/StreamHelper.cs +++ b/Emby.Server.Implementations/IO/StreamHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -8,168 +9,213 @@ namespace Emby.Server.Implementations.IO { public class StreamHelper : IStreamHelper { + private const int StreamCopyToBufferSize = 81920; + public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken) { - byte[] buffer = new byte[bufferSize]; - int read; - while ((read = source.Read(buffer, 0, buffer.Length)) != 0) + byte[] buffer = ArrayPool.Shared.Rent(bufferSize); + try { - cancellationToken.ThrowIfCancellationRequested(); + int read; + while ((read = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) != 0) + { + cancellationToken.ThrowIfCancellationRequested(); - await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); + await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); - if (onStarted != null) - { - onStarted(); - onStarted = null; + if (onStarted != null) + { + onStarted(); + onStarted = null; + } } } + finally + { + ArrayPool.Shared.Return(buffer); + } } public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken) { - byte[] buffer = new byte[bufferSize]; - - if (emptyReadLimit <= 0) + byte[] buffer = ArrayPool.Shared.Rent(bufferSize); + try { - int read; - while ((read = source.Read(buffer, 0, buffer.Length)) != 0) + if (emptyReadLimit <= 0) { - cancellationToken.ThrowIfCancellationRequested(); + int read; + while ((read = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) != 0) + { + cancellationToken.ThrowIfCancellationRequested(); - await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); - } + await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); + } - return; - } + return; + } - var eofCount = 0; + var eofCount = 0; - while (eofCount < emptyReadLimit) - { - cancellationToken.ThrowIfCancellationRequested(); + while (eofCount < emptyReadLimit) + { + cancellationToken.ThrowIfCancellationRequested(); - var bytesRead = source.Read(buffer, 0, buffer.Length); + var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); - if (bytesRead == 0) - { - eofCount++; - await Task.Delay(50, cancellationToken).ConfigureAwait(false); - } - else - { - eofCount = 0; + if (bytesRead == 0) + { + eofCount++; + await Task.Delay(50, cancellationToken).ConfigureAwait(false); + } + else + { + eofCount = 0; - await destination.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); + await destination.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); + } } } + finally + { + ArrayPool.Shared.Return(buffer); + } } - const int StreamCopyToBufferSize = 81920; public async Task CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken) { - var array = new byte[StreamCopyToBufferSize]; - int bytesRead; - int totalBytesRead = 0; - - while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) + byte[] buffer = ArrayPool.Shared.Rent(StreamCopyToBufferSize); + try { - var bytesToWrite = bytesRead; + int totalBytesRead = 0; - if (bytesToWrite > 0) + int bytesRead; + while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + var bytesToWrite = bytesRead; - totalBytesRead += bytesRead; + if (bytesToWrite > 0) + { + await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + + totalBytesRead += bytesRead; + } } - } - return totalBytesRead; + return totalBytesRead; + } + finally + { + ArrayPool.Shared.Return(buffer); + } } public async Task CopyToAsyncWithSyncRead(Stream source, Stream destination, CancellationToken cancellationToken) { - var array = new byte[StreamCopyToBufferSize]; - int bytesRead; - int totalBytesRead = 0; - - while ((bytesRead = source.Read(array, 0, array.Length)) != 0) + byte[] buffer = ArrayPool.Shared.Rent(StreamCopyToBufferSize); + try { - var bytesToWrite = bytesRead; + int bytesRead; + int totalBytesRead = 0; - if (bytesToWrite > 0) + while ((bytesRead = source.Read(buffer, 0, buffer.Length)) != 0) { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + var bytesToWrite = bytesRead; - totalBytesRead += bytesRead; + if (bytesToWrite > 0) + { + await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + + totalBytesRead += bytesRead; + } } - } - return totalBytesRead; + return totalBytesRead; + } + finally + { + ArrayPool.Shared.Return(buffer); + } } public async Task CopyToAsyncWithSyncRead(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) { - var array = new byte[StreamCopyToBufferSize]; - int bytesRead; - - while ((bytesRead = source.Read(array, 0, array.Length)) != 0) + byte[] buffer = ArrayPool.Shared.Rent(StreamCopyToBufferSize); + try { - var bytesToWrite = Math.Min(bytesRead, copyLength); + int bytesRead; - if (bytesToWrite > 0) + while ((bytesRead = source.Read(buffer, 0, buffer.Length)) != 0) { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); - } + var bytesToWrite = Math.Min(bytesRead, copyLength); - copyLength -= bytesToWrite; + if (bytesToWrite > 0) + { + await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + } - if (copyLength <= 0) - { - break; + copyLength -= bytesToWrite; + + if (copyLength <= 0) + { + break; + } } } + finally + { + ArrayPool.Shared.Return(buffer); + } } public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) { - var array = new byte[StreamCopyToBufferSize]; - int bytesRead; - - while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) + byte[] buffer = ArrayPool.Shared.Rent(StreamCopyToBufferSize); + try { - var bytesToWrite = Math.Min(bytesRead, copyLength); + int bytesRead; - if (bytesToWrite > 0) + while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); - } + var bytesToWrite = Math.Min(bytesRead, copyLength); - copyLength -= bytesToWrite; + if (bytesToWrite > 0) + { + await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + } - if (copyLength <= 0) - { - break; + copyLength -= bytesToWrite; + + if (copyLength <= 0) + { + break; + } } } + finally + { + ArrayPool.Shared.Return(buffer); + } } public async Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken) { - byte[] buffer = new byte[bufferSize]; - - while (!cancellationToken.IsCancellationRequested) + byte[] buffer = ArrayPool.Shared.Rent(bufferSize); + try { - var bytesRead = await CopyToAsyncInternal(source, target, buffer, cancellationToken).ConfigureAwait(false); - - //var position = fs.Position; - //_logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - - if (bytesRead == 0) + while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(100).ConfigureAwait(false); + var bytesRead = await CopyToAsyncInternal(source, target, buffer, cancellationToken).ConfigureAwait(false); + + if (bytesRead == 0) + { + await Task.Delay(100).ConfigureAwait(false); + } } } + finally + { + ArrayPool.Shared.Return(buffer); + } } private static async Task CopyToAsyncInternal(Stream source, Stream destination, byte[] buffer, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 8774371d52..7f426ea31f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -151,7 +151,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun }); } - private static int RtpHeaderBytes = 12; + private const int RtpHeaderBytes = 12; + private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) { var bufferSize = 81920; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 1f8ca276e7..ece2cbd547 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public string OriginalStreamId { get; set; } public bool EnableStreamSharing { get; set; } - public string UniqueId { get; private set; } + public string UniqueId { get; } protected readonly IFileSystem FileSystem; protected readonly IServerApplicationPaths AppPaths; @@ -31,12 +31,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected readonly ILogger Logger; protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource(); - public string TunerHostId { get; private set; } + public string TunerHostId { get; } public DateTime DateOpened { get; protected set; } - public Func OnClose { get; set; } - public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths) { OriginalMediaSource = mediaSource; @@ -76,26 +74,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts LiveStreamCancellationTokenSource.Cancel(); - if (OnClose != null) - { - return CloseWithExternalFn(); - } - return Task.CompletedTask; } - private async Task CloseWithExternalFn() - { - try - { - await OnClose().ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error closing live stream"); - } - } - protected Stream GetInputStream(string path, bool allowAsyncFileRead) { var fileOpenOptions = FileOpenOptions.SequentialScan; @@ -113,26 +94,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return DeleteTempFiles(GetStreamFilePaths()); } - protected async Task DeleteTempFiles(List paths, int retryCount = 0) + protected async Task DeleteTempFiles(IEnumerable paths, int retryCount = 0) { if (retryCount == 0) { - Logger.LogInformation("Deleting temp files {0}", string.Join(", ", paths.ToArray())); + Logger.LogInformation("Deleting temp files {0}", paths); } var failedFiles = new List(); foreach (var path in paths) { - try - { - FileSystem.DeleteFile(path); - } - catch (DirectoryNotFoundException) + if (!File.Exists(path)) { + continue; } - catch (FileNotFoundException) + + try { + FileSystem.DeleteFile(path); } catch (Exception ex) { @@ -157,8 +137,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token; - var allowAsync = false; - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + // use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039 + var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT; bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10; @@ -181,28 +161,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Logger.LogInformation("Live Stream ended."); } - private Tuple GetNextFile(string currentFile) + private (string file, bool isLastFile) GetNextFile(string currentFile) { var files = GetStreamFilePaths(); - //logger.LogInformation("Live stream files: {0}", string.Join(", ", files.ToArray())); - if (string.IsNullOrEmpty(currentFile)) { - return new Tuple(files.Last(), true); + return (files.Last(), true); } var nextIndex = files.FindIndex(i => string.Equals(i, currentFile, StringComparison.OrdinalIgnoreCase)) + 1; var isLastFile = nextIndex == files.Count - 1; - return new Tuple(files.ElementAtOrDefault(nextIndex), isLastFile); + return (files.ElementAtOrDefault(nextIndex), isLastFile); } private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken) { - //logger.LogInformation("Opening live stream file {0}. Empty read limit: {1}", path, emptyReadLimit); - using (var inputStream = (FileStream)GetInputStream(path, allowAsync)) { if (seekFile) @@ -218,7 +194,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private void TrySeek(FileStream stream, long offset) { - //logger.LogInformation("TrySeek live stream"); + if (!stream.CanSeek) + { + return; + } + try { stream.Seek(offset, SeekOrigin.End); diff --git a/Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs b/Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs deleted file mode 100644 index 304b445651..0000000000 --- a/Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; - -namespace Emby.Server.Implementations.Net -{ - /// - /// Correclty implements the interface and pattern for an object containing only managed resources, and adds a few common niceities not on the interface such as an property. - /// - public abstract class DisposableManagedObjectBase : IDisposable - { - - #region Public Methods - - /// - /// Override this method and dispose any objects you own the lifetime of if disposing is true; - /// - /// True if managed objects should be disposed, if false, only unmanaged resources should be released. - protected abstract void Dispose(bool disposing); - - - //TODO Remove and reimplement using the IsDisposed property directly. - /// - /// Throws an if the property is true. - /// - /// - /// Thrown if the property is true. - /// - protected virtual void ThrowIfDisposed() - { - if (IsDisposed) throw new ObjectDisposedException(GetType().Name); - } - - #endregion - - #region Public Properties - - /// - /// Sets or returns a boolean indicating whether or not this instance has been disposed. - /// - /// - public bool IsDisposed - { - get; - private set; - } - - #endregion - - #region IDisposable Members - - /// - /// Disposes this object instance and all internally managed resources. - /// - /// - /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes. - /// - /// - public void Dispose() - { - IsDisposed = true; - - Dispose(true); - } - - #endregion - } -} diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 6beb14f558..492f48abe8 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -4,7 +4,6 @@ using System.Net; using System.Net.Sockets; using Emby.Server.Implementations.Networking; using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Net { @@ -19,7 +18,10 @@ namespace Emby.Server.Implementations.Net public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort) { - if (remotePort < 0) throw new ArgumentException("remotePort cannot be less than zero.", nameof(remotePort)); + if (remotePort < 0) + { + throw new ArgumentException("remotePort cannot be less than zero.", nameof(remotePort)); + } var addressFamily = remoteAddress.AddressFamily == IpAddressFamily.InterNetwork ? AddressFamily.InterNetwork @@ -42,8 +44,7 @@ namespace Emby.Server.Implementations.Net } catch { - if (retVal != null) - retVal.Dispose(); + retVal?.Dispose(); throw; } @@ -55,7 +56,10 @@ namespace Emby.Server.Implementations.Net /// An integer specifying the local port to bind the acceptSocket to. public ISocket CreateUdpSocket(int localPort) { - if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + if (localPort < 0) + { + throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + } var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try @@ -65,8 +69,7 @@ namespace Emby.Server.Implementations.Net } catch { - if (retVal != null) - retVal.Dispose(); + retVal?.Dispose(); throw; } @@ -74,7 +77,10 @@ namespace Emby.Server.Implementations.Net public ISocket CreateUdpBroadcastSocket(int localPort) { - if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + if (localPort < 0) + { + throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + } var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try @@ -86,8 +92,7 @@ namespace Emby.Server.Implementations.Net } catch { - if (retVal != null) - retVal.Dispose(); + retVal?.Dispose(); throw; } @@ -99,7 +104,10 @@ namespace Emby.Server.Implementations.Net /// An implementation of the interface used by RSSDP components to perform acceptSocket operations. public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort) { - if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + if (localPort < 0) + { + throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + } var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try @@ -114,8 +122,7 @@ namespace Emby.Server.Implementations.Net } catch { - if (retVal != null) - retVal.Dispose(); + retVal?.Dispose(); throw; } @@ -130,10 +137,25 @@ namespace Emby.Server.Implementations.Net /// public ISocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort) { - if (ipAddress == null) throw new ArgumentNullException(nameof(ipAddress)); - if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", nameof(ipAddress)); - if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", nameof(multicastTimeToLive)); - if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + if (ipAddress == null) + { + throw new ArgumentNullException(nameof(ipAddress)); + } + + if (ipAddress.Length == 0) + { + throw new ArgumentException("ipAddress cannot be an empty string.", nameof(ipAddress)); + } + + if (multicastTimeToLive <= 0) + { + throw new ArgumentException("multicastTimeToLive cannot be zero or less.", nameof(multicastTimeToLive)); + } + + if (localPort < 0) + { + throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + } var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); @@ -172,87 +194,13 @@ namespace Emby.Server.Implementations.Net } catch { - if (retVal != null) - retVal.Dispose(); + retVal?.Dispose(); throw; } } public Stream CreateNetworkStream(ISocket socket, bool ownsSocket) - { - var netSocket = (UdpSocket)socket; - - return new SocketStream(netSocket.Socket, ownsSocket); - } + => new NetworkStream(((UdpSocket)socket).Socket, ownsSocket); } - - public class SocketStream : Stream - { - private readonly Socket _socket; - - public SocketStream(Socket socket, bool ownsSocket) - { - _socket = socket; - } - - public override void Flush() - { - } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - _socket.Send(buffer, offset, count, SocketFlags.None); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _socket.BeginSend(buffer, offset, count, SocketFlags.None, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - _socket.EndSend(asyncResult); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return _socket.Receive(buffer, offset, count, SocketFlags.None); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _socket.BeginReceive(buffer, offset, count, SocketFlags.None, callback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - return _socket.EndReceive(asyncResult); - } - } - } diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index d488554860..6c55085c83 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -11,12 +11,15 @@ namespace Emby.Server.Implementations.Net // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS // Be careful to check any changes compile and work for all platform projects it is shared in. - public sealed class UdpSocket : DisposableManagedObjectBase, ISocket + public sealed class UdpSocket : ISocket, IDisposable { - private Socket _Socket; - private int _LocalPort; + private Socket _socket; + private int _localPort; + private bool _disposed = false; - public Socket Socket => _Socket; + public Socket Socket => _socket; + + public IpAddressInfo LocalIPAddress { get; } private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() { @@ -35,11 +38,11 @@ namespace Emby.Server.Implementations.Net { if (socket == null) throw new ArgumentNullException(nameof(socket)); - _Socket = socket; - _LocalPort = localPort; + _socket = socket; + _localPort = localPort; LocalIPAddress = NetworkManager.ToIpAddressInfo(ip); - _Socket.Bind(new IPEndPoint(ip, _LocalPort)); + _socket.Bind(new IPEndPoint(ip, _localPort)); InitReceiveSocketAsyncEventArgs(); } @@ -101,32 +104,26 @@ namespace Emby.Server.Implementations.Net { if (socket == null) throw new ArgumentNullException(nameof(socket)); - _Socket = socket; - _Socket.Connect(NetworkManager.ToIPEndPoint(endPoint)); + _socket = socket; + _socket.Connect(NetworkManager.ToIPEndPoint(endPoint)); InitReceiveSocketAsyncEventArgs(); } - public IpAddressInfo LocalIPAddress - { - get; - private set; - } - public IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback) { ThrowIfDisposed(); EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); - return _Socket.BeginReceiveFrom(buffer, offset, count, SocketFlags.None, ref receivedFromEndPoint, callback, buffer); + return _socket.BeginReceiveFrom(buffer, offset, count, SocketFlags.None, ref receivedFromEndPoint, callback, buffer); } public int Receive(byte[] buffer, int offset, int count) { ThrowIfDisposed(); - return _Socket.Receive(buffer, 0, buffer.Length, SocketFlags.None); + return _socket.Receive(buffer, 0, buffer.Length, SocketFlags.None); } public SocketReceiveResult EndReceive(IAsyncResult result) @@ -136,7 +133,7 @@ namespace Emby.Server.Implementations.Net var sender = new IPEndPoint(IPAddress.Any, 0); var remoteEndPoint = (EndPoint)sender; - var receivedBytes = _Socket.EndReceiveFrom(result, ref remoteEndPoint); + var receivedBytes = _socket.EndReceiveFrom(result, ref remoteEndPoint); var buffer = (byte[])result.AsyncState; @@ -236,35 +233,40 @@ namespace Emby.Server.Implementations.Net var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint); - return _Socket.BeginSendTo(buffer, offset, size, SocketFlags.None, ipEndPoint, callback, state); + return _socket.BeginSendTo(buffer, offset, size, SocketFlags.None, ipEndPoint, callback, state); } public int EndSendTo(IAsyncResult result) { ThrowIfDisposed(); - return _Socket.EndSendTo(result); + return _socket.EndSendTo(result); } - protected override void Dispose(bool disposing) + private void ThrowIfDisposed() { - if (disposing) + if (_disposed) { - var socket = _Socket; - if (socket != null) - socket.Dispose(); + throw new ObjectDisposedException(nameof(UdpSocket)); + } + } - var tcs = _currentReceiveTaskCompletionSource; - if (tcs != null) - { - tcs.TrySetCanceled(); - } - var sendTcs = _currentSendTaskCompletionSource; - if (sendTcs != null) - { - sendTcs.TrySetCanceled(); - } + public void Dispose() + { + if (_disposed) + { + return; } + + _socket?.Dispose(); + _currentReceiveTaskCompletionSource?.TrySetCanceled(); + _currentSendTaskCompletionSource?.TrySetCanceled(); + + _socket = null; + _currentReceiveTaskCompletionSource = null; + _currentSendTaskCompletionSource = null; + + _disposed = true; } private static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint) diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 486d5e8a71..e41ad540ad 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -23,7 +23,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.LiveTv @@ -695,27 +694,36 @@ namespace MediaBrowser.Api.LiveTv private readonly IHttpClient _httpClient; private readonly ILibraryManager _libraryManager; private readonly IDtoService _dtoService; - private readonly IFileSystem _fileSystem; private readonly IAuthorizationContext _authContext; private readonly ISessionContext _sessionContext; - private ICryptoProvider _cryptographyProvider; - private IStreamHelper _streamHelper; - private IMediaSourceManager _mediaSourceManager; - - public LiveTvService(ICryptoProvider crypto, IMediaSourceManager mediaSourceManager, IStreamHelper streamHelper, ILiveTvManager liveTvManager, IUserManager userManager, IServerConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService, IFileSystem fileSystem, IAuthorizationContext authContext, ISessionContext sessionContext) + private readonly ICryptoProvider _cryptographyProvider; + private readonly IStreamHelper _streamHelper; + private readonly IMediaSourceManager _mediaSourceManager; + + public LiveTvService( + ICryptoProvider crypto, + IMediaSourceManager mediaSourceManager, + IStreamHelper streamHelper, + ILiveTvManager liveTvManager, + IUserManager userManager, + IServerConfigurationManager config, + IHttpClient httpClient, + ILibraryManager libraryManager, + IDtoService dtoService, + IAuthorizationContext authContext, + ISessionContext sessionContext) { + _cryptographyProvider = crypto; + _mediaSourceManager = mediaSourceManager; + _streamHelper = streamHelper; _liveTvManager = liveTvManager; _userManager = userManager; _config = config; _httpClient = httpClient; _libraryManager = libraryManager; _dtoService = dtoService; - _fileSystem = fileSystem; _authContext = authContext; _sessionContext = sessionContext; - _cryptographyProvider = crypto; - _streamHelper = streamHelper; - _mediaSourceManager = mediaSourceManager; } public object Get(GetTunerHostTypes request) @@ -729,7 +737,7 @@ namespace MediaBrowser.Api.LiveTv var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); var folders = _liveTvManager.GetRecordingFolders(user); - var returnArray = _dtoService.GetBaseItemDtos(folders.ToArray(), new DtoOptions(), user); + var returnArray = _dtoService.GetBaseItemDtos(folders, new DtoOptions(), user); var result = new QueryResult { @@ -754,7 +762,7 @@ namespace MediaBrowser.Api.LiveTv [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType(path) }; - return new ProgressiveFileCopier(_fileSystem, _streamHelper, path, outputHeaders, Logger) + return new ProgressiveFileCopier(_streamHelper, path, outputHeaders, Logger) { AllowEndOfFile = false }; diff --git a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs index 51552d928b..4c608d9a33 100644 --- a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs +++ b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Threading; @@ -11,22 +12,17 @@ namespace MediaBrowser.Api.LiveTv { public class ProgressiveFileCopier : IAsyncStreamWriter, IHasHeaders { - private readonly IFileSystem _fileSystem; private readonly ILogger _logger; private readonly string _path; private readonly Dictionary _outputHeaders; - const int StreamCopyToBufferSize = 81920; - - public long StartPosition { get; set; } public bool AllowEndOfFile = true; private readonly IDirectStreamProvider _directStreamProvider; private IStreamHelper _streamHelper; - public ProgressiveFileCopier(IFileSystem fileSystem, IStreamHelper streamHelper, string path, Dictionary outputHeaders, ILogger logger) + public ProgressiveFileCopier(IStreamHelper streamHelper, string path, Dictionary outputHeaders, ILogger logger) { - _fileSystem = fileSystem; _path = path; _outputHeaders = outputHeaders; _logger = logger; @@ -43,18 +39,6 @@ namespace MediaBrowser.Api.LiveTv public IDictionary Headers => _outputHeaders; - private Stream GetInputStream(bool allowAsyncFileRead) - { - var fileOpenOptions = FileOpenOptions.SequentialScan; - - if (allowAsyncFileRead) - { - fileOpenOptions |= FileOpenOptions.Asynchronous; - } - - return _fileSystem.GetFileStream(_path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, fileOpenOptions); - } - public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken) { if (_directStreamProvider != null) @@ -63,28 +47,23 @@ namespace MediaBrowser.Api.LiveTv return; } - var eofCount = 0; + var fileOptions = FileOptions.SequentialScan; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - var allowAsyncFileRead = true; - - using (var inputStream = GetInputStream(allowAsyncFileRead)) + if (Environment.OSVersion.Platform != PlatformID.Win32NT) { - if (StartPosition > 0) - { - inputStream.Position = StartPosition; - } + fileOptions |= FileOptions.Asynchronous; + } + using (var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, fileOptions)) + { var emptyReadLimit = AllowEndOfFile ? 20 : 100; - + var eofCount = 0; while (eofCount < emptyReadLimit) { int bytesRead; bytesRead = await _streamHelper.CopyToAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false); - //var position = fs.Position; - //_logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - if (bytesRead == 0) { eofCount++; diff --git a/MediaBrowser.Model/Net/HttpResponse.cs b/MediaBrowser.Model/Net/HttpResponse.cs deleted file mode 100644 index 286b1c0afd..0000000000 --- a/MediaBrowser.Model/Net/HttpResponse.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; - -namespace MediaBrowser.Model.Net -{ - public class HttpResponse : IDisposable - { - /// - /// Gets or sets the type of the content. - /// - /// The type of the content. - public string ContentType { get; set; } - - /// - /// Gets or sets the response URL. - /// - /// The response URL. - public string ResponseUrl { get; set; } - - /// - /// Gets or sets the content. - /// - /// The content. - public Stream Content { get; set; } - - /// - /// Gets or sets the status code. - /// - /// The status code. - public HttpStatusCode StatusCode { get; set; } - - /// - /// Gets or sets the length of the content. - /// - /// The length of the content. - public long? ContentLength { get; set; } - - /// - /// Gets or sets the headers. - /// - /// The headers. - public Dictionary Headers { get; set; } - - private readonly IDisposable _disposable; - - public HttpResponse(IDisposable disposable) - { - _disposable = disposable; - } - public HttpResponse() - { - } - - public void Dispose() - { - if (_disposable != null) - { - _disposable.Dispose(); - } - } - } -} diff --git a/MediaBrowser.Model/Net/IAcceptSocket.cs b/MediaBrowser.Model/Net/IAcceptSocket.cs deleted file mode 100644 index 2b21d3e660..0000000000 --- a/MediaBrowser.Model/Net/IAcceptSocket.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Net -{ - public class SocketCreateException : Exception - { - public SocketCreateException(string errorCode, Exception originalException) - : base(errorCode, originalException) - { - ErrorCode = errorCode; - } - - public string ErrorCode { get; private set; } - } -} -- cgit v1.2.3 From bf43dc00bb193fb4c7aca882aadbe086ed5a1815 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Mar 2019 22:32:52 +0100 Subject: More warning fixes --- Emby.Server.Implementations/ApplicationHost.cs | 209 +++++++------- .../Diagnostics/CommonProcess.cs | 72 +++-- .../Library/LibraryManager.cs | 319 +++++++++++---------- .../Localization/LocalizationManager.cs | 37 +-- .../Session/SessionManager.cs | 244 +++++++++------- .../Sorting/AlbumArtistComparer.cs | 2 +- .../Sorting/SeriesSortNameComparer.cs | 14 +- MediaBrowser.Api/BaseApiService.cs | 35 +-- MediaBrowser.Api/UserLibrary/ArtistsService.cs | 10 - MediaBrowser.Providers/Manager/ProviderManager.cs | 39 +-- .../Music/MusicBrainzAlbumProvider.cs | 24 +- .../Music/MusicBrainzArtistProvider.cs | 18 +- 12 files changed, 534 insertions(+), 489 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 1810064e69..94eaffa479 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -34,7 +34,6 @@ using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Library; using Emby.Server.Implementations.LiveTv; using Emby.Server.Implementations.Localization; -using Emby.Server.Implementations.Middleware; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.ScheduledTasks; @@ -161,7 +160,7 @@ namespace Emby.Server.Implementations public event EventHandler> ApplicationUpdated; /// - /// Gets or sets a value indicating whether this instance has changes that require the entire application to restart. + /// Gets a value indicating whether this instance has changes that require the entire application to restart. /// /// true if this instance has pending application restart; otherwise, false. public bool HasPendingRestart { get; private set; } @@ -175,7 +174,7 @@ namespace Emby.Server.Implementations protected ILogger Logger { get; set; } /// - /// Gets or sets the plugins. + /// Gets the plugins. /// /// The plugins. public IPlugin[] Plugins { get; protected set; } @@ -187,13 +186,13 @@ namespace Emby.Server.Implementations public ILoggerFactory LoggerFactory { get; protected set; } /// - /// Gets the application paths. + /// Gets or sets the application paths. /// /// The application paths. protected ServerApplicationPaths ApplicationPaths { get; set; } /// - /// Gets all concrete types. + /// Gets or sets all concrete types. /// /// All concrete types. public Type[] AllConcreteTypes { get; protected set; } @@ -201,7 +200,7 @@ namespace Emby.Server.Implementations /// /// The disposable parts /// - protected readonly List DisposableParts = new List(); + protected readonly List _disposableParts = new List(); /// /// Gets the configuration manager. @@ -240,27 +239,33 @@ namespace Emby.Server.Implementations /// /// The user manager. public IUserManager UserManager { get; set; } + /// /// Gets or sets the library manager. /// /// The library manager. internal ILibraryManager LibraryManager { get; set; } + /// /// Gets or sets the directory watchers. /// /// The directory watchers. private ILibraryMonitor LibraryMonitor { get; set; } + /// /// Gets or sets the provider manager. /// /// The provider manager. private IProviderManager ProviderManager { get; set; } + /// /// Gets or sets the HTTP server. /// /// The HTTP server. private IHttpServer HttpServer { get; set; } + private IDtoService DtoService { get; set; } + public IImageProcessor ImageProcessor { get; set; } /// @@ -268,6 +273,7 @@ namespace Emby.Server.Implementations /// /// The media encoder. private IMediaEncoder MediaEncoder { get; set; } + private ISubtitleEncoder SubtitleEncoder { get; set; } private ISessionManager SessionManager { get; set; } @@ -277,6 +283,7 @@ namespace Emby.Server.Implementations public LocalizationManager LocalizationManager { get; set; } private IEncodingManager EncodingManager { get; set; } + private IChannelManager ChannelManager { get; set; } /// @@ -284,20 +291,29 @@ namespace Emby.Server.Implementations /// /// The user data repository. private IUserDataManager UserDataManager { get; set; } + private IUserRepository UserRepository { get; set; } + internal SqliteItemRepository ItemRepository { get; set; } private INotificationManager NotificationManager { get; set; } + private ISubtitleManager SubtitleManager { get; set; } + private IChapterManager ChapterManager { get; set; } + private IDeviceManager DeviceManager { get; set; } internal IUserViewManager UserViewManager { get; set; } private IAuthenticationRepository AuthenticationRepository { get; set; } + private ITVSeriesManager TVSeriesManager { get; set; } + private ICollectionManager CollectionManager { get; set; } + private IMediaSourceManager MediaSourceManager { get; set; } + private IPlaylistManager PlaylistManager { get; set; } private readonly IConfiguration _configuration; @@ -313,28 +329,37 @@ namespace Emby.Server.Implementations /// /// The zip client. protected IZipClient ZipClient { get; private set; } + protected IHttpResultFactory HttpResultFactory { get; private set; } + protected IAuthService AuthService { get; private set; } - public IStartupOptions StartupOptions { get; private set; } + public IStartupOptions StartupOptions { get; } internal IImageEncoder ImageEncoder { get; private set; } protected IProcessFactory ProcessFactory { get; private set; } + protected ICryptoProvider CryptographyProvider = new CryptographyProvider(); protected readonly IXmlSerializer XmlSerializer; protected ISocketFactory SocketFactory { get; private set; } + protected ITaskManager TaskManager { get; private set; } + public IHttpClient HttpClient { get; private set; } + protected INetworkManager NetworkManager { get; set; } + public IJsonSerializer JsonSerializer { get; private set; } + protected IIsoManager IsoManager { get; private set; } /// /// Initializes a new instance of the class. /// - public ApplicationHost(ServerApplicationPaths applicationPaths, + public ApplicationHost( + ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, IFileSystem fileSystem, @@ -395,7 +420,7 @@ namespace Emby.Server.Implementations _validAddressResults.Clear(); } - public string ApplicationVersion => typeof(ApplicationHost).Assembly.GetName().Version.ToString(3); + public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3); /// /// Gets the current application user agent @@ -404,13 +429,16 @@ namespace Emby.Server.Implementations public string ApplicationUserAgent => Name.Replace(' ','-') + "/" + ApplicationVersion; private string _productName; + /// /// Gets the current application name /// /// The application name. - public string ApplicationProductName => _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName); + public string ApplicationProductName + => _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName); private DeviceId _deviceId; + public string SystemId { get @@ -441,15 +469,15 @@ namespace Emby.Server.Implementations /// /// Creates an instance of type and resolves all constructor dependencies /// - /// The type. - /// System.Object. + /// /// The type + /// T public T CreateInstance() => ActivatorUtilities.CreateInstance(_serviceProvider); /// /// Creates the instance safe. /// - /// The type information. + /// The type. /// System.Object. protected object CreateInstanceSafe(Type type) { @@ -468,14 +496,14 @@ namespace Emby.Server.Implementations /// /// Resolves this instance. /// - /// + /// The type /// ``0. public T Resolve() => _serviceProvider.GetService(); /// /// Gets the export types. /// - /// + /// The type /// IEnumerable{Type}. public IEnumerable GetExportTypes() { @@ -487,22 +515,22 @@ namespace Emby.Server.Implementations /// /// Gets the exports. /// - /// + /// The type /// if set to true [manage lifetime]. /// IEnumerable{``0}. public IEnumerable GetExports(bool manageLifetime = true) { var parts = GetExportTypes() - .Select(x => CreateInstanceSafe(x)) + .Select(CreateInstanceSafe) .Where(i => i != null) .Cast() .ToList(); // Convert to list so this isn't executed for each iteration if (manageLifetime) { - lock (DisposableParts) + lock (_disposableParts) { - DisposableParts.AddRange(parts.OfType()); + _disposableParts.AddRange(parts.OfType()); } } @@ -522,29 +550,20 @@ namespace Emby.Server.Implementations MediaEncoder.SetFFmpegPath(); - //if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath)) - //{ - // if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted) - // { - // ServerConfigurationManager.Configuration.IsStartupWizardCompleted = false; - // ServerConfigurationManager.SaveConfiguration(); - // } - //} - Logger.LogInformation("ServerId: {0}", SystemId); - var entryPoints = GetExports(); + var entryPoints = GetExports().ToList(); var stopWatch = new Stopwatch(); stopWatch.Start(); - await Task.WhenAll(StartEntryPoints(entryPoints, true)); + await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false); Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); HttpServer.GlobalResponse = null; stopWatch.Restart(); - await Task.WhenAll(StartEntryPoints(entryPoints, false)); + await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); stopWatch.Stop(); } @@ -594,7 +613,7 @@ namespace Emby.Server.Implementations SetHttpLimit(); - await RegisterResources(serviceCollection); + await RegisterResources(serviceCollection).ConfigureAwait(false); FindParts(); @@ -631,7 +650,7 @@ namespace Emby.Server.Implementations }) .Build(); - await host.StartAsync(); + await host.StartAsync().ConfigureAwait(false); } private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) @@ -729,8 +748,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ServerConfigurationManager); - LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory); - await LocalizationManager.LoadAll(); + LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory); + await LocalizationManager.LoadAll().ConfigureAwait(false); serviceCollection.AddSingleton(LocalizationManager); serviceCollection.AddSingleton(new BdInfoExaminer(FileSystemManager)); @@ -769,16 +788,19 @@ namespace Emby.Server.Implementations CertificateInfo = GetCertificateInfo(true); Certificate = GetCertificate(CertificateInfo); - HttpServer = new HttpListenerHost(this, + HttpServer = new HttpListenerHost( + this, LoggerFactory, ServerConfigurationManager, _configuration, NetworkManager, JsonSerializer, XmlSerializer, - CreateHttpListener()); + CreateHttpListener()) + { + GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading") + }; - HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); serviceCollection.AddSingleton(HttpServer); ImageProcessor = GetImageProcessor(); @@ -933,7 +955,7 @@ namespace Emby.Server.Implementations var password = string.IsNullOrWhiteSpace(info.Password) ? null : info.Password; var localCert = new X509Certificate2(certificateLocation, password); - //localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; + // localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; if (!localCert.HasPrivateKey) { Logger.LogError("No private key included in SSL cert {CertificateLocation}.", certificateLocation); @@ -1034,13 +1056,15 @@ namespace Emby.Server.Implementations HttpServer.Init(GetExports(false), GetExports(), GetUrlPrefixes()); - LibraryManager.AddParts(GetExports(), + LibraryManager.AddParts( + GetExports(), GetExports(), GetExports(), GetExports(), GetExports()); - ProviderManager.AddParts(GetExports(), + ProviderManager.AddParts( + GetExports(), GetExports(), GetExports(), GetExports(), @@ -1121,6 +1145,7 @@ namespace Emby.Server.Implementations } private CertificateInfo CertificateInfo { get; set; } + protected X509Certificate2 Certificate { get; private set; } private IEnumerable GetUrlPrefixes() @@ -1131,7 +1156,7 @@ namespace Emby.Server.Implementations { var prefixes = new List { - "http://"+i+":" + HttpPort + "/" + "http://" + i + ":" + HttpPort + "/" }; if (CertificateInfo != null) @@ -1160,30 +1185,12 @@ namespace Emby.Server.Implementations // Generate self-signed cert var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns); var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N") + ".pfx"); - var password = "embycert"; - - //if (generateCertificate) - //{ - // if (!File.Exists(certPath)) - // { - // FileSystemManager.CreateDirectory(FileSystemManager.GetDirectoryName(certPath)); - - // try - // { - // CertificateGenerator.CreateSelfSignCertificatePfx(certPath, certHost, password, Logger); - // } - // catch (Exception ex) - // { - // Logger.LogError(ex, "Error creating ssl cert"); - // return null; - // } - // } - //} + const string Password = "embycert"; return new CertificateInfo { Path = certPath, - Password = password + Password = Password }; } @@ -1218,9 +1225,9 @@ namespace Emby.Server.Implementations requiresRestart = true; } - var currentCertPath = CertificateInfo == null ? null : CertificateInfo.Path; + var currentCertPath = CertificateInfo?.Path; var newCertInfo = GetCertificateInfo(false); - var newCertPath = newCertInfo == null ? null : newCertInfo.Path; + var newCertPath = newCertInfo?.Path; if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase)) { @@ -1353,6 +1360,7 @@ namespace Emby.Server.Implementations /// /// Gets the system status. /// + /// The cancellation token /// SystemInfo. public async Task GetSystemInfo(CancellationToken cancellationToken) { @@ -1447,19 +1455,19 @@ namespace Emby.Server.Implementations public async Task GetWanApiUrl(CancellationToken cancellationToken) { - const string url = "http://ipv4.icanhazip.com"; + const string Url = "http://ipv4.icanhazip.com"; try { using (var response = await HttpClient.Get(new HttpRequestOptions { - Url = url, + Url = Url, LogErrorResponseBody = false, LogErrors = false, LogRequest = false, TimeoutMs = 10000, BufferContent = false, CancellationToken = cancellationToken - })) + }).ConfigureAwait(false)) { return GetLocalApiUrl(response.ReadToEnd().Trim()); } @@ -1547,10 +1555,12 @@ namespace Emby.Server.Implementations { return result; } + return null; } private readonly ConcurrentDictionary _validAddressResults = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private async Task IsIpAddressValidAsync(IpAddressInfo address, CancellationToken cancellationToken) { if (address.Equals(IpAddressInfo.Loopback) || @@ -1567,26 +1577,26 @@ namespace Emby.Server.Implementations return cachedResult; } - var logPing = false; - #if DEBUG - logPing = true; + const bool LogPing = true; +#else + const bool LogPing = false; #endif try { - using (var response = await HttpClient.SendAsync(new HttpRequestOptions - { - Url = apiUrl, - LogErrorResponseBody = false, - LogErrors = logPing, - LogRequest = logPing, - TimeoutMs = 5000, - BufferContent = false, - - CancellationToken = cancellationToken - - }, "POST").ConfigureAwait(false)) + using (var response = await HttpClient.SendAsync( + new HttpRequestOptions + { + Url = apiUrl, + LogErrorResponseBody = false, + LogErrors = LogPing, + LogRequest = LogPing, + TimeoutMs = 5000, + BufferContent = false, + + CancellationToken = cancellationToken + }, "POST").ConfigureAwait(false)) { using (var reader = new StreamReader(response.Content)) { @@ -1651,6 +1661,7 @@ namespace Emby.Server.Implementations public event EventHandler HasUpdateAvailableChanged; private bool _hasUpdateAvailable; + public bool HasUpdateAvailable { get => _hasUpdateAvailable; @@ -1711,7 +1722,7 @@ namespace Emby.Server.Implementations var process = ProcessFactory.Create(new ProcessOptions { FileName = url, - //EnableRaisingEvents = true, + EnableRaisingEvents = true, UseShellExecute = true, ErrorDialog = false }); @@ -1746,26 +1757,25 @@ namespace Emby.Server.Implementations { Logger.LogInformation("Application has been updated to version {0}", package.versionStr); - ApplicationUpdated?.Invoke(this, new GenericEventArgs - { - Argument = package - }); + ApplicationUpdated?.Invoke( + this, + new GenericEventArgs() + { + Argument = package + }); NotifyPendingRestart(); } - private bool _disposed; + private bool _disposed = false; + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { - if (!_disposed) - { - _disposed = true; - - Dispose(true); - } + Dispose(true); + GC.SuppressFinalize(this); } /// @@ -1774,14 +1784,19 @@ namespace Emby.Server.Implementations /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { + if (_disposed) + { + return; + } + if (dispose) { var type = GetType(); Logger.LogInformation("Disposing {Type}", type.Name); - var parts = DisposableParts.Distinct().Where(i => i.GetType() != type).ToList(); - DisposableParts.Clear(); + var parts = _disposableParts.Distinct().Where(i => i.GetType() != type).ToList(); + _disposableParts.Clear(); foreach (var part in parts) { @@ -1797,6 +1812,8 @@ namespace Emby.Server.Implementations } } } + + _disposed = true; } } diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index 2c4ef170dc..175a8f3ce4 100644 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs @@ -9,14 +9,14 @@ namespace Emby.Server.Implementations.Diagnostics { public class CommonProcess : IProcess { - public event EventHandler Exited; - - private readonly ProcessOptions _options; private readonly Process _process; + private bool _disposed = false; + private bool _hasExited; + public CommonProcess(ProcessOptions options) { - _options = options; + StartInfo = options; var startInfo = new ProcessStartInfo { @@ -27,10 +27,10 @@ namespace Emby.Server.Implementations.Diagnostics CreateNoWindow = options.CreateNoWindow, RedirectStandardError = options.RedirectStandardError, RedirectStandardInput = options.RedirectStandardInput, - RedirectStandardOutput = options.RedirectStandardOutput + RedirectStandardOutput = options.RedirectStandardOutput, + ErrorDialog = options.ErrorDialog }; - startInfo.ErrorDialog = options.ErrorDialog; if (options.IsHidden) { @@ -45,11 +45,22 @@ namespace Emby.Server.Implementations.Diagnostics if (options.EnableRaisingEvents) { _process.EnableRaisingEvents = true; - _process.Exited += _process_Exited; + _process.Exited += OnProcessExited; } } - private bool _hasExited; + public event EventHandler Exited; + + public ProcessOptions StartInfo { get; } + + public StreamWriter StandardInput => _process.StandardInput; + + public StreamReader StandardError => _process.StandardError; + + public StreamReader StandardOutput => _process.StandardOutput; + + public int ExitCode => _process.ExitCode; + private bool HasExited { get @@ -72,25 +83,6 @@ namespace Emby.Server.Implementations.Diagnostics } } - private void _process_Exited(object sender, EventArgs e) - { - _hasExited = true; - if (Exited != null) - { - Exited(this, e); - } - } - - public ProcessOptions StartInfo => _options; - - public StreamWriter StandardInput => _process.StandardInput; - - public StreamReader StandardError => _process.StandardError; - - public StreamReader StandardOutput => _process.StandardOutput; - - public int ExitCode => _process.ExitCode; - public void Start() { _process.Start(); @@ -108,7 +100,7 @@ namespace Emby.Server.Implementations.Diagnostics public Task WaitForExitAsync(int timeMs) { - //Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true. + // Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true. if (HasExited) { @@ -130,7 +122,29 @@ namespace Emby.Server.Implementations.Diagnostics public void Dispose() { - _process?.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _process?.Dispose(); + } + + _disposed = true; + } + + private void OnProcessExited(object sender, EventArgs e) + { + _hasExited = true; + Exited?.Invoke(this, e); } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6591d54c56..1673e37776 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -58,22 +58,23 @@ namespace Emby.Server.Implementations.Library private ILibraryPostScanTask[] PostscanTasks { get; set; } /// - /// Gets the intro providers. + /// Gets or sets the intro providers. /// /// The intro providers. private IIntroProvider[] IntroProviders { get; set; } /// - /// Gets the list of entity resolution ignore rules + /// Gets or sets the list of entity resolution ignore rules /// /// The entity resolution ignore rules. private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } /// - /// Gets the list of currently registered entity resolvers + /// Gets or sets the list of currently registered entity resolvers /// /// The entity resolvers enumerable. private IItemResolver[] EntityResolvers { get; set; } + private IMultiItemResolver[] MultiItemResolvers { get; set; } /// @@ -83,7 +84,7 @@ namespace Emby.Server.Implementations.Library private IBaseItemComparer[] Comparers { get; set; } /// - /// Gets the active item repository + /// Gets or sets the active item repository /// /// The item repository. public IItemRepository ItemRepository { get; set; } @@ -133,12 +134,14 @@ namespace Emby.Server.Implementations.Library private readonly Func _providerManagerFactory; private readonly Func _userviewManager; public bool IsScanRunning { get; private set; } + private IServerApplicationHost _appHost; /// /// The _library items cache /// private readonly ConcurrentDictionary _libraryItemsCache; + /// /// Gets the library items cache. /// @@ -150,7 +153,8 @@ namespace Emby.Server.Implementations.Library /// /// Initializes a new instance of the class. /// - /// The logger. + /// The application host + /// The logger factory. /// The task manager. /// The user manager. /// The configuration manager. @@ -167,6 +171,7 @@ namespace Emby.Server.Implementations.Library Func providerManagerFactory, Func userviewManager) { + _appHost = appHost; _logger = loggerFactory.CreateLogger(nameof(LibraryManager)); _taskManager = taskManager; _userManager = userManager; @@ -176,7 +181,7 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; _providerManagerFactory = providerManagerFactory; _userviewManager = userviewManager; - _appHost = appHost; + _libraryItemsCache = new ConcurrentDictionary(); ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -191,8 +196,9 @@ namespace Emby.Server.Implementations.Library /// The resolvers. /// The intro providers. /// The item comparers. - /// The postscan tasks. - public void AddParts(IEnumerable rules, + /// The post scan tasks. + public void AddParts( + IEnumerable rules, IEnumerable resolvers, IEnumerable introProviders, IEnumerable itemComparers, @@ -203,24 +209,19 @@ namespace Emby.Server.Implementations.Library MultiItemResolvers = EntityResolvers.OfType().ToArray(); IntroProviders = introProviders.ToArray(); Comparers = itemComparers.ToArray(); - - PostscanTasks = postscanTasks.OrderBy(i => - { - var hasOrder = i as IHasOrder; - - return hasOrder == null ? 0 : hasOrder.Order; - - }).ToArray(); + PostscanTasks = postscanTasks.ToArray(); } /// /// The _root folder /// private volatile AggregateFolder _rootFolder; + /// /// The _root folder sync lock /// private readonly object _rootFolderSyncLock = new object(); + /// /// Gets the root folder. /// @@ -239,11 +240,13 @@ namespace Emby.Server.Implementations.Library } } } + return _rootFolder; } } private bool _wizardCompleted; + /// /// Records the configuration values. /// @@ -258,7 +261,7 @@ namespace Emby.Server.Implementations.Library /// /// The sender. /// The instance containing the event data. - void ConfigurationUpdated(object sender, EventArgs e) + private void ConfigurationUpdated(object sender, EventArgs e) { var config = ConfigurationManager.Configuration; @@ -335,12 +338,14 @@ namespace Emby.Server.Implementations.Library // channel no longer installed } } + options.DeleteFileLocation = false; } if (item is LiveTvProgram) { - _logger.LogDebug("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + _logger.LogDebug( + "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", item.GetType().Name, item.Name ?? "Unknown name", item.Path ?? string.Empty, @@ -348,7 +353,8 @@ namespace Emby.Server.Implementations.Library } else { - _logger.LogInformation("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + _logger.LogInformation( + "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", item.GetType().Name, item.Name ?? "Unknown name", item.Path ?? string.Empty, @@ -488,12 +494,13 @@ namespace Emby.Server.Implementations.Library { throw new ArgumentNullException(nameof(key)); } + if (type == null) { throw new ArgumentNullException(nameof(type)); } - if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath)) + if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal)) { // Try to normalize paths located underneath program-data in an attempt to make them more portable key = key.Substring(ConfigurationManager.ApplicationPaths.ProgramDataPath.Length) @@ -511,13 +518,11 @@ namespace Emby.Server.Implementations.Library return key.GetMD5(); } - public BaseItem ResolvePath(FileSystemMetadata fileInfo, - Folder parent = null) - { - return ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent); - } + public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null) + => ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent); - private BaseItem ResolvePath(FileSystemMetadata fileInfo, + private BaseItem ResolvePath( + FileSystemMetadata fileInfo, IDirectoryService directoryService, IItemResolver[] resolvers, Folder parent = null, @@ -572,7 +577,7 @@ namespace Emby.Server.Implementations.Library { _logger.LogError(ex, "Error in GetFilteredFileSystemEntries isPhysicalRoot: {0} IsVf: {1}", isPhysicalRoot, isVf); - files = new FileSystemMetadata[] { }; + files = Array.Empty(); } else { @@ -600,13 +605,7 @@ namespace Emby.Server.Implementations.Library } public bool IgnoreFile(FileSystemMetadata file, BaseItem parent) - { - if (EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent))) - { - return true; - } - return false; - } + => EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent)); public List NormalizeRootPathList(IEnumerable paths) { @@ -646,7 +645,8 @@ namespace Emby.Server.Implementations.Library return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers); } - public IEnumerable ResolvePaths(IEnumerable files, + public IEnumerable ResolvePaths( + IEnumerable files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, @@ -672,6 +672,7 @@ namespace Emby.Server.Implementations.Library { ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService); } + items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions)); return items; } @@ -681,7 +682,8 @@ namespace Emby.Server.Implementations.Library return ResolveFileList(fileList, directoryService, parent, collectionType, resolvers, libraryOptions); } - private IEnumerable ResolveFileList(IEnumerable fileList, + private IEnumerable ResolveFileList( + IEnumerable fileList, IDirectoryService directoryService, Folder parent, string collectionType, @@ -766,6 +768,7 @@ namespace Emby.Server.Implementations.Library private volatile UserRootFolder _userRootFolder; private readonly object _syncLock = new object(); + public Folder GetUserRootFolder() { if (_userRootFolder == null) @@ -810,8 +813,6 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(path)); } - //_logger.LogInformation("FindByPath {0}", path); - var query = new InternalItemsQuery { Path = path, @@ -885,7 +886,6 @@ namespace Emby.Server.Implementations.Library /// /// The value. /// Task{Year}. - /// public Year GetYear(int value) { if (value <= 0) @@ -1027,20 +1027,25 @@ namespace Emby.Server.Implementations.Library private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken) { - var rootChildren = RootFolder.Children.ToList(); - rootChildren = GetUserRootFolder().Children.ToList(); - await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); // Start by just validating the children of the root, but go no further - await RootFolder.ValidateChildren(new SimpleProgress(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false); + await RootFolder.ValidateChildren( + new SimpleProgress(), + cancellationToken, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), + recursive: false).ConfigureAwait(false); await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false); - await GetUserRootFolder().ValidateChildren(new SimpleProgress(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false).ConfigureAwait(false); + await GetUserRootFolder().ValidateChildren( + new SimpleProgress(), + cancellationToken, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), + recursive: false).ConfigureAwait(false); // Quickly scan CollectionFolders for changes - foreach (var folder in GetUserRootFolder().Children.OfType().ToList()) + foreach (var folder in GetUserRootFolder().Children.OfType()) { await folder.RefreshMetadata(cancellationToken).ConfigureAwait(false); } @@ -1204,7 +1209,7 @@ namespace Emby.Server.Implementations.Library private string GetCollectionType(string path) { return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false) - .Select(i => Path.GetFileNameWithoutExtension(i)) + .Select(Path.GetFileNameWithoutExtension) .FirstOrDefault(i => !string.IsNullOrEmpty(i)); } @@ -1218,7 +1223,7 @@ namespace Emby.Server.Implementations.Library { if (id == Guid.Empty) { - throw new ArgumentException(nameof(id), "Guid can't be empty"); + throw new ArgumentException("Guid can't be empty", nameof(id)); } if (LibraryItemsCache.TryGetValue(id, out BaseItem item)) @@ -1386,17 +1391,7 @@ namespace Emby.Server.Implementations.Library var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList(); - if (parents.All(i => - { - if (i is ICollectionFolder || i is UserView) - { - return true; - } - - //_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name); - return false; - - })) + if (parents.All(i => i is ICollectionFolder || i is UserView)) { // Optimize by querying against top level views query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); @@ -1452,17 +1447,7 @@ namespace Emby.Server.Implementations.Library private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List parents) { - if (parents.All(i => - { - if (i is ICollectionFolder || i is UserView) - { - return true; - } - - //_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name); - return false; - - })) + if (parents.All(i => i is ICollectionFolder || i is UserView)) { // Optimize by querying against top level views query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); @@ -1511,11 +1496,9 @@ namespace Emby.Server.Implementations.Library private IEnumerable GetTopParentIdsForQuery(BaseItem item, User user) { - var view = item as UserView; - - if (view != null) + if (item is UserView view) { - if (string.Equals(view.ViewType, CollectionType.LiveTv)) + if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.Ordinal)) { return new[] { view.Id }; } @@ -1528,8 +1511,10 @@ namespace Emby.Server.Implementations.Library { return GetTopParentIdsForQuery(displayParent, user); } + return Array.Empty(); } + if (!view.ParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(view.ParentId); @@ -1537,6 +1522,7 @@ namespace Emby.Server.Implementations.Library { return GetTopParentIdsForQuery(displayParent, user); } + return Array.Empty(); } @@ -1550,11 +1536,11 @@ namespace Emby.Server.Implementations.Library .Where(i => user.IsFolderGrouped(i.Id)) .SelectMany(i => GetTopParentIdsForQuery(i, user)); } + return Array.Empty(); } - var collectionFolder = item as CollectionFolder; - if (collectionFolder != null) + if (item is CollectionFolder collectionFolder) { return collectionFolder.PhysicalFolderIds; } @@ -1564,6 +1550,7 @@ namespace Emby.Server.Implementations.Library { return new[] { topParent.Id }; } + return Array.Empty(); } @@ -1760,19 +1747,16 @@ namespace Emby.Server.Implementations.Library { var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase)); - if (comparer != null) + // If it requires a user, create a new one, and assign the user + if (comparer is IUserBaseItemComparer) { - // If it requires a user, create a new one, and assign the user - if (comparer is IUserBaseItemComparer) - { - var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType()); + var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType()); - userComparer.User = user; - userComparer.UserManager = _userManager; - userComparer.UserDataRepository = _userDataRepository; + userComparer.User = user; + userComparer.UserManager = _userManager; + userComparer.UserDataRepository = _userDataRepository; - return userComparer; - } + return userComparer; } return comparer; @@ -1783,7 +1767,6 @@ namespace Emby.Server.Implementations.Library /// /// The item. /// The parent item. - /// Task. public void CreateItem(BaseItem item, BaseItem parent) { CreateItems(new[] { item }, parent, CancellationToken.None); @@ -1793,20 +1776,23 @@ namespace Emby.Server.Implementations.Library /// Creates the items. /// /// The items. + /// The parent item /// The cancellation token. - /// Task. public void CreateItems(IEnumerable items, BaseItem parent, CancellationToken cancellationToken) { - ItemRepository.SaveItems(items, cancellationToken); + // Don't iterate multiple times + var itemsList = items.ToList(); + + ItemRepository.SaveItems(itemsList, cancellationToken); - foreach (var item in items) + foreach (var item in itemsList) { RegisterItem(item); } if (ItemAdded != null) { - foreach (var item in items) + foreach (var item in itemsList) { // With the live tv guide this just creates too much noise if (item.SourceType != SourceType.Library) @@ -1816,11 +1802,13 @@ namespace Emby.Server.Implementations.Library try { - ItemAdded(this, new ItemChangeEventArgs - { - Item = item, - Parent = parent ?? item.GetParent() - }); + ItemAdded( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent ?? item.GetParent() + }); } catch (Exception ex) { @@ -1842,7 +1830,10 @@ namespace Emby.Server.Implementations.Library /// public void UpdateItems(IEnumerable items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - foreach (var item in items) + // Don't iterate multiple times + var itemsList = items.ToList(); + + foreach (var item in itemsList) { if (item.IsFileProtocol) { @@ -1854,14 +1845,11 @@ namespace Emby.Server.Implementations.Library RegisterItem(item); } - //var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name; - //_logger.LogDebug("Saving {0} to database.", logName); - - ItemRepository.SaveItems(items, cancellationToken); + ItemRepository.SaveItems(itemsList, cancellationToken); if (ItemUpdated != null) { - foreach (var item in items) + foreach (var item in itemsList) { // With the live tv guide this just creates too much noise if (item.SourceType != SourceType.Library) @@ -1871,12 +1859,14 @@ namespace Emby.Server.Implementations.Library try { - ItemUpdated(this, new ItemChangeEventArgs - { - Item = item, - Parent = parent, - UpdateReason = updateReason - }); + ItemUpdated( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent, + UpdateReason = updateReason + }); } catch (Exception ex) { @@ -1890,9 +1880,9 @@ namespace Emby.Server.Implementations.Library /// Updates the item. /// /// The item. + /// The parent item. /// The update reason. /// The cancellation token. - /// Task. public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { UpdateItems(new [] { item }, parent, updateReason, cancellationToken); @@ -1902,17 +1892,20 @@ namespace Emby.Server.Implementations.Library /// Reports the item removed. /// /// The item. + /// The parent item. public void ReportItemRemoved(BaseItem item, BaseItem parent) { if (ItemRemoved != null) { try { - ItemRemoved(this, new ItemChangeEventArgs - { - Item = item, - Parent = parent - }); + ItemRemoved( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent + }); } catch (Exception ex) { @@ -2038,8 +2031,7 @@ namespace Emby.Server.Implementations.Library public string GetConfiguredContentType(BaseItem item, bool inheritConfiguredPath) { - var collectionFolder = item as ICollectionFolder; - if (collectionFolder != null) + if (item is ICollectionFolder collectionFolder) { return collectionFolder.CollectionType; } @@ -2049,13 +2041,11 @@ namespace Emby.Server.Implementations.Library private string GetContentTypeOverride(string path, bool inherit) { - var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) || (inherit && !string.IsNullOrEmpty(i.Name) && _fileSystem.ContainsSubPath(i.Name, path))); - if (nameValuePair != null) - { - return nameValuePair.Value; - } - - return null; + var nameValuePair = ConfigurationManager.Configuration.ContentTypes + .FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) + || (inherit && !string.IsNullOrEmpty(i.Name) + && _fileSystem.ContainsSubPath(i.Name, path))); + return nameValuePair?.Value; } private string GetTopFolderContentType(BaseItem item) @@ -2072,6 +2062,7 @@ namespace Emby.Server.Implementations.Library { break; } + item = parent; } @@ -2083,9 +2074,9 @@ namespace Emby.Server.Implementations.Library } private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); - //private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromMinutes(1); - public UserView GetNamedView(User user, + public UserView GetNamedView( + User user, string name, string viewType, string sortName) @@ -2093,13 +2084,15 @@ namespace Emby.Server.Implementations.Library return GetNamedView(user, name, Guid.Empty, viewType, sortName); } - public UserView GetNamedView(string name, + public UserView GetNamedView( + string name, string viewType, string sortName) { - var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, - "views", - _fileSystem.GetValidFilename(viewType)); + var path = Path.Combine( + ConfigurationManager.ApplicationPaths.InternalMetadataPath, + "views", + _fileSystem.GetValidFilename(viewType)); var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView)); @@ -2135,7 +2128,8 @@ namespace Emby.Server.Implementations.Library return item; } - public UserView GetNamedView(User user, + public UserView GetNamedView( + User user, string name, Guid parentId, string viewType, @@ -2164,10 +2158,10 @@ namespace Emby.Server.Implementations.Library Name = name, ViewType = viewType, ForcedSortName = sortName, - UserId = user.Id + UserId = user.Id, + DisplayParentId = parentId }; - item.DisplayParentId = parentId; CreateItem(item, null); @@ -2184,20 +2178,24 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - // Need to force save to increment DateLastSaved - ForceSave = true + _providerManagerFactory().QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true - }, RefreshPriority.Normal); + }, + RefreshPriority.Normal); } return item; } - public UserView GetShadowView(BaseItem parent, - string viewType, - string sortName) + public UserView GetShadowView( + BaseItem parent, + string viewType, + string sortName) { if (parent == null) { @@ -2248,18 +2246,21 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - // Need to force save to increment DateLastSaved - ForceSave = true - - }, RefreshPriority.Normal); + _providerManagerFactory().QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true + }, + RefreshPriority.Normal); } return item; } - public UserView GetNamedView(string name, + public UserView GetNamedView( + string name, Guid parentId, string viewType, string sortName, @@ -2322,17 +2323,21 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - // Need to force save to increment DateLastSaved - ForceSave = true - }, RefreshPriority.Normal); + _providerManagerFactory().QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true + }, + RefreshPriority.Normal); } return item; } - public void AddExternalSubtitleStreams(List streams, + public void AddExternalSubtitleStreams( + List streams, string videoPath, string[] files) { @@ -2436,6 +2441,7 @@ namespace Emby.Server.Implementations.Library { changed = true; } + episode.IndexNumber = episodeInfo.EpisodeNumber; } @@ -2445,6 +2451,7 @@ namespace Emby.Server.Implementations.Library { changed = true; } + episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber; } @@ -2454,6 +2461,7 @@ namespace Emby.Server.Implementations.Library { changed = true; } + episode.ParentIndexNumber = episodeInfo.SeasonNumber; } } @@ -2483,6 +2491,7 @@ namespace Emby.Server.Implementations.Library private NamingOptions _namingOptions; private string[] _videoFileExtensions; + private NamingOptions GetNamingOptionsInternal() { if (_namingOptions == null) @@ -2679,7 +2688,7 @@ namespace Emby.Server.Implementations.Library var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase); var changed = false; - if (!string.Equals(newPath, path)) + if (!string.Equals(newPath, path, StringComparison.Ordinal)) { if (to.IndexOf('/') != -1) { @@ -2803,6 +2812,7 @@ namespace Emby.Server.Implementations.Library { continue; } + throw; } } @@ -2907,6 +2917,7 @@ namespace Emby.Server.Implementations.Library } private const string ShortcutFileExtension = ".mblink"; + public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo) { AddMediaPathInternal(virtualFolderName, pathInfo, true); @@ -2923,7 +2934,7 @@ namespace Emby.Server.Implementations.Library if (string.IsNullOrWhiteSpace(path)) { - throw new ArgumentNullException(nameof(path)); + throw new ArgumentException(nameof(path)); } if (!Directory.Exists(path)) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 762649b716..8c49b64055 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -11,7 +11,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -35,7 +34,6 @@ namespace Emby.Server.Implementations.Localization private readonly Dictionary> _allParentalRatings = new Dictionary>(StringComparer.OrdinalIgnoreCase); - private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly; @@ -44,40 +42,38 @@ namespace Emby.Server.Implementations.Localization /// Initializes a new instance of the class. /// /// The configuration manager. - /// The file system. /// The json serializer. + /// The logger factory public LocalizationManager( IServerConfigurationManager configurationManager, - IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILoggerFactory loggerFactory) { _configurationManager = configurationManager; - _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; _logger = loggerFactory.CreateLogger(nameof(LocalizationManager)); } public async Task LoadAll() { - const string ratingsResource = "Emby.Server.Implementations.Localization.Ratings."; + const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings."; // Extract from the assembly foreach (var resource in _assembly.GetManifestResourceNames()) { - if (!resource.StartsWith(ratingsResource)) + if (!resource.StartsWith(RatingsResource, StringComparison.Ordinal)) { continue; } - string countryCode = resource.Substring(ratingsResource.Length, 2); + string countryCode = resource.Substring(RatingsResource.Length, 2); var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); using (var str = _assembly.GetManifestResourceStream(resource)) using (var reader = new StreamReader(str)) { string line; - while ((line = await reader.ReadLineAsync()) != null) + while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) { if (string.IsNullOrWhiteSpace(line)) { @@ -102,7 +98,7 @@ namespace Emby.Server.Implementations.Localization _allParentalRatings[countryCode] = dict; } - await LoadCultures(); + await LoadCultures().ConfigureAwait(false); } public string NormalizeFormKD(string text) @@ -121,14 +117,14 @@ namespace Emby.Server.Implementations.Localization { List list = new List(); - const string path = "Emby.Server.Implementations.Localization.iso6392.txt"; + const string ResourcePath = "Emby.Server.Implementations.Localization.iso6392.txt"; - using (var stream = _assembly.GetManifestResourceStream(path)) + using (var stream = _assembly.GetManifestResourceStream(ResourcePath)) using (var reader = new StreamReader(stream)) { while (!reader.EndOfStream) { - var line = await reader.ReadLineAsync(); + var line = await reader.ReadLineAsync().ConfigureAwait(false); if (string.IsNullOrWhiteSpace(line)) { @@ -154,11 +150,11 @@ namespace Emby.Server.Implementations.Localization string[] threeletterNames; if (string.IsNullOrWhiteSpace(parts[1])) { - threeletterNames = new [] { parts[0] }; + threeletterNames = new[] { parts[0] }; } else { - threeletterNames = new [] { parts[0], parts[1] }; + threeletterNames = new[] { parts[0], parts[1] }; } list.Add(new CultureDto @@ -218,6 +214,7 @@ namespace Emby.Server.Implementations.Localization /// Gets the ratings. /// /// The country code. + /// The ratings private Dictionary GetRatings(string countryCode) { _allParentalRatings.TryGetValue(countryCode, out var value); @@ -227,9 +224,12 @@ namespace Emby.Server.Implementations.Localization private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; + /// /// /// Gets the rating level. /// + /// Rating field + /// The rating level> public int? GetRatingLevel(string rating) { if (string.IsNullOrEmpty(rating)) @@ -301,6 +301,7 @@ namespace Emby.Server.Implementations.Localization { culture = _configurationManager.Configuration.UICulture; } + if (string.IsNullOrEmpty(culture)) { culture = DefaultCulture; @@ -346,8 +347,8 @@ namespace Emby.Server.Implementations.Localization var namespaceName = GetType().Namespace + "." + prefix; - await CopyInto(dictionary, namespaceName + "." + baseFilename); - await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)); + await CopyInto(dictionary, namespaceName + "." + baseFilename).ConfigureAwait(false); + await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)).ConfigureAwait(false); return dictionary; } @@ -359,7 +360,7 @@ namespace Emby.Server.Implementations.Localization // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain if (stream != null) { - var dict = await _jsonSerializer.DeserializeFromStreamAsync>(stream); + var dict = await _jsonSerializer.DeserializeFromStreamAsync>(stream).ConfigureAwait(false); foreach (var key in dict.Keys) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 03e7b26545..985748caf2 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -116,14 +116,14 @@ namespace Emby.Server.Implementations.Session _authRepo = authRepo; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; - _deviceManager.DeviceOptionsUpdated += _deviceManager_DeviceOptionsUpdated; + _deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated; } - private void _deviceManager_DeviceOptionsUpdated(object sender, GenericEventArgs> e) + private void OnDeviceManagerDeviceOptionsUpdated(object sender, GenericEventArgs> e) { foreach (var session in Sessions) { - if (string.Equals(session.DeviceId, e.Argument.Item1)) + if (string.Equals(session.DeviceId, e.Argument.Item1, StringComparison.Ordinal)) { if (!string.IsNullOrWhiteSpace(e.Argument.Item2.CustomName)) { @@ -138,11 +138,29 @@ namespace Emby.Server.Implementations.Session } } - private bool _disposed; + private bool _disposed = false; + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + // TODO: dispose stuff + } + + _deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated; + _disposed = true; - _deviceManager.DeviceOptionsUpdated -= _deviceManager_DeviceOptionsUpdated; } public void CheckDisposed() @@ -157,7 +175,7 @@ namespace Emby.Server.Implementations.Session /// Gets all connections. /// /// All connections. - public IEnumerable Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate).ToList(); + public IEnumerable Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate); private void OnSessionStarted(SessionInfo info) { @@ -171,20 +189,27 @@ namespace Emby.Server.Implementations.Session } } - EventHelper.QueueEventIfNotNull(SessionStarted, this, new SessionEventArgs - { - SessionInfo = info - - }, _logger); + EventHelper.QueueEventIfNotNull( + SessionStarted, + this, + new SessionEventArgs + { + SessionInfo = info + }, + _logger); } private void OnSessionEnded(SessionInfo info) { - EventHelper.QueueEventIfNotNull(SessionEnded, this, new SessionEventArgs - { - SessionInfo = info + EventHelper.QueueEventIfNotNull( + SessionEnded, + this, + new SessionEventArgs + { + SessionInfo = info - }, _logger); + }, + _logger); info.Dispose(); } @@ -192,9 +217,6 @@ namespace Emby.Server.Implementations.Session public void UpdateDeviceName(string sessionId, string deviceName) { var session = GetSession(sessionId); - - var key = GetSessionKey(session.Client, session.DeviceId); - if (session != null) { session.DeviceName = deviceName; @@ -210,10 +232,10 @@ namespace Emby.Server.Implementations.Session /// Name of the device. /// The remote end point. /// The user. - /// Task. + /// SessionInfo. /// user - /// - public SessionInfo LogSessionActivity(string appName, + public SessionInfo LogSessionActivity( + string appName, string appVersion, string deviceId, string deviceName, @@ -226,10 +248,12 @@ namespace Emby.Server.Implementations.Session { throw new ArgumentNullException(nameof(appName)); } + if (string.IsNullOrEmpty(appVersion)) { throw new ArgumentNullException(nameof(appVersion)); } + if (string.IsNullOrEmpty(deviceId)) { throw new ArgumentNullException(nameof(deviceId)); @@ -260,10 +284,12 @@ namespace Emby.Server.Implementations.Session if ((activityDate - lastActivityDate).TotalSeconds > 10) { - SessionActivity?.Invoke(this, new SessionEventArgs - { - SessionInfo = session - }); + SessionActivity?.Invoke( + this, + new SessionEventArgs + { + SessionInfo = session + }); } return session; @@ -304,6 +330,7 @@ namespace Emby.Server.Implementations.Session /// /// Updates the now playing item id. /// + /// Task. private async Task UpdateNowPlayingItem(SessionInfo session, PlaybackProgressInfo info, BaseItem libraryItem, bool updateLastCheckInTime) { if (string.IsNullOrEmpty(info.MediaSourceId)) @@ -418,7 +445,7 @@ namespace Emby.Server.Implementations.Session }); sessionInfo.UserId = user == null ? Guid.Empty : user.Id; - sessionInfo.UserName = user == null ? null : user.Name; + sessionInfo.UserName = user?.Name; sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); sessionInfo.RemoteEndPoint = remoteEndPoint; sessionInfo.Client = appName; @@ -432,7 +459,7 @@ namespace Emby.Server.Implementations.Session if (user == null) { - sessionInfo.AdditionalUsers = new SessionUserInfo[] { }; + sessionInfo.AdditionalUsers = Array.Empty(); } return sessionInfo; @@ -449,9 +476,9 @@ namespace Emby.Server.Implementations.Session ServerId = _appHost.SystemId }; - var username = user == null ? null : user.Name; + var username = user?.Name; - sessionInfo.UserId = user == null ? Guid.Empty : user.Id; + sessionInfo.UserId = user?.Id ?? Guid.Empty; sessionInfo.UserName = username; sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); sessionInfo.RemoteEndPoint = remoteEndPoint; @@ -508,6 +535,7 @@ namespace Emby.Server.Implementations.Session _idleTimer = new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); } } + private void StopIdleCheckTimer() { if (_idleTimer != null) @@ -539,9 +567,9 @@ namespace Emby.Server.Implementations.Session Item = session.NowPlayingItem, ItemId = session.NowPlayingItem == null ? Guid.Empty : session.NowPlayingItem.Id, SessionId = session.Id, - MediaSourceId = session.PlayState == null ? null : session.PlayState.MediaSourceId, - PositionTicks = session.PlayState == null ? null : session.PlayState.PositionTicks - }); + MediaSourceId = session.PlayState?.MediaSourceId, + PositionTicks = session.PlayState?.PositionTicks + }).ConfigureAwait(false); } catch (Exception ex) { @@ -616,18 +644,22 @@ namespace Emby.Server.Implementations.Session // Nothing to save here // Fire events to inform plugins - EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs - { - Item = libraryItem, - Users = users, - MediaSourceId = info.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - Session = session - - }, _logger); + EventHelper.QueueEventIfNotNull( + PlaybackStart, + this, + new PlaybackProgressEventArgs + { + Item = libraryItem, + Users = users, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session + + }, + _logger); StartIdleCheckTimer(); } @@ -667,6 +699,7 @@ namespace Emby.Server.Implementations.Session /// /// Used to report playback progress for an item /// + /// Task. public async Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated) { CheckDisposed(); @@ -695,21 +728,23 @@ namespace Emby.Server.Implementations.Session } } - PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs - { - Item = libraryItem, - Users = users, - PlaybackPositionTicks = session.PlayState.PositionTicks, - MediaSourceId = session.PlayState.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - IsPaused = info.IsPaused, - PlaySessionId = info.PlaySessionId, - IsAutomated = isAutomated, - Session = session - }); + PlaybackProgress?.Invoke( + this, + new PlaybackProgressEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = session.PlayState.PositionTicks, + MediaSourceId = session.PlayState.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + IsPaused = info.IsPaused, + PlaySessionId = info.PlaySessionId, + IsAutomated = isAutomated, + Session = session + }); if (!isAutomated) { @@ -830,8 +865,7 @@ namespace Emby.Server.Implementations.Session { MediaSourceInfo mediaSource = null; - var hasMediaSources = libraryItem as IHasMediaSources; - if (hasMediaSources != null) + if (libraryItem is IHasMediaSources hasMediaSources) { mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); } @@ -848,7 +882,8 @@ namespace Emby.Server.Implementations.Session { var msString = info.PositionTicks.HasValue ? (info.PositionTicks.Value / 10000).ToString(CultureInfo.InvariantCulture) : "unknown"; - _logger.LogInformation("Playback stopped reported by app {0} {1} playing {2}. Stopped at {3} ms", + _logger.LogInformation( + "Playback stopped reported by app {0} {1} playing {2}. Stopped at {3} ms", session.Client, session.ApplicationVersion, info.Item.Name, @@ -887,20 +922,24 @@ namespace Emby.Server.Implementations.Session } } - EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackStopEventArgs - { - Item = libraryItem, - Users = users, - PlaybackPositionTicks = info.PositionTicks, - PlayedToCompletion = playedToCompletion, - MediaSourceId = info.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - Session = session + EventHelper.QueueEventIfNotNull( + PlaybackStopped, + this, + new PlaybackStopEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = info.PositionTicks, + PlayedToCompletion = playedToCompletion, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session - }, _logger); + }, + _logger); } private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed) @@ -936,11 +975,10 @@ namespace Emby.Server.Implementations.Session /// The session identifier. /// if set to true [throw on missing]. /// SessionInfo. - /// + /// sessionId private SessionInfo GetSession(string sessionId, bool throwOnMissing = true) { - var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId)); - + var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal)); if (session == null && throwOnMissing) { throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId)); @@ -952,7 +990,7 @@ namespace Emby.Server.Implementations.Session private SessionInfo GetSessionToRemoteControl(string sessionId) { // Accept either device id or session id - var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId)); + var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal)); if (session == null) { @@ -1061,10 +1099,12 @@ namespace Emby.Server.Implementations.Session var series = episode.Series; if (series != null) { - var episodes = series.GetEpisodes(user, new DtoOptions(false) - { - EnableImages = false - }) + var episodes = series.GetEpisodes( + user, + new DtoOptions(false) + { + EnableImages = false + }) .Where(i => !i.IsVirtualItem) .SkipWhile(i => i.Id != episode.Id) .ToList(); @@ -1100,9 +1140,7 @@ namespace Emby.Server.Implementations.Session return new List(); } - var byName = item as IItemByName; - - if (byName != null) + if (item is IItemByName byName) { return byName.GetTaggedItems(new InternalItemsQuery(user) { @@ -1152,7 +1190,7 @@ namespace Emby.Server.Implementations.Session if (item == null) { - _logger.LogError("A non-existant item Id {0} was passed into TranslateItemForInstantMix", id); + _logger.LogError("A non-existent item Id {0} was passed into TranslateItemForInstantMix", id); return new List(); } @@ -1163,13 +1201,15 @@ namespace Emby.Server.Implementations.Session { var generalCommand = new GeneralCommand { - Name = GeneralCommandType.DisplayContent.ToString() + Name = GeneralCommandType.DisplayContent.ToString(), + Arguments = + { + ["ItemId"] = command.ItemId, + ["ItemName"] = command.ItemName, + ["ItemType"] = command.ItemType + } }; - generalCommand.Arguments["ItemId"] = command.ItemId; - generalCommand.Arguments["ItemName"] = command.ItemName; - generalCommand.Arguments["ItemType"] = command.ItemType; - return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken); } @@ -1410,7 +1450,8 @@ namespace Emby.Server.Implementations.Session var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); - var session = LogSessionActivity(request.App, + var session = LogSessionActivity( + request.App, request.AppVersion, request.DeviceId, request.DeviceName, @@ -1454,9 +1495,9 @@ namespace Emby.Server.Implementations.Session { Logout(auth); } - catch + catch (Exception ex) { - + _logger.LogError(ex, "Error while logging out."); } } } @@ -1572,7 +1613,8 @@ namespace Emby.Server.Implementations.Session ReportCapabilities(session, capabilities, true); } - private void ReportCapabilities(SessionInfo session, + private void ReportCapabilities( + SessionInfo session, ClientCapabilities capabilities, bool saveCapabilities) { @@ -1580,10 +1622,12 @@ namespace Emby.Server.Implementations.Session if (saveCapabilities) { - CapabilitiesChanged?.Invoke(this, new SessionEventArgs - { - SessionInfo = session - }); + CapabilitiesChanged?.Invoke( + this, + new SessionEventArgs + { + SessionInfo = session + }); try { diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 535f123f90..0804b01fca 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Sorting { var audio = x as IHasAlbumArtist; - return audio != null ? audio.AlbumArtists.FirstOrDefault() : null; + return audio?.AlbumArtists.FirstOrDefault(); } /// diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index 46e0dd9186..504b6d2838 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -18,17 +18,17 @@ namespace Emby.Server.Implementations.Sorting return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } - private static string GetValue(BaseItem item) - { - var hasSeries = item as IHasSeries; - - return hasSeries != null ? hasSeries.FindSeriesSortName() : null; - } - /// /// Gets the name. /// /// The name. public string Name => ItemSortBy.SeriesSortName; + + private static string GetValue(BaseItem item) + { + var hasSeries = item as IHasSeries; + + return hasSeries?.FindSeriesSortName(); + } } } diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 69673a49c1..49f8c6ace0 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -165,6 +165,7 @@ namespace MediaBrowser.Api { options.ImageTypeLimit = hasDtoOptions.ImageTypeLimit.Value; } + if (hasDtoOptions.EnableUserData.HasValue) { options.EnableUserData = hasDtoOptions.EnableUserData.Value; @@ -307,7 +308,7 @@ namespace MediaBrowser.Api return pathInfo[index]; } - private string[] Parse(string pathUri) + private static string[] Parse(string pathUri) { var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None); @@ -329,38 +330,32 @@ namespace MediaBrowser.Api /// protected BaseItem GetItemByName(string name, string type, ILibraryManager libraryManager, DtoOptions dtoOptions) { - BaseItem item; - - if (type.IndexOf("Person", StringComparison.OrdinalIgnoreCase) == 0) - { - item = GetPerson(name, libraryManager, dtoOptions); - } - else if (type.IndexOf("Artist", StringComparison.OrdinalIgnoreCase) == 0) + if (type.Equals("Person", StringComparison.OrdinalIgnoreCase)) { - item = GetArtist(name, libraryManager, dtoOptions); + return GetPerson(name, libraryManager, dtoOptions); } - else if (type.IndexOf("Genre", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("Artist", StringComparison.OrdinalIgnoreCase)) { - item = GetGenre(name, libraryManager, dtoOptions); + return GetArtist(name, libraryManager, dtoOptions); } - else if (type.IndexOf("MusicGenre", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("Genre", StringComparison.OrdinalIgnoreCase)) { - item = GetMusicGenre(name, libraryManager, dtoOptions); + return GetGenre(name, libraryManager, dtoOptions); } - else if (type.IndexOf("Studio", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("MusicGenre", StringComparison.OrdinalIgnoreCase)) { - item = GetStudio(name, libraryManager, dtoOptions); + return GetMusicGenre(name, libraryManager, dtoOptions); } - else if (type.IndexOf("Year", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("Studio", StringComparison.OrdinalIgnoreCase)) { - item = libraryManager.GetYear(int.Parse(name)); + return GetStudio(name, libraryManager, dtoOptions); } - else + else if (type.Equals("Year", StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentException(); + return libraryManager.GetYear(int.Parse(name)); } - return item; + throw new ArgumentException("Invalid type", nameof(type)); } } } diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index 7a8455ff26..a30f8adfed 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -87,11 +87,6 @@ namespace MediaBrowser.Api.UserLibrary /// System.Object. public object Get(GetArtists request) { - if (string.IsNullOrWhiteSpace(request.IncludeItemTypes)) - { - //request.IncludeItemTypes = "Audio,MusicVideo"; - } - return GetResultSlim(request); } @@ -102,11 +97,6 @@ namespace MediaBrowser.Api.UserLibrary /// System.Object. public object Get(GetAlbumArtists request) { - if (string.IsNullOrWhiteSpace(request.IncludeItemTypes)) - { - //request.IncludeItemTypes = "Audio,MusicVideo"; - } - var result = GetResultSlim(request); return ToOptimizedResult(result); diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index f26087fda3..860ea13cf6 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -942,10 +942,7 @@ namespace MediaBrowser.Providers.Manager _activeRefreshes[id] = 0; } - if (RefreshStarted != null) - { - RefreshStarted(this, new GenericEventArgs(item)); - } + RefreshStarted?.Invoke(this, new GenericEventArgs(item)); } public void OnRefreshComplete(BaseItem item) @@ -956,10 +953,7 @@ namespace MediaBrowser.Providers.Manager _activeRefreshes.Remove(item.Id); } - if (RefreshCompleted != null) - { - RefreshCompleted(this, new GenericEventArgs(item)); - } + RefreshCompleted?.Invoke(this, new GenericEventArgs(item)); } public double? GetRefreshProgress(Guid id) @@ -986,10 +980,7 @@ namespace MediaBrowser.Providers.Manager { _activeRefreshes[id] = progress; - if (RefreshProgress != null) - { - RefreshProgress(this, new GenericEventArgs>(new Tuple(item, progress))); - } + RefreshProgress?.Invoke(this, new GenericEventArgs>(new Tuple(item, progress))); } else { @@ -1079,17 +1070,14 @@ namespace MediaBrowser.Providers.Manager await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); // Collection folders don't validate their children so we'll have to simulate that here - var collectionFolder = item as CollectionFolder; - if (collectionFolder != null) + if (item is CollectionFolder collectionFolder) { await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(false); } else { - var folder = item as Folder; - - if (folder != null) + if (item is Folder folder) { await folder.ValidateChildren(new SimpleProgress(), cancellationToken, options).ConfigureAwait(false); } @@ -1098,16 +1086,11 @@ namespace MediaBrowser.Providers.Manager private async Task RefreshCollectionFolderChildren(MetadataRefreshOptions options, CollectionFolder collectionFolder, CancellationToken cancellationToken) { - foreach (var child in collectionFolder.GetPhysicalFolders().ToList()) + foreach (var child in collectionFolder.GetPhysicalFolders()) { await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); - if (child.IsFolder) - { - var folder = (Folder)child; - - await folder.ValidateChildren(new SimpleProgress(), cancellationToken, options, true).ConfigureAwait(false); - } + await child.ValidateChildren(new SimpleProgress(), cancellationToken, options, true).ConfigureAwait(false); } } @@ -1116,20 +1099,18 @@ namespace MediaBrowser.Providers.Manager var albums = _libraryManagerFactory() .GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, + IncludeItemTypes = new[] { nameof(MusicAlbum) }, ArtistIds = new[] { item.Id }, DtoOptions = new DtoOptions(false) { EnableImages = false } }) - .OfType() - .ToList(); + .OfType(); var musicArtists = albums .Select(i => i.MusicArtist) - .Where(i => i != null) - .ToList(); + .Where(i => i != null); var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new SimpleProgress(), cancellationToken, options, true)); diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index aee9921034..f86cdeab9f 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.Providers.Music var releaseId = searchInfo.GetReleaseId(); var releaseGroupId = searchInfo.GetReleaseGroupId(); - string url = null; + string url; var isNameSearch = false; bool forceMusicBrainzProper = false; @@ -100,10 +100,10 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } - private List GetResultsFromResponse(Stream stream) + private IEnumerable GetResultsFromResponse(Stream stream) { using (var oReader = new StreamReader(stream, Encoding.UTF8)) { @@ -149,7 +149,7 @@ namespace MediaBrowser.Providers.Music return result; - }).ToList(); + }); } } } @@ -301,7 +301,7 @@ namespace MediaBrowser.Providers.Music public List> Artists = new List>(); - public static List Parse(XmlReader reader) + public static IEnumerable Parse(XmlReader reader) { reader.MoveToContent(); reader.Read(); @@ -338,13 +338,11 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } - private static List ParseReleaseList(XmlReader reader) + private static IEnumerable ParseReleaseList(XmlReader reader) { - var list = new List(); - reader.MoveToContent(); reader.Read(); @@ -369,7 +367,7 @@ namespace MediaBrowser.Providers.Music var release = ParseRelease(subReader, releaseId); if (release != null) { - list.Add(release); + yield return release; } } break; @@ -386,8 +384,6 @@ namespace MediaBrowser.Providers.Music reader.Read(); } } - - return list; } private static ReleaseResult ParseRelease(XmlReader reader, string releaseId) @@ -552,7 +548,7 @@ namespace MediaBrowser.Providers.Music return (null, null); } - private static ValueTuple ParseArtistArtistCredit(XmlReader reader, string artistId) + private static (string name, string id) ParseArtistArtistCredit(XmlReader reader, string artistId) { reader.MoveToContent(); reader.Read(); @@ -586,7 +582,7 @@ namespace MediaBrowser.Providers.Music } } - return new ValueTuple(name, artistId); + return (name, artistId); } private async Task GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs index 924b712507..55aab7778a 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Providers.Music { using (var stream = response.Content) { - var results = GetResultsFromResponse(stream); + var results = GetResultsFromResponse(stream).ToList(); if (results.Count > 0) { @@ -74,10 +74,10 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } - private List GetResultsFromResponse(Stream stream) + private IEnumerable GetResultsFromResponse(Stream stream) { using (var oReader = new StreamReader(stream, Encoding.UTF8)) { @@ -126,15 +126,13 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } } } - private List ParseArtistList(XmlReader reader) + private IEnumerable ParseArtistList(XmlReader reader) { - var list = new List(); - reader.MoveToContent(); reader.Read(); @@ -159,7 +157,7 @@ namespace MediaBrowser.Providers.Music var artist = ParseArtist(subReader, mbzId); if (artist != null) { - list.Add(artist); + yield return artist; } } break; @@ -176,8 +174,6 @@ namespace MediaBrowser.Providers.Music reader.Read(); } } - - return list; } private RemoteSearchResult ParseArtist(XmlReader reader, string artistId) @@ -277,7 +273,7 @@ namespace MediaBrowser.Providers.Music /// /// The name. /// System.String. - private string UrlEncode(string name) + private static string UrlEncode(string name) { return WebUtility.UrlEncode(name); } -- cgit v1.2.3 From 21cc38fcf426f5ecaedbe8a94007a89663de749c Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Thu, 14 Mar 2019 22:17:56 +0100 Subject: Adjusted AssemblyCopyright attribute values. --- BDInfo/Properties/AssemblyInfo.cs | 2 +- DvdLib/Properties/AssemblyInfo.cs | 2 +- Emby.Dlna/Properties/AssemblyInfo.cs | 2 +- Emby.Drawing/Properties/AssemblyInfo.cs | 2 +- Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs | 2 +- Emby.Naming/Properties/AssemblyInfo.cs | 2 +- Emby.Notifications/Properties/AssemblyInfo.cs | 2 +- Emby.Photos/Properties/AssemblyInfo.cs | 2 +- Emby.Server.Implementations/Properties/AssemblyInfo.cs | 2 +- Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs | 2 +- Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs | 2 +- Jellyfin.Server/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Api/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Common/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Controller/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Model/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Providers/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs | 2 +- Mono.Nat/Properties/AssemblyInfo.cs | 2 +- RSSDP/Properties/AssemblyInfo.cs | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/BDInfo/Properties/AssemblyInfo.cs b/BDInfo/Properties/AssemblyInfo.cs index 482cd376a1..f65c7036a4 100644 --- a/BDInfo/Properties/AssemblyInfo.cs +++ b/BDInfo/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/DvdLib/Properties/AssemblyInfo.cs b/DvdLib/Properties/AssemblyInfo.cs index 09d1a3801a..6acd571d68 100644 --- a/DvdLib/Properties/AssemblyInfo.cs +++ b/DvdLib/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Dlna/Properties/AssemblyInfo.cs b/Emby.Dlna/Properties/AssemblyInfo.cs index 0e3b03a322..a2c1e0db8b 100644 --- a/Emby.Dlna/Properties/AssemblyInfo.cs +++ b/Emby.Dlna/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Resources; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Drawing/Properties/AssemblyInfo.cs b/Emby.Drawing/Properties/AssemblyInfo.cs index 4429839606..281008e370 100644 --- a/Emby.Drawing/Properties/AssemblyInfo.cs +++ b/Emby.Drawing/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs b/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs index 702aed3b5a..5956fc3b31 100644 --- a/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs +++ b/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Naming/Properties/AssemblyInfo.cs b/Emby.Naming/Properties/AssemblyInfo.cs index 9295a1ec75..f26e0ba794 100644 --- a/Emby.Naming/Properties/AssemblyInfo.cs +++ b/Emby.Naming/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Notifications/Properties/AssemblyInfo.cs b/Emby.Notifications/Properties/AssemblyInfo.cs index 53b80e3dc1..5c82c90c47 100644 --- a/Emby.Notifications/Properties/AssemblyInfo.cs +++ b/Emby.Notifications/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Photos/Properties/AssemblyInfo.cs b/Emby.Photos/Properties/AssemblyInfo.cs index c3ebc4e08e..a3bb4362a9 100644 --- a/Emby.Photos/Properties/AssemblyInfo.cs +++ b/Emby.Photos/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Server.Implementations/Properties/AssemblyInfo.cs b/Emby.Server.Implementations/Properties/AssemblyInfo.cs index 79ba9374ce..a1933f66ef 100644 --- a/Emby.Server.Implementations/Properties/AssemblyInfo.cs +++ b/Emby.Server.Implementations/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs b/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs index 69a532c357..7beec09cbd 100644 --- a/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs +++ b/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs b/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs index 9fdb9529f5..e7db09449d 100644 --- a/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs +++ b/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Jellyfin.Server/Properties/AssemblyInfo.cs b/Jellyfin.Server/Properties/AssemblyInfo.cs index 1add32c8c1..5de1e653d9 100644 --- a/Jellyfin.Server/Properties/AssemblyInfo.cs +++ b/Jellyfin.Server/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.Api/Properties/AssemblyInfo.cs b/MediaBrowser.Api/Properties/AssemblyInfo.cs index fb7d43f456..35bcbea5cd 100644 --- a/MediaBrowser.Api/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Api/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.Common/Properties/AssemblyInfo.cs b/MediaBrowser.Common/Properties/AssemblyInfo.cs index d428cea2fd..538e89fd1c 100644 --- a/MediaBrowser.Common/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Common/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.Controller/Properties/AssemblyInfo.cs b/MediaBrowser.Controller/Properties/AssemblyInfo.cs index 0841e84f24..60e7923091 100644 --- a/MediaBrowser.Controller/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Controller/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs b/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs index 4d09084d20..580cef9da6 100644 --- a/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs +++ b/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs index 8851ec5277..a9491374bd 100644 --- a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs +++ b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.Model/Properties/AssemblyInfo.cs b/MediaBrowser.Model/Properties/AssemblyInfo.cs index f118294817..f99e9ece96 100644 --- a/MediaBrowser.Model/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Model/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.Providers/Properties/AssemblyInfo.cs b/MediaBrowser.Providers/Properties/AssemblyInfo.cs index eb2599e35a..f1c46899ce 100644 --- a/MediaBrowser.Providers/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Providers/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs b/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs index 346853f588..584d490216 100644 --- a/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs +++ b/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs b/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs index 343befb7e2..b3e2f27179 100644 --- a/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs +++ b/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs index c24a4fc6c2..dc47f2ffeb 100644 --- a/Mono.Nat/Properties/AssemblyInfo.cs +++ b/Mono.Nat/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2006 Alan McGovern. Copyright © 2007 Ben Motmans. Code releases unde the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2006 Alan McGovern. Copyright © 2007 Ben Motmans. Code releases under the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/RSSDP/Properties/AssemblyInfo.cs b/RSSDP/Properties/AssemblyInfo.cs index 690ddb807c..55f7b6a834 100644 --- a/RSSDP/Properties/AssemblyInfo.cs +++ b/RSSDP/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2015 Troy Willmot. Code released under the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2015 Troy Willmot. Code released under the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] -- cgit v1.2.3 From 427688a0a08e04e87774d01e7bfec22eecc71e7d Mon Sep 17 00:00:00 2001 From: redSpoutnik <15638041+redSpoutnik@users.noreply.github.com> Date: Thu, 14 Mar 2019 22:31:51 +0100 Subject: Change subtitles DisplayTitle behavior --- Emby.Server.Implementations/ApplicationHost.cs | 5 +++-- .../Data/SqliteItemRepository.cs | 13 ++++++++++- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 8 +++++-- .../Probing/ProbeResultNormalizer.cs | 8 ++++++- MediaBrowser.Model/Entities/MediaStream.cs | 26 +++++++++++++++++----- 5 files changed, 49 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b3bb4f740e..f185d37adf 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -703,7 +703,7 @@ namespace Emby.Server.Implementations var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager); serviceCollection.AddSingleton(displayPreferencesRepo); - ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo); + ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo, LocalizationManager); serviceCollection.AddSingleton(ItemRepository); AuthenticationRepository = GetAuthenticationRepository(); @@ -979,7 +979,8 @@ namespace Emby.Server.Implementations HttpClient, ZipClient, ProcessFactory, - 5000); + 5000, + LocalizationManager); MediaEncoder = mediaEncoder; serviceCollection.AddSingleton(MediaEncoder); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 06f6563a32..1aeb3b9c5c 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -22,6 +22,7 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Reflection; @@ -56,6 +57,8 @@ namespace Emby.Server.Implementations.Data private readonly IServerConfigurationManager _config; private IServerApplicationHost _appHost; + private readonly ILocalizationManager _localization; + public IImageProcessor ImageProcessor { get; set; } /// @@ -66,7 +69,8 @@ namespace Emby.Server.Implementations.Data IServerApplicationHost appHost, IJsonSerializer jsonSerializer, ILoggerFactory loggerFactory, - IAssemblyInfo assemblyInfo) + IAssemblyInfo assemblyInfo, + ILocalizationManager localization) : base(loggerFactory.CreateLogger(nameof(SqliteItemRepository))) { if (config == null) @@ -83,6 +87,7 @@ namespace Emby.Server.Implementations.Data _config = config; _jsonSerializer = jsonSerializer; _typeMapper = new TypeMapper(assemblyInfo); + _localization = localization; DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db"); } @@ -6189,6 +6194,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type item.ColorTransfer = reader[34].ToString(); } + if (item.Type == MediaStreamType.Subtitle){ + item.localizedUndefined = _localization.GetLocalizedString("Undefined"); + item.localizedDefault = _localization.GetLocalizedString("Default"); + item.localizedForced = _localization.GetLocalizedString("Forced"); + } + return item; } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 7f29c06b4c..d7449d57d2 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -19,6 +19,7 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; @@ -69,6 +70,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly string _originalFFMpegPath; private readonly string _originalFFProbePath; private readonly int DefaultImageExtractionTimeoutMs; + private readonly ILocalizationManager _localization; public MediaEncoder( ILoggerFactory loggerFactory, @@ -88,7 +90,8 @@ namespace MediaBrowser.MediaEncoding.Encoder IHttpClient httpClient, IZipClient zipClient, IProcessFactory processFactory, - int defaultImageExtractionTimeoutMs) + int defaultImageExtractionTimeoutMs, + ILocalizationManager localization) { _logger = loggerFactory.CreateLogger(nameof(MediaEncoder)); _jsonSerializer = jsonSerializer; @@ -110,6 +113,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _originalFFProbePath = ffProbePath; _originalFFMpegPath = ffMpegPath; _hasExternalEncoder = hasExternalEncoder; + _localization = localization; } public string EncoderLocationType @@ -537,7 +541,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); + return new ProbeResultNormalizer(_logger, FileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); } catch { diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 5099ccb2aa..3e6e5864cd 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; @@ -20,11 +21,13 @@ namespace MediaBrowser.MediaEncoding.Probing private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly ILogger _logger; private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; - public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem) + public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, ILocalizationManager localization) { _logger = logger; _fileSystem = fileSystem; + _localization = localization; } public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType? videoType, bool isAudio, string path, MediaProtocol protocol) @@ -599,6 +602,9 @@ namespace MediaBrowser.MediaEncoding.Probing { stream.Type = MediaStreamType.Subtitle; stream.Codec = NormalizeSubtitleCodec(stream.Codec); + stream.localizedUndefined = _localization.GetLocalizedString("Undefined"); + stream.localizedDefault = _localization.GetLocalizedString("Default"); + stream.localizedForced = _localization.GetLocalizedString("Forced"); } else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index fc346df376..8dcf679a3d 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Text; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.MediaInfo; @@ -65,6 +67,10 @@ namespace MediaBrowser.Model.Entities } } + public string localizedUndefined { get; set; } + public string localizedDefault { get; set; } + public string localizedForced { get; set; } + public string DisplayTitle { get @@ -141,22 +147,30 @@ namespace MediaBrowser.Model.Entities } else { - attributes.Add("Und"); + attributes.Add(string.IsNullOrEmpty(localizedUndefined) ? "Und" : localizedUndefined); } if (IsDefault) { - attributes.Add("Default"); + attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault); } if (IsForced) { - attributes.Add("Forced"); + attributes.Add(string.IsNullOrEmpty(localizedForced) ? "Forced" : localizedForced); } - string name = string.Join(" ", attributes.ToArray()); + if (!string.IsNullOrEmpty(Title)) + { + return attributes.AsEnumerable() + // keep Tags that are not already in Title + .Where(tag => Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1) + // attributes concatenation, starting with Title + .Aggregate(new StringBuilder(Title), (builder, attr) => builder.Append(" - ").Append(attr)) + .ToString(); + } - return name; + return string.Join(" - ", attributes.ToArray()); } if (Type == MediaStreamType.Video) @@ -220,6 +234,7 @@ namespace MediaBrowser.Model.Entities return null; } + /* private string AddLanguageIfNeeded(string title) { if (!string.IsNullOrEmpty(Language) && @@ -241,6 +256,7 @@ namespace MediaBrowser.Model.Entities return false; } + */ public string NalLengthSize { get; set; } -- cgit v1.2.3 From d125fbc43d453be7de08375dede1748d4c19a8cf Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Thu, 14 Mar 2019 21:32:27 +0000 Subject: Added contact email to user agent MusicBrainz request a contact email address is supplied in comment section of user agent field. --- Emby.Server.Implementations/ApplicationHost.cs | 6 ++++++ MediaBrowser.Common/IApplicationHost.cs | 6 ++++++ MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs | 5 +++-- 3 files changed, 15 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 94eaffa479..eaea8844d4 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -428,6 +428,12 @@ namespace Emby.Server.Implementations /// The application user agent. public string ApplicationUserAgent => Name.Replace(' ','-') + "/" + ApplicationVersion; + /// + /// Gets the email address for use within a comment section of a user agent field. + /// Presently used to provide contact information to MusicBrainz service. + /// + public string ApplicationUserAgentAddress { get; } = "team@jellyfin.org"; + private string _productName; /// diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 966448d630..2925a3efd9 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -71,6 +71,12 @@ namespace MediaBrowser.Common /// The application user agent. string ApplicationUserAgent { get; } + /// + /// Gets the email address for use within a comment section of a user agent field. + /// Presently used to provide contact information to MusicBrainz service. + /// + string ApplicationUserAgentAddress { get; } + /// /// Gets the exports. /// diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index a1366e0fde..b716f40f04 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -735,7 +734,9 @@ namespace MediaBrowser.Providers.Music { Url = MusicBrainzBaseUrl.TrimEnd('/') + url, CancellationToken = cancellationToken, - UserAgent = _appHost.ApplicationUserAgent, + // MusicBrainz request a contact email address is supplied, as comment, in user agent field: + // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent + UserAgent = string.Format("{0} ( {1} )", _appHost.ApplicationUserAgent, _appHost.ApplicationUserAgentAddress), BufferContent = false }; -- cgit v1.2.3 From 221389089cc9ca4b69907d6bf3e9d38bf94393ea Mon Sep 17 00:00:00 2001 From: Phallacy Date: Fri, 15 Mar 2019 21:25:19 -0700 Subject: quick fix for auth bug --- Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 3ec1f81d31..3d15a8afbb 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library PasswordHash readyHash = new PasswordHash(resolvedUser.Password); byte[] calculatedHash; string calculatedHashString; - if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) + if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id) { if (string.IsNullOrEmpty(readyHash.Salt)) { -- cgit v1.2.3 From 1ee016c99745ed4a29f8995de1478ab6a6e410e9 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sat, 16 Mar 2019 00:18:52 -0700 Subject: configurable user lockout --- Emby.Server.Implementations/Library/UserManager.cs | 14 +++++++++++--- MediaBrowser.Model/Users/UserPolicy.cs | 3 +++ 2 files changed, 14 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index efb1ef4a50..e20af003d4 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -219,7 +219,7 @@ 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), dashes (-), underscores (_), apostrophes ('), and periods (.) - return Regex.IsMatch(username, "^[\\w-'._@]*$"); + return Regex.IsMatch(username, @"^[\w-'._@]*$"); } private static bool IsValidUsernameCharacter(char i) @@ -448,11 +448,19 @@ namespace Emby.Server.Implementations.Library user.Policy.InvalidLoginAttemptCount = newValue; - var maxCount = user.Policy.IsAdministrator ? 3 : 5; + // Check for users without a value here and then fill in the default value + // also protect from an always lockout if misconfigured + if (user.Policy.LoginAttemptsBeforeLockout == null || user.Policy.LoginAttemptsBeforeLockout == 0) + { + user.Policy.LoginAttemptsBeforeLockout = user.Policy.IsAdministrator ? 5 : 3; + } + + var maxCount = user.Policy.LoginAttemptsBeforeLockout; var fireLockout = false; - if (newValue >= maxCount) + // -1 can be used to specify no lockout value + if (maxCount != -1 && newValue >= maxCount) { _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue); user.Policy.IsDisabled = true; diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 27ce23778e..5415fd5e81 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -66,6 +66,7 @@ namespace MediaBrowser.Model.Users public bool EnableAllFolders { get; set; } public int InvalidLoginAttemptCount { get; set; } + public int? LoginAttemptsBeforeLockout { get; set; } public bool EnablePublicSharing { get; set; } @@ -104,6 +105,8 @@ namespace MediaBrowser.Model.Users AccessSchedules = Array.Empty(); + LoginAttemptsBeforeLockout = -1; + EnableAllChannels = true; EnabledChannels = Array.Empty(); -- cgit v1.2.3 From 7f0fa74467d5f3560addd6cfefbfb2dbd24f4060 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sat, 16 Mar 2019 00:38:31 -0700 Subject: updated regex to string literal with escaped - --- Emby.Server.Implementations/Library/UserManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index efb1ef4a50..7e2419b680 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -216,10 +216,10 @@ namespace Emby.Server.Implementations.Library public static bool IsValidUsername(string username) { - //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 + // 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), dashes (-), underscores (_), apostrophes ('), and periods (.) - return Regex.IsMatch(username, "^[\\w-'._@]*$"); + return Regex.IsMatch(username, @"^[\w\-'._@]*$"); } private static bool IsValidUsernameCharacter(char i) -- cgit v1.2.3 From e3dbed1c1aff986e51e25ba244a6a6bf52816dbe Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sat, 16 Mar 2019 10:16:23 -0700 Subject: Update Emby.Server.Implementations/Library/UserManager.cs Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- Emby.Server.Implementations/Library/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 7e2419b680..ee90b3558e 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -218,7 +218,7 @@ 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), 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\-'._@]*$"); } -- cgit v1.2.3 From fc28c9237cf08bd0100629ad5830824397e52565 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sat, 16 Mar 2019 21:34:26 -0700 Subject: fixed line endings --- .../Library/DefaultPasswordResetProvider.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs new file mode 100644 index 0000000000..87dbe684cd --- /dev/null +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Entities; + +namespace Emby.Server.Implementations.Library +{ + public class DefaultPasswordResetProvider : IPasswordResetProvider + { + public string Name => "Default Password Reset"; + + public bool IsEnabled => true; + + // set our default timeout to an hour since we'll be making the PIN it generates a little less fragile + public TimeSpan PasswordResetTimeout => new TimeSpan(1,0,0); + + public Task ResetPassword(User user) + { + throw new NotImplementedException(); + } + } +} -- cgit v1.2.3 From 80aedcd7e24a7708f44491e3cd359f911a124c8f Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sat, 16 Mar 2019 21:36:45 -0700 Subject: really fixed line endings --- .../Library/DefaultPasswordResetProvider.cs | 22 - Emby.Server.Implementations/Library/UserManager.cs | 2438 ++++++++++---------- 2 files changed, 1219 insertions(+), 1241 deletions(-) delete mode 100644 Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs deleted file mode 100644 index 87dbe684cd..0000000000 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Threading.Tasks; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; - -namespace Emby.Server.Implementations.Library -{ - public class DefaultPasswordResetProvider : IPasswordResetProvider - { - public string Name => "Default Password Reset"; - - public bool IsEnabled => true; - - // set our default timeout to an hour since we'll be making the PIN it generates a little less fragile - public TimeSpan PasswordResetTimeout => new TimeSpan(1,0,0); - - public Task ResetPassword(User user) - { - throw new NotImplementedException(); - } - } -} diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index ce8c016602..4cf703add5 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -1,1219 +1,1219 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -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; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.Library -{ - /// - /// Class UserManager - /// - public class UserManager : IUserManager - { - /// - /// Gets the users. - /// - /// The users. - public IEnumerable Users => _users; - - private User[] _users; - - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// Gets or sets the configuration manager. - /// - /// The configuration manager. - private IServerConfigurationManager ConfigurationManager { get; set; } - - /// - /// Gets the active user repository - /// - /// The user repository. - private IUserRepository UserRepository { get; set; } - public event EventHandler> UserPasswordChanged; - - private readonly IXmlSerializer _xmlSerializer; - private readonly IJsonSerializer _jsonSerializer; - - private readonly INetworkManager _networkManager; - - private readonly Func _imageProcessorFactory; - private readonly Func _dtoServiceFactory; - private readonly IServerApplicationHost _appHost; - private readonly IFileSystem _fileSystem; - - private IAuthenticationProvider[] _authenticationProviders; - private DefaultAuthenticationProvider _defaultAuthenticationProvider; - - public UserManager( - ILoggerFactory loggerFactory, - IServerConfigurationManager configurationManager, - IUserRepository userRepository, - IXmlSerializer xmlSerializer, - INetworkManager networkManager, - Func imageProcessorFactory, - Func dtoServiceFactory, - IServerApplicationHost appHost, - IJsonSerializer jsonSerializer, - IFileSystem fileSystem) - { - _logger = loggerFactory.CreateLogger(nameof(UserManager)); - UserRepository = userRepository; - _xmlSerializer = xmlSerializer; - _networkManager = networkManager; - _imageProcessorFactory = imageProcessorFactory; - _dtoServiceFactory = dtoServiceFactory; - _appHost = appHost; - _jsonSerializer = jsonSerializer; - _fileSystem = fileSystem; - ConfigurationManager = configurationManager; - _users = Array.Empty(); - - DeletePinFile(); - } - - public NameIdPair[] GetAuthenticationProviders() - { - return _authenticationProviders - .Where(i => i.IsEnabled) - .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) - .ThenBy(i => i.Name) - .Select(i => new NameIdPair - { - Name = i.Name, - Id = GetAuthenticationProviderId(i) - }) - .ToArray(); - } - - public void AddParts(IEnumerable authenticationProviders) - { - _authenticationProviders = authenticationProviders.ToArray(); - - _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); - } - - #region UserUpdated Event - /// - /// Occurs when [user updated]. - /// - public event EventHandler> UserUpdated; - public event EventHandler> UserPolicyUpdated; - public event EventHandler> UserConfigurationUpdated; - public event EventHandler> UserLockedOut; - - /// - /// Called when [user updated]. - /// - /// The user. - private void OnUserUpdated(User user) - { - UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - #region UserDeleted Event - /// - /// Occurs when [user deleted]. - /// - public event EventHandler> UserDeleted; - /// - /// Called when [user deleted]. - /// - /// The user. - private void OnUserDeleted(User user) - { - UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - /// - /// Gets a User by Id - /// - /// The id. - /// User. - /// - public User GetUserById(Guid id) - { - if (id == Guid.Empty) - { - throw new ArgumentException(nameof(id), "Guid can't be empty"); - } - - return Users.FirstOrDefault(u => u.Id == id); - } - - /// - /// Gets the user by identifier. - /// - /// The identifier. - /// User. - public User GetUserById(string id) - { - return GetUserById(new Guid(id)); - } - - public User GetUserByName(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase)); - } - - public void Initialize() - { - _users = LoadUsers(); - - var users = Users.ToList(); - - // If there are no local users with admin rights, make them all admins - if (!users.Any(i => i.Policy.IsAdministrator)) - { - foreach (var user in users) - { - user.Policy.IsAdministrator = true; - UpdateUserPolicy(user, user.Policy, false); - } - } - } - - public static bool IsValidUsername(string username) - { - // 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 (.) - return Regex.IsMatch(username, @"^[\w\-'._@]*$"); - } - - private static bool IsValidUsernameCharacter(char i) - { - return IsValidUsername(i.ToString()); - } - - public string MakeValidUsername(string username) - { - if (IsValidUsername(username)) - { - return username; - } - - // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) - var builder = new StringBuilder(); - - foreach (var c in username) - { - if (IsValidUsernameCharacter(c)) - { - builder.Append(c); - } - } - return builder.ToString(); - } - - public async Task AuthenticateUser(string username, string password, string hashedPassword, string remoteEndPoint, bool isUserSession) - { - if (string.IsNullOrWhiteSpace(username)) - { - throw new ArgumentNullException(nameof(username)); - } - - var user = Users - .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - - var success = false; - IAuthenticationProvider authenticationProvider = null; - - if (user != null) - { - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.Item1; - success = authResult.Item2; - } - else - { - // user is null - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.Item1; - success = authResult.Item2; - - if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) - { - user = await CreateUser(username).ConfigureAwait(false); - - var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; - if (hasNewUserPolicy != null) - { - var policy = hasNewUserPolicy.GetNewUserPolicy(); - UpdateUserPolicy(user, policy, true); - } - } - } - - if (success && user != null && authenticationProvider != null) - { - var providerId = GetAuthenticationProviderId(authenticationProvider); - - if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) - { - user.Policy.AuthenticationProviderId = providerId; - UpdateUserPolicy(user, user.Policy, true); - } - } - - if (user == null) - { - throw new SecurityException("Invalid username or password entered."); - } - - if (user.Policy.IsDisabled) - { - throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); - } - - if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) - { - throw new SecurityException("Forbidden."); - } - - if (!user.IsParentalScheduleAllowed()) - { - throw new SecurityException("User is not allowed access at this time."); - } - - // Update LastActivityDate and LastLoginDate, then save - if (success) - { - if (isUserSession) - { - user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - UpdateUser(user); - } - UpdateInvalidLoginAttemptCount(user, 0); - } - else - { - UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1); - } - - _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied"); - - return success ? user : null; - } - - private static string GetAuthenticationProviderId(IAuthenticationProvider provider) - { - return provider.GetType().FullName; - } - - private IAuthenticationProvider GetAuthenticationProvider(User user) - { - return GetAuthenticationProviders(user).First(); - } - - private IAuthenticationProvider[] GetAuthenticationProviders(User user) - { - var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; - - var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); - - if (!string.IsNullOrEmpty(authenticationProviderId)) - { - providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); - } - - if (providers.Length == 0) - { - providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; - } - - return providers; - } - - private async Task AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) - { - try - { - var requiresResolvedUser = provider as IRequiresResolvedUser; - if (requiresResolvedUser != null) - { - await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); - } - else - { - await provider.Authenticate(username, password).ConfigureAwait(false); - } - - return true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name); - - return false; - } - } - - private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) - { - bool success = false; - IAuthenticationProvider authenticationProvider = null; - - if (password != null && user != null) - { - // Doesn't look like this is even possible to be used, because of password == null checks below - hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password); - } - - if (password == null) - { - // legacy - success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } - else - { - foreach (var provider in GetAuthenticationProviders(user)) - { - success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - - if (success) - { - authenticationProvider = provider; - break; - } - } - } - - if (user != null) - { - if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) - { - if (password == null) - { - // 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 new Tuple(authenticationProvider, success); - } - - private void UpdateInvalidLoginAttemptCount(User user, int newValue) - { - if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) - { - return; - } - - user.Policy.InvalidLoginAttemptCount = newValue; - - // Check for users without a value here and then fill in the default value - // also protect from an always lockout if misconfigured - if (user.Policy.LoginAttemptsBeforeLockout == null || user.Policy.LoginAttemptsBeforeLockout == 0) - { - user.Policy.LoginAttemptsBeforeLockout = user.Policy.IsAdministrator ? 5 : 3; - } - - var maxCount = user.Policy.LoginAttemptsBeforeLockout; - - var fireLockout = false; - - // -1 can be used to specify no lockout value - if (maxCount != -1 && newValue >= maxCount) - { - _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue); - user.Policy.IsDisabled = true; - - fireLockout = true; - } - - UpdateUserPolicy(user, user.Policy, false); - - if (fireLockout) - { - UserLockedOut?.Invoke(this, new GenericEventArgs(user)); - } - } - - private string GetLocalPasswordHash(User user) - { - return string.IsNullOrEmpty(user.EasyPassword) - ? null - : user.EasyPassword; - } - - /// - /// Loads the users from the repository - /// - /// IEnumerable{User}. - private User[] LoadUsers() - { - var users = UserRepository.RetrieveAllUsers(); - - // There always has to be at least one user. - if (users.Count == 0) - { - var defaultName = Environment.UserName; - if (string.IsNullOrWhiteSpace(defaultName)) - { - defaultName = "MyJellyfinUser"; - } - var name = MakeValidUsername(defaultName); - - var user = InstantiateNewUser(name); - - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.CreateUser(user); - - users.Add(user); - - user.Policy.IsAdministrator = true; - user.Policy.EnableContentDeletion = true; - user.Policy.EnableRemoteControlOfOtherUsers = true; - UpdateUserPolicy(user, user.Policy, false); - } - - return users.ToArray(); - } - - public UserDto GetUserDto(User user, string remoteEndPoint = null) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; - bool hasConfiguredEasyPassword = string.IsNullOrEmpty(GetLocalPasswordHash(user)); - - bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? - hasConfiguredEasyPassword : - hasConfiguredPassword; - - UserDto dto = new UserDto - { - Id = user.Id, - Name = user.Name, - HasPassword = hasPassword, - HasConfiguredPassword = hasConfiguredPassword, - HasConfiguredEasyPassword = hasConfiguredEasyPassword, - LastActivityDate = user.LastActivityDate, - LastLoginDate = user.LastLoginDate, - Configuration = user.Configuration, - ServerId = _appHost.SystemId, - Policy = user.Policy - }; - - if (!hasPassword && Users.Count() == 1) - { - dto.EnableAutoLogin = true; - } - - ItemImageInfo image = user.GetImageInfo(ImageType.Primary, 0); - - if (image != null) - { - dto.PrimaryImageTag = GetImageCacheTag(user, image); - - try - { - _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user); - } - catch (Exception ex) - { - // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions - _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name); - } - } - - return dto; - } - - public UserDto GetOfflineUserDto(User user) - { - var dto = GetUserDto(user); - - dto.ServerName = _appHost.FriendlyName; - - return dto; - } - - private string GetImageCacheTag(BaseItem item, ItemImageInfo image) - { - try - { - return _imageProcessorFactory().GetImageCacheTag(item, image); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path); - return null; - } - } - - /// - /// Refreshes metadata for each user - /// - /// The cancellation token. - /// Task. - public async Task RefreshUsersMetadata(CancellationToken cancellationToken) - { - foreach (var user in Users) - { - await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Renames the user. - /// - /// The user. - /// The new name. - /// Task. - /// user - /// - public async Task RenameUser(User user, string newName) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrEmpty(newName)) - { - throw new ArgumentNullException(nameof(newName)); - } - - if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName)); - } - - if (user.Name.Equals(newName, StringComparison.Ordinal)) - { - throw new ArgumentException("The new and old names must be different."); - } - - await user.Rename(newName); - - OnUserUpdated(user); - } - - /// - /// Updates the user. - /// - /// The user. - /// user - /// - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id))) - { - throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id)); - } - - user.DateModified = DateTime.UtcNow; - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.UpdateUser(user); - - OnUserUpdated(user); - } - - public event EventHandler> UserCreated; - - private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); - - /// - /// Creates the user. - /// - /// The name. - /// User. - /// name - /// - public async Task CreateUser(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - if (!IsValidUsername(name)) - { - throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); - } - - if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - 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 list = Users.ToList(); - list.Add(user); - _users = list.ToArray(); - - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.CreateUser(user); - - EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); - - return user; - } - finally - { - _userListLock.Release(); - } - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - /// - public async Task DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var allUsers = Users.ToList(); - - if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null) - { - 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)); - } - - if (allUsers.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)); - } - - if (user.Policy.IsAdministrator && allUsers.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)); - } - - await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); - - 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); - } - finally - { - _userListLock.Release(); - } - } - - /// - /// Resets the password by clearing it. - /// - /// Task. - public Task ResetPassword(User user) - { - return ChangePassword(user, string.Empty); - } - - public void ResetEasyPassword(User user) - { - ChangeEasyPassword(user, string.Empty, null); - } - - public async Task ChangePassword(User user, string newPassword) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (newPassword != null) - { - newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword); - } - - if (string.IsNullOrWhiteSpace(newPasswordHash)) - { - throw new ArgumentNullException(nameof(newPasswordHash)); - } - - user.EasyPassword = newPasswordHash; - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - /// - /// Instantiates the new user. - /// - /// The name. - /// User. - private static User InstantiateNewUser(string name) - { - return new User - { - Name = name, - Id = Guid.NewGuid(), - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - UsesIdForConfigurationPath = true, - //Salt = BCrypt.GenerateSalt() - }; - } - - private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); - - private string _lastPin; - private PasswordPinCreationResult _lastPasswordPinCreationResult; - private int _pinAttempts; - - private async Task CreatePasswordResetPin() - { - var num = new Random().Next(1, 9999); - - var path = PasswordResetFile; - - var pin = num.ToString("0000", CultureInfo.InvariantCulture); - _lastPin = pin; - - var time = TimeSpan.FromMinutes(5); - var expiration = DateTime.UtcNow.Add(time); - - var text = new StringBuilder(); - - var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty; - - text.AppendLine("Use your web browser to visit:"); - text.AppendLine(string.Empty); - text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html"); - text.AppendLine(string.Empty); - text.AppendLine("Enter the following pin code:"); - text.AppendLine(string.Empty); - text.AppendLine(pin); - text.AppendLine(string.Empty); - - var localExpirationTime = expiration.ToLocalTime(); - // Tuesday, 22 August 2006 06:30 AM - text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture)); - - File.WriteAllText(path, text.ToString(), Encoding.UTF8); - - var result = new PasswordPinCreationResult - { - PinFile = path, - ExpirationDate = expiration - }; - - _lastPasswordPinCreationResult = result; - _pinAttempts = 0; - - return result; - } - - public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) - { - DeletePinFile(); - - var user = string.IsNullOrWhiteSpace(enteredUsername) ? - null : - GetUserByName(enteredUsername); - - var action = ForgotPasswordAction.InNetworkRequired; - string pinFile = null; - DateTime? expirationDate = null; - - if (user != null && !user.Policy.IsAdministrator) - { - action = ForgotPasswordAction.ContactAdmin; - } - else - { - if (isInNetwork) - { - action = ForgotPasswordAction.PinCode; - } - - var result = await CreatePasswordResetPin().ConfigureAwait(false); - pinFile = result.PinFile; - expirationDate = result.ExpirationDate; - } - - return new ForgotPasswordResult - { - Action = action, - PinFile = pinFile, - PinExpirationDate = expirationDate - }; - } - - public async Task RedeemPasswordResetPin(string pin) - { - DeletePinFile(); - - var usersReset = new List(); - - var valid = !string.IsNullOrWhiteSpace(_lastPin) && - string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) && - _lastPasswordPinCreationResult != null && - _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow; - - if (valid) - { - _lastPin = null; - _lastPasswordPinCreationResult = null; - - foreach (var user in Users) - { - await ResetPassword(user).ConfigureAwait(false); - - if (user.Policy.IsDisabled) - { - user.Policy.IsDisabled = false; - UpdateUserPolicy(user, user.Policy, true); - } - usersReset.Add(user.Name); - } - } - else - { - _pinAttempts++; - if (_pinAttempts >= 3) - { - _lastPin = null; - _lastPasswordPinCreationResult = null; - } - } - - return new PinRedeemResult - { - Success = valid, - UsersReset = usersReset.ToArray() - }; - } - - private void DeletePinFile() - { - try - { - _fileSystem.DeleteFile(PasswordResetFile); - } - catch - { - - } - } - - class PasswordPinCreationResult - { - public string PinFile { get; set; } - public DateTime ExpirationDate { get; set; } - } - - public UserPolicy GetUserPolicy(User user) - { - var path = GetPolicyFilePath(user); - - if (!File.Exists(path)) - { - return GetDefaultPolicy(user); - } - - try - { - lock (_policySyncLock) - { - return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path); - } - } - catch (IOException) - { - return GetDefaultPolicy(user); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {path}", path); - - return GetDefaultPolicy(user); - } - } - - private static UserPolicy GetDefaultPolicy(User user) - { - return new UserPolicy - { - EnableContentDownloading = true, - EnableSyncTranscoding = true - }; - } - - private readonly object _policySyncLock = new object(); - public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) - { - var user = GetUserById(userId); - UpdateUserPolicy(user, userPolicy, true); - } - - private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent) - { - // The xml serializer will output differently if the type is not exact - if (userPolicy.GetType() != typeof(UserPolicy)) - { - var json = _jsonSerializer.SerializeToString(userPolicy); - userPolicy = _jsonSerializer.DeserializeFromString(json); - } - - var path = GetPolicyFilePath(user); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_policySyncLock) - { - _xmlSerializer.SerializeToFile(userPolicy, path); - user.Policy = userPolicy; - } - - if (fireEvent) - { - UserPolicyUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - } - - private void DeleteUserPolicy(User user) - { - var path = GetPolicyFilePath(user); - - try - { - lock (_policySyncLock) - { - _fileSystem.DeleteFile(path); - } - } - catch (IOException) - { - - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deleting policy file"); - } - } - - private static string GetPolicyFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml"); - } - - private static string GetConfigurationFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "config.xml"); - } - - public UserConfiguration GetUserConfiguration(User user) - { - var path = GetConfigurationFilePath(user); - - if (!File.Exists(path)) - { - return new UserConfiguration(); - } - - try - { - lock (_configSyncLock) - { - return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path); - } - } - catch (IOException) - { - return new UserConfiguration(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {path}", path); - - return new UserConfiguration(); - } - } - - private readonly object _configSyncLock = new object(); - public void UpdateConfiguration(Guid userId, UserConfiguration config) - { - var user = GetUserById(userId); - UpdateConfiguration(user, config); - } - - public void UpdateConfiguration(User user, UserConfiguration config) - { - UpdateConfiguration(user, config, true); - } - - private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent) - { - var path = GetConfigurationFilePath(user); - - // The xml serializer will output differently if the type is not exact - if (config.GetType() != typeof(UserConfiguration)) - { - var json = _jsonSerializer.SerializeToString(config); - config = _jsonSerializer.DeserializeFromString(json); - } - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_configSyncLock) - { - _xmlSerializer.SerializeToFile(config, path); - user.Configuration = config; - } - - if (fireEvent) - { - UserConfigurationUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - } - } - - public class DeviceAccessEntryPoint : IServerEntryPoint - { - private IUserManager _userManager; - private IAuthenticationRepository _authRepo; - private IDeviceManager _deviceManager; - private ISessionManager _sessionManager; - - public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) - { - _userManager = userManager; - _authRepo = authRepo; - _deviceManager = deviceManager; - _sessionManager = sessionManager; - } - - public Task RunAsync() - { - _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; - - return Task.CompletedTask; - } - - private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) - { - var user = e.Argument; - if (!user.Policy.EnableAllDevices) - { - UpdateDeviceAccess(user); - } - } - - private void UpdateDeviceAccess(User user) - { - var existing = _authRepo.Get(new AuthenticationInfoQuery - { - UserId = user.Id - - }).Items; - - foreach (var authInfo in existing) - { - if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) - { - _sessionManager.Logout(authInfo); - } - } - } - - public void Dispose() - { - - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +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; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library +{ + /// + /// Class UserManager + /// + public class UserManager : IUserManager + { + /// + /// Gets the users. + /// + /// The users. + public IEnumerable Users => _users; + + private User[] _users; + + /// + /// The _logger + /// + private readonly ILogger _logger; + + /// + /// Gets or sets the configuration manager. + /// + /// The configuration manager. + private IServerConfigurationManager ConfigurationManager { get; set; } + + /// + /// Gets the active user repository + /// + /// The user repository. + private IUserRepository UserRepository { get; set; } + public event EventHandler> UserPasswordChanged; + + private readonly IXmlSerializer _xmlSerializer; + private readonly IJsonSerializer _jsonSerializer; + + private readonly INetworkManager _networkManager; + + private readonly Func _imageProcessorFactory; + private readonly Func _dtoServiceFactory; + private readonly IServerApplicationHost _appHost; + private readonly IFileSystem _fileSystem; + + private IAuthenticationProvider[] _authenticationProviders; + private DefaultAuthenticationProvider _defaultAuthenticationProvider; + + public UserManager( + ILoggerFactory loggerFactory, + IServerConfigurationManager configurationManager, + IUserRepository userRepository, + IXmlSerializer xmlSerializer, + INetworkManager networkManager, + Func imageProcessorFactory, + Func dtoServiceFactory, + IServerApplicationHost appHost, + IJsonSerializer jsonSerializer, + IFileSystem fileSystem) + { + _logger = loggerFactory.CreateLogger(nameof(UserManager)); + UserRepository = userRepository; + _xmlSerializer = xmlSerializer; + _networkManager = networkManager; + _imageProcessorFactory = imageProcessorFactory; + _dtoServiceFactory = dtoServiceFactory; + _appHost = appHost; + _jsonSerializer = jsonSerializer; + _fileSystem = fileSystem; + ConfigurationManager = configurationManager; + _users = Array.Empty(); + + DeletePinFile(); + } + + public NameIdPair[] GetAuthenticationProviders() + { + return _authenticationProviders + .Where(i => i.IsEnabled) + .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = GetAuthenticationProviderId(i) + }) + .ToArray(); + } + + public void AddParts(IEnumerable authenticationProviders) + { + _authenticationProviders = authenticationProviders.ToArray(); + + _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); + } + + #region UserUpdated Event + /// + /// Occurs when [user updated]. + /// + public event EventHandler> UserUpdated; + public event EventHandler> UserPolicyUpdated; + public event EventHandler> UserConfigurationUpdated; + public event EventHandler> UserLockedOut; + + /// + /// Called when [user updated]. + /// + /// The user. + private void OnUserUpdated(User user) + { + UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + #endregion + + #region UserDeleted Event + /// + /// Occurs when [user deleted]. + /// + public event EventHandler> UserDeleted; + /// + /// Called when [user deleted]. + /// + /// The user. + private void OnUserDeleted(User user) + { + UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); + } + #endregion + + /// + /// Gets a User by Id + /// + /// The id. + /// User. + /// + public User GetUserById(Guid id) + { + if (id == Guid.Empty) + { + throw new ArgumentException(nameof(id), "Guid can't be empty"); + } + + return Users.FirstOrDefault(u => u.Id == id); + } + + /// + /// Gets the user by identifier. + /// + /// The identifier. + /// User. + public User GetUserById(string id) + { + return GetUserById(new Guid(id)); + } + + public User GetUserByName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase)); + } + + public void Initialize() + { + _users = LoadUsers(); + + var users = Users.ToList(); + + // If there are no local users with admin rights, make them all admins + if (!users.Any(i => i.Policy.IsAdministrator)) + { + foreach (var user in users) + { + user.Policy.IsAdministrator = true; + UpdateUserPolicy(user, user.Policy, false); + } + } + } + + public static bool IsValidUsername(string username) + { + // 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 (.) + return Regex.IsMatch(username, @"^[\w\-'._@]*$"); + } + + private static bool IsValidUsernameCharacter(char i) + { + return IsValidUsername(i.ToString()); + } + + public string MakeValidUsername(string username) + { + if (IsValidUsername(username)) + { + return username; + } + + // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) + var builder = new StringBuilder(); + + foreach (var c in username) + { + if (IsValidUsernameCharacter(c)) + { + builder.Append(c); + } + } + return builder.ToString(); + } + + public async Task AuthenticateUser(string username, string password, string hashedPassword, string remoteEndPoint, bool isUserSession) + { + if (string.IsNullOrWhiteSpace(username)) + { + throw new ArgumentNullException(nameof(username)); + } + + var user = Users + .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + + var success = false; + IAuthenticationProvider authenticationProvider = null; + + if (user != null) + { + var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); + authenticationProvider = authResult.Item1; + success = authResult.Item2; + } + else + { + // user is null + var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); + authenticationProvider = authResult.Item1; + success = authResult.Item2; + + if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) + { + user = await CreateUser(username).ConfigureAwait(false); + + var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; + if (hasNewUserPolicy != null) + { + var policy = hasNewUserPolicy.GetNewUserPolicy(); + UpdateUserPolicy(user, policy, true); + } + } + } + + if (success && user != null && authenticationProvider != null) + { + var providerId = GetAuthenticationProviderId(authenticationProvider); + + if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) + { + user.Policy.AuthenticationProviderId = providerId; + UpdateUserPolicy(user, user.Policy, true); + } + } + + if (user == null) + { + throw new SecurityException("Invalid username or password entered."); + } + + if (user.Policy.IsDisabled) + { + throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); + } + + if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) + { + throw new SecurityException("Forbidden."); + } + + if (!user.IsParentalScheduleAllowed()) + { + throw new SecurityException("User is not allowed access at this time."); + } + + // Update LastActivityDate and LastLoginDate, then save + if (success) + { + if (isUserSession) + { + user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; + UpdateUser(user); + } + UpdateInvalidLoginAttemptCount(user, 0); + } + else + { + UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1); + } + + _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied"); + + return success ? user : null; + } + + private static string GetAuthenticationProviderId(IAuthenticationProvider provider) + { + return provider.GetType().FullName; + } + + private IAuthenticationProvider GetAuthenticationProvider(User user) + { + return GetAuthenticationProviders(user).First(); + } + + private IAuthenticationProvider[] GetAuthenticationProviders(User user) + { + var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; + + var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(authenticationProviderId)) + { + providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); + } + + if (providers.Length == 0) + { + providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; + } + + return providers; + } + + private async Task AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) + { + try + { + var requiresResolvedUser = provider as IRequiresResolvedUser; + if (requiresResolvedUser != null) + { + await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); + } + else + { + await provider.Authenticate(username, password).ConfigureAwait(false); + } + + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name); + + return false; + } + } + + private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) + { + bool success = false; + IAuthenticationProvider authenticationProvider = null; + + if (password != null && user != null) + { + // Doesn't look like this is even possible to be used, because of password == null checks below + hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password); + } + + if (password == null) + { + // legacy + success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + foreach (var provider in GetAuthenticationProviders(user)) + { + success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + + if (success) + { + authenticationProvider = provider; + break; + } + } + } + + if (user != null) + { + if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) + { + if (password == null) + { + // 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 new Tuple(authenticationProvider, success); + } + + private void UpdateInvalidLoginAttemptCount(User user, int newValue) + { + if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) + { + return; + } + + user.Policy.InvalidLoginAttemptCount = newValue; + + // Check for users without a value here and then fill in the default value + // also protect from an always lockout if misconfigured + if (user.Policy.LoginAttemptsBeforeLockout == null || user.Policy.LoginAttemptsBeforeLockout == 0) + { + user.Policy.LoginAttemptsBeforeLockout = user.Policy.IsAdministrator ? 5 : 3; + } + + var maxCount = user.Policy.LoginAttemptsBeforeLockout; + + var fireLockout = false; + + // -1 can be used to specify no lockout value + if (maxCount != -1 && newValue >= maxCount) + { + _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue); + user.Policy.IsDisabled = true; + + fireLockout = true; + } + + UpdateUserPolicy(user, user.Policy, false); + + if (fireLockout) + { + UserLockedOut?.Invoke(this, new GenericEventArgs(user)); + } + } + + private string GetLocalPasswordHash(User user) + { + return string.IsNullOrEmpty(user.EasyPassword) + ? null + : user.EasyPassword; + } + + /// + /// Loads the users from the repository + /// + /// IEnumerable{User}. + private User[] LoadUsers() + { + var users = UserRepository.RetrieveAllUsers(); + + // There always has to be at least one user. + if (users.Count == 0) + { + var defaultName = Environment.UserName; + if (string.IsNullOrWhiteSpace(defaultName)) + { + defaultName = "MyJellyfinUser"; + } + var name = MakeValidUsername(defaultName); + + var user = InstantiateNewUser(name); + + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.CreateUser(user); + + users.Add(user); + + user.Policy.IsAdministrator = true; + user.Policy.EnableContentDeletion = true; + user.Policy.EnableRemoteControlOfOtherUsers = true; + UpdateUserPolicy(user, user.Policy, false); + } + + return users.ToArray(); + } + + public UserDto GetUserDto(User user, string remoteEndPoint = null) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; + bool hasConfiguredEasyPassword = string.IsNullOrEmpty(GetLocalPasswordHash(user)); + + bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? + hasConfiguredEasyPassword : + hasConfiguredPassword; + + UserDto dto = new UserDto + { + Id = user.Id, + Name = user.Name, + HasPassword = hasPassword, + HasConfiguredPassword = hasConfiguredPassword, + HasConfiguredEasyPassword = hasConfiguredEasyPassword, + LastActivityDate = user.LastActivityDate, + LastLoginDate = user.LastLoginDate, + Configuration = user.Configuration, + ServerId = _appHost.SystemId, + Policy = user.Policy + }; + + if (!hasPassword && Users.Count() == 1) + { + dto.EnableAutoLogin = true; + } + + ItemImageInfo image = user.GetImageInfo(ImageType.Primary, 0); + + if (image != null) + { + dto.PrimaryImageTag = GetImageCacheTag(user, image); + + try + { + _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user); + } + catch (Exception ex) + { + // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions + _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name); + } + } + + return dto; + } + + public UserDto GetOfflineUserDto(User user) + { + var dto = GetUserDto(user); + + dto.ServerName = _appHost.FriendlyName; + + return dto; + } + + private string GetImageCacheTag(BaseItem item, ItemImageInfo image) + { + try + { + return _imageProcessorFactory().GetImageCacheTag(item, image); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path); + return null; + } + } + + /// + /// Refreshes metadata for each user + /// + /// The cancellation token. + /// Task. + public async Task RefreshUsersMetadata(CancellationToken cancellationToken) + { + foreach (var user in Users) + { + await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Renames the user. + /// + /// The user. + /// The new name. + /// Task. + /// user + /// + public async Task RenameUser(User user, string newName) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrEmpty(newName)) + { + throw new ArgumentNullException(nameof(newName)); + } + + if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName)); + } + + if (user.Name.Equals(newName, StringComparison.Ordinal)) + { + throw new ArgumentException("The new and old names must be different."); + } + + await user.Rename(newName); + + OnUserUpdated(user); + } + + /// + /// Updates the user. + /// + /// The user. + /// user + /// + public void UpdateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id))) + { + throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id)); + } + + user.DateModified = DateTime.UtcNow; + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.UpdateUser(user); + + OnUserUpdated(user); + } + + public event EventHandler> UserCreated; + + private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); + + /// + /// Creates the user. + /// + /// The name. + /// User. + /// name + /// + public async Task CreateUser(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + if (!IsValidUsername(name)) + { + throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); + } + + if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + 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 list = Users.ToList(); + list.Add(user); + _users = list.ToArray(); + + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.CreateUser(user); + + EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); + + return user; + } + finally + { + _userListLock.Release(); + } + } + + /// + /// Deletes the user. + /// + /// The user. + /// Task. + /// user + /// + public async Task DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var allUsers = Users.ToList(); + + if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null) + { + 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)); + } + + if (allUsers.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)); + } + + if (user.Policy.IsAdministrator && allUsers.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)); + } + + await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + + 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); + } + finally + { + _userListLock.Release(); + } + } + + /// + /// Resets the password by clearing it. + /// + /// Task. + public Task ResetPassword(User user) + { + return ChangePassword(user, string.Empty); + } + + public void ResetEasyPassword(User user) + { + ChangeEasyPassword(user, string.Empty, null); + } + + public async Task ChangePassword(User user, string newPassword) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); + + UpdateUser(user); + + UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (newPassword != null) + { + newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException(nameof(newPasswordHash)); + } + + user.EasyPassword = newPasswordHash; + + UpdateUser(user); + + UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + /// + /// Instantiates the new user. + /// + /// The name. + /// User. + private static User InstantiateNewUser(string name) + { + return new User + { + Name = name, + Id = Guid.NewGuid(), + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow, + UsesIdForConfigurationPath = true, + //Salt = BCrypt.GenerateSalt() + }; + } + + private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); + + private string _lastPin; + private PasswordPinCreationResult _lastPasswordPinCreationResult; + private int _pinAttempts; + + private async Task CreatePasswordResetPin() + { + var num = new Random().Next(1, 9999); + + var path = PasswordResetFile; + + var pin = num.ToString("0000", CultureInfo.InvariantCulture); + _lastPin = pin; + + var time = TimeSpan.FromMinutes(5); + var expiration = DateTime.UtcNow.Add(time); + + var text = new StringBuilder(); + + var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty; + + text.AppendLine("Use your web browser to visit:"); + text.AppendLine(string.Empty); + text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html"); + text.AppendLine(string.Empty); + text.AppendLine("Enter the following pin code:"); + text.AppendLine(string.Empty); + text.AppendLine(pin); + text.AppendLine(string.Empty); + + var localExpirationTime = expiration.ToLocalTime(); + // Tuesday, 22 August 2006 06:30 AM + text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture)); + + File.WriteAllText(path, text.ToString(), Encoding.UTF8); + + var result = new PasswordPinCreationResult + { + PinFile = path, + ExpirationDate = expiration + }; + + _lastPasswordPinCreationResult = result; + _pinAttempts = 0; + + return result; + } + + public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) + { + DeletePinFile(); + + var user = string.IsNullOrWhiteSpace(enteredUsername) ? + null : + GetUserByName(enteredUsername); + + var action = ForgotPasswordAction.InNetworkRequired; + string pinFile = null; + DateTime? expirationDate = null; + + if (user != null && !user.Policy.IsAdministrator) + { + action = ForgotPasswordAction.ContactAdmin; + } + else + { + if (isInNetwork) + { + action = ForgotPasswordAction.PinCode; + } + + var result = await CreatePasswordResetPin().ConfigureAwait(false); + pinFile = result.PinFile; + expirationDate = result.ExpirationDate; + } + + return new ForgotPasswordResult + { + Action = action, + PinFile = pinFile, + PinExpirationDate = expirationDate + }; + } + + public async Task RedeemPasswordResetPin(string pin) + { + DeletePinFile(); + + var usersReset = new List(); + + var valid = !string.IsNullOrWhiteSpace(_lastPin) && + string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) && + _lastPasswordPinCreationResult != null && + _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow; + + if (valid) + { + _lastPin = null; + _lastPasswordPinCreationResult = null; + + foreach (var user in Users) + { + await ResetPassword(user).ConfigureAwait(false); + + if (user.Policy.IsDisabled) + { + user.Policy.IsDisabled = false; + UpdateUserPolicy(user, user.Policy, true); + } + usersReset.Add(user.Name); + } + } + else + { + _pinAttempts++; + if (_pinAttempts >= 3) + { + _lastPin = null; + _lastPasswordPinCreationResult = null; + } + } + + return new PinRedeemResult + { + Success = valid, + UsersReset = usersReset.ToArray() + }; + } + + private void DeletePinFile() + { + try + { + _fileSystem.DeleteFile(PasswordResetFile); + } + catch + { + + } + } + + class PasswordPinCreationResult + { + public string PinFile { get; set; } + public DateTime ExpirationDate { get; set; } + } + + public UserPolicy GetUserPolicy(User user) + { + var path = GetPolicyFilePath(user); + + if (!File.Exists(path)) + { + return GetDefaultPolicy(user); + } + + try + { + lock (_policySyncLock) + { + return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path); + } + } + catch (IOException) + { + return GetDefaultPolicy(user); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading policy file: {path}", path); + + return GetDefaultPolicy(user); + } + } + + private static UserPolicy GetDefaultPolicy(User user) + { + return new UserPolicy + { + EnableContentDownloading = true, + EnableSyncTranscoding = true + }; + } + + private readonly object _policySyncLock = new object(); + public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) + { + var user = GetUserById(userId); + UpdateUserPolicy(user, userPolicy, true); + } + + private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent) + { + // The xml serializer will output differently if the type is not exact + if (userPolicy.GetType() != typeof(UserPolicy)) + { + var json = _jsonSerializer.SerializeToString(userPolicy); + userPolicy = _jsonSerializer.DeserializeFromString(json); + } + + var path = GetPolicyFilePath(user); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_policySyncLock) + { + _xmlSerializer.SerializeToFile(userPolicy, path); + user.Policy = userPolicy; + } + + if (fireEvent) + { + UserPolicyUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + } + + private void DeleteUserPolicy(User user) + { + var path = GetPolicyFilePath(user); + + try + { + lock (_policySyncLock) + { + _fileSystem.DeleteFile(path); + } + } + catch (IOException) + { + + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting policy file"); + } + } + + private static string GetPolicyFilePath(User user) + { + return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml"); + } + + private static string GetConfigurationFilePath(User user) + { + return Path.Combine(user.ConfigurationDirectoryPath, "config.xml"); + } + + public UserConfiguration GetUserConfiguration(User user) + { + var path = GetConfigurationFilePath(user); + + if (!File.Exists(path)) + { + return new UserConfiguration(); + } + + try + { + lock (_configSyncLock) + { + return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path); + } + } + catch (IOException) + { + return new UserConfiguration(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading policy file: {path}", path); + + return new UserConfiguration(); + } + } + + private readonly object _configSyncLock = new object(); + public void UpdateConfiguration(Guid userId, UserConfiguration config) + { + var user = GetUserById(userId); + UpdateConfiguration(user, config); + } + + public void UpdateConfiguration(User user, UserConfiguration config) + { + UpdateConfiguration(user, config, true); + } + + private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent) + { + var path = GetConfigurationFilePath(user); + + // The xml serializer will output differently if the type is not exact + if (config.GetType() != typeof(UserConfiguration)) + { + var json = _jsonSerializer.SerializeToString(config); + config = _jsonSerializer.DeserializeFromString(json); + } + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_configSyncLock) + { + _xmlSerializer.SerializeToFile(config, path); + user.Configuration = config; + } + + if (fireEvent) + { + UserConfigurationUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + } + } + + public class DeviceAccessEntryPoint : IServerEntryPoint + { + private IUserManager _userManager; + private IAuthenticationRepository _authRepo; + private IDeviceManager _deviceManager; + private ISessionManager _sessionManager; + + public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) + { + _userManager = userManager; + _authRepo = authRepo; + _deviceManager = deviceManager; + _sessionManager = sessionManager; + } + + public Task RunAsync() + { + _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; + + return Task.CompletedTask; + } + + private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) + { + var user = e.Argument; + if (!user.Policy.EnableAllDevices) + { + UpdateDeviceAccess(user); + } + } + + private void UpdateDeviceAccess(User user) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + UserId = user.Id + + }).Items; + + foreach (var authInfo in existing) + { + if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) + { + _sessionManager.Logout(authInfo); + } + } + } + + public void Dispose() + { + + } + } +} -- cgit v1.2.3 From 4cd8903abc1e0c19d717e2930382c37836706e88 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 19 Mar 2019 23:13:02 -0400 Subject: Fix default value for Expires header --- Emby.Server.Implementations/HttpServer/HttpResultFactory.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 4632658626..134f3c8418 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.HttpServer if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string expires)) { - responseHeaders[HeaderNames.Expires] = "-1"; + responseHeaders[HeaderNames.Expires] = "0"; } AddResponseHeaders(result, responseHeaders); @@ -146,7 +146,7 @@ namespace Emby.Server.Implementations.HttpServer if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _)) { - responseHeaders[HeaderNames.Expires] = "-1"; + responseHeaders[HeaderNames.Expires] = "0"; } AddResponseHeaders(result, responseHeaders); @@ -190,7 +190,7 @@ namespace Emby.Server.Implementations.HttpServer if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _)) { - responseHeaders[HeaderNames.Expires] = "-1"; + responseHeaders[HeaderNames.Expires] = "0"; } AddResponseHeaders(result, responseHeaders); @@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); } - responseHeaders[HeaderNames.Expires] = "-1"; + responseHeaders[HeaderNames.Expires] = "0"; return ToOptimizedResultInternal(requestContext, result, responseHeaders); } -- cgit v1.2.3 From 09921a00aaad31c0ea4a0650e8d0ddb890dca735 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Fri, 22 Mar 2019 00:01:23 -0700 Subject: made password resets an interface and per user --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Library/DefaultPasswordResetProvider.cs | 118 +++++++++++++ Emby.Server.Implementations/Library/UserManager.cs | 192 +++++++-------------- MediaBrowser.Api/Session/SessionsService.cs | 11 ++ .../Authentication/IPasswordResetProvider.cs | 20 +++ MediaBrowser.Controller/Library/IUserManager.cs | 3 +- MediaBrowser.Model/Users/UserPolicy.cs | 1 + 7 files changed, 220 insertions(+), 127 deletions(-) create mode 100644 Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs create mode 100644 MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 484942946c..fc1b2eda83 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1088,7 +1088,7 @@ namespace Emby.Server.Implementations MediaSourceManager.AddParts(GetExports()); NotificationManager.AddParts(GetExports(), GetExports()); - UserManager.AddParts(GetExports()); + UserManager.AddParts(GetExports(), GetExports()); IsoManager.AddParts(GetExports()); } diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs new file mode 100644 index 0000000000..ae6fe8239a --- /dev/null +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; +using ServiceStack; +using TvDbSharper.Dto; + +namespace Emby.Server.Implementations.Library +{ + public class DefaultPasswordResetProvider : IPasswordResetProvider + { + public string Name => "Default Password Reset Provider"; + + public bool IsEnabled => true; + + private readonly string _passwordResetFileBase; + private readonly string _passwordResetFileBaseDir; + private readonly string _passwordResetFileBaseName = "passwordreset"; + + private IJsonSerializer _jsonSerializer; + private IUserManager _userManager; + + public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager) + { + _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; + _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName); + _jsonSerializer = jsonSerializer; + _userManager = userManager; + } + + public async Task RedeemPasswordResetPin(string pin) + { + HashSet usersreset = new HashSet(); + foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) + { + var spr = (SerializablePasswordReset) _jsonSerializer.DeserializeFromFile(typeof(SerializablePasswordReset), resetfile); + if (spr.ExpirationDate > DateTime.Now) + { + File.Delete(resetfile); + } + else + { + if (spr.Pin == pin) + { + var resetUser = _userManager.GetUserByName(spr.UserName); + if (!string.IsNullOrEmpty(resetUser.Password)) + { + await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); + usersreset.Add(resetUser.Name); + } + } + } + } + + if (usersreset.Count < 1) + { + throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); + } + else + { + return new PinRedeemResult + { + Success = true, + UsersReset = usersreset.ToArray() + }; + } + throw new System.NotImplementedException(); + } + + public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) + { + string pin = new Random().Next(99999999).ToString("00000000",CultureInfo.InvariantCulture); + DateTime expireTime = DateTime.Now.AddMinutes(30); + string filePath = _passwordResetFileBase + user.Name.ToLowerInvariant() + ".json"; + SerializablePasswordReset spr = new SerializablePasswordReset + { + ExpirationDate = expireTime, + Pin = pin, + PinFile = filePath, + UserName = user.Name + }; + + try + { + await Task.Run(() => File.WriteAllText(filePath, _jsonSerializer.SerializeToString(spr))).ConfigureAwait(false); + } + catch (Exception e) + { + throw new Exception($"Error serializing or writing password reset for {user.Name} to location:{filePath}", e); + } + + return new ForgotPasswordResult + { + Action = ForgotPasswordAction.PinCode, + PinExpirationDate = expireTime, + PinFile = filePath + }; + } + + private class SerializablePasswordReset : PasswordPinCreationResult + { + public string Pin { get; set; } + + public string UserName { get; set; } + } + } +} diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 4cf703add5..500bb8d665 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -79,6 +79,10 @@ namespace Emby.Server.Implementations.Library private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; + private IPasswordResetProvider[] _passwordResetProviders; + private DefaultPasswordResetProvider _defaultPasswordResetProvider; + private Dictionary _activeResets = new Dictionary(); + public UserManager( ILoggerFactory loggerFactory, IServerConfigurationManager configurationManager, @@ -102,8 +106,6 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; ConfigurationManager = configurationManager; _users = Array.Empty(); - - DeletePinFile(); } public NameIdPair[] GetAuthenticationProviders() @@ -120,11 +122,29 @@ namespace Emby.Server.Implementations.Library .ToArray(); } - public void AddParts(IEnumerable authenticationProviders) + public NameIdPair[] GetPasswordResetProviders() + { + return _passwordResetProviders + .Where(i => i.IsEnabled) + .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = GetPasswordResetProviderId(i) + }) + .ToArray(); + } + + public void AddParts(IEnumerable authenticationProviders,IEnumerable passwordResetProviders) { _authenticationProviders = authenticationProviders.ToArray(); _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); + + _passwordResetProviders = passwordResetProviders.ToArray(); + + _defaultPasswordResetProvider = passwordResetProviders.OfType().First(); } #region UserUpdated Event @@ -342,11 +362,21 @@ namespace Emby.Server.Implementations.Library return provider.GetType().FullName; } + private static string GetPasswordResetProviderId(IPasswordResetProvider provider) + { + return provider.GetType().FullName; + } + private IAuthenticationProvider GetAuthenticationProvider(User user) { return GetAuthenticationProviders(user).First(); } + private IPasswordResetProvider GetPasswordResetProvider(User user) + { + return GetPasswordResetProviders(user).First(); + } + private IAuthenticationProvider[] GetAuthenticationProviders(User user) { var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; @@ -366,6 +396,25 @@ namespace Emby.Server.Implementations.Library return providers; } + private IPasswordResetProvider[] GetPasswordResetProviders(User user) + { + var passwordResetProviderId = user == null ? null : user.Policy.PasswordResetProviderId; + + var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(passwordResetProviderId)) + { + providers = providers.Where(i => string.Equals(passwordResetProviderId, GetPasswordResetProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); + } + + if (providers.Length == 0) + { + providers = new IPasswordResetProvider[] { _defaultPasswordResetProvider }; + } + + return providers; + } + private async Task AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) { try @@ -844,159 +893,52 @@ namespace Emby.Server.Implementations.Library Id = Guid.NewGuid(), DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow, - UsesIdForConfigurationPath = true, - //Salt = BCrypt.GenerateSalt() - }; - } - - private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); - - private string _lastPin; - private PasswordPinCreationResult _lastPasswordPinCreationResult; - private int _pinAttempts; - - private async Task CreatePasswordResetPin() - { - var num = new Random().Next(1, 9999); - - var path = PasswordResetFile; - - var pin = num.ToString("0000", CultureInfo.InvariantCulture); - _lastPin = pin; - - var time = TimeSpan.FromMinutes(5); - var expiration = DateTime.UtcNow.Add(time); - - var text = new StringBuilder(); - - var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty; - - text.AppendLine("Use your web browser to visit:"); - text.AppendLine(string.Empty); - text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html"); - text.AppendLine(string.Empty); - text.AppendLine("Enter the following pin code:"); - text.AppendLine(string.Empty); - text.AppendLine(pin); - text.AppendLine(string.Empty); - - var localExpirationTime = expiration.ToLocalTime(); - // Tuesday, 22 August 2006 06:30 AM - text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture)); - - File.WriteAllText(path, text.ToString(), Encoding.UTF8); - - var result = new PasswordPinCreationResult - { - PinFile = path, - ExpirationDate = expiration + UsesIdForConfigurationPath = true }; - - _lastPasswordPinCreationResult = result; - _pinAttempts = 0; - - return result; } public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) { - DeletePinFile(); - var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); var action = ForgotPasswordAction.InNetworkRequired; - string pinFile = null; - DateTime? expirationDate = null; - if (user != null && !user.Policy.IsAdministrator) + if (user != null && isInNetwork) { - action = ForgotPasswordAction.ContactAdmin; + var passwordResetProvider = GetPasswordResetProvider(user); + _activeResets.Add(user.Name, passwordResetProvider); + return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); } else { - if (isInNetwork) + return new ForgotPasswordResult { - action = ForgotPasswordAction.PinCode; - } - - var result = await CreatePasswordResetPin().ConfigureAwait(false); - pinFile = result.PinFile; - expirationDate = result.ExpirationDate; + Action = action, + PinFile = string.Empty + }; } - - return new ForgotPasswordResult - { - Action = action, - PinFile = pinFile, - PinExpirationDate = expirationDate - }; } public async Task RedeemPasswordResetPin(string pin) { - DeletePinFile(); - - var usersReset = new List(); - - var valid = !string.IsNullOrWhiteSpace(_lastPin) && - string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) && - _lastPasswordPinCreationResult != null && - _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow; - - if (valid) + foreach (var provider in _passwordResetProviders) { - _lastPin = null; - _lastPasswordPinCreationResult = null; - - foreach (var user in Users) + var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false); + if (result.Success) { - await ResetPassword(user).ConfigureAwait(false); - - if (user.Policy.IsDisabled) - { - user.Policy.IsDisabled = false; - UpdateUserPolicy(user, user.Policy, true); - } - usersReset.Add(user.Name); - } - } - else - { - _pinAttempts++; - if (_pinAttempts >= 3) - { - _lastPin = null; - _lastPasswordPinCreationResult = null; + return result; } } return new PinRedeemResult { - Success = valid, - UsersReset = usersReset.ToArray() + Success = false, + UsersReset = Array.Empty() }; } - private void DeletePinFile() - { - try - { - _fileSystem.DeleteFile(PasswordResetFile); - } - catch - { - - } - } - - class PasswordPinCreationResult - { - public string PinFile { get; set; } - public DateTime ExpirationDate { get; set; } - } - public UserPolicy GetUserPolicy(User user) { var path = GetPolicyFilePath(user); diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index f011e6e417..4109b12bfa 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -245,6 +245,12 @@ namespace MediaBrowser.Api.Session { } + [Route("/Auth/PasswordResetProviders", "GET")] + [Authenticated(Roles = "Admin")] + public class GetPasswordResetProviders : IReturn + { + } + [Route("/Auth/Keys/{Key}", "DELETE")] [Authenticated(Roles = "Admin")] public class RevokeKey @@ -294,6 +300,11 @@ namespace MediaBrowser.Api.Session return _userManager.GetAuthenticationProviders(); } + public object Get(GetPasswordResetProviders request) + { + return _userManager.GetPasswordResetProviders(); + } + public void Delete(RevokeKey request) { _sessionManager.RevokeToken(request.Key); diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs new file mode 100644 index 0000000000..9e5cd88160 --- /dev/null +++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Users; + +namespace MediaBrowser.Controller.Authentication +{ + public interface IPasswordResetProvider + { + string Name { get; } + bool IsEnabled { get; } + Task StartForgotPasswordProcess(User user, bool isInNetwork); + Task RedeemPasswordResetPin(string pin); + } + public class PasswordPinCreationResult + { + public string PinFile { get; set; } + public DateTime ExpirationDate { get; set; } + } +} diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 925d91a375..7f73708931 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -200,8 +200,9 @@ namespace MediaBrowser.Controller.Library /// System.String. string MakeValidUsername(string username); - void AddParts(IEnumerable authenticationProviders); + void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders); NameIdPair[] GetAuthenticationProviders(); + NameIdPair[] GetPasswordResetProviders(); } } diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 5415fd5e81..f63ab2bb4c 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -75,6 +75,7 @@ namespace MediaBrowser.Model.Users public int RemoteClientBitrateLimit { get; set; } public string AuthenticationProviderId { get; set; } + public string PasswordResetProviderId { get; set; } public UserPolicy() { -- cgit v1.2.3 From 758e35baba95278fb3b55a89dc9295e6f6dad5ac Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sun, 24 Mar 2019 00:30:16 -0700 Subject: greaterthen/lessthen reversal fix --- Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index ae6fe8239a..2e537c7e5c 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.Library foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) { var spr = (SerializablePasswordReset) _jsonSerializer.DeserializeFromFile(typeof(SerializablePasswordReset), resetfile); - if (spr.ExpirationDate > DateTime.Now) + if (spr.ExpirationDate < DateTime.Now) { File.Delete(resetfile); } @@ -111,8 +111,8 @@ namespace Emby.Server.Implementations.Library private class SerializablePasswordReset : PasswordPinCreationResult { public string Pin { get; set; } - + public string UserName { get; set; } } } -} +} -- cgit v1.2.3 From 414a318a0d422a893401d1ecf84526043df36210 Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 11:59:40 +0100 Subject: WAN Address should use public ports instead of local ports. https://github.com/jellyfin/jellyfin/issues/601#issuecomment-475941080 --- Emby.Server.Implementations/ApplicationHost.cs | 39 ++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 484942946c..5f4c30f0f1 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1476,7 +1476,7 @@ namespace Emby.Server.Implementations CancellationToken = cancellationToken }).ConfigureAwait(false)) { - return GetLocalApiUrl(response.ReadToEnd().Trim()); + return GetWanApiUrl(response.ReadToEnd().Trim()); } } catch (Exception ex) @@ -1493,16 +1493,45 @@ namespace Emby.Server.Implementations return GetLocalApiUrl("[" + ipAddress.Address + "]"); } - return GetLocalApiUrl(ipAddress.Address); + return GetLocalApiUrlWithPort(ipAddress.Address); } - public string GetLocalApiUrl(string host) + public string GetLocalApiUrlWithPort(string host) { + if (EnableHttps) + { + return string.Format("http://{0}:{1}", + host, + HttpsPort.ToString(CultureInfo.InvariantCulture)); + } return string.Format("http://{0}:{1}", - host, - HttpPort.ToString(CultureInfo.InvariantCulture)); + host, + HttpPort.ToString(CultureInfo.InvariantCulture)); } + public string GetWanApiUrl(IpAddressInfo ipAddress) + { + if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6) + { + return GetLocalApiUrl("[" + ipAddress.Address + "]"); + } + + return GetWanApiUrlWithPort(ipAddress.Address); + } + + public string GetWanApiUrlWithPort(string host) + { + if (EnableHttps) + { + return string.Format("http://{0}:{1}", + host, + ServerConfiguration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture)); + } + return string.Format("http://{0}:{1}", + host, + ServerConfiguration.PublicPort.ToString(CultureInfo.InvariantCulture)); + } + public Task> GetLocalIpAddresses(CancellationToken cancellationToken) { return GetLocalIpAddressesInternal(true, 0, cancellationToken); -- cgit v1.2.3 From 69cc5814d85733f5bec9cac03e78caa1324406fe Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 12:11:46 +0100 Subject: Change WAN IP behaviour: Use ServerConfiguration.WanDdns if set in configuration. --- Emby.Server.Implementations/ApplicationHost.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 5f4c30f0f1..7e236002a2 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1372,7 +1372,15 @@ namespace Emby.Server.Implementations public async Task GetSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - var wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); + + if ( String.IsNullOrEmpty(ServerConfiguration.WanDdns) ){ + var wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); + } else { + // Use the (dynmic) domain name set in the configuration if available instead of querying + // an external service to get the IP address + // The domain resolution to the ip should be part of the client, not the server. + var wanAddress = ServerConfiguration.WanDdns; + } return new SystemInfo { -- cgit v1.2.3 From 4ffec8ad260fb8829ea220137934596de19a1c1b Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 12:19:10 +0100 Subject: Fix build, missing changes. --- Emby.Server.Implementations/ApplicationHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7e236002a2..1ea9c599b0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1498,7 +1498,7 @@ namespace Emby.Server.Implementations { if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6) { - return GetLocalApiUrl("[" + ipAddress.Address + "]"); + return GetLocalApiUrlWithPort("[" + ipAddress.Address + "]"); } return GetLocalApiUrlWithPort(ipAddress.Address); @@ -1521,7 +1521,7 @@ namespace Emby.Server.Implementations { if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6) { - return GetLocalApiUrl("[" + ipAddress.Address + "]"); + return GetWanApiUrlWithPort("[" + ipAddress.Address + "]"); } return GetWanApiUrlWithPort(ipAddress.Address); -- cgit v1.2.3 From f30af9cd5f484dad763eb67b5b7076cd03bd59df Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Mar 2019 16:47:42 +0100 Subject: Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 1ea9c599b0..1ecbeedad7 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1373,7 +1373,7 @@ namespace Emby.Server.Implementations { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - if ( String.IsNullOrEmpty(ServerConfiguration.WanDdns) ){ + if (string.IsNullOrEmpty(ServerConfiguration.WanDdns)){ var wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); } else { // Use the (dynmic) domain name set in the configuration if available instead of querying -- cgit v1.2.3 From cf36aaef2b1724931b94bf12b95d2b6c43f68133 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Mar 2019 16:47:48 +0100 Subject: Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 1ecbeedad7..6b7796be7b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1508,7 +1508,7 @@ namespace Emby.Server.Implementations { if (EnableHttps) { - return string.Format("http://{0}:{1}", + return string.Format("https://{0}:{1}", host, HttpsPort.ToString(CultureInfo.InvariantCulture)); } -- cgit v1.2.3 From 598b1c99660547d7c8e3b253c4664a6faa78a03d Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Mar 2019 16:47:59 +0100 Subject: Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 6b7796be7b..20a3c23ae6 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1531,7 +1531,7 @@ namespace Emby.Server.Implementations { if (EnableHttps) { - return string.Format("http://{0}:{1}", + return string.Format("https://{0}:{1}", host, ServerConfiguration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture)); } -- cgit v1.2.3 From 7ebb043249d9c13c3bf2cc41c52d71c4e73f40f9 Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 16:50:39 +0100 Subject: Removed comment, renamed methods consistently. --- Emby.Server.Implementations/ApplicationHost.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 20a3c23ae6..1a64c8eaaf 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1376,9 +1376,6 @@ namespace Emby.Server.Implementations if (string.IsNullOrEmpty(ServerConfiguration.WanDdns)){ var wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); } else { - // Use the (dynmic) domain name set in the configuration if available instead of querying - // an external service to get the IP address - // The domain resolution to the ip should be part of the client, not the server. var wanAddress = ServerConfiguration.WanDdns; } @@ -1498,13 +1495,13 @@ namespace Emby.Server.Implementations { if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6) { - return GetLocalApiUrlWithPort("[" + ipAddress.Address + "]"); + return GetLocalApiUrl("[" + ipAddress.Address + "]"); } - return GetLocalApiUrlWithPort(ipAddress.Address); + return GetLocalApiUrl(ipAddress.Address); } - public string GetLocalApiUrlWithPort(string host) + public string GetLocalApiUrl(string host) { if (EnableHttps) { @@ -1521,13 +1518,13 @@ namespace Emby.Server.Implementations { if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6) { - return GetWanApiUrlWithPort("[" + ipAddress.Address + "]"); + return GetWanApiUrl("[" + ipAddress.Address + "]"); } - return GetWanApiUrlWithPort(ipAddress.Address); + return GetWanApiUrl(ipAddress.Address); } - public string GetWanApiUrlWithPort(string host) + public string GetWanApiUrl(string host) { if (EnableHttps) { -- cgit v1.2.3 From 030fcaac156e9d75d4e7e84b0bea1291b91c0079 Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 17:02:03 +0100 Subject: Proper access to configuration objects --- Emby.Server.Implementations/ApplicationHost.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 1a64c8eaaf..fea23a3d7d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1372,11 +1372,10 @@ namespace Emby.Server.Implementations public async Task GetSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + var wanAddress = ServerConfigurationManager.Configuration.WanDdns; - if (string.IsNullOrEmpty(ServerConfiguration.WanDdns)){ + if (string.IsNullOrEmpty(wanAddress)){ var wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); - } else { - var wanAddress = ServerConfiguration.WanDdns; } return new SystemInfo @@ -1530,11 +1529,11 @@ namespace Emby.Server.Implementations { return string.Format("https://{0}:{1}", host, - ServerConfiguration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture)); + ServerConfigurationManager.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture)); } return string.Format("http://{0}:{1}", host, - ServerConfiguration.PublicPort.ToString(CultureInfo.InvariantCulture)); + ServerConfigurationManager.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture)); } public Task> GetLocalIpAddresses(CancellationToken cancellationToken) -- cgit v1.2.3 From d18252542d81e1c279b10af0198be30a8b94edea Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 17:11:21 +0100 Subject: Also add the WAN switch to the public system info. --- Emby.Server.Implementations/ApplicationHost.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index fea23a3d7d..a20838daf0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1375,7 +1375,7 @@ namespace Emby.Server.Implementations var wanAddress = ServerConfigurationManager.Configuration.WanDdns; if (string.IsNullOrEmpty(wanAddress)){ - var wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); + wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); } return new SystemInfo @@ -1426,7 +1426,11 @@ namespace Emby.Server.Implementations public async Task GetPublicSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - var wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); + var wanAddress = ServerConfigurationManager.Configuration.WanDdns; + + if (string.IsNullOrEmpty(wanAddress)){ + wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); + } return new PublicSystemInfo { Version = ApplicationVersion, -- cgit v1.2.3 From fb7f29de18bc6089e247041c7aa15b2ad7677339 Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 18:33:21 +0100 Subject: Format the WAN API Url correctly with https and Port. --- Emby.Server.Implementations/ApplicationHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a20838daf0..8f66ff5f98 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1372,7 +1372,7 @@ namespace Emby.Server.Implementations public async Task GetSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - var wanAddress = ServerConfigurationManager.Configuration.WanDdns; + var wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); if (string.IsNullOrEmpty(wanAddress)){ wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); @@ -1426,7 +1426,7 @@ namespace Emby.Server.Implementations public async Task GetPublicSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - var wanAddress = ServerConfigurationManager.Configuration.WanDdns; + var wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); if (string.IsNullOrEmpty(wanAddress)){ wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); -- cgit v1.2.3 From 26fe4040bfc9ef5f9e723e3c9a410fb24fb8b9b1 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sun, 24 Mar 2019 11:40:00 -0700 Subject: fixes some usings --- .../Library/DefaultPasswordResetProvider.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 2e537c7e5c..1ae8960eea 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -1,20 +1,15 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; -using ServiceStack; -using TvDbSharper.Dto; namespace Emby.Server.Implementations.Library { -- cgit v1.2.3 From 4e2841f0d747a9501d454fab7c7df5ce4ff86890 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Mar 2019 11:41:03 -0700 Subject: Update Emby.Server.Implementations/Library/UserManager.cs Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- Emby.Server.Implementations/Library/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 500bb8d665..bddec70ed7 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -398,7 +398,7 @@ namespace Emby.Server.Implementations.Library private IPasswordResetProvider[] GetPasswordResetProviders(User user) { - var passwordResetProviderId = user == null ? null : user.Policy.PasswordResetProviderId; + var passwordResetProviderId = user?.Policy.PasswordResetProviderId; var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); -- cgit v1.2.3 From 86772bd7bdd570264565c0078ddc66964860f389 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sun, 24 Mar 2019 12:17:32 -0700 Subject: removes needless dictionary --- Emby.Server.Implementations/Library/UserManager.cs | 2 -- 1 file changed, 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index bddec70ed7..05ec750ba9 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -81,7 +81,6 @@ namespace Emby.Server.Implementations.Library private IPasswordResetProvider[] _passwordResetProviders; private DefaultPasswordResetProvider _defaultPasswordResetProvider; - private Dictionary _activeResets = new Dictionary(); public UserManager( ILoggerFactory loggerFactory, @@ -908,7 +907,6 @@ namespace Emby.Server.Implementations.Library if (user != null && isInNetwork) { var passwordResetProvider = GetPasswordResetProvider(user); - _activeResets.Add(user.Name, passwordResetProvider); return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); } else -- cgit v1.2.3 From 087d4153aed8a82651a057ddf898dea7f97ded4a Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 21:47:18 +0100 Subject: Fix check for available WAN address. --- Emby.Server.Implementations/ApplicationHost.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8f66ff5f98..52d5255525 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1372,10 +1372,13 @@ namespace Emby.Server.Implementations public async Task GetSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - var wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); + var wanAddress = System.String.Empty; - if (string.IsNullOrEmpty(wanAddress)){ + if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)){ wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); + } else + { + wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); } return new SystemInfo @@ -1425,11 +1428,14 @@ namespace Emby.Server.Implementations public async Task GetPublicSystemInfo(CancellationToken cancellationToken) { - var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - var wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); + var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + var wanAddress = System.String.Empty; - if (string.IsNullOrEmpty(wanAddress)){ + if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)){ wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); + } else + { + wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); } return new PublicSystemInfo { -- cgit v1.2.3 From 2c4c56d6d638ee22617c000719aabcef5b949d32 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:17:40 +0100 Subject: Formatting update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 52d5255525..410f5becd6 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1376,7 +1376,8 @@ namespace Emby.Server.Implementations if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)){ wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); - } else + } + else { wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); } -- cgit v1.2.3 From 89f2dfd78a5c73c9d9d5cc832ff0427fa732ae3e Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:17:53 +0100 Subject: Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 410f5becd6..8ff17f35c0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1430,7 +1430,7 @@ namespace Emby.Server.Implementations public async Task GetPublicSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - var wanAddress = System.String.Empty; + var wanAddress = string.Empty; if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)){ wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); -- cgit v1.2.3 From 3474568ce2864d05fc47575fed3bfe5c7b21a435 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:18:04 +0100 Subject: Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8ff17f35c0..ba705f1f24 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1372,7 +1372,7 @@ namespace Emby.Server.Implementations public async Task GetSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - var wanAddress = System.String.Empty; + var wanAddress = string.Empty; if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)){ wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); -- cgit v1.2.3 From f7e7d726880f87c18e799420ee6eb0ab42d86a47 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:18:18 +0100 Subject: Formatting update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ba705f1f24..5c5e8e5bf5 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1374,7 +1374,8 @@ namespace Emby.Server.Implementations var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); var wanAddress = string.Empty; - if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)){ + if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)) + { wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); } else -- cgit v1.2.3 From e36d424b5f61e2785c26af49db7d120d66933935 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:18:47 +0100 Subject: Formatting update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 5c5e8e5bf5..9ace9a48f4 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1433,7 +1433,8 @@ namespace Emby.Server.Implementations var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); var wanAddress = string.Empty; - if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)){ + if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)) + { wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); } else { -- cgit v1.2.3 From 6480cfcc87b65331b15bf787633f978b30e2e829 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:19:08 +0100 Subject: Formatting update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 9ace9a48f4..7c8fb7f62b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1436,7 +1436,8 @@ namespace Emby.Server.Implementations if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)) { wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); - } else + } + else { wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); } -- cgit v1.2.3 From 6566c9136057f7b272b0d35501ed85034034d11e Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 25 Mar 2019 21:27:03 +0100 Subject: Seperate changes from #1023 The unrelated changes from #1023 (and more) --- .../Activity/ActivityLogEntryPoint.cs | 33 ++++++--- Emby.Server.Implementations/ApplicationHost.cs | 5 +- .../Channels/ChannelManager.cs | 9 --- Emby.Server.Implementations/Dto/DtoService.cs | 28 +++----- .../Security/AuthenticationRepository.cs | 4 -- .../Services/StringMapTypeDeserializer.cs | 6 +- .../Session/SessionManager.cs | 78 +++++++--------------- MediaBrowser.Controller/Entities/UserView.cs | 5 +- .../Entities/UserViewBuilder.cs | 12 ++-- 9 files changed, 72 insertions(+), 108 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 98cd97c318..f32ad7196e 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -3,12 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -29,31 +27,39 @@ namespace Emby.Server.Implementations.Activity { public class ActivityLogEntryPoint : IServerEntryPoint { + private readonly ILogger _logger; private readonly IInstallationManager _installationManager; private readonly ISessionManager _sessionManager; private readonly ITaskManager _taskManager; private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; - private readonly ILibraryManager _libraryManager; private readonly ISubtitleManager _subManager; private readonly IUserManager _userManager; - private readonly IServerConfigurationManager _config; private readonly IServerApplicationHost _appHost; private readonly IDeviceManager _deviceManager; - public ActivityLogEntryPoint(ISessionManager sessionManager, IDeviceManager deviceManager, ITaskManager taskManager, IActivityManager activityManager, ILocalizationManager localization, IInstallationManager installationManager, ILibraryManager libraryManager, ISubtitleManager subManager, IUserManager userManager, IServerConfigurationManager config, IServerApplicationHost appHost) + public ActivityLogEntryPoint( + ILogger logger, + ISessionManager sessionManager, + IDeviceManager deviceManager, + ITaskManager taskManager, + IActivityManager activityManager, + ILocalizationManager localization, + IInstallationManager installationManager, + ISubtitleManager subManager, + IUserManager userManager, + IServerApplicationHost appHost) { + _logger = logger; _sessionManager = sessionManager; + _deviceManager = deviceManager; _taskManager = taskManager; _activityManager = activityManager; _localization = localization; _installationManager = installationManager; - _libraryManager = libraryManager; _subManager = subManager; _userManager = userManager; - _config = config; _appHost = appHost; - _deviceManager = deviceManager; } public Task RunAsync() @@ -124,7 +130,7 @@ namespace Emby.Server.Implementations.Activity if (item == null) { - //_logger.LogWarning("PlaybackStopped reported with null media info."); + _logger.LogWarning("PlaybackStopped reported with null media info."); return; } @@ -155,7 +161,7 @@ namespace Emby.Server.Implementations.Activity if (item == null) { - //_logger.LogWarning("PlaybackStart reported with null media info."); + _logger.LogWarning("PlaybackStart reported with null media info."); return; } @@ -203,6 +209,7 @@ namespace Emby.Server.Implementations.Activity { return NotificationType.AudioPlayback.ToString(); } + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { return NotificationType.VideoPlayback.ToString(); @@ -217,6 +224,7 @@ namespace Emby.Server.Implementations.Activity { return NotificationType.AudioPlaybackStopped.ToString(); } + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { return NotificationType.VideoPlaybackStopped.ToString(); @@ -415,6 +423,7 @@ namespace Emby.Server.Implementations.Activity { vals.Add(e.Result.ErrorMessage); } + if (!string.IsNullOrEmpty(e.Result.LongErrorMessage)) { vals.Add(e.Result.LongErrorMessage); @@ -424,7 +433,7 @@ namespace Emby.Server.Implementations.Activity { Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), Type = NotificationType.TaskFailed.ToString(), - Overview = string.Join(Environment.NewLine, vals.ToArray()), + Overview = string.Join(Environment.NewLine, vals), ShortOverview = runningTime, Severity = LogLevel.Error }); @@ -503,6 +512,7 @@ namespace Emby.Server.Implementations.Activity { values.Add(CreateValueString(span.Hours, "hour")); } + // Number of minutes if (span.Minutes >= 1) { @@ -526,6 +536,7 @@ namespace Emby.Server.Implementations.Activity builder.Append(values[i]); } + // Return result return builder.ToString(); } diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 484942946c..ff49c74a3b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -831,10 +831,10 @@ namespace Emby.Server.Implementations DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager); serviceCollection.AddSingleton(DtoService); - ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager); + ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager); serviceCollection.AddSingleton(ChannelManager); - SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); + SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, this, AuthenticationRepository, DeviceManager, MediaSourceManager); serviceCollection.AddSingleton(SessionManager); serviceCollection.AddSingleton( @@ -1035,7 +1035,6 @@ namespace Emby.Server.Implementations Video.LiveTvManager = LiveTvManager; Folder.UserViewManager = UserViewManager; UserView.TVSeriesManager = TVSeriesManager; - UserView.PlaylistManager = PlaylistManager; UserView.CollectionManager = CollectionManager; BaseItem.MediaSourceManager = MediaSourceManager; CollectionFolder.XmlSerializer = XmlSerializer; diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 7e50650d70..e9961e8bd7 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -20,7 +19,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; @@ -40,11 +38,8 @@ namespace Emby.Server.Implementations.Channels private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; - private readonly IHttpClient _httpClient; private readonly IProviderManager _providerManager; - private readonly ILocalizationManager _localization; - public ChannelManager( IUserManager userManager, IDtoService dtoService, @@ -54,8 +49,6 @@ namespace Emby.Server.Implementations.Channels IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, - ILocalizationManager localization, - IHttpClient httpClient, IProviderManager providerManager) { _userManager = userManager; @@ -66,8 +59,6 @@ namespace Emby.Server.Implementations.Channels _fileSystem = fileSystem; _userDataManager = userDataManager; _jsonSerializer = jsonSerializer; - _localization = localization; - _httpClient = httpClient; _providerManager = providerManager; } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 7b28a22a84..2f1b60be9c 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -89,14 +89,11 @@ namespace Emby.Server.Implementations.Dto var channelTuples = new List>(); var index = 0; - var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType().ToList(); - foreach (var item in items) { - var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user, owner); + var dto = GetBaseItemDtoInternal(item, options, user, owner); - var tvChannel = item as LiveTvChannel; - if (tvChannel != null) + if (item is LiveTvChannel tvChannel) { channelTuples.Add(new Tuple(dto, tvChannel)); } @@ -105,9 +102,7 @@ namespace Emby.Server.Implementations.Dto programTuples.Add(new Tuple(item, dto)); } - var byName = item as IItemByName; - - if (byName != null) + if (item is IItemByName byName) { if (options.ContainsField(ItemFields.ItemCounts)) { @@ -130,8 +125,7 @@ namespace Emby.Server.Implementations.Dto if (programTuples.Count > 0) { - var task = _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user); - Task.WaitAll(task); + _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult(); } if (channelTuples.Count > 0) @@ -144,8 +138,7 @@ namespace Emby.Server.Implementations.Dto public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { - var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType().ToList(); - var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user, owner); + var dto = GetBaseItemDtoInternal(item, options, user, owner); var tvChannel = item as LiveTvChannel; if (tvChannel != null) { @@ -188,7 +181,7 @@ namespace Emby.Server.Implementations.Dto }); } - private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, List allCollectionFolders, User user = null, BaseItem owner = null) + private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { var dto = new BaseItemDto { @@ -312,6 +305,7 @@ namespace Emby.Server.Implementations.Dto { path = path.TrimStart('.'); } + if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase)) { fileExtensionContainer = path; @@ -325,8 +319,7 @@ namespace Emby.Server.Implementations.Dto public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, User user = null) { - var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType().ToList(); - var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user); + var dto = GetBaseItemDtoInternal(item, options, user); if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts)) { @@ -1051,14 +1044,15 @@ namespace Emby.Server.Implementations.Dto } else { - mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, item.Id.ToString("N"), StringComparison.OrdinalIgnoreCase)) + string id = item.Id.ToString("N"); + mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.MediaStreams) .ToArray(); } } else { - mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true).First().MediaStreams.ToArray(); + mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true)[0].MediaStreams.ToArray(); } dto.MediaStreams = mediaStreams; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index c81a937672..29b8dfd3d7 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -15,13 +15,9 @@ namespace Emby.Server.Implementations.Security { public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository { - private readonly IServerConfigurationManager _config; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public AuthenticationRepository(ILoggerFactory loggerFactory, IServerConfigurationManager config) : base(loggerFactory.CreateLogger(nameof(AuthenticationRepository))) { - _config = config; DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db"); } diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index f835aa1b5b..7d42b2b510 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.Services foreach (var propertyInfo in RestPath.GetSerializableProperties(type)) { - var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo); + var propertySetFn = TypeAccessor.GetSetPropertyMethod(propertyInfo); var propertyType = propertyInfo.PropertyType; var propertyParseStringFn = GetParseFn(propertyType); var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn, propertyType); @@ -110,9 +110,9 @@ namespace Emby.Server.Implementations.Services } } - internal class TypeAccessor + internal static class TypeAccessor { - public static Action GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) + public static Action GetSetPropertyMethod(PropertyInfo propertyInfo) { if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 985748caf2..dc23551db5 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Devices; @@ -25,7 +24,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; @@ -53,8 +51,6 @@ namespace Emby.Server.Implementations.Session private readonly IImageProcessor _imageProcessor; private readonly IMediaSourceManager _mediaSourceManager; - private readonly IHttpClient _httpClient; - private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; private readonly IAuthenticationRepository _authRepo; @@ -96,9 +92,7 @@ namespace Emby.Server.Implementations.Session IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, - IJsonSerializer jsonSerializer, IServerApplicationHost appHost, - IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager) @@ -110,9 +104,7 @@ namespace Emby.Server.Implementations.Session _musicManager = musicManager; _dtoService = dtoService; _imageProcessor = imageProcessor; - _jsonSerializer = jsonSerializer; _appHost = appHost; - _httpClient = httpClient; _authRepo = authRepo; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; @@ -347,8 +339,7 @@ namespace Emby.Server.Implementations.Session var runtimeTicks = libraryItem.RunTimeTicks; MediaSourceInfo mediaSource = null; - var hasMediaSources = libraryItem as IHasMediaSources; - if (hasMediaSources != null) + if (libraryItem is IHasMediaSources hasMediaSources) { mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); @@ -1841,64 +1832,49 @@ namespace Emby.Server.Implementations.Session var data = dataFn(); - var tasks = sessions.Select(session => Task.Run(async () => + IEnumerable GetTasks() { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) + foreach (var session in sessions) { - _logger.LogError("Error sending message", ex); + yield return SendMessageToSession(session, name, data, cancellationToken); } + } - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return Task.WhenAll(GetTasks()); } public Task SendMessageToUserSessions(List userIds, string name, T data, CancellationToken cancellationToken) { CheckDisposed(); - var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)).ToList(); + var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)); - var tasks = sessions.Select(session => Task.Run(async () => + IEnumerable GetTasks() { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) + foreach (var session in sessions) { - _logger.LogError("Error sending message", ex); + yield return SendMessageToSession(session, name, data, cancellationToken); } + } - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return Task.WhenAll(GetTasks()); } public Task SendMessageToUserDeviceSessions(string deviceId, string name, T data, CancellationToken cancellationToken) { CheckDisposed(); - var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)).ToList(); + var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); - var tasks = sessions.Select(session => Task.Run(async () => + IEnumerable GetTasks() { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) + foreach (var session in sessions) { - _logger.LogError("Error sending message", ex); + yield return SendMessageToSession(session, name, data, cancellationToken); } + } - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return Task.WhenAll(GetTasks()); } public Task SendMessageToUserDeviceAndAdminSessions(string deviceId, string name, T data, CancellationToken cancellationToken) @@ -1906,23 +1882,17 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); var sessions = Sessions - .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)) - .ToList(); + .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)); - var tasks = sessions.Select(session => Task.Run(async () => + IEnumerable GetTasks() { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) + foreach (var session in sessions) { - _logger.LogError("Error sending message", ex); + yield return SendMessageToSession(session, name, data, cancellationToken); } + } - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return Task.WhenAll(GetTasks()); } private bool IsAdminSession(SessionInfo s) diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 3e2191376b..4a6d32dcec 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; @@ -17,7 +16,6 @@ namespace MediaBrowser.Controller.Entities public Guid? UserId { get; set; } public static ITVSeriesManager TVSeriesManager; - public static IPlaylistManager PlaylistManager; [IgnoreDataMember] public string CollectionType => ViewType; @@ -38,6 +36,7 @@ namespace MediaBrowser.Controller.Entities { list.Add(Id); } + return list; } @@ -65,7 +64,7 @@ namespace MediaBrowser.Controller.Entities parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent; } - return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager, PlaylistManager) + return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager) .GetUserItems(parent, this, CollectionType, query); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 683218a9e9..e483c8f349 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -21,9 +20,14 @@ namespace MediaBrowser.Controller.Entities private readonly IUserDataManager _userDataManager; private readonly ITVSeriesManager _tvSeriesManager; private readonly IServerConfigurationManager _config; - private readonly IPlaylistManager _playlistManager; - public UserViewBuilder(IUserViewManager userViewManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, IServerConfigurationManager config, IPlaylistManager playlistManager) + public UserViewBuilder( + IUserViewManager userViewManager, + ILibraryManager libraryManager, + ILogger logger, + IUserDataManager userDataManager, + ITVSeriesManager tvSeriesManager, + IServerConfigurationManager config) { _userViewManager = userViewManager; _libraryManager = libraryManager; @@ -31,7 +35,6 @@ namespace MediaBrowser.Controller.Entities _userDataManager = userDataManager; _tvSeriesManager = tvSeriesManager; _config = config; - _playlistManager = playlistManager; } public QueryResult GetUserItems(Folder queryParent, Folder displayParent, string viewType, InternalItemsQuery query) @@ -110,6 +113,7 @@ namespace MediaBrowser.Controller.Entities { return GetResult(GetMediaFolders(user).OfType().SelectMany(i => i.GetChildren(user, true)), queryParent, query); } + return queryParent.GetItems(query); } } -- cgit v1.2.3 From 2696ac5eacfb4702d629bc06a8b42b868c316116 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 24 Feb 2019 03:16:19 +0100 Subject: Lower the amount of running tasks --- .../HttpServer/HttpListenerHost.cs | 15 +++------ .../Session/SessionWebSocketListener.cs | 6 ++-- .../SocketSharp/RequestMono.cs | 16 ++------- .../SocketSharp/WebSocketSharpRequest.cs | 39 +++++++++++----------- Jellyfin.Server/Program.cs | 6 ++-- MediaBrowser.Api/Library/LibraryService.cs | 19 +++++------ .../Net/BasePeriodicWebSocketListener.cs | 4 +-- MediaBrowser.Controller/Net/IWebSocketListener.cs | 2 +- RSSDP/HttpParserBase.cs | 6 ++-- RSSDP/SsdpCommunicationsServer.cs | 21 +++++------- 10 files changed, 53 insertions(+), 81 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e8d47cad52..9214f2afe2 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Services; -using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -823,19 +822,15 @@ namespace Emby.Server.Implementations.HttpServer Logger.LogDebug("Websocket message received: {0}", result.MessageType); - var tasks = _webSocketListeners.Select(i => Task.Run(async () => + IEnumerable GetTasks() { - try - { - await i.ProcessMessage(result).ConfigureAwait(false); - } - catch (Exception ex) + foreach (var x in _webSocketListeners) { - Logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty); + yield return x.ProcessMessageAsync(result); } - })); + } - return Task.WhenAll(tasks); + return Task.WhenAll(GetTasks()); } public void Dispose() diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index a551433ed9..63ec757626 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -89,10 +89,8 @@ namespace Emby.Server.Implementations.Session /// /// The message. /// Task. - public Task ProcessMessage(WebSocketMessageInfo message) - { - return Task.CompletedTask; - } + public Task ProcessMessageAsync(WebSocketMessageInfo message) + => Task.CompletedTask; private void EnsureController(SessionInfo session, IWebSocketConnection connection) { diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index 373f6d7580..ec637186f0 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -86,8 +86,7 @@ namespace Emby.Server.Implementations.SocketSharp else { // We use a substream, as in 2.x we will support large uploads streamed to disk, - var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length); - files[e.Name] = sub; + files[e.Name] = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length); } } } @@ -374,7 +373,7 @@ namespace Emby.Server.Implementations.SocketSharp var elem = new Element(); ReadOnlySpan header; - while ((header = ReadHeaders().AsSpan()) != null) + while ((header = ReadLine().AsSpan()).Length != 0) { if (header.StartsWith("Content-Disposition:".AsSpan(), StringComparison.OrdinalIgnoreCase)) { @@ -513,17 +512,6 @@ namespace Emby.Server.Implementations.SocketSharp return false; } - private string ReadHeaders() - { - string s = ReadLine(); - if (s.Length == 0) - { - return null; - } - - return s; - } - private static bool CompareBytes(byte[] orig, byte[] other) { for (int i = orig.Length - 1; i >= 0; i--) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index e0a0ee2861..6fdc6a3c8d 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; +using System.Linq; using System.Text; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; @@ -474,27 +474,28 @@ namespace Emby.Server.Implementations.SocketSharp { get { - if (httpFiles == null) + if (httpFiles != null) { - if (files == null) - { - return httpFiles = Array.Empty(); - } + return httpFiles; + } - httpFiles = new IHttpFile[files.Count]; - var i = 0; - foreach (var pair in files) + if (files == null) + { + return httpFiles = Array.Empty(); + } + + var values = files.Values; + httpFiles = new IHttpFile[values.Count]; + for (int i = 0; i < values.Count; i++) + { + var reqFile = values.ElementAt(i); + httpFiles[i] = new HttpFile { - var reqFile = pair.Value; - httpFiles[i] = new HttpFile - { - ContentType = reqFile.ContentType, - ContentLength = reqFile.ContentLength, - FileName = reqFile.FileName, - InputStream = reqFile.InputStream, - }; - i++; - } + ContentType = reqFile.ContentType, + ContentLength = reqFile.ContentLength, + FileName = reqFile.FileName, + InputStream = reqFile.InputStream, + }; } return httpFiles; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 82a76c6378..fbeb7a2e82 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -37,7 +37,7 @@ namespace Jellyfin.Server private static bool _restartOnShutdown; private static IConfiguration appConfig; - public static async Task Main(string[] args) + public static Task Main(string[] args) { // For backwards compatibility. // Modify any input arguments now which start with single-hyphen to POSIX standard @@ -51,8 +51,8 @@ namespace Jellyfin.Server } // Parse the command line arguments and either start the app or exit indicating error - await Parser.Default.ParseArguments(args) - .MapResult(StartApp, _ => Task.CompletedTask).ConfigureAwait(false); + return Parser.Default.ParseArguments(args) + .MapResult(StartApp, _ => Task.CompletedTask); } public static void Shutdown() diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 8eefbdf2ca..8a5a793dfd 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -988,19 +988,16 @@ namespace MediaBrowser.Api.Library /// Posts the specified request. /// /// The request. - public void Post(RefreshLibrary request) + public async Task Post(RefreshLibrary request) { - Task.Run(() => + try { - try - { - _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error refreshing library"); - } - }); + await _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error refreshing library"); + } } /// diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 8444125462..ee5c1a165a 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Controller.Net /// /// The message. /// Task. - public Task ProcessMessage(WebSocketMessageInfo message) + public Task ProcessMessageAsync(WebSocketMessageInfo message) { if (message == null) { @@ -74,7 +74,7 @@ namespace MediaBrowser.Controller.Net Stop(message); } - return Task.FromResult(true); + return Task.CompletedTask; } protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); diff --git a/MediaBrowser.Controller/Net/IWebSocketListener.cs b/MediaBrowser.Controller/Net/IWebSocketListener.cs index e38f0e259f..0f472a2bc7 100644 --- a/MediaBrowser.Controller/Net/IWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/IWebSocketListener.cs @@ -12,6 +12,6 @@ namespace MediaBrowser.Controller.Net /// /// The message. /// Task. - Task ProcessMessage(WebSocketMessageInfo message); + Task ProcessMessageAsync(WebSocketMessageInfo message); } } diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs index 18712470da..76d816e7bd 100644 --- a/RSSDP/HttpParserBase.cs +++ b/RSSDP/HttpParserBase.cs @@ -23,8 +23,6 @@ namespace Rssdp.Infrastructure #region Public Methods - private static byte[] EmptyByteArray = new byte[]{}; - /// /// Parses the provided into either a or object. /// @@ -46,7 +44,7 @@ namespace Rssdp.Infrastructure if (data.Length == 0) throw new ArgumentException("data cannot be an empty string.", nameof(data)); if (!LineTerminators.Any(data.Contains)) throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", nameof(data)); - using (var retVal = new ByteArrayContent(EmptyByteArray)) + using (var retVal = new ByteArrayContent(Array.Empty())) { var lines = data.Split(LineTerminators, StringSplitOptions.None); @@ -209,4 +207,4 @@ namespace Rssdp.Infrastructure #endregion } -} \ No newline at end of file +} diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index d9a4b6ac01..5d2afc37a0 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -355,7 +355,7 @@ namespace Rssdp.Infrastructure { var socket = _SocketFactory.CreateUdpMulticastSocket(SsdpConstants.MulticastLocalAdminAddress, _MulticastTtl, SsdpConstants.MulticastPort); - ListenToSocket(socket); + _ = ListenToSocketInternal(socket); return socket; } @@ -389,19 +389,12 @@ namespace Rssdp.Infrastructure foreach (var socket in sockets) { - ListenToSocket(socket); + _ = ListenToSocketInternal(socket); } return sockets; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capturing task to local variable removes compiler warning, task is not otherwise required.")] - private void ListenToSocket(ISocket socket) - { - // Tasks are captured to local variables even if we don't use them just to avoid compiler warnings. - var t = Task.Run(() => ListenToSocketInternal(socket)); - } - private async Task ListenToSocketInternal(ISocket socket) { var cancelled = false; @@ -448,10 +441,10 @@ namespace Rssdp.Infrastructure private void ProcessMessage(string data, IpEndPointInfo endPoint, IpAddressInfo receivedOnLocalIpAddress) { - //Responses start with the HTTP version, prefixed with HTTP/ while - //requests start with a method which can vary and might be one we haven't - //seen/don't know. We'll check if this message is a request or a response - //by checking for the HTTP/ prefix on the start of the message. + // Responses start with the HTTP version, prefixed with HTTP/ while + // requests start with a method which can vary and might be one we haven't + // seen/don't know. We'll check if this message is a request or a response + // by checking for the HTTP/ prefix on the start of the message. if (data.StartsWith("HTTP/", StringComparison.OrdinalIgnoreCase)) { HttpResponseMessage responseMessage = null; @@ -465,7 +458,9 @@ namespace Rssdp.Infrastructure } if (responseMessage != null) + { OnResponseReceived(responseMessage, endPoint, receivedOnLocalIpAddress); + } } else { -- cgit v1.2.3 From 4c8f8cf64cc81fb5f1bb32aeb8349ce38badf457 Mon Sep 17 00:00:00 2001 From: Phlogi Date: Mon, 25 Mar 2019 21:34:55 +0100 Subject: Removed trailing spaces, renamed get wan IP function. --- Emby.Server.Implementations/ApplicationHost.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7c8fb7f62b..b5582ae4fd 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1376,7 +1376,7 @@ namespace Emby.Server.Implementations if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)) { - wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); + wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); } else { @@ -1435,7 +1435,7 @@ namespace Emby.Server.Implementations if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)) { - wanAddress = await GetWanApiUrl(cancellationToken).ConfigureAwait(false); + wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); } else { @@ -1478,7 +1478,7 @@ namespace Emby.Server.Implementations return null; } - public async Task GetWanApiUrl(CancellationToken cancellationToken) + public async Task GetWanApiUrlFromExternal(CancellationToken cancellationToken) { const string Url = "http://ipv4.icanhazip.com"; try @@ -1524,7 +1524,7 @@ namespace Emby.Server.Implementations } return string.Format("http://{0}:{1}", host, - HttpPort.ToString(CultureInfo.InvariantCulture)); + HttpPort.ToString(CultureInfo.InvariantCulture)); } public string GetWanApiUrl(IpAddressInfo ipAddress) -- cgit v1.2.3 From 1b03f078b92021b8f3c2e7c148f1e67debd27fac Mon Sep 17 00:00:00 2001 From: Phlogi Date: Mon, 25 Mar 2019 21:43:50 +0100 Subject: No need to assign empty string. --- Emby.Server.Implementations/ApplicationHost.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b5582ae4fd..49da672923 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1372,15 +1372,14 @@ namespace Emby.Server.Implementations public async Task GetSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - var wanAddress = string.Empty; if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)) { - wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); + var wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); } else { - wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); + var wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); } return new SystemInfo @@ -1431,15 +1430,14 @@ namespace Emby.Server.Implementations public async Task GetPublicSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - var wanAddress = string.Empty; if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)) { - wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); + var wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); } else { - wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); + var wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); } return new PublicSystemInfo { -- cgit v1.2.3 From b44a70ff368f228fc27a184e2139d288bada85cc Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 25 Mar 2019 22:25:32 +0100 Subject: Simplify/remove/clean code * Remove useless runtime check (we only support one) * Remove unused args * Remove a global constant And ofc fix some warnings ;) --- Emby.Server.Implementations/ApplicationHost.cs | 18 +++++------- .../Cryptography/CryptographyProvider.cs | 3 +- .../Data/SqliteDisplayPreferencesRepository.cs | 3 +- .../EntryPoints/LibraryChangedNotifier.cs | 4 +-- .../HttpServer/Security/AuthService.cs | 4 +-- .../HttpServer/StreamWriter.cs | 3 -- .../LiveTv/EmbyTV/EmbyTV.cs | 4 +-- .../LiveTv/LiveTvManager.cs | 4 +-- .../LiveTv/TunerHosts/M3UTunerHost.cs | 14 ++++------ .../LiveTv/TunerHosts/M3uParser.cs | 20 ++++++++------ .../Updates/InstallationManager.cs | 32 ++++------------------ Jellyfin.Server/Program.cs | 1 - MediaBrowser.Common/Extensions/BaseExtensions.cs | 10 ++++--- jellyfin.ruleset | 3 ++ 14 files changed, 50 insertions(+), 73 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 484942946c..e15cb68e9e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -200,7 +200,7 @@ namespace Emby.Server.Implementations /// /// The disposable parts /// - protected readonly List _disposableParts = new List(); + private readonly List _disposableParts = new List(); /// /// Gets the configuration manager. @@ -216,8 +216,9 @@ namespace Emby.Server.Implementations { #if BETA return PackageVersionClass.Beta; -#endif +#else return PackageVersionClass.Release; +#endif } } @@ -340,7 +341,6 @@ namespace Emby.Server.Implementations protected IProcessFactory ProcessFactory { get; private set; } - protected ICryptoProvider CryptographyProvider = new CryptographyProvider(); protected readonly IXmlSerializer XmlSerializer; protected ISocketFactory SocketFactory { get; private set; } @@ -369,9 +369,6 @@ namespace Emby.Server.Implementations { _configuration = configuration; - // hack alert, until common can target .net core - BaseExtensions.CryptographyProvider = CryptographyProvider; - XmlSerializer = new MyXmlSerializer(fileSystem, loggerFactory); NetworkManager = networkManager; @@ -735,13 +732,12 @@ namespace Emby.Server.Implementations ApplicationHost.StreamHelper = new StreamHelper(); serviceCollection.AddSingleton(StreamHelper); - serviceCollection.AddSingleton(CryptographyProvider); + serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider)); SocketFactory = new SocketFactory(); serviceCollection.AddSingleton(SocketFactory); - InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, ZipClient, PackageRuntime); - serviceCollection.AddSingleton(InstallationManager); + serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager)); ZipClient = new ZipClient(); serviceCollection.AddSingleton(ZipClient); @@ -908,8 +904,6 @@ namespace Emby.Server.Implementations _serviceProvider = serviceCollection.BuildServiceProvider(); } - public virtual string PackageRuntime => "netcore"; - public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) { // Distinct these to prevent users from reporting problems that aren't actually problems @@ -1049,6 +1043,8 @@ namespace Emby.Server.Implementations /// protected void FindParts() { + InstallationManager = _serviceProvider.GetService(); + if (!ServerConfigurationManager.Configuration.IsPortAuthorized) { ServerConfigurationManager.Configuration.IsPortAuthorized = true; diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 982bba625d..6d7193ce20 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Text; -using System.Linq; using MediaBrowser.Model.Cryptography; namespace Emby.Server.Implementations.Cryptography @@ -136,7 +135,7 @@ namespace Emby.Server.Implementations.Cryptography { return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations); } - + public byte[] ComputeHash(PasswordHash hash) { int iterations = _defaultIterations; diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 3d60925da3..47552806d4 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -90,9 +90,10 @@ namespace Emby.Server.Implementations.Data { throw new ArgumentNullException(nameof(displayPreferences)); } + if (string.IsNullOrEmpty(displayPreferences.Id)) { - throw new ArgumentNullException(nameof(displayPreferences.Id)); + throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences)); } cancellationToken.ThrowIfCancellationRequested(); diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 0389656477..8369f4f593 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -388,7 +388,7 @@ namespace Emby.Server.Implementations.EntryPoints FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), - CollectionFolders = GetTopParentIds(newAndRemoved, user, allUserRootChildren).ToArray() + CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray() }; } @@ -407,7 +407,7 @@ namespace Emby.Server.Implementations.EntryPoints return item.SourceType == SourceType.Library; } - private IEnumerable GetTopParentIds(List items, User user, List allUserRootChildren) + private IEnumerable GetTopParentIds(List items, List allUserRootChildren) { var list = new List(); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 499a334fc9..1027883ed9 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.HttpServer.Security // This code is executed before the service var auth = AuthorizationContext.GetAuthorizationInfo(request); - if (!IsExemptFromAuthenticationToken(auth, authAttribtues, request)) + if (!IsExemptFromAuthenticationToken(authAttribtues, request)) { ValidateSecurityToken(request, auth.Token); } @@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request) + private bool IsExemptFromAuthenticationToken(IAuthenticationAttributes authAttribtues, IRequest request) { if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard) { diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index cf30bbc326..324f9085e5 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -14,8 +13,6 @@ namespace Emby.Server.Implementations.HttpServer /// public class StreamWriter : IAsyncStreamWriter, IHasHeaders { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - /// /// Gets or sets the source stream. /// diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 58b3b6a69f..7b210d2313 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -261,7 +261,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public string HomePageUrl => "https://github.com/jellyfin/jellyfin"; - public async Task RefreshSeriesTimers(CancellationToken cancellationToken, IProgress progress) + public async Task RefreshSeriesTimers(CancellationToken cancellationToken) { var seriesTimers = await GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false); @@ -271,7 +271,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - public async Task RefreshTimers(CancellationToken cancellationToken, IProgress progress) + public async Task RefreshTimers(CancellationToken cancellationToken) { var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index f7ef16fb09..9093d9740f 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1087,8 +1087,8 @@ namespace Emby.Server.Implementations.LiveTv if (coreService != null) { - await coreService.RefreshSeriesTimers(cancellationToken, new SimpleProgress()).ConfigureAwait(false); - await coreService.RefreshTimers(cancellationToken, new SimpleProgress()).ConfigureAwait(false); + await coreService.RefreshSeriesTimers(cancellationToken).ConfigureAwait(false); + await coreService.RefreshTimers(cancellationToken).ConfigureAwait(false); } // Load these now which will prefetch metadata diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 588dcb843b..2d9bec53f0 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -10,14 +10,12 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -52,9 +50,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { var channelIdPrefix = GetFullChannelIdPrefix(info); - var result = await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false); - - return result.Cast().ToList(); + return await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false); } public Task> GetTunerInfos(CancellationToken cancellationToken) @@ -73,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return Task.FromResult(list); } - private string[] _disallowedSharedStreamExtensions = new string[] + private static readonly string[] _disallowedSharedStreamExtensions = new string[] { ".mkv", ".mp4", @@ -88,9 +84,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (tunerCount > 0) { var tunerHostId = info.Id; - var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase)).ToList(); + var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase)); - if (liveStreams.Count >= tunerCount) + if (liveStreams.Count() >= tunerCount) { throw new LiveTvConflictException("M3U simultaneous stream limit has been reached."); } @@ -98,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var sources = await GetChannelStreamMediaSources(info, channelInfo, cancellationToken).ConfigureAwait(false); - var mediaSource = sources.First(); + var mediaSource = sources[0]; if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index ad124bb0ff..814031b126 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -11,7 +11,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.TunerHosts @@ -62,12 +61,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return Task.FromResult((Stream)File.OpenRead(url)); } - const string ExtInfPrefix = "#EXTINF:"; + private const string ExtInfPrefix = "#EXTINF:"; + private List GetChannels(TextReader reader, string channelIdPrefix, string tunerHostId) { var channels = new List(); string line; - string extInf = ""; + string extInf = string.Empty; while ((line = reader.ReadLine()) != null) { @@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts channel.Path = line; channels.Add(channel); - extInf = ""; + extInf = string.Empty; } } @@ -110,8 +110,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private ChannelInfo GetChannelnfo(string extInf, string tunerHostId, string mediaUrl) { - var channel = new ChannelInfo(); - channel.TunerHostId = tunerHostId; + var channel = new ChannelInfo() + { + TunerHostId = tunerHostId + }; extInf = extInf.Trim(); @@ -137,13 +139,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { channelIdValues.Add(channelId); } + if (!string.IsNullOrWhiteSpace(tvgId)) { channelIdValues.Add(tvgId); } + if (channelIdValues.Count > 0) { - channel.Id = string.Join("_", channelIdValues.ToArray()); + channel.Id = string.Join("_", channelIdValues); } return channel; @@ -152,7 +156,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private string GetChannelNumber(string extInf, Dictionary attributes, string mediaUrl) { var nameParts = extInf.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null; + var nameInExtInf = nameParts.Length > 1 ? nameParts[nameParts.Length - 1].Trim() : null; string numberString = null; string attributeValue; diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 301802b8a6..7310de55d4 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -12,7 +12,6 @@ using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Progress; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; @@ -39,11 +38,10 @@ namespace Emby.Server.Implementations.Updates /// /// The completed installations /// - private ConcurrentBag CompletedInstallationsInternal { get; set; } + private ConcurrentBag _completedInstallationsInternal; - public IEnumerable CompletedInstallations => CompletedInstallationsInternal; + public IEnumerable CompletedInstallations => _completedInstallationsInternal; - #region PluginUninstalled Event /// /// Occurs when [plugin uninstalled]. /// @@ -57,9 +55,7 @@ namespace Emby.Server.Implementations.Updates { PluginUninstalled?.Invoke(this, new GenericEventArgs { Argument = plugin }); } - #endregion - #region PluginUpdated Event /// /// Occurs when [plugin updated]. /// @@ -77,9 +73,7 @@ namespace Emby.Server.Implementations.Updates _applicationHost.NotifyPendingRestart(); } - #endregion - #region PluginInstalled Event /// /// Occurs when [plugin updated]. /// @@ -96,7 +90,6 @@ namespace Emby.Server.Implementations.Updates _applicationHost.NotifyPendingRestart(); } - #endregion /// /// The _logger @@ -115,12 +108,8 @@ namespace Emby.Server.Implementations.Updates /// The application host. private readonly IApplicationHost _applicationHost; - private readonly ICryptoProvider _cryptographyProvider; private readonly IZipClient _zipClient; - // netframework or netcore - private readonly string _packageRuntime; - public InstallationManager( ILoggerFactory loggerFactory, IApplicationHost appHost, @@ -129,9 +118,7 @@ namespace Emby.Server.Implementations.Updates IJsonSerializer jsonSerializer, IServerConfigurationManager config, IFileSystem fileSystem, - ICryptoProvider cryptographyProvider, - IZipClient zipClient, - string packageRuntime) + IZipClient zipClient) { if (loggerFactory == null) { @@ -139,18 +126,16 @@ namespace Emby.Server.Implementations.Updates } CurrentInstallations = new List>(); - CompletedInstallationsInternal = new ConcurrentBag(); + _completedInstallationsInternal = new ConcurrentBag(); + _logger = loggerFactory.CreateLogger(nameof(InstallationManager)); _applicationHost = appHost; _appPaths = appPaths; _httpClient = httpClient; _jsonSerializer = jsonSerializer; _config = config; _fileSystem = fileSystem; - _cryptographyProvider = cryptographyProvider; _zipClient = zipClient; - _packageRuntime = packageRuntime; - _logger = loggerFactory.CreateLogger(nameof(InstallationManager)); } private static Version GetPackageVersion(PackageVersionInfo version) @@ -222,11 +207,6 @@ namespace Emby.Server.Implementations.Updates continue; } - if (string.IsNullOrEmpty(version.runtimes) || version.runtimes.IndexOf(_packageRuntime, StringComparison.OrdinalIgnoreCase) == -1) - { - continue; - } - versions.Add(version); } @@ -448,7 +428,7 @@ namespace Emby.Server.Implementations.Updates CurrentInstallations.Remove(tuple); } - CompletedInstallationsInternal.Add(installationInfo); + _completedInstallationsInternal.Add(installationInfo); PackageInstallationCompleted?.Invoke(this, installationEventArgs); } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 82a76c6378..d4b10c8c84 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -19,7 +19,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs index db0514bb18..40c16b9573 100644 --- a/MediaBrowser.Common/Extensions/BaseExtensions.cs +++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs @@ -1,6 +1,7 @@ using System; +using System.Text; using System.Text.RegularExpressions; -using MediaBrowser.Model.Cryptography; +using System.Security.Cryptography; namespace MediaBrowser.Common.Extensions { @@ -9,8 +10,6 @@ namespace MediaBrowser.Common.Extensions /// public static class BaseExtensions { - public static ICryptoProvider CryptographyProvider { get; set; } - /// /// Strips the HTML. /// @@ -31,7 +30,10 @@ namespace MediaBrowser.Common.Extensions /// Guid. public static Guid GetMD5(this string str) { - return CryptographyProvider.GetMD5(str); + using (var provider = MD5.Create()) + { + return new Guid(provider.ComputeHash(Encoding.Unicode.GetBytes(str))); + } } } } diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 0a04b4c557..262121a325 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -20,6 +20,9 @@ + + + -- cgit v1.2.3 From 122cba2aa755df7d7c3e63aaa441f639b126b55c Mon Sep 17 00:00:00 2001 From: Phlogi Date: Mon, 25 Mar 2019 22:26:05 +0100 Subject: Correct use of local variable wanAddress. --- Emby.Server.Implementations/ApplicationHost.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 49da672923..fd14cb89d7 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1373,13 +1373,15 @@ namespace Emby.Server.Implementations { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + string wanAddress; + if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)) { - var wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); + wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); } else { - var wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); + wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); } return new SystemInfo @@ -1431,13 +1433,15 @@ namespace Emby.Server.Implementations { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + string wanAddress; + if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)) { - var wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); + wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); } else { - var wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); + wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); } return new PublicSystemInfo { -- cgit v1.2.3 From 740c95d557515cedd3912983f7aec50bdfefb0d4 Mon Sep 17 00:00:00 2001 From: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> Date: Mon, 25 Mar 2019 21:40:10 -0700 Subject: Apply minor suggestions from code review Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Library/DefaultPasswordResetProvider.cs | 223 +++++++++++---------- Emby.Server.Implementations/Library/UserManager.cs | 2 +- 2 files changed, 113 insertions(+), 112 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 1ae8960eea..46f3732d68 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -1,113 +1,114 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; - -namespace Emby.Server.Implementations.Library -{ - public class DefaultPasswordResetProvider : IPasswordResetProvider - { - public string Name => "Default Password Reset Provider"; - - public bool IsEnabled => true; - - private readonly string _passwordResetFileBase; - private readonly string _passwordResetFileBaseDir; - private readonly string _passwordResetFileBaseName = "passwordreset"; - - private IJsonSerializer _jsonSerializer; - private IUserManager _userManager; - - public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager) - { - _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; - _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName); - _jsonSerializer = jsonSerializer; - _userManager = userManager; - } - - public async Task RedeemPasswordResetPin(string pin) - { - HashSet usersreset = new HashSet(); - foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) - { - var spr = (SerializablePasswordReset) _jsonSerializer.DeserializeFromFile(typeof(SerializablePasswordReset), resetfile); - if (spr.ExpirationDate < DateTime.Now) - { - File.Delete(resetfile); - } - else - { - if (spr.Pin == pin) - { - var resetUser = _userManager.GetUserByName(spr.UserName); - if (!string.IsNullOrEmpty(resetUser.Password)) - { - await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); - } - } - } - } - - if (usersreset.Count < 1) - { - throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); - } - else - { - return new PinRedeemResult - { - Success = true, - UsersReset = usersreset.ToArray() - }; - } - throw new System.NotImplementedException(); - } - - public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) - { - string pin = new Random().Next(99999999).ToString("00000000",CultureInfo.InvariantCulture); - DateTime expireTime = DateTime.Now.AddMinutes(30); - string filePath = _passwordResetFileBase + user.Name.ToLowerInvariant() + ".json"; - SerializablePasswordReset spr = new SerializablePasswordReset - { - ExpirationDate = expireTime, - Pin = pin, - PinFile = filePath, - UserName = user.Name - }; - - try - { - await Task.Run(() => File.WriteAllText(filePath, _jsonSerializer.SerializeToString(spr))).ConfigureAwait(false); - } - catch (Exception e) - { - throw new Exception($"Error serializing or writing password reset for {user.Name} to location:{filePath}", e); - } - - return new ForgotPasswordResult - { - Action = ForgotPasswordAction.PinCode, - PinExpirationDate = expireTime, - PinFile = filePath - }; - } - - private class SerializablePasswordReset : PasswordPinCreationResult - { - public string Pin { get; set; } +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; - public string UserName { get; set; } - } - } +namespace Emby.Server.Implementations.Library +{ + public class DefaultPasswordResetProvider : IPasswordResetProvider + { + public string Name => "Default Password Reset Provider"; + + public bool IsEnabled => true; + + private readonly string _passwordResetFileBase; + private readonly string _passwordResetFileBaseDir; + private readonly string _passwordResetFileBaseName = "passwordreset"; + + private IJsonSerializer _jsonSerializer; + private IUserManager _userManager; + + public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager) + { + _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; + _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName); + _jsonSerializer = jsonSerializer; + _userManager = userManager; + } + + public async Task RedeemPasswordResetPin(string pin) + { + HashSet usersreset = new HashSet(); + foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) + { + var spr = (SerializablePasswordReset) _jsonSerializer.DeserializeFromFile(typeof(SerializablePasswordReset), resetfile); + if (spr.ExpirationDate < DateTime.Now) + { + File.Delete(resetfile); + } + else + { + if (spr.Pin == pin) + { + var resetUser = _userManager.GetUserByName(spr.UserName); + if (!string.IsNullOrEmpty(resetUser.Password)) + { + await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); + usersreset.Add(resetUser.Name); + } + } + } + } + + if (usersreset.Count < 1) + { + throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); + } + else + { + return new PinRedeemResult + { + Success = true, + UsersReset = usersreset.ToArray() + }; + } + + throw new System.NotImplementedException(); + } + + public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) + { + string pin = new Random().Next(99999999).ToString("00000000",CultureInfo.InvariantCulture); + DateTime expireTime = DateTime.Now.AddMinutes(30); + string filePath = _passwordResetFileBase + user.Name.ToLowerInvariant() + ".json"; + SerializablePasswordReset spr = new SerializablePasswordReset + { + ExpirationDate = expireTime, + Pin = pin, + PinFile = filePath, + UserName = user.Name + }; + + try + { + await Task.Run(() => File.WriteAllText(filePath, _jsonSerializer.SerializeToString(spr))).ConfigureAwait(false); + } + catch (Exception e) + { + throw new Exception($"Error serializing or writing password reset for {user.Name} to location: {filePath}", e); + } + + return new ForgotPasswordResult + { + Action = ForgotPasswordAction.PinCode, + PinExpirationDate = expireTime, + PinFile = filePath + }; + } + + private class SerializablePasswordReset : PasswordPinCreationResult + { + public string Pin { get; set; } + + public string UserName { get; set; } + } + } } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 05ec750ba9..75c82ca715 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -373,7 +373,7 @@ namespace Emby.Server.Implementations.Library private IPasswordResetProvider GetPasswordResetProvider(User user) { - return GetPasswordResetProviders(user).First(); + return GetPasswordResetProviders(user)[0]; } private IAuthenticationProvider[] GetAuthenticationProviders(User user) -- cgit v1.2.3 From 6be8624373bba6cf25a659390874613a4ea6ba79 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 25 Mar 2019 22:17:23 -0700 Subject: async improvements and post reset cleanups --- .../Library/DefaultPasswordResetProvider.cs | 27 +++++++++++----------- 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 46f3732d68..a589d61689 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; +using Microsoft.Win32.SafeHandles; namespace Emby.Server.Implementations.Library { @@ -39,21 +40,19 @@ namespace Emby.Server.Implementations.Library HashSet usersreset = new HashSet(); foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) { - var spr = (SerializablePasswordReset) _jsonSerializer.DeserializeFromFile(typeof(SerializablePasswordReset), resetfile); + var spr = await _jsonSerializer.DeserializeFromStreamAsync(File.OpenRead(resetfile)).ConfigureAwait(false); if (spr.ExpirationDate < DateTime.Now) { File.Delete(resetfile); } - else + else if (spr.Pin == pin) { - if (spr.Pin == pin) + var resetUser = _userManager.GetUserByName(spr.UserName); + if (resetUser != null) { - var resetUser = _userManager.GetUserByName(spr.UserName); - if (!string.IsNullOrEmpty(resetUser.Password)) - { - await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); - } + await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); + usersreset.Add(resetUser.Name); + File.Delete(resetfile); } } } @@ -70,15 +69,13 @@ namespace Emby.Server.Implementations.Library UsersReset = usersreset.ToArray() }; } - - throw new System.NotImplementedException(); } public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) { string pin = new Random().Next(99999999).ToString("00000000",CultureInfo.InvariantCulture); DateTime expireTime = DateTime.Now.AddMinutes(30); - string filePath = _passwordResetFileBase + user.Name.ToLowerInvariant() + ".json"; + string filePath = _passwordResetFileBase + user.InternalId + ".json"; SerializablePasswordReset spr = new SerializablePasswordReset { ExpirationDate = expireTime, @@ -88,8 +85,10 @@ namespace Emby.Server.Implementations.Library }; try - { - await Task.Run(() => File.WriteAllText(filePath, _jsonSerializer.SerializeToString(spr))).ConfigureAwait(false); + { + FileStream fileStream = File.OpenWrite(filePath); + _jsonSerializer.SerializeToStream(spr,fileStream); + await fileStream.FlushAsync().ConfigureAwait(false); } catch (Exception e) { -- cgit v1.2.3 From a332092769cec5a3b17b7fb49b2d7c66bfe289bd Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 Mar 2019 19:20:40 +0100 Subject: Reduce complexity http routes --- Emby.Server.Implementations/ApplicationHost.cs | 37 +------- .../HttpServer/HttpListenerHost.cs | 50 +++------- .../Services/ServiceController.cs | 54 +++++------ .../Services/ServiceHandler.cs | 103 ++++++++++----------- .../Services/SwaggerService.cs | 20 ++-- Jellyfin.Server/Program.cs | 5 +- 6 files changed, 105 insertions(+), 164 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 484942946c..ce08f2a284 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -617,8 +617,6 @@ namespace Emby.Server.Implementations DiscoverTypes(); - SetHttpLimit(); - await RegisterResources(serviceCollection).ConfigureAwait(false); FindParts(); @@ -918,8 +916,7 @@ namespace Emby.Server.Implementations .Distinct(); logger.LogInformation("Arguments: {Args}", commandLineArgs); - // FIXME: @bond this logs the kernel version, not the OS version - logger.LogInformation("Operating system: {OS} {OSVersion}", OperatingSystem.Name, Environment.OSVersion.Version); + logger.LogInformation("Operating system: {OS}", OperatingSystem.Name); logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture); logger.LogInformation("64-Bit Process: {Is64Bit}", Environment.Is64BitProcess); logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive); @@ -929,19 +926,6 @@ namespace Emby.Server.Implementations logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath); } - private void SetHttpLimit() - { - try - { - // Increase the max http request limit - ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error setting http limit"); - } - } - private X509Certificate2 GetCertificate(CertificateInfo info) { var certificateLocation = info?.Path; @@ -1483,6 +1467,7 @@ namespace Emby.Server.Implementations { Logger.LogError(ex, "Error getting WAN Ip address information"); } + return null; } @@ -1756,24 +1741,6 @@ namespace Emby.Server.Implementations { } - /// - /// Called when [application updated]. - /// - /// The package. - protected void OnApplicationUpdated(PackageVersionInfo package) - { - Logger.LogInformation("Application has been updated to version {0}", package.versionStr); - - ApplicationUpdated?.Invoke( - this, - new GenericEventArgs() - { - Argument = package - }); - - NotifyPendingRestart(); - } - private bool _disposed = false; /// diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e8d47cad52..79b8f52d78 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; using System.Net.Sockets; @@ -11,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Services; -using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -127,12 +125,12 @@ namespace Emby.Server.Implementations.HttpServer private List GetRequestFilterAttributes(Type requestDtoType) { - var attributes = requestDtoType.GetTypeInfo().GetCustomAttributes(true).OfType().ToList(); + var attributes = requestDtoType.GetCustomAttributes(true).OfType().ToList(); var serviceType = GetServiceTypeByRequest(requestDtoType); if (serviceType != null) { - attributes.AddRange(serviceType.GetTypeInfo().GetCustomAttributes(true).OfType()); + attributes.AddRange(serviceType.GetCustomAttributes(true).OfType()); } attributes.Sort((x, y) => x.Priority - y.Priority); @@ -154,7 +152,7 @@ namespace Emby.Server.Implementations.HttpServer QueryString = e.QueryString ?? new QueryCollection() }; - connection.Closed += Connection_Closed; + connection.Closed += OnConnectionClosed; lock (_webSocketConnections) { @@ -164,7 +162,7 @@ namespace Emby.Server.Implementations.HttpServer WebSocketConnected?.Invoke(this, new GenericEventArgs(connection)); } - private void Connection_Closed(object sender, EventArgs e) + private void OnConnectionClosed(object sender, EventArgs e) { lock (_webSocketConnections) { @@ -322,14 +320,14 @@ namespace Emby.Server.Implementations.HttpServer private static string NormalizeConfiguredLocalAddress(string address) { - var index = address.Trim('/').IndexOf('/'); - + var add = address.AsSpan().Trim('/'); + int index = add.IndexOf('/'); if (index != -1) { - address = address.Substring(index + 1); + add = add.Slice(index + 1); } - return address.Trim('/'); + return add.TrimStart('/').ToString(); } private bool ValidateHost(string host) @@ -399,8 +397,8 @@ namespace Emby.Server.Implementations.HttpServer if (urlString.IndexOf("https://", StringComparison.OrdinalIgnoreCase) == -1) { // These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected - if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1 || - urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1) + if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1 + || urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1) { return true; } @@ -572,7 +570,7 @@ namespace Emby.Server.Implementations.HttpServer if (handler != null) { - await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, httpReq.OperationName, cancellationToken).ConfigureAwait(false); + await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, cancellationToken).ConfigureAwait(false); } else { @@ -613,21 +611,11 @@ namespace Emby.Server.Implementations.HttpServer { var pathInfo = httpReq.PathInfo; - var pathParts = pathInfo.TrimStart('/').Split('/'); - if (pathParts.Length == 0) - { - Logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl); - return null; - } - - var restPath = ServiceHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out string contentType); + pathInfo = ServiceHandler.GetSanitizedPathInfo(pathInfo, out string contentType); + var restPath = ServiceController.GetRestPathForRequest(httpReq.HttpMethod, pathInfo); if (restPath != null) { - return new ServiceHandler - { - RestPath = restPath, - ResponseContentType = contentType - }; + return new ServiceHandler(restPath, contentType); } Logger.LogError("Could not find handler for {PathInfo}", pathInfo); @@ -655,11 +643,6 @@ namespace Emby.Server.Implementations.HttpServer } else { - // TODO what is this? - var httpsUrl = url - .Replace("http://", "https://", StringComparison.OrdinalIgnoreCase) - .Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase); - RedirectToUrl(httpRes, url); } } @@ -684,10 +667,7 @@ namespace Emby.Server.Implementations.HttpServer UrlPrefixes = urlPrefixes.ToArray(); ServiceController = new ServiceController(); - Logger.LogInformation("Calling ServiceStack AppHost.Init"); - - var types = services.Select(r => r.GetType()).ToArray(); - + var types = services.Select(r => r.GetType()); ServiceController.Init(this, types); ResponseFilters = new Action[] diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index 5796956d87..5e3d529c68 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -1,26 +1,17 @@ using System; using System.Collections.Generic; -using System.Reflection; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.Services { - public delegate Task InstanceExecFn(IRequest requestContext, object intance, object request); public delegate object ActionInvokerFn(object intance, object request); public delegate void VoidActionInvokerFn(object intance, object request); public class ServiceController { - public static ServiceController Instance; - - public ServiceController() - { - Instance = this; - } - - public void Init(HttpListenerHost appHost, Type[] serviceTypes) + public void Init(HttpListenerHost appHost, IEnumerable serviceTypes) { foreach (var serviceType in serviceTypes) { @@ -37,7 +28,11 @@ namespace Emby.Server.Implementations.Services foreach (var mi in serviceType.GetActions()) { var requestType = mi.GetParameters()[0].ParameterType; - if (processedReqs.Contains(requestType)) continue; + if (processedReqs.Contains(requestType)) + { + continue; + } + processedReqs.Add(requestType); ServiceExecGeneral.CreateServiceRunnersFor(requestType, actions); @@ -55,18 +50,6 @@ namespace Emby.Server.Implementations.Services } } - public static Type FirstGenericType(Type type) - { - while (type != null) - { - if (type.GetTypeInfo().IsGenericType) - return type; - - type = type.GetTypeInfo().BaseType; - } - return null; - } - public readonly RestPath.RestPathMap RestPathMap = new RestPath.RestPathMap(); public void RegisterRestPaths(HttpListenerHost appHost, Type requestType, Type serviceType) @@ -84,17 +67,24 @@ namespace Emby.Server.Implementations.Services public void RegisterRestPath(RestPath restPath) { - if (!restPath.Path.StartsWith("/")) + if (restPath.Path[0] != '/') + { throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName())); + } + if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1) + { throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. ", restPath.Path, restPath.RequestType.GetMethodName())); + } - if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out List pathsAtFirstMatch)) + if (RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out List pathsAtFirstMatch)) { - pathsAtFirstMatch = new List(); - RestPathMap[restPath.FirstMatchHashKey] = pathsAtFirstMatch; + pathsAtFirstMatch.Add(restPath); + } + else + { + RestPathMap[restPath.FirstMatchHashKey] = new List() { restPath }; } - pathsAtFirstMatch.Add(restPath); } public RestPath GetRestPathForRequest(string httpMethod, string pathInfo) @@ -155,17 +145,15 @@ namespace Emby.Server.Implementations.Services return null; } - public Task Execute(HttpListenerHost appHost, object requestDto, IRequest req) + public Task Execute(HttpListenerHost httpHost, object requestDto, IRequest req) { req.Dto = requestDto; var requestType = requestDto.GetType(); req.OperationName = requestType.Name; - var serviceType = appHost.GetServiceTypeByRequest(requestType); - - var service = appHost.CreateInstance(serviceType); + var serviceType = httpHost.GetServiceTypeByRequest(requestType); - //var service = typeFactory.CreateInstance(serviceType); + var service = httpHost.CreateInstance(serviceType); var serviceRequiresContext = service as IRequiresRequest; if (serviceRequiresContext != null) diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 3c8adfc983..243d2cca27 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -11,6 +11,18 @@ namespace Emby.Server.Implementations.Services { public class ServiceHandler { + private readonly ServiceController _serviceController; + + public RestPath RestPath { get; } + + public string ResponseContentType { get; } + + internal ServiceHandler(RestPath restPath, string responseContentType) + { + RestPath = restPath; + ResponseContentType = responseContentType; + } + protected static Task CreateContentTypeRequest(HttpListenerHost host, IRequest httpReq, Type requestType, string contentType) { if (!string.IsNullOrEmpty(contentType) && httpReq.ContentLength > 0) @@ -21,21 +33,22 @@ namespace Emby.Server.Implementations.Services return deserializer(requestType, httpReq.InputStream); } } + return Task.FromResult(host.CreateInstance(requestType)); } - public static RestPath FindMatchingRestPath(string httpMethod, string pathInfo, out string contentType) + public RestPath FindMatchingRestPath(string httpMethod, string pathInfo, out string contentType) { pathInfo = GetSanitizedPathInfo(pathInfo, out contentType); - return ServiceController.Instance.GetRestPathForRequest(httpMethod, pathInfo); + return _serviceController.GetRestPathForRequest(httpMethod, pathInfo); } public static string GetSanitizedPathInfo(string pathInfo, out string contentType) { contentType = null; var pos = pathInfo.LastIndexOf('.'); - if (pos >= 0) + if (pos != -1) { var format = pathInfo.Substring(pos + 1); contentType = GetFormatContentType(format); @@ -44,58 +57,38 @@ namespace Emby.Server.Implementations.Services pathInfo = pathInfo.Substring(0, pos); } } + return pathInfo; } private static string GetFormatContentType(string format) { //built-in formats - if (format == "json") - return "application/json"; - if (format == "xml") - return "application/xml"; - - return null; - } - - public RestPath GetRestPath(string httpMethod, string pathInfo) - { - if (this.RestPath == null) + switch (format) { - this.RestPath = FindMatchingRestPath(httpMethod, pathInfo, out string contentType); - - if (contentType != null) - ResponseContentType = contentType; + case "json": return "application/json"; + case "xml": return "application/xml"; + default: return null; } - return this.RestPath; } - public RestPath RestPath { get; set; } - - // Set from SSHHF.GetHandlerForPathInfo() - public string ResponseContentType { get; set; } - - public async Task ProcessRequestAsync(HttpListenerHost appHost, IRequest httpReq, IResponse httpRes, ILogger logger, string operationName, CancellationToken cancellationToken) + public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, IResponse httpRes, ILogger logger, CancellationToken cancellationToken) { - var restPath = GetRestPath(httpReq.Verb, httpReq.PathInfo); - if (restPath == null) - { - throw new NotSupportedException("No RestPath found for: " + httpReq.Verb + " " + httpReq.PathInfo); - } - - SetRoute(httpReq, restPath); + httpReq.Items["__route"] = RestPath; if (ResponseContentType != null) + { httpReq.ResponseContentType = ResponseContentType; + } - var request = httpReq.Dto = await CreateRequest(appHost, httpReq, restPath, logger).ConfigureAwait(false); + var request = httpReq.Dto = await CreateRequest(httpHost, httpReq, RestPath, logger).ConfigureAwait(false); - appHost.ApplyRequestFilters(httpReq, httpRes, request); + httpHost.ApplyRequestFilters(httpReq, httpRes, request); - var response = await appHost.ServiceController.Execute(appHost, request, httpReq).ConfigureAwait(false); + var response = await httpHost.ServiceController.Execute(httpHost, request, httpReq).ConfigureAwait(false); // Apply response filters - foreach (var responseFilter in appHost.ResponseFilters) + foreach (var responseFilter in httpHost.ResponseFilters) { responseFilter(httpReq, httpRes, response); } @@ -152,7 +145,11 @@ namespace Emby.Server.Implementations.Services foreach (var name in request.QueryString.Keys) { - if (name == null) continue; //thank you ASP.NET + if (name == null) + { + // thank you ASP.NET + continue; + } var values = request.QueryString[name]; if (values.Count == 1) @@ -175,7 +172,11 @@ namespace Emby.Server.Implementations.Services { foreach (var name in formData.Keys) { - if (name == null) continue; //thank you ASP.NET + if (name == null) + { + // thank you ASP.NET + continue; + } var values = formData.GetValues(name); if (values.Count == 1) @@ -210,7 +211,12 @@ namespace Emby.Server.Implementations.Services foreach (var name in request.QueryString.Keys) { - if (name == null) continue; //thank you ASP.NET + if (name == null) + { + // thank you ASP.NET + continue; + } + map[name] = request.QueryString[name]; } @@ -221,7 +227,12 @@ namespace Emby.Server.Implementations.Services { foreach (var name in formData.Keys) { - if (name == null) continue; //thank you ASP.NET + if (name == null) + { + // thank you ASP.NET + continue; + } + map[name] = formData[name]; } } @@ -229,17 +240,5 @@ namespace Emby.Server.Implementations.Services return map; } - - private static void SetRoute(IRequest req, RestPath route) - { - req.Items["__route"] = route; - } - - private static RestPath GetRoute(IRequest req) - { - req.Items.TryGetValue("__route", out var route); - return route as RestPath; - } } - } diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs index 3e6970eefd..d223864364 100644 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Services; +using Emby.Server.Implementations.HttpServer; namespace Emby.Server.Implementations.Services { @@ -109,10 +109,16 @@ namespace Emby.Server.Implementations.Services public class SwaggerService : IService, IRequiresRequest { + private readonly IHttpServer _httpServer; private SwaggerSpec _spec; public IRequest Request { get; set; } + public SwaggerService(IHttpServer httpServer) + { + _httpServer = httpServer; + } + public object Get(GetSwaggerSpec request) { return _spec ?? (_spec = GetSpec()); @@ -181,7 +187,8 @@ namespace Emby.Server.Implementations.Services { var paths = new SortedDictionary>(); - var all = ServiceController.Instance.RestPathMap.OrderBy(i => i.Key, StringComparer.OrdinalIgnoreCase).ToList(); + // REVIEW: this can be done better + var all = ((HttpListenerHost)_httpServer).ServiceController.RestPathMap.OrderBy(i => i.Key, StringComparer.OrdinalIgnoreCase).ToList(); foreach (var current in all) { @@ -192,11 +199,8 @@ namespace Emby.Server.Implementations.Services continue; } - if (info.Path.StartsWith("/mediabrowser", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - if (info.Path.StartsWith("/jellyfin", StringComparison.OrdinalIgnoreCase)) + if (info.Path.StartsWith("/mediabrowser", StringComparison.OrdinalIgnoreCase) + || info.Path.StartsWith("/jellyfin", StringComparison.OrdinalIgnoreCase)) { continue; } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 82a76c6378..fab584befc 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -19,7 +19,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -119,6 +118,10 @@ namespace Jellyfin.Server SQLitePCL.Batteries_V2.Init(); + // Increase the max http request limit + // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others. + ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); + // Allow all https requests ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); -- cgit v1.2.3 From 157a86d0f139d149083036e6ea962e0fd7d77057 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 27 Mar 2019 12:43:46 +0100 Subject: Remove dead code --- Emby.Server.Implementations/Services/ServiceHandler.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 243d2cca27..621be4fcb5 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -11,8 +11,6 @@ namespace Emby.Server.Implementations.Services { public class ServiceHandler { - private readonly ServiceController _serviceController; - public RestPath RestPath { get; } public string ResponseContentType { get; } @@ -28,22 +26,12 @@ namespace Emby.Server.Implementations.Services if (!string.IsNullOrEmpty(contentType) && httpReq.ContentLength > 0) { var deserializer = RequestHelper.GetRequestReader(host, contentType); - if (deserializer != null) - { - return deserializer(requestType, httpReq.InputStream); - } + return deserializer?.Invoke(requestType, httpReq.InputStream); } return Task.FromResult(host.CreateInstance(requestType)); } - public RestPath FindMatchingRestPath(string httpMethod, string pathInfo, out string contentType) - { - pathInfo = GetSanitizedPathInfo(pathInfo, out contentType); - - return _serviceController.GetRestPathForRequest(httpMethod, pathInfo); - } - public static string GetSanitizedPathInfo(string pathInfo, out string contentType) { contentType = null; -- cgit v1.2.3 From 8ed5d154b746c086ccb6b1163772ea4d05067abd Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 27 Mar 2019 16:07:08 +0100 Subject: Remove duplicate code --- .../Session/SessionManager.cs | 62 ++++++++-------------- 1 file changed, 23 insertions(+), 39 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index dc23551db5..b7974dd7e8 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1037,6 +1037,25 @@ namespace Emby.Server.Implementations.Session } } + private static Task SendMessageToSessions(IEnumerable sessions, string name, T data, CancellationToken cancellationToken) + { + IEnumerable GetTasks() + { + foreach (var session in sessions) + { + var controllers = session.SessionControllers; + var messageId = Guid.NewGuid().ToString("N"); + + foreach (var controller in controllers) + { + yield return controller.SendMessage(name, messageId, data, controllers, cancellationToken); + } + } + } + + return Task.WhenAll(GetTasks()); + } + public async Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken) { CheckDisposed(); @@ -1832,15 +1851,7 @@ namespace Emby.Server.Implementations.Session var data = dataFn(); - IEnumerable GetTasks() - { - foreach (var session in sessions) - { - yield return SendMessageToSession(session, name, data, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); + return SendMessageToSessions(sessions, name, data, cancellationToken); } public Task SendMessageToUserSessions(List userIds, string name, T data, CancellationToken cancellationToken) @@ -1848,16 +1859,7 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)); - - IEnumerable GetTasks() - { - foreach (var session in sessions) - { - yield return SendMessageToSession(session, name, data, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); + return SendMessageToSessions(sessions, name, data, cancellationToken); } public Task SendMessageToUserDeviceSessions(string deviceId, string name, T data, CancellationToken cancellationToken) @@ -1865,16 +1867,7 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); - - IEnumerable GetTasks() - { - foreach (var session in sessions) - { - yield return SendMessageToSession(session, name, data, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); + return SendMessageToSessions(sessions, name, data, cancellationToken); } public Task SendMessageToUserDeviceAndAdminSessions(string deviceId, string name, T data, CancellationToken cancellationToken) @@ -1883,16 +1876,7 @@ namespace Emby.Server.Implementations.Session var sessions = Sessions .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)); - - IEnumerable GetTasks() - { - foreach (var session in sessions) - { - yield return SendMessageToSession(session, name, data, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); + return SendMessageToSessions(sessions, name, data, cancellationToken); } private bool IsAdminSession(SessionInfo s) -- cgit v1.2.3 From 6c0e2e249ddbab24c67b655b701e473d9343c2ef Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 27 Mar 2019 16:13:36 +0100 Subject: Even more duplicate code removed --- .../Session/SessionManager.cs | 56 +++------------------- 1 file changed, 6 insertions(+), 50 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b7974dd7e8..6b9573b1dd 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1242,12 +1242,13 @@ namespace Emby.Server.Implementations.Session return SendMessageToSession(session, "Playstate", command, cancellationToken); } - private void AssertCanControl(SessionInfo session, SessionInfo controllingSession) + private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession) { if (session == null) { throw new ArgumentNullException(nameof(session)); } + if (controllingSession == null) { throw new ArgumentNullException(nameof(controllingSession)); @@ -1259,26 +1260,11 @@ namespace Emby.Server.Implementations.Session /// /// The cancellation token. /// Task. - public async Task SendRestartRequiredNotification(CancellationToken cancellationToken) + public Task SendRestartRequiredNotification(CancellationToken cancellationToken) { CheckDisposed(); - var sessions = Sessions.ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, "RestartRequired", string.Empty, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error in SendRestartRequiredNotification.", ex); - } - - }, cancellationToken)).ToArray(); - - await Task.WhenAll(tasks).ConfigureAwait(false); + return SendMessageToSessions(Sessions, "RestartRequired", string.Empty, cancellationToken); } /// @@ -1290,22 +1276,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - var sessions = Sessions.ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, "ServerShuttingDown", string.Empty, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error in SendServerShutdownNotification.", ex); - } - - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return SendMessageToSessions(Sessions, "ServerShuttingDown", string.Empty, cancellationToken); } /// @@ -1319,22 +1290,7 @@ namespace Emby.Server.Implementations.Session _logger.LogDebug("Beginning SendServerRestartNotification"); - var sessions = Sessions.ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, "ServerRestarting", string.Empty, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error in SendServerRestartNotification.", ex); - } - - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return SendMessageToSessions(Sessions, "ServerRestarting", string.Empty, cancellationToken); } /// -- cgit v1.2.3 From b69b19ddce63651306bde494adfa656fd658b230 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 27 Mar 2019 16:28:52 +0100 Subject: Move messageId out of outer loop --- Emby.Server.Implementations/Session/SessionManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6b9573b1dd..53ed5fc223 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1041,11 +1041,10 @@ namespace Emby.Server.Implementations.Session { IEnumerable GetTasks() { + var messageId = Guid.NewGuid().ToString("N"); foreach (var session in sessions) { var controllers = session.SessionControllers; - var messageId = Guid.NewGuid().ToString("N"); - foreach (var controller in controllers) { yield return controller.SendMessage(name, messageId, data, controllers, cancellationToken); -- cgit v1.2.3 From b07c146fd96d9ed7676adffda0333ec85f0c05b6 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 27 Mar 2019 16:17:18 -0700 Subject: Update Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Library/DefaultPasswordResetProvider.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index a589d61689..da65967434 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -40,7 +40,10 @@ namespace Emby.Server.Implementations.Library HashSet usersreset = new HashSet(); foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) { - var spr = await _jsonSerializer.DeserializeFromStreamAsync(File.OpenRead(resetfile)).ConfigureAwait(false); + using (var str = File.OpenRead(resetfile)) + { + var spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); + } if (spr.ExpirationDate < DateTime.Now) { File.Delete(resetfile); @@ -51,7 +54,7 @@ namespace Emby.Server.Implementations.Library if (resetUser != null) { await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); + usersreset.Add(resetUser.Name); File.Delete(resetfile); } } @@ -85,8 +88,8 @@ namespace Emby.Server.Implementations.Library }; try - { - FileStream fileStream = File.OpenWrite(filePath); + { + FileStream fileStream = File.OpenWrite(filePath); _jsonSerializer.SerializeToStream(spr,fileStream); await fileStream.FlushAsync().ConfigureAwait(false); } -- cgit v1.2.3 From 5e8496bc593399f062169c90b1820c1b8b75a73e Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 27 Mar 2019 22:46:25 -0700 Subject: minor fixes and usings --- .../Library/DefaultPasswordResetProvider.cs | 27 +++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index da65967434..63ebc7c727 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -10,7 +10,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; -using Microsoft.Win32.SafeHandles; namespace Emby.Server.Implementations.Library { @@ -37,13 +36,15 @@ namespace Emby.Server.Implementations.Library public async Task RedeemPasswordResetPin(string pin) { + SerializablePasswordReset spr; HashSet usersreset = new HashSet(); foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) { using (var str = File.OpenRead(resetfile)) { - var spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); - } + spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); + } + if (spr.ExpirationDate < DateTime.Now) { File.Delete(resetfile); @@ -51,12 +52,14 @@ namespace Emby.Server.Implementations.Library else if (spr.Pin == pin) { var resetUser = _userManager.GetUserByName(spr.UserName); - if (resetUser != null) + if (resetUser == null) { - await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); - File.Delete(resetfile); + throw new Exception($"User with a username of {spr.UserName} not found"); } + + await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); + usersreset.Add(resetUser.Name); + File.Delete(resetfile); } } @@ -76,7 +79,7 @@ namespace Emby.Server.Implementations.Library public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) { - string pin = new Random().Next(99999999).ToString("00000000",CultureInfo.InvariantCulture); + string pin = new Random().Next(99999999).ToString("00000000", CultureInfo.InvariantCulture); DateTime expireTime = DateTime.Now.AddMinutes(30); string filePath = _passwordResetFileBase + user.InternalId + ".json"; SerializablePasswordReset spr = new SerializablePasswordReset @@ -89,9 +92,11 @@ namespace Emby.Server.Implementations.Library try { - FileStream fileStream = File.OpenWrite(filePath); - _jsonSerializer.SerializeToStream(spr,fileStream); - await fileStream.FlushAsync().ConfigureAwait(false); + using (FileStream fileStream = File.OpenWrite(filePath)) + { + _jsonSerializer.SerializeToStream(spr, fileStream); + await fileStream.FlushAsync().ConfigureAwait(false); + } } catch (Exception e) { -- cgit v1.2.3 From 48b50a22a43dde00c795fb01521fcd731c323de7 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 28 Mar 2019 08:15:53 -0700 Subject: switched to a hexa string with crypto random backing --- .../Library/DefaultPasswordResetProvider.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 63ebc7c727..b726fa2d0a 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -8,6 +8,7 @@ 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; @@ -25,13 +26,15 @@ namespace Emby.Server.Implementations.Library private IJsonSerializer _jsonSerializer; private IUserManager _userManager; + private ICryptoProvider _crypto; - public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager) + public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider) { _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName); _jsonSerializer = jsonSerializer; _userManager = userManager; + _crypto = cryptoProvider; } public async Task RedeemPasswordResetPin(string pin) @@ -49,7 +52,7 @@ namespace Emby.Server.Implementations.Library { File.Delete(resetfile); } - else if (spr.Pin == pin) + else if (spr.Pin.Equals(pin, StringComparison.InvariantCultureIgnoreCase)) { var resetUser = _userManager.GetUserByName(spr.UserName); if (resetUser == null) @@ -79,7 +82,14 @@ namespace Emby.Server.Implementations.Library public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) { - string pin = new Random().Next(99999999).ToString("00000000", CultureInfo.InvariantCulture); + string pin = string.Empty; + using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create()) + { + byte[] bytes = new byte[4]; + cryptoRandom.GetBytes(bytes); + pin = bytes.ToString(); + } + DateTime expireTime = DateTime.Now.AddMinutes(30); string filePath = _passwordResetFileBase + user.InternalId + ".json"; SerializablePasswordReset spr = new SerializablePasswordReset -- cgit v1.2.3 From 3001f21f8d8592586add36661a0be0679e427d63 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 28 Mar 2019 19:11:05 +0100 Subject: Hacky fix for a hacky issue --- Emby.Server.Implementations/ApplicationHost.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 1c9a4776a0..ce8483a322 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1042,8 +1042,6 @@ namespace Emby.Server.Implementations CollectionFolder.JsonSerializer = JsonSerializer; CollectionFolder.ApplicationHost = this; AuthenticatedAttribute.AuthService = AuthService; - - InstallationManager.PluginInstalled += PluginInstalled; } private async void PluginInstalled(object sender, GenericEventArgs args) @@ -1085,6 +1083,7 @@ namespace Emby.Server.Implementations protected void FindParts() { InstallationManager = _serviceProvider.GetService(); + InstallationManager.PluginInstalled += PluginInstalled; if (!ServerConfigurationManager.Configuration.IsPortAuthorized) { -- cgit v1.2.3 From 41df562419d8f1681a9720ab1c62ffb9ad0f96cb Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 28 Mar 2019 23:19:56 +0100 Subject: Improve IO code * Style changes * Remove remnants of SMB support * Use `GetInvalidFileNameChars` instead of rolling our own * Remove possible unexpected behaviour with async file streams * Remove some dead code --- Emby.Server.Implementations/IO/FileRefresher.cs | 6 +- Emby.Server.Implementations/IO/LibraryMonitor.cs | 125 +++----- .../IO/ManagedFileSystem.cs | 68 ++-- Emby.Server.Implementations/IO/StreamHelper.cs | 74 +---- Emby.Server.Implementations/IO/ThrottledStream.cs | 355 --------------------- 5 files changed, 82 insertions(+), 546 deletions(-) delete mode 100644 Emby.Server.Implementations/IO/ThrottledStream.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 73242d0ade..40e8ed5dc7 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -6,10 +6,6 @@ using System.Threading; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; -using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.IO @@ -61,6 +57,7 @@ namespace Emby.Server.Implementations.IO { AddAffectedPath(path); } + RestartTimer(); } @@ -103,6 +100,7 @@ namespace Emby.Server.Implementations.IO AddAffectedPath(affectedFile); } } + RestartTimer(); } diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index df4dc41b99..aeb541c540 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -9,9 +9,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; -using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations.IO { @@ -21,6 +19,7 @@ namespace Emby.Server.Implementations.IO /// The file system watchers /// private readonly ConcurrentDictionary _fileSystemWatchers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + /// /// The affected paths /// @@ -97,7 +96,7 @@ namespace Emby.Server.Implementations.IO throw new ArgumentNullException(nameof(path)); } - // This is an arbitraty amount of time, but delay it because file system writes often trigger events long after the file was actually written to. + // This is an arbitrary amount of time, but delay it because file system writes often trigger events long after the file was actually written to. // Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds // But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata await Task.Delay(45000).ConfigureAwait(false); @@ -162,10 +161,10 @@ namespace Emby.Server.Implementations.IO public void Start() { - LibraryManager.ItemAdded += LibraryManager_ItemAdded; - LibraryManager.ItemRemoved += LibraryManager_ItemRemoved; + LibraryManager.ItemAdded += OnLibraryManagerItemAdded; + LibraryManager.ItemRemoved += OnLibraryManagerItemRemoved; - var pathsToWatch = new List { }; + var pathsToWatch = new List(); var paths = LibraryManager .RootFolder @@ -204,7 +203,7 @@ namespace Emby.Server.Implementations.IO /// /// The source of the event. /// The instance containing the event data. - void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) + private void OnLibraryManagerItemRemoved(object sender, ItemChangeEventArgs e) { if (e.Parent is AggregateFolder) { @@ -217,7 +216,7 @@ namespace Emby.Server.Implementations.IO /// /// The source of the event. /// The instance containing the event data. - void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + private void OnLibraryManagerItemAdded(object sender, ItemChangeEventArgs e) { if (e.Parent is AggregateFolder) { @@ -244,7 +243,7 @@ namespace Emby.Server.Implementations.IO return lst.Any(str => { - //this should be a little quicker than examining each actual parent folder... + // this should be a little quicker than examining each actual parent folder... var compare = str.TrimEnd(Path.DirectorySeparatorChar); return path.Equals(compare, StringComparison.OrdinalIgnoreCase) || (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == Path.DirectorySeparatorChar); @@ -260,19 +259,10 @@ namespace Emby.Server.Implementations.IO if (!Directory.Exists(path)) { // Seeing a crash in the mono runtime due to an exception being thrown on a different thread - Logger.LogInformation("Skipping realtime monitor for {0} because the path does not exist", path); + Logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path); return; } - if (OperatingSystem.Id != OperatingSystemId.Windows) - { - if (path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase) || path.StartsWith("smb://", StringComparison.OrdinalIgnoreCase)) - { - // not supported - return; - } - } - // Already being watched if (_fileSystemWatchers.ContainsKey(path)) { @@ -286,23 +276,21 @@ namespace Emby.Server.Implementations.IO { var newWatcher = new FileSystemWatcher(path, "*") { - IncludeSubdirectories = true + IncludeSubdirectories = true, + InternalBufferSize = 65536, + NotifyFilter = NotifyFilters.CreationTime | + NotifyFilters.DirectoryName | + NotifyFilters.FileName | + NotifyFilters.LastWrite | + NotifyFilters.Size | + NotifyFilters.Attributes }; - newWatcher.InternalBufferSize = 65536; - - newWatcher.NotifyFilter = NotifyFilters.CreationTime | - NotifyFilters.DirectoryName | - NotifyFilters.FileName | - NotifyFilters.LastWrite | - NotifyFilters.Size | - NotifyFilters.Attributes; - - newWatcher.Created += watcher_Changed; - newWatcher.Deleted += watcher_Changed; - newWatcher.Renamed += watcher_Changed; - newWatcher.Changed += watcher_Changed; - newWatcher.Error += watcher_Error; + newWatcher.Created += OnWatcherChanged; + newWatcher.Deleted += OnWatcherChanged; + newWatcher.Renamed += OnWatcherChanged; + newWatcher.Changed += OnWatcherChanged; + newWatcher.Error += OnWatcherError; if (_fileSystemWatchers.TryAdd(path, newWatcher)) { @@ -343,32 +331,16 @@ namespace Emby.Server.Implementations.IO { using (watcher) { - Logger.LogInformation("Stopping directory watching for path {path}", watcher.Path); + Logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path); - watcher.Created -= watcher_Changed; - watcher.Deleted -= watcher_Changed; - watcher.Renamed -= watcher_Changed; - watcher.Changed -= watcher_Changed; - watcher.Error -= watcher_Error; + watcher.Created -= OnWatcherChanged; + watcher.Deleted -= OnWatcherChanged; + watcher.Renamed -= OnWatcherChanged; + watcher.Changed -= OnWatcherChanged; + watcher.Error -= OnWatcherError; - try - { - watcher.EnableRaisingEvents = false; - } - catch (InvalidOperationException) - { - // Seeing this under mono on linux sometimes - // Collection was modified; enumeration operation may not execute. - } + watcher.EnableRaisingEvents = false; } - } - catch (NotImplementedException) - { - // the dispose method on FileSystemWatcher is sometimes throwing NotImplementedException on Xamarin Android - } - catch - { - } finally { @@ -385,7 +357,7 @@ namespace Emby.Server.Implementations.IO /// The watcher. private void RemoveWatcherFromList(FileSystemWatcher watcher) { - _fileSystemWatchers.TryRemove(watcher.Path, out var removed); + _fileSystemWatchers.TryRemove(watcher.Path, out _); } /// @@ -393,12 +365,12 @@ namespace Emby.Server.Implementations.IO /// /// The source of the event. /// The instance containing the event data. - void watcher_Error(object sender, ErrorEventArgs e) + private void OnWatcherError(object sender, ErrorEventArgs e) { var ex = e.GetException(); var dw = (FileSystemWatcher)sender; - Logger.LogError(ex, "Error in Directory watcher for: {path}", dw.Path); + Logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path); DisposeWatcher(dw, true); } @@ -408,15 +380,11 @@ namespace Emby.Server.Implementations.IO /// /// The source of the event. /// The instance containing the event data. - void watcher_Changed(object sender, FileSystemEventArgs e) + private void OnWatcherChanged(object sender, FileSystemEventArgs e) { try { - //logger.LogDebug("Changed detected of type " + e.ChangeType + " to " + e.FullPath); - - var path = e.FullPath; - - ReportFileSystemChanged(path); + ReportFileSystemChanged(e.FullPath); } catch (Exception ex) { @@ -446,25 +414,22 @@ namespace Emby.Server.Implementations.IO { if (_fileSystem.AreEqual(i, path)) { - Logger.LogDebug("Ignoring change to {path}", path); + Logger.LogDebug("Ignoring change to {Path}", path); return true; } if (_fileSystem.ContainsSubPath(i, path)) { - Logger.LogDebug("Ignoring change to {path}", path); + Logger.LogDebug("Ignoring change to {Path}", path); return true; } // Go up a level var parent = Path.GetDirectoryName(i); - if (!string.IsNullOrEmpty(parent)) + if (!string.IsNullOrEmpty(parent) && _fileSystem.AreEqual(parent, path)) { - if (_fileSystem.AreEqual(parent, path)) - { - Logger.LogDebug("Ignoring change to {path}", path); - return true; - } + Logger.LogDebug("Ignoring change to {Path}", path); + return true; } return false; @@ -487,8 +452,7 @@ namespace Emby.Server.Implementations.IO lock (_activeRefreshers) { - var refreshers = _activeRefreshers.ToList(); - foreach (var refresher in refreshers) + foreach (var refresher in _activeRefreshers) { // Path is already being refreshed if (_fileSystem.AreEqual(path, refresher.Path)) @@ -536,8 +500,8 @@ namespace Emby.Server.Implementations.IO /// public void Stop() { - LibraryManager.ItemAdded -= LibraryManager_ItemAdded; - LibraryManager.ItemRemoved -= LibraryManager_ItemRemoved; + LibraryManager.ItemAdded -= OnLibraryManagerItemAdded; + LibraryManager.ItemRemoved -= OnLibraryManagerItemRemoved; foreach (var watcher in _fileSystemWatchers.Values.ToList()) { @@ -565,17 +529,20 @@ namespace Emby.Server.Implementations.IO { refresher.Dispose(); } + _activeRefreshers.Clear(); } } - private bool _disposed; + private bool _disposed = false; + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 47cea7269d..4b5cfe3b9f 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -19,8 +19,6 @@ namespace Emby.Server.Implementations.IO { protected ILogger Logger; - private readonly bool _supportsAsyncFileStreams; - private char[] _invalidFileNameChars; private readonly List _shortcutHandlers = new List(); private readonly string _tempPath; @@ -32,11 +30,8 @@ namespace Emby.Server.Implementations.IO IApplicationPaths applicationPaths) { Logger = loggerFactory.CreateLogger("FileSystem"); - _supportsAsyncFileStreams = true; _tempPath = applicationPaths.TempDirectory; - SetInvalidFileNameChars(OperatingSystem.Id == OperatingSystemId.Windows); - _isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows; } @@ -45,20 +40,6 @@ namespace Emby.Server.Implementations.IO _shortcutHandlers.Add(handler); } - protected void SetInvalidFileNameChars(bool enableManagedInvalidFileNameChars) - { - if (enableManagedInvalidFileNameChars) - { - _invalidFileNameChars = Path.GetInvalidFileNameChars(); - } - else - { - // Be consistent across platforms because the windows server will fail to query network shares that don't follow windows conventions - // https://referencesource.microsoft.com/#mscorlib/system/io/path.cs - _invalidFileNameChars = new char[] { '\"', '<', '>', '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, (char)31, ':', '*', '?', '\\', '/' }; - } - } - /// /// Determines whether the specified filename is shortcut. /// @@ -92,20 +73,25 @@ namespace Emby.Server.Implementations.IO var extension = Path.GetExtension(filename); var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); - if (handler != null) - { - return handler.Resolve(filename); - } - - return null; + return handler?.Resolve(filename); } public virtual string MakeAbsolutePath(string folderPath, string filePath) { - if (string.IsNullOrWhiteSpace(filePath)) return filePath; + if (string.IsNullOrWhiteSpace(filePath)) + { + return filePath; + } + + if (filePath.Contains("://")) + { + return filePath; // stream + } - if (filePath.Contains(@"://")) return filePath; //stream - if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/') return filePath; //absolute local path + if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/') + { + return filePath; // absolute local path + } // unc path if (filePath.StartsWith("\\\\")) @@ -125,9 +111,7 @@ namespace Emby.Server.Implementations.IO } try { - string path = System.IO.Path.Combine(folderPath, filePath); - path = System.IO.Path.GetFullPath(path); - return path; + return Path.Combine(Path.GetFullPath(folderPath), filePath); } catch (ArgumentException) { @@ -166,7 +150,7 @@ namespace Emby.Server.Implementations.IO } var extension = Path.GetExtension(shortcutPath); - var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); + var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); if (handler != null) { @@ -244,12 +228,13 @@ namespace Emby.Server.Implementations.IO private FileSystemMetadata GetFileSystemMetadata(FileSystemInfo info) { - var result = new FileSystemMetadata(); - - result.Exists = info.Exists; - result.FullName = info.FullName; - result.Extension = info.Extension; - result.Name = info.Name; + var result = new FileSystemMetadata + { + Exists = info.Exists, + FullName = info.FullName, + Extension = info.Extension, + Name = info.Name + }; if (result.Exists) { @@ -260,8 +245,7 @@ namespace Emby.Server.Implementations.IO // result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden; //} - var fileInfo = info as FileInfo; - if (fileInfo != null) + if (info is FileInfo fileInfo) { result.Length = fileInfo.Length; result.DirectoryName = fileInfo.DirectoryName; @@ -307,7 +291,7 @@ namespace Emby.Server.Implementations.IO { var builder = new StringBuilder(filename); - foreach (var c in _invalidFileNameChars) + foreach (var c in Path.GetInvalidFileNameChars()) { builder = builder.Replace(c, ' '); } @@ -394,7 +378,7 @@ namespace Emby.Server.Implementations.IO /// FileStream. public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false) { - if (_supportsAsyncFileStreams && isAsync) + if (isAsync) { return GetFileStream(path, mode, access, share, FileOpenOptions.Asynchronous); } diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs index d02cd84a03..7c8c079e39 100644 --- a/Emby.Server.Implementations/IO/StreamHelper.cs +++ b/Emby.Server.Implementations/IO/StreamHelper.cs @@ -17,11 +17,11 @@ namespace Emby.Server.Implementations.IO try { int read; - while ((read = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) != 0) + while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) { cancellationToken.ThrowIfCancellationRequested(); - await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); + await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false); if (onStarted != null) { @@ -44,11 +44,11 @@ namespace Emby.Server.Implementations.IO if (emptyReadLimit <= 0) { int read; - while ((read = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) != 0) + while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) { cancellationToken.ThrowIfCancellationRequested(); - await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); + await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false); } return; @@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.IO { cancellationToken.ThrowIfCancellationRequested(); - var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); + var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); if (bytesRead == 0) { @@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.IO { eofCount = 0; - await destination.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); } } } @@ -109,64 +109,6 @@ namespace Emby.Server.Implementations.IO } } - public async Task CopyToAsyncWithSyncRead(Stream source, Stream destination, CancellationToken cancellationToken) - { - byte[] buffer = ArrayPool.Shared.Rent(StreamCopyToBufferSize); - try - { - int bytesRead; - int totalBytesRead = 0; - - while ((bytesRead = source.Read(buffer, 0, buffer.Length)) != 0) - { - var bytesToWrite = bytesRead; - - if (bytesToWrite > 0) - { - await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); - - totalBytesRead += bytesRead; - } - } - - return totalBytesRead; - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - - public async Task CopyToAsyncWithSyncRead(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) - { - byte[] buffer = ArrayPool.Shared.Rent(StreamCopyToBufferSize); - try - { - int bytesRead; - - while ((bytesRead = source.Read(buffer, 0, buffer.Length)) != 0) - { - var bytesToWrite = Math.Min(bytesRead, copyLength); - - if (bytesToWrite > 0) - { - await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); - } - - copyLength -= bytesToWrite; - - if (copyLength <= 0) - { - break; - } - } - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) { byte[] buffer = ArrayPool.Shared.Rent(StreamCopyToBufferSize); @@ -208,7 +150,7 @@ namespace Emby.Server.Implementations.IO if (bytesRead == 0) { - await Task.Delay(100).ConfigureAwait(false); + await Task.Delay(100, cancellationToken).ConfigureAwait(false); } } } @@ -225,7 +167,7 @@ namespace Emby.Server.Implementations.IO while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) { - await destination.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); totalBytesRead += bytesRead; } diff --git a/Emby.Server.Implementations/IO/ThrottledStream.cs b/Emby.Server.Implementations/IO/ThrottledStream.cs deleted file mode 100644 index 81e8abc983..0000000000 --- a/Emby.Server.Implementations/IO/ThrottledStream.cs +++ /dev/null @@ -1,355 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Emby.Server.Implementations.IO -{ - /// - /// Class for streaming data with throttling support. - /// - public class ThrottledStream : Stream - { - /// - /// A constant used to specify an infinite number of bytes that can be transferred per second. - /// - public const long Infinite = 0; - - #region Private members - /// - /// The base stream. - /// - private readonly Stream _baseStream; - - /// - /// The maximum bytes per second that can be transferred through the base stream. - /// - private long _maximumBytesPerSecond; - - /// - /// The number of bytes that has been transferred since the last throttle. - /// - private long _byteCount; - - /// - /// The start time in milliseconds of the last throttle. - /// - private long _start; - #endregion - - #region Properties - /// - /// Gets the current milliseconds. - /// - /// The current milliseconds. - protected long CurrentMilliseconds => Environment.TickCount; - - /// - /// Gets or sets the maximum bytes per second that can be transferred through the base stream. - /// - /// The maximum bytes per second. - public long MaximumBytesPerSecond - { - get => _maximumBytesPerSecond; - set - { - if (MaximumBytesPerSecond != value) - { - _maximumBytesPerSecond = value; - Reset(); - } - } - } - - /// - /// Gets a value indicating whether the current stream supports reading. - /// - /// true if the stream supports reading; otherwise, false. - public override bool CanRead => _baseStream.CanRead; - - /// - /// Gets a value indicating whether the current stream supports seeking. - /// - /// - /// true if the stream supports seeking; otherwise, false. - public override bool CanSeek => _baseStream.CanSeek; - - /// - /// Gets a value indicating whether the current stream supports writing. - /// - /// - /// true if the stream supports writing; otherwise, false. - public override bool CanWrite => _baseStream.CanWrite; - - /// - /// Gets the length in bytes of the stream. - /// - /// - /// A long value representing the length of the stream in bytes. - /// The base stream does not support seeking. - /// Methods were called after the stream was closed. - public override long Length => _baseStream.Length; - - /// - /// Gets or sets the position within the current stream. - /// - /// - /// The current position within the stream. - /// An I/O error occurs. - /// The base stream does not support seeking. - /// Methods were called after the stream was closed. - public override long Position - { - get => _baseStream.Position; - set => _baseStream.Position = value; - } - #endregion - - public long MinThrottlePosition; - - #region Ctor - /// - /// Initializes a new instance of the class. - /// - /// The base stream. - /// The maximum bytes per second that can be transferred through the base stream. - /// Thrown when is a null reference. - /// Thrown when is a negative value. - public ThrottledStream(Stream baseStream, long maximumBytesPerSecond) - { - if (baseStream == null) - { - throw new ArgumentNullException(nameof(baseStream)); - } - - if (maximumBytesPerSecond < 0) - { - throw new ArgumentOutOfRangeException(nameof(maximumBytesPerSecond), - maximumBytesPerSecond, "The maximum number of bytes per second can't be negative."); - } - - _baseStream = baseStream; - _maximumBytesPerSecond = maximumBytesPerSecond; - _start = CurrentMilliseconds; - _byteCount = 0; - } - #endregion - - #region Public methods - /// - /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. - /// - /// An I/O error occurs. - public override void Flush() - { - _baseStream.Flush(); - } - - /// - /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - /// - /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. - /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. - /// The maximum number of bytes to be read from the current stream. - /// - /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. - /// - /// The sum of offset and count is larger than the buffer length. - /// Methods were called after the stream was closed. - /// The base stream does not support reading. - /// buffer is null. - /// An I/O error occurs. - /// offset or count is negative. - public override int Read(byte[] buffer, int offset, int count) - { - Throttle(count); - - return _baseStream.Read(buffer, offset, count); - } - - /// - /// Sets the position within the current stream. - /// - /// A byte offset relative to the origin parameter. - /// A value of type indicating the reference point used to obtain the new position. - /// - /// The new position within the current stream. - /// - /// An I/O error occurs. - /// The base stream does not support seeking, such as if the stream is constructed from a pipe or console output. - /// Methods were called after the stream was closed. - public override long Seek(long offset, SeekOrigin origin) - { - return _baseStream.Seek(offset, origin); - } - - /// - /// Sets the length of the current stream. - /// - /// The desired length of the current stream in bytes. - /// The base stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. - /// An I/O error occurs. - /// Methods were called after the stream was closed. - public override void SetLength(long value) - { - _baseStream.SetLength(value); - } - - private long _bytesWritten; - - /// - /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - /// - /// An array of bytes. This method copies count bytes from buffer to the current stream. - /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. - /// The number of bytes to be written to the current stream. - /// An I/O error occurs. - /// The base stream does not support writing. - /// Methods were called after the stream was closed. - /// buffer is null. - /// The sum of offset and count is greater than the buffer length. - /// offset or count is negative. - public override void Write(byte[] buffer, int offset, int count) - { - Throttle(count); - - _baseStream.Write(buffer, offset, count); - - _bytesWritten += count; - } - - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - await ThrottleAsync(count, cancellationToken).ConfigureAwait(false); - - await _baseStream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); - - _bytesWritten += count; - } - - /// - /// Returns a that represents the current . - /// - /// - /// A that represents the current . - /// - public override string ToString() - { - return _baseStream.ToString(); - } - #endregion - - private bool ThrottleCheck(int bufferSizeInBytes) - { - if (_bytesWritten < MinThrottlePosition) - { - return false; - } - - // Make sure the buffer isn't empty. - if (_maximumBytesPerSecond <= 0 || bufferSizeInBytes <= 0) - { - return false; - } - - return true; - } - - #region Protected methods - /// - /// Throttles for the specified buffer size in bytes. - /// - /// The buffer size in bytes. - protected void Throttle(int bufferSizeInBytes) - { - if (!ThrottleCheck(bufferSizeInBytes)) - { - return; - } - - _byteCount += bufferSizeInBytes; - long elapsedMilliseconds = CurrentMilliseconds - _start; - - if (elapsedMilliseconds > 0) - { - // Calculate the current bps. - long bps = _byteCount * 1000L / elapsedMilliseconds; - - // If the bps are more then the maximum bps, try to throttle. - if (bps > _maximumBytesPerSecond) - { - // Calculate the time to sleep. - long wakeElapsed = _byteCount * 1000L / _maximumBytesPerSecond; - int toSleep = (int)(wakeElapsed - elapsedMilliseconds); - - if (toSleep > 1) - { - try - { - // The time to sleep is more then a millisecond, so sleep. - var task = Task.Delay(toSleep); - Task.WaitAll(task); - } - catch - { - // Eatup ThreadAbortException. - } - - // A sleep has been done, reset. - Reset(); - } - } - } - } - - protected async Task ThrottleAsync(int bufferSizeInBytes, CancellationToken cancellationToken) - { - if (!ThrottleCheck(bufferSizeInBytes)) - { - return; - } - - _byteCount += bufferSizeInBytes; - long elapsedMilliseconds = CurrentMilliseconds - _start; - - if (elapsedMilliseconds > 0) - { - // Calculate the current bps. - long bps = _byteCount * 1000L / elapsedMilliseconds; - - // If the bps are more then the maximum bps, try to throttle. - if (bps > _maximumBytesPerSecond) - { - // Calculate the time to sleep. - long wakeElapsed = _byteCount * 1000L / _maximumBytesPerSecond; - int toSleep = (int)(wakeElapsed - elapsedMilliseconds); - - if (toSleep > 1) - { - // The time to sleep is more then a millisecond, so sleep. - await Task.Delay(toSleep, cancellationToken).ConfigureAwait(false); - - // A sleep has been done, reset. - Reset(); - } - } - } - } - - /// - /// Will reset the bytecount to 0 and reset the start time to the current time. - /// - protected void Reset() - { - long difference = CurrentMilliseconds - _start; - - // Only reset counters when a known history is available of more then 1 second. - if (difference > 1000) - { - _byteCount = 0; - _start = CurrentMilliseconds; - } - } - #endregion - } -} -- cgit v1.2.3 From b56031b9f3ccfd4a8ac0413657f45645fe2e0f1e Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 28 Mar 2019 20:49:11 -0700 Subject: fix byte string --- Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index b726fa2d0a..56540cc089 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Authentication; @@ -87,7 +88,7 @@ namespace Emby.Server.Implementations.Library { byte[] bytes = new byte[4]; cryptoRandom.GetBytes(bytes); - pin = bytes.ToString(); + pin = BitConverter.ToString(bytes); } DateTime expireTime = DateTime.Now.AddMinutes(30); -- cgit v1.2.3 From 2d396cb589722bf8a950f80abb6d6137fe084a52 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Fri, 29 Mar 2019 07:10:49 -0700 Subject: adds readonly to properties --- Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 56540cc089..256399d2f0 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -25,9 +25,9 @@ namespace Emby.Server.Implementations.Library private readonly string _passwordResetFileBaseDir; private readonly string _passwordResetFileBaseName = "passwordreset"; - private IJsonSerializer _jsonSerializer; - private IUserManager _userManager; - private ICryptoProvider _crypto; + private readonly IJsonSerializer _jsonSerializer; + private readonly IUserManager _userManager; + private readonly ICryptoProvider _crypto; public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider) { -- cgit v1.2.3 From f911fda34fdc1af76dc475c5af042ff6b44262ab Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 29 Mar 2019 20:34:42 +0100 Subject: Merge ifs --- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 4b5cfe3b9f..0dea5041a1 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -78,16 +78,13 @@ namespace Emby.Server.Implementations.IO public virtual string MakeAbsolutePath(string folderPath, string filePath) { - if (string.IsNullOrWhiteSpace(filePath)) + if (string.IsNullOrWhiteSpace(filePath) + // stream + || filePath.Contains("://")) { return filePath; } - if (filePath.Contains("://")) - { - return filePath; // stream - } - if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/') { return filePath; // absolute local path -- cgit v1.2.3 From 13e94a8b1b78d570a528eee65ff777412f0e83c8 Mon Sep 17 00:00:00 2001 From: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> Date: Fri, 29 Mar 2019 12:48:07 -0700 Subject: Remove dashes from pins --- .../Library/DefaultPasswordResetProvider.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index 256399d2f0..c6d4755208 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -47,13 +47,13 @@ namespace Emby.Server.Implementations.Library using (var str = File.OpenRead(resetfile)) { spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); - } + } if (spr.ExpirationDate < DateTime.Now) { File.Delete(resetfile); } - else if (spr.Pin.Equals(pin, StringComparison.InvariantCultureIgnoreCase)) + else if (spr.Pin.Replace('-', '').Equals(pin.Replace('-', ''), StringComparison.InvariantCultureIgnoreCase)) { var resetUser = _userManager.GetUserByName(spr.UserName); if (resetUser == null) @@ -85,11 +85,11 @@ namespace Emby.Server.Implementations.Library { string pin = string.Empty; using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create()) - { + { byte[] bytes = new byte[4]; cryptoRandom.GetBytes(bytes); pin = BitConverter.ToString(bytes); - } + } DateTime expireTime = DateTime.Now.AddMinutes(30); string filePath = _passwordResetFileBase + user.InternalId + ".json"; -- cgit v1.2.3 From f0fbd0232cd2367dda26f3f895926c1d0f742bdd Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Fri, 29 Mar 2019 19:13:01 -0400 Subject: Correct bad quote characters --- Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index c6d4755208..e218749d90 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Library { File.Delete(resetfile); } - else if (spr.Pin.Replace('-', '').Equals(pin.Replace('-', ''), StringComparison.InvariantCultureIgnoreCase)) + else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase)) { var resetUser = _userManager.GetUserByName(spr.UserName); if (resetUser == null) -- cgit v1.2.3 From 38fcd31917af1d904ea61de0f90918d44122d2e4 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 2 Apr 2019 18:19:19 -0400 Subject: Search all subdirectories for Plugins This was added in #801 which broke the previous plugin install behaviour. Previously plugins could be loaded from subdirectories but this search was only for the highest level. Change it to search all subdirectories instead to restore the previous behaviour. Also modifies the same option from #934, though I'm not 100% sure if this is needed here. --- Emby.Server.Implementations/ApplicationHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 41ca2a1025..dbdf246a2d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1047,7 +1047,7 @@ namespace Emby.Server.Implementations private async void PluginInstalled(object sender, GenericEventArgs args) { string dir = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(args.Argument.targetFilename)); - var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.TopDirectoryOnly) + var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories) .Select(x => Assembly.LoadFrom(x)) .SelectMany(x => x.ExportedTypes) .Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType) @@ -1346,7 +1346,7 @@ namespace Emby.Server.Implementations { if (Directory.Exists(ApplicationPaths.PluginsPath)) { - foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly)) + foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.AllDirectories)) { Logger.LogInformation("Loading assembly {Path}", file); yield return Assembly.LoadFrom(file); -- cgit v1.2.3 From 05a4161fd388d7ecda2f1b4a6ec6d02ee7b4488e Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Wed, 3 Apr 2019 19:43:02 -0400 Subject: Correct the installation and removal of plugins Upgrading plugins was broken for various reasons. There are four fixes and a minor one: 1. Use a directory name based only on the `Name` of the plugin, not the source filename, which contains the version. Avoids strange duplication of the plugin. 2. Use the new directory name for the deletes if it's present, so that installation and removal happen at that directory level and we don't leave empty folders laying around. Ensures we properly remove additional resources in plugins too, not just the main `.dll` file. 3. Ignore the incoming `target` when installing, and always set it ourself to the proper directory, which would matter when reinstalling. 4. Deletes an existing target directory before installing if it exists. Note that not calling any of the plugin removal code is intentional; I suspect that would delete configurations unexpectedly when upgrading which would be annoying. This way, it just replaces the files and then reloads. 5. (Minor) Added some actual debug messages around the plugin download section so failures can be more accurately seen. --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Updates/InstallationManager.cs | 41 ++++++++++++++++++---- 2 files changed, 36 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index dbdf246a2d..05f8a8a5e7 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1046,7 +1046,7 @@ namespace Emby.Server.Implementations private async void PluginInstalled(object sender, GenericEventArgs args) { - string dir = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(args.Argument.targetFilename)); + string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name); var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories) .Select(x => Assembly.LoadFrom(x)) .SelectMany(x => x.ExportedTypes) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 7310de55d4..e5ba813a6c 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -85,9 +85,11 @@ namespace Emby.Server.Implementations.Updates private void OnPluginInstalled(PackageVersionInfo package) { _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification); + _logger.LogDebug("{String}", package.name); PluginInstalled?.Invoke(this, new GenericEventArgs { Argument = package }); + _logger.LogDebug("{String}", package.name); _applicationHost.NotifyPendingRestart(); } @@ -518,12 +520,12 @@ namespace Emby.Server.Implementations.Updates return; } - if (target == null) - { - target = Path.Combine(_appPaths.PluginsPath, Path.GetFileNameWithoutExtension(package.targetFilename)); - } + // Always override the passed-in target (which is a file) and figure it out again + target = Path.Combine(_appPaths.PluginsPath, package.name); + _logger.LogDebug("Installing plugin to {Filename}.", target); // Download to temporary file so that, if interrupted, it won't destroy the existing installation + _logger.LogDebug("Downloading ZIP."); var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions { Url = package.sourceUrl, @@ -536,9 +538,17 @@ namespace Emby.Server.Implementations.Updates // TODO: Validate with a checksum, *properly* + // Check if the target directory already exists, and remove it if so + if (Directory.Exists(target)) + { + _logger.LogDebug("Deleting existing plugin at {Filename}.", target); + Directory.Delete(target, true); + } + // Success - move it to the real target try { + _logger.LogDebug("Extracting ZIP {TempFile} to {Filename}.", tempFile, target); using (var stream = File.OpenRead(tempFile)) { _zipClient.ExtractAllFromZip(stream, target, true); @@ -552,6 +562,7 @@ namespace Emby.Server.Implementations.Updates try { + _logger.LogDebug("Deleting temporary file {Filename}.", tempFile); _fileSystem.DeleteFile(tempFile); } catch (IOException ex) @@ -574,7 +585,18 @@ namespace Emby.Server.Implementations.Updates _applicationHost.RemovePlugin(plugin); var path = plugin.AssemblyFilePath; - _logger.LogInformation("Deleting plugin file {0}", path); + bool is_path_directory = false; + // Check if we have a plugin directory we should remove too + if (Path.GetDirectoryName(plugin.AssemblyFilePath) != _appPaths.PluginsPath) + { + path = Path.GetDirectoryName(plugin.AssemblyFilePath); + is_path_directory = true; + _logger.LogInformation("Deleting plugin directory {0}", path); + } + else + { + _logger.LogInformation("Deleting plugin file {0}", path); + } // Make this case-insensitive to account for possible incorrect assembly naming var file = _fileSystem.GetFilePaths(Path.GetDirectoryName(path)) @@ -585,7 +607,14 @@ namespace Emby.Server.Implementations.Updates path = file; } - _fileSystem.DeleteFile(path); + if (is_path_directory) + { + Directory.Delete(path, true); + } + else + { + _fileSystem.DeleteFile(path); + } var list = _config.Configuration.UninstalledPlugins.ToList(); var filename = Path.GetFileName(path); -- cgit v1.2.3 From 09505e0988ec53538c3ecc3d243b1d1a5edac8c6 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Thu, 4 Apr 2019 01:54:31 -0400 Subject: Apply review feedback Remove a few superfluous/testing log statements, and print the deletion debug messages when it occurs rather than earlier. Use a nicer name for the isDirectory variable. --- .../Updates/InstallationManager.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index e5ba813a6c..95fbefe00a 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -85,11 +85,9 @@ namespace Emby.Server.Implementations.Updates private void OnPluginInstalled(PackageVersionInfo package) { _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification); - _logger.LogDebug("{String}", package.name); PluginInstalled?.Invoke(this, new GenericEventArgs { Argument = package }); - _logger.LogDebug("{String}", package.name); _applicationHost.NotifyPendingRestart(); } @@ -585,17 +583,12 @@ namespace Emby.Server.Implementations.Updates _applicationHost.RemovePlugin(plugin); var path = plugin.AssemblyFilePath; - bool is_path_directory = false; + bool isDirectory = false; // Check if we have a plugin directory we should remove too if (Path.GetDirectoryName(plugin.AssemblyFilePath) != _appPaths.PluginsPath) { path = Path.GetDirectoryName(plugin.AssemblyFilePath); - is_path_directory = true; - _logger.LogInformation("Deleting plugin directory {0}", path); - } - else - { - _logger.LogInformation("Deleting plugin file {0}", path); + isDirectory = true; } // Make this case-insensitive to account for possible incorrect assembly naming @@ -607,12 +600,14 @@ namespace Emby.Server.Implementations.Updates path = file; } - if (is_path_directory) + if (isDirectory) { + _logger.LogInformation("Deleting plugin directory {0}", path); Directory.Delete(path, true); } else { + _logger.LogInformation("Deleting plugin file {0}", path); _fileSystem.DeleteFile(path); } -- cgit v1.2.3 From 754e76a61bbf4bbda5a4a1073737c07bf28f5f3a Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Thu, 4 Apr 2019 02:34:23 -0400 Subject: Add TODO to remove string target --- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 95fbefe00a..6833c20c3b 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -509,6 +509,8 @@ namespace Emby.Server.Implementations.Updates private async Task PerformPackageInstallation(IProgress progress, string target, PackageVersionInfo package, CancellationToken cancellationToken) { + // TODO: Remove the `string target` argument as it is not used any longer + var extension = Path.GetExtension(package.targetFilename); var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase); -- cgit v1.2.3 From 1af9c047fbc0283f7abfb4b98918454258dfb348 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 7 Apr 2019 19:51:45 -0400 Subject: Override username with AuthenticationProvider Pass back the Username directive returned by an AuthenticationProvider to the calling code, so we may override the user-provided Username value if the authentication provider passes this back. Useful for instance in an LDAP scenario where what the user types may not necessarily be the "username" that is mapped in the system, e.g. the user providing 'mail' while 'uid' is the "username" value. Could also then be extensible to other authentication providers as well, should they wish to do a similar thing. --- Emby.Server.Implementations/Library/UserManager.cs | 44 ++++++++++++++++------ 1 file changed, 33 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 75c82ca715..952cc6896b 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -277,24 +277,35 @@ namespace Emby.Server.Implementations.Library .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); var success = false; + string updatedUsername = null; IAuthenticationProvider authenticationProvider = null; if (user != null) { var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); authenticationProvider = authResult.Item1; - success = authResult.Item2; + updatedUsername = authResult.Item2; + success = authResult.Item3; } else { // user is null var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); authenticationProvider = authResult.Item1; - success = authResult.Item2; + updatedUsername = authResult.Item2; + success = authResult.Item3; if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) { - user = await CreateUser(username).ConfigureAwait(false); + // We should trust the user that the authprovider says, not what was typed + if (updatedUsername != username) + { + username = updatedUsername; + } + + // Search the database for the user again; the authprovider might have created it + user = Users + .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; if (hasNewUserPolicy != null) @@ -414,32 +425,40 @@ namespace Emby.Server.Implementations.Library return providers; } - private async Task AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) + private async Task> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) { try { var requiresResolvedUser = provider as IRequiresResolvedUser; + ProviderAuthenticationResult authenticationResult = null; if (requiresResolvedUser != null) { - await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); + authenticationResult = await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); } else { - await provider.Authenticate(username, password).ConfigureAwait(false); + authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false); + } + + if(authenticationResult.Username != username) + { + _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); + username = authenticationResult.Username; } - return true; + return new Tuple(username, true); } catch (Exception ex) { _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name); - return false; + return new Tuple(username, false); } } - private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) + private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) { + string updatedUsername = null; bool success = false; IAuthenticationProvider authenticationProvider = null; @@ -458,11 +477,14 @@ namespace Emby.Server.Implementations.Library { foreach (var provider in GetAuthenticationProviders(user)) { - success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + updatedUsername = providerAuthResult.Item1; + success = providerAuthResult.Item2; if (success) { authenticationProvider = provider; + username = updatedUsername; break; } } @@ -484,7 +506,7 @@ namespace Emby.Server.Implementations.Library } } - return new Tuple(authenticationProvider, success); + return new Tuple(authenticationProvider, username, success); } private void UpdateInvalidLoginAttemptCount(User user, int newValue) -- cgit v1.2.3 From a1d50a6d055c414c95fa0ea2bf58db79afb6dc74 Mon Sep 17 00:00:00 2001 From: Ulysse <5031221+voodoos@users.noreply.github.com> Date: Tue, 9 Apr 2019 20:19:27 +0200 Subject: Clean `WebSocketSharpRequest.PathInfo` (#1212) * rm useless ResolvePathInfoFromMappedPath method * rm useless NormalizePathInfo method * Use request.Path instead of RawUrl * Removing unused `HandlerFactoryPath` field * Use an expression body definition and rm field `pathInfo` * More (syntactic) sugar * Who needs blocks in cases ? --- .../SocketSharp/WebSocketSharpRequest.cs | 114 +-------------------- 1 file changed, 1 insertion(+), 113 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index e0a0ee2861..792615a0f6 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -25,8 +25,6 @@ namespace Emby.Server.Implementations.SocketSharp this.OperationName = operationName; this.request = httpContext; this.Response = new WebSocketSharpResponse(logger, response); - - // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); } public HttpRequest HttpRequest => request; @@ -100,7 +98,6 @@ namespace Emby.Server.Implementations.SocketSharp switch (crlf) { case 0: - { if (c == '\r') { crlf = 1; @@ -117,10 +114,8 @@ namespace Emby.Server.Implementations.SocketSharp } break; - } case 1: - { if (c == '\n') { crlf = 2; @@ -128,10 +123,8 @@ namespace Emby.Server.Implementations.SocketSharp } throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); - } case 2: - { if (c == ' ' || c == '\t') { crlf = 0; @@ -139,7 +132,6 @@ namespace Emby.Server.Implementations.SocketSharp } throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); - } } } @@ -312,97 +304,7 @@ namespace Emby.Server.Implementations.SocketSharp return pos == -1 ? strVal : strVal.Slice(0, pos); } - public static string HandlerFactoryPath; - - private string pathInfo; - public string PathInfo - { - get - { - if (this.pathInfo == null) - { - var mode = HandlerFactoryPath; - - var pos = RawUrl.IndexOf("?", StringComparison.Ordinal); - if (pos != -1) - { - var path = RawUrl.Substring(0, pos); - this.pathInfo = GetPathInfo( - path, - mode, - mode ?? string.Empty); - } - else - { - this.pathInfo = RawUrl; - } - - this.pathInfo = WebUtility.UrlDecode(pathInfo); - this.pathInfo = NormalizePathInfo(pathInfo, mode).ToString(); - } - - return this.pathInfo; - } - } - - private static string GetPathInfo(string fullPath, string mode, string appPath) - { - var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode); - if (!string.IsNullOrEmpty(pathInfo)) - { - return pathInfo; - } - - // Wildcard mode relies on this to work out the handlerPath - pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath); - if (!string.IsNullOrEmpty(pathInfo)) - { - return pathInfo; - } - - return fullPath; - } - - private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot) - { - if (mappedPathRoot == null) - { - return null; - } - - var sbPathInfo = new StringBuilder(); - var fullPathParts = fullPath.Split('/'); - var mappedPathRootParts = mappedPathRoot.Split('/'); - var fullPathIndexOffset = mappedPathRootParts.Length - 1; - var pathRootFound = false; - - for (var fullPathIndex = 0; fullPathIndex < fullPathParts.Length; fullPathIndex++) - { - if (pathRootFound) - { - sbPathInfo.Append("/" + fullPathParts[fullPathIndex]); - } - else if (fullPathIndex - fullPathIndexOffset >= 0) - { - pathRootFound = true; - for (var mappedPathRootIndex = 0; mappedPathRootIndex < mappedPathRootParts.Length; mappedPathRootIndex++) - { - if (!string.Equals(fullPathParts[fullPathIndex - fullPathIndexOffset + mappedPathRootIndex], mappedPathRootParts[mappedPathRootIndex], StringComparison.OrdinalIgnoreCase)) - { - pathRootFound = false; - break; - } - } - } - } - - if (!pathRootFound) - { - return null; - } - - return sbPathInfo.Length > 1 ? sbPathInfo.ToString().TrimEnd('/') : "/"; - } + public string PathInfo => this.request.Path.Value; public string UserAgent => request.Headers[HeaderNames.UserAgent]; @@ -500,19 +402,5 @@ namespace Emby.Server.Implementations.SocketSharp return httpFiles; } } - - public static ReadOnlySpan NormalizePathInfo(string pathInfo, string handlerPath) - { - if (handlerPath != null) - { - var trimmed = pathInfo.AsSpan().TrimStart('/'); - if (trimmed.StartsWith(handlerPath.AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - return trimmed.Slice(handlerPath.Length).ToString().AsSpan(); - } - } - - return pathInfo.AsSpan(); - } } } -- cgit v1.2.3 From a6e1b23eb04d620e241e4babbcb6ee0ffbf156a9 Mon Sep 17 00:00:00 2001 From: VooDooS Date: Thu, 11 Apr 2019 16:58:28 +0200 Subject: Simplify headers use in WSS --- .../SocketSharp/WebSocketSharpRequest.cs | 16 +++++----------- MediaBrowser.Model/Services/IHttpRequest.cs | 20 -------------------- 2 files changed, 5 insertions(+), 31 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 792615a0f6..957371df62 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; +using HeaderNames = MediaBrowser.Common.Net.MoreHeaderNames; using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; using IResponse = MediaBrowser.Model.Services.IResponse; @@ -38,16 +39,9 @@ namespace Emby.Server.Implementations.SocketSharp public string RawUrl => request.GetEncodedPathAndQuery(); public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); + // Header[name] returns "" when undefined - public string XForwardedFor - => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString(); - - public int? XForwardedPort - => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture); - - public string XForwardedProtocol => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"].ToString(); - - public string XRealIp => StringValues.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"].ToString(); + private string GetHeader(string name) => request.Headers[name].ToString(); private string remoteIp; public string RemoteIp @@ -59,13 +53,13 @@ namespace Emby.Server.Implementations.SocketSharp return remoteIp; } - var temp = CheckBadChars(XForwardedFor.AsSpan()); + var temp = CheckBadChars(GetHeader(HeaderNames.XForwardedFor).AsSpan()); if (temp.Length != 0) { return remoteIp = temp.ToString(); } - temp = CheckBadChars(XRealIp.AsSpan()); + temp = CheckBadChars(GetHeader(HeaderNames.XRealIP).AsSpan()); if (temp.Length != 0) { return remoteIp = NormalizeIp(temp).ToString(); diff --git a/MediaBrowser.Model/Services/IHttpRequest.cs b/MediaBrowser.Model/Services/IHttpRequest.cs index 50c6076f30..daf91488f0 100644 --- a/MediaBrowser.Model/Services/IHttpRequest.cs +++ b/MediaBrowser.Model/Services/IHttpRequest.cs @@ -7,26 +7,6 @@ namespace MediaBrowser.Model.Services /// string HttpMethod { get; } - /// - /// The IP Address of the X-Forwarded-For header, null if null or empty - /// - string XForwardedFor { get; } - - /// - /// The Port number of the X-Forwarded-Port header, null if null or empty - /// - int? XForwardedPort { get; } - - /// - /// The http or https scheme of the X-Forwarded-Proto header, null if null or empty - /// - string XForwardedProtocol { get; } - - /// - /// The value of the X-Real-IP header, null if null or empty - /// - string XRealIp { get; } - /// /// The value of the Accept HTTP Request Header /// -- cgit v1.2.3 From 56d1050bac3a56249acd7f3b3615f796683e0783 Mon Sep 17 00:00:00 2001 From: VooDooS Date: Thu, 11 Apr 2019 17:13:40 +0200 Subject: Replace custom ip "normalization" by methods from `IPAddress` --- .../SocketSharp/WebSocketSharpRequest.cs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 957371df62..d153a85a32 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -62,10 +62,10 @@ namespace Emby.Server.Implementations.SocketSharp temp = CheckBadChars(GetHeader(HeaderNames.XRealIP).AsSpan()); if (temp.Length != 0) { - return remoteIp = NormalizeIp(temp).ToString(); + return remoteIp = NormalizeIp(temp.ToString()).ToString(); } - return remoteIp = NormalizeIp(request.HttpContext.Connection.RemoteIpAddress.ToString().AsSpan()).ToString(); + return remoteIp = NormalizeIp(request.HttpContext.Connection.RemoteIpAddress).ToString(); } } @@ -137,22 +137,21 @@ namespace Emby.Server.Implementations.SocketSharp return name; } - private ReadOnlySpan NormalizeIp(ReadOnlySpan ip) + private IPAddress NormalizeIp(IPAddress ip) { - if (ip.Length != 0 && !ip.IsWhiteSpace()) + if (ip.IsIPv4MappedToIPv6) { - // Handle ipv4 mapped to ipv6 - const string srch = "::ffff:"; - var index = ip.IndexOf(srch.AsSpan(), StringComparison.OrdinalIgnoreCase); - if (index == 0) - { - ip = ip.Slice(srch.Length); - } + return ip.MapToIPv4(); } return ip; } + private IPAddress NormalizeIp(string sip) + { + return NormalizeIp(IPAddress.Parse(sip)); + } + public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); private Dictionary items; -- cgit v1.2.3 From bb807554e2c33f066402898a3ff79913865d50e3 Mon Sep 17 00:00:00 2001 From: VooDooS Date: Thu, 11 Apr 2019 17:17:48 +0200 Subject: Replace CRLF injection mitigation by use of .NET ip parsing --- .../SocketSharp/WebSocketSharpRequest.cs | 93 +++------------------- 1 file changed, 10 insertions(+), 83 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index d153a85a32..38a860a511 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -53,91 +53,23 @@ namespace Emby.Server.Implementations.SocketSharp return remoteIp; } - var temp = CheckBadChars(GetHeader(HeaderNames.XForwardedFor).AsSpan()); - if (temp.Length != 0) - { - return remoteIp = temp.ToString(); - } - - temp = CheckBadChars(GetHeader(HeaderNames.XRealIP).AsSpan()); - if (temp.Length != 0) - { - return remoteIp = NormalizeIp(temp.ToString()).ToString(); - } - - return remoteIp = NormalizeIp(request.HttpContext.Connection.RemoteIpAddress).ToString(); - } - } - - private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; - - // CheckBadChars - throws on invalid chars to be not found in header name/value - internal static ReadOnlySpan CheckBadChars(ReadOnlySpan name) - { - if (name.Length == 0) - { - return name; - } + IPAddress ip; - // VALUE check - // Trim spaces from both ends - name = name.Trim(HttpTrimCharacters); - - // First, check for correctly formed multi-line value - // Second, check for absence of CTL characters - int crlf = 0; - for (int i = 0; i < name.Length; ++i) - { - char c = (char)(0x000000ff & (uint)name[i]); - switch (crlf) + // "Real" remote ip might be in X-Forwarded-For of X-Real-Ip + // (if the server is behind a reverse proxy for example) + if (!IPAddress.TryParse(GetHeader(HeaderNames.XForwardedFor), out ip)) { - case 0: - if (c == '\r') - { - crlf = 1; - } - else if (c == '\n') - { - // Technically this is bad HTTP. But it would be a breaking change to throw here. - // Is there an exploit? - crlf = 2; - } - else if (c == 127 || (c < ' ' && c != '\t')) - { - throw new ArgumentException("net_WebHeaderInvalidControlChars", nameof(name)); - } - - break; - - case 1: - if (c == '\n') - { - crlf = 2; - break; - } - - throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); - - case 2: - if (c == ' ' || c == '\t') - { - crlf = 0; - break; - } - - throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); + if (!IPAddress.TryParse(GetHeader(HeaderNames.XRealIP), out ip)) + { + ip = request.HttpContext.Connection.RemoteIpAddress; + } } - } - if (crlf != 0) - { - throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); + return remoteIp = NormalizeIp(ip).ToString(); } - - return name; } - private IPAddress NormalizeIp(IPAddress ip) + private static IPAddress NormalizeIp(IPAddress ip) { if (ip.IsIPv4MappedToIPv6) { @@ -147,11 +79,6 @@ namespace Emby.Server.Implementations.SocketSharp return ip; } - private IPAddress NormalizeIp(string sip) - { - return NormalizeIp(IPAddress.Parse(sip)); - } - public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); private Dictionary items; -- cgit v1.2.3 From ba12d96d23a53ce16a1da1b2fcf68a301050b858 Mon Sep 17 00:00:00 2001 From: VooDooS Date: Fri, 12 Apr 2019 15:25:18 +0200 Subject: Removed wrapping of HeaderNames fields --- .../SocketSharp/WebSocketSharpRequest.cs | 6 +- MediaBrowser.Common/Net/CustomHeaderNames.cs | 11 +++ MediaBrowser.Common/Net/MoreHeaderNames.cs | 83 ---------------------- 3 files changed, 14 insertions(+), 86 deletions(-) create mode 100644 MediaBrowser.Common/Net/CustomHeaderNames.cs delete mode 100644 MediaBrowser.Common/Net/MoreHeaderNames.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 38a860a511..00465b63eb 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -4,13 +4,13 @@ using System.Globalization; using System.IO; using System.Net; using System.Text; +using MediaBrowser.Common.Net; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -using HeaderNames = MediaBrowser.Common.Net.MoreHeaderNames; using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; using IResponse = MediaBrowser.Model.Services.IResponse; @@ -57,9 +57,9 @@ namespace Emby.Server.Implementations.SocketSharp // "Real" remote ip might be in X-Forwarded-For of X-Real-Ip // (if the server is behind a reverse proxy for example) - if (!IPAddress.TryParse(GetHeader(HeaderNames.XForwardedFor), out ip)) + if (!IPAddress.TryParse(GetHeader(CustomHeaderNames.XForwardedFor), out ip)) { - if (!IPAddress.TryParse(GetHeader(HeaderNames.XRealIP), out ip)) + if (!IPAddress.TryParse(GetHeader(CustomHeaderNames.XRealIP), out ip)) { ip = request.HttpContext.Connection.RemoteIpAddress; } diff --git a/MediaBrowser.Common/Net/CustomHeaderNames.cs b/MediaBrowser.Common/Net/CustomHeaderNames.cs new file mode 100644 index 0000000000..ff148dc801 --- /dev/null +++ b/MediaBrowser.Common/Net/CustomHeaderNames.cs @@ -0,0 +1,11 @@ +namespace MediaBrowser.Common.Net +{ + public static class CustomHeaderNames + { + // Other Headers + public const string XForwardedFor = "X-Forwarded-For"; + public const string XForwardedPort = "X-Forwarded-Port"; + public const string XForwardedProto = "X-Forwarded-Proto"; + public const string XRealIP = "X-Real-IP"; + } +} \ No newline at end of file diff --git a/MediaBrowser.Common/Net/MoreHeaderNames.cs b/MediaBrowser.Common/Net/MoreHeaderNames.cs deleted file mode 100644 index 669646db1e..0000000000 --- a/MediaBrowser.Common/Net/MoreHeaderNames.cs +++ /dev/null @@ -1,83 +0,0 @@ -using HN = Microsoft.Net.Http.Headers.HeaderNames; - -namespace MediaBrowser.Common.Net -{ - public static class MoreHeaderNames - { - // Other Headers - public const string XForwardedFor = "X-Forwarded-For"; - public const string XForwardedPort = "X-Forwarded-Port"; - public const string XForwardedProto = "X-Forwarded-Proto"; - public const string XRealIP = "X-Real-IP"; - - // Headers from Microsoft.Net.Http.Headers.HeaderNames - public const string Accept = HN.Accept; - public const string AcceptCharset = HN.AcceptCharset; - public const string AcceptEncoding = HN.AcceptEncoding; - public const string AcceptLanguage = HN.AcceptLanguage; - public const string AcceptRanges = HN.AcceptRanges; - public const string AccessControlAllowCredentials = HN.AccessControlAllowCredentials; - public const string AccessControlAllowHeaders = HN.AccessControlAllowHeaders; - public const string AccessControlAllowMethods = HN.AccessControlAllowMethods; - public const string AccessControlAllowOrigin = HN.AccessControlAllowOrigin; - public const string AccessControlExposeHeaders = HN.AccessControlExposeHeaders; - public const string AccessControlMaxAge = HN.AccessControlMaxAge; - public const string AccessControlRequestHeaders = HN.AccessControlRequestHeaders; - public const string AccessControlRequestMethod = HN.AccessControlRequestMethod; - public const string Age = HN.Age; - public const string Allow = HN.Allow; - public const string Authority = HN.Authority; - public const string Authorization = HN.Authorization; - public const string CacheControl = HN.CacheControl; - public const string Connection = HN.Connection; - public const string ContentDisposition = HN.ContentDisposition; - public const string ContentEncoding = HN.ContentEncoding; - public const string ContentLanguage = HN.ContentLanguage; - public const string ContentLength = HN.ContentLength; - public const string ContentLocation = HN.ContentLocation; - public const string ContentMD5 = HN.ContentMD5; - public const string ContentRange = HN.ContentRange; - public const string ContentSecurityPolicy = HN.ContentSecurityPolicy; - public const string ContentSecurityPolicyReportOnly = HN.ContentSecurityPolicyReportOnly; - public const string ContentType = HN.ContentType; - public const string Cookie = HN.Cookie; - public const string Date = HN.Date; - public const string ETag = HN.ETag; - public const string Expires = HN.Expires; - public const string Expect = HN.Expect; - public const string From = HN.From; - public const string Host = HN.Host; - public const string IfMatch = HN.IfMatch; - public const string IfModifiedSince = HN.IfModifiedSince; - public const string IfNoneMatch = HN.IfNoneMatch; - public const string IfRange = HN.IfRange; - public const string IfUnmodifiedSince = HN.IfUnmodifiedSince; - public const string LastModified = HN.LastModified; - public const string Location = HN.Location; - public const string MaxForwards = HN.MaxForwards; - public const string Method = HN.Method; - public const string Origin = HN.Origin; - public const string Path = HN.Path; - public const string Pragma = HN.Pragma; - public const string ProxyAuthenticate = HN.ProxyAuthenticate; - public const string ProxyAuthorization = HN.ProxyAuthorization; - public const string Range = HN.Range; - public const string Referer = HN.Referer; - public const string RetryAfter = HN.RetryAfter; - public const string Scheme = HN.Scheme; - public const string Server = HN.Server; - public const string SetCookie = HN.SetCookie; - public const string Status = HN.Status; - public const string StrictTransportSecurity = HN.StrictTransportSecurity; - public const string TE = HN.TE; - public const string Trailer = HN.Trailer; - public const string TransferEncoding = HN.TransferEncoding; - public const string Upgrade = HN.Upgrade; - public const string UserAgent = HN.UserAgent; - public const string Vary = HN.Vary; - public const string Via = HN.Via; - public const string Warning = HN.Warning; - public const string WebSocketSubProtocols = HN.WebSocketSubProtocols; - public const string WWWAuthenticate = HN.WWWAuthenticate; - } -} \ No newline at end of file -- cgit v1.2.3 From b2f94c0e4015160ba3102cd6617397fc07c6ba35 Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Mon, 15 Apr 2019 00:21:14 -0400 Subject: Remove the old message responders Leaves only an answer to "Who is Jellyfin", removing older ones for EmbyServer and MediaBrowser_v2. --- Emby.Server.Implementations/Udp/UdpServer.cs | 2 -- 1 file changed, 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index bd86c6cdc8..e7cda2993f 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -41,8 +41,6 @@ namespace Emby.Server.Implementations.Udp _socketFactory = socketFactory; AddMessageResponder("who is JellyfinServer?", true, RespondToV2Message); - AddMessageResponder("who is EmbyServer?", true, RespondToV2Message); - AddMessageResponder("who is MediaBrowserServer_v2?", false, RespondToV2Message); } private void AddMessageResponder(string message, bool isSubstring, Func responder) -- cgit v1.2.3 From 34ab99caf18ee862a1dc29a6afb83253db35c219 Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Tue, 16 Apr 2019 01:16:02 -0400 Subject: Move the ProductName to the public endpoint Moves the ProductName field over from the private system/info point to the public one, for easier identification --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 05f8a8a5e7..82042f5ca7 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1425,7 +1425,6 @@ namespace Emby.Server.Implementations HasPendingRestart = HasPendingRestart, IsShuttingDown = IsShuttingDown, Version = ApplicationVersion, - ProductName = ApplicationProductName, WebSocketPortNumber = HttpPort, CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(), Id = SystemId, @@ -1482,6 +1481,7 @@ namespace Emby.Server.Implementations return new PublicSystemInfo { Version = ApplicationVersion, + ProductName = ApplicationProductName, Id = SystemId, OperatingSystem = OperatingSystem.Id.ToString(), WanAddress = wanAddress, -- cgit v1.2.3 From c7fedfbca388d3e43ca266e7a4a6caccfc4bb5c7 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 17 Apr 2019 11:41:14 +0200 Subject: Fix metadata path save --- .../Configuration/ServerConfigurationManager.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index 18e279c2f0..c4fa68cac4 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -74,23 +74,14 @@ namespace Emby.Server.Implementations.Configuration /// private void UpdateMetadataPath() { - string metadataPath; - if (string.IsNullOrWhiteSpace(Configuration.MetadataPath)) { - metadataPath = GetInternalMetadataPath(); + ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Path.Combine(ApplicationPaths.ProgramDataPath, "metadata"); } else { - metadataPath = Path.Combine(Configuration.MetadataPath, "metadata"); + ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Configuration.MetadataPath; } - - ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath; - } - - private string GetInternalMetadataPath() - { - return Path.Combine(ApplicationPaths.ProgramDataPath, "metadata"); } /// -- cgit v1.2.3 From 250e0c75dfaebca54e93be6c11c70cb0d19e589a Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Wed, 17 Apr 2019 22:31:06 -0400 Subject: Add MethodNotAllowedException with code 405 --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e8d47cad52..831391cee6 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -203,6 +203,7 @@ namespace Emby.Server.Implementations.HttpServer case DirectoryNotFoundException _: case FileNotFoundException _: case ResourceNotFoundException _: return 404; + case MethodNotAllowedException _: return 405; case RemoteServiceUnavailableException _: return 502; default: return 500; } -- cgit v1.2.3 From 8f703f4744b2701843e316210263c8e4cd3256bd Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 18 Apr 2019 13:19:16 +0200 Subject: Remove unused event Release builds were failing because of this unused event. --- .../Activity/ActivityLogEntryPoint.cs | 14 -------------- Emby.Server.Implementations/ApplicationHost.cs | 21 ++++++++------------- MediaBrowser.Common/IApplicationHost.cs | 5 ----- 3 files changed, 8 insertions(+), 32 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 98cd97c318..190e4d55c0 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -83,8 +83,6 @@ namespace Emby.Server.Implementations.Activity _deviceManager.CameraImageUploaded += OnCameraImageUploaded; - _appHost.ApplicationUpdated += OnApplicationUpdated; - return Task.CompletedTask; } @@ -275,16 +273,6 @@ namespace Emby.Server.Implementations.Activity }); } - private void OnApplicationUpdated(object sender, GenericEventArgs e) - { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format(_localization.GetLocalizedString("MessageApplicationUpdatedTo"), e.Argument.versionStr), - Type = NotificationType.ApplicationUpdateInstalled.ToString(), - Overview = e.Argument.description - }); - } - private void OnUserPolicyUpdated(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry @@ -460,8 +448,6 @@ namespace Emby.Server.Implementations.Activity _userManager.UserLockedOut -= OnUserLockedOut; _deviceManager.CameraImageUploaded -= OnCameraImageUploaded; - - _appHost.ApplicationUpdated -= OnApplicationUpdated; } /// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 9804f28cf3..0ebbeea57e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -154,11 +154,6 @@ namespace Emby.Server.Implementations /// public event EventHandler HasPendingRestartChanged; - /// - /// Occurs when [application updated]. - /// - public event EventHandler> ApplicationUpdated; - /// /// Gets a value indicating whether this instance has changes that require the entire application to restart. /// @@ -1392,9 +1387,9 @@ namespace Emby.Server.Implementations public async Task GetSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - - string wanAddress; - + + string wanAddress; + if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)) { wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); @@ -1451,10 +1446,10 @@ namespace Emby.Server.Implementations public async Task GetPublicSystemInfo(CancellationToken cancellationToken) { - var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - + var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + string wanAddress; - + if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns)) { wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false); @@ -1570,9 +1565,9 @@ namespace Emby.Server.Implementations } return string.Format("http://{0}:{1}", host, - ServerConfigurationManager.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture)); + ServerConfigurationManager.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture)); } - + public Task> GetLocalIpAddresses(CancellationToken cancellationToken) { return GetLocalIpAddressesInternal(true, 0, cancellationToken); diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 2925a3efd9..cb7343440a 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -25,11 +25,6 @@ namespace MediaBrowser.Common /// The device identifier. string SystemId { get; } - /// - /// Occurs when [application updated]. - /// - event EventHandler> ApplicationUpdated; - /// /// Gets or sets a value indicating whether this instance has pending kernel reload. /// -- cgit v1.2.3 From 90c04a46403ba158e11c444c7109f5a56f12eff3 Mon Sep 17 00:00:00 2001 From: tinganhsu Date: Fri, 19 Apr 2019 04:32:51 +0000 Subject: Added translation using Weblate (Chinese (Traditional)) --- Emby.Server.Implementations/Localization/Core/zh_Hant.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/zh_Hant.json (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh_Hant.json b/Emby.Server.Implementations/Localization/Core/zh_Hant.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/zh_Hant.json @@ -0,0 +1 @@ +{} -- cgit v1.2.3 From ba684d6d3a4bfc9a0dc3f9de16a3b32bb95da1a1 Mon Sep 17 00:00:00 2001 From: tinganhsu Date: Fri, 19 Apr 2019 04:33:31 +0000 Subject: Translated using Weblate (Chinese (Traditional)) Currently translated at 96.8% (91 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant/ --- .../Localization/Core/zh_Hant.json | 94 +++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh_Hant.json b/Emby.Server.Implementations/Localization/Core/zh_Hant.json index 0967ef424b..effff5566e 100644 --- a/Emby.Server.Implementations/Localization/Core/zh_Hant.json +++ b/Emby.Server.Implementations/Localization/Core/zh_Hant.json @@ -1 +1,93 @@ -{} +{ + "Albums": "專輯", + "AppDeviceValues": "應用: {0}, 裝置: {1}", + "Application": "應用程式", + "Artists": "演出者", + "AuthenticationSucceededWithUserName": "{0} 成功授權", + "Books": "圖書", + "CameraImageUploadedFrom": "{0} 已經成功上傳一張相片", + "Channels": "頻道", + "ChapterNameValue": "章節 {0}", + "Collections": "合輯", + "DeviceOfflineWithName": "{0} 已經斷線", + "DeviceOnlineWithName": "{0} 已經連線", + "FailedLoginAttemptWithUserName": "來自 {0} 的失敗登入嘗試", + "Favorites": "我的最愛", + "Folders": "資料夾", + "Genres": "風格", + "HeaderAlbumArtists": "專輯演出者", + "HeaderCameraUploads": "相機上傳", + "HeaderContinueWatching": "繼續觀賞", + "HeaderFavoriteAlbums": "最愛專輯", + "HeaderFavoriteArtists": "最愛演出者", + "HeaderFavoriteEpisodes": "最愛級數", + "HeaderFavoriteShows": "最愛節目", + "HeaderFavoriteSongs": "最愛歌曲", + "HeaderLiveTV": "電視直播", + "HeaderNextUp": "接下來", + "HomeVideos": "自製影片", + "ItemAddedWithName": "{0} 已新增至媒體庫", + "ItemRemovedWithName": "{0} 已從媒體庫移除", + "LabelIpAddressValue": "IP 位置: {0}", + "LabelRunningTimeValue": "運行時間: {0}", + "Latest": "最新", + "MessageApplicationUpdated": "Jellyfin Server 已經更新", + "MessageApplicationUpdatedTo": "Jellyfin Server 已經更新至 {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 部分已經更新", + "MessageServerConfigurationUpdated": "伺服器設定已經更新", + "MixedContent": "混合內容", + "Movies": "電影", + "Music": "音樂", + "MusicVideos": "音樂MV", + "NameInstallFailed": "{0} 安裝失敗", + "NameSeasonNumber": "第 {0} 季", + "NameSeasonUnknown": "未知季數", + "NewVersionIsAvailable": "新版本的Jellyfin Server 軟體已經推出可供下載。", + "NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新", + "NotificationOptionApplicationUpdateInstalled": "應用程式已更新", + "NotificationOptionAudioPlayback": "音樂開始播放", + "NotificationOptionAudioPlaybackStopped": "音樂停止播放", + "NotificationOptionCameraImageUploaded": "相機相片已上傳", + "NotificationOptionInstallationFailed": "安裝失敗", + "NotificationOptionNewLibraryContent": "已新增新內容", + "NotificationOptionPluginError": "外掛失敗", + "NotificationOptionPluginInstalled": "外掛已安裝", + "NotificationOptionPluginUninstalled": "外掛已移除", + "NotificationOptionPluginUpdateInstalled": "已更新外掛", + "NotificationOptionServerRestartRequired": "伺服器需要重新啟動", + "NotificationOptionTaskFailed": "排程任務失敗", + "NotificationOptionUserLockedOut": "使用者已鎖定", + "NotificationOptionVideoPlayback": "影片開始播放", + "NotificationOptionVideoPlaybackStopped": "影片停止播放", + "Photos": "相片", + "Playlists": "播放清單", + "Plugin": "外掛", + "PluginInstalledWithName": "{0} 已安裝", + "PluginUninstalledWithName": "{0} 已移除", + "PluginUpdatedWithName": "{0} 已更新", + "ProviderValue": "提供商: {0}", + "ScheduledTaskFailedWithName": "{0} 已失敗", + "ScheduledTaskStartedWithName": "{0} 已開始", + "ServerNameNeedsToBeRestarted": "{0} 需要重新啟動", + "Shows": "節目", + "Songs": "歌曲", + "StartupEmbyServerIsLoading": "Jellyfin Server正在啟動,請稍後再試一次。", + "SubtitlesDownloadedForItem": "已為 {0} 下載字幕", + "Sync": "同步", + "System": "系統", + "TvShows": "電視節目", + "User": "使用者", + "UserCreatedWithName": "使用者 {0} 已建立", + "UserDeletedWithName": "使用者 {0} 已移除", + "UserDownloadingItemWithValues": "{0} 正在下載 {1}", + "UserLockedOutWithName": "使用者 {0} 已鎖定", + "UserOfflineFromDevice": "{0} 已從 {1} 斷線", + "UserOnlineFromDevice": "{0} 已連線,來自 {1}", + "UserPasswordChangedWithName": "使用者 {0} 的密碼已變更", + "UserPolicyUpdatedWithName": "使用者條約已更新為 {0}", + "UserStartedPlayingItemWithValues": "{0}正在使用 {2} 播放 {1}", + "UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 播放 {1}", + "ValueHasBeenAddedToLibrary": "{0} 已新增至您的媒體庫", + "ValueSpecialEpisodeName": "特典 - {0}", + "VersionNumber": "版本 {0}" +} -- cgit v1.2.3 From d6622818dca410f15764a94c2e19cf9dee1393a6 Mon Sep 17 00:00:00 2001 From: Libor Filípek Date: Sat, 13 Apr 2019 12:06:54 +0000 Subject: Translated using Weblate (Czech) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/ --- .../Localization/Core/cs.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index a8b4a44244..c2a101a4da 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -5,7 +5,7 @@ "Artists": "Umělci", "AuthenticationSucceededWithUserName": "{0} úspěšně ověřen", "Books": "Knihy", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "Z {0} byla nahrána nová fotografie", "Channels": "Kanály", "ChapterNameValue": "Kapitola {0}", "Collections": "Kolekce", @@ -16,14 +16,14 @@ "Folders": "Složky", "Genres": "Žánry", "HeaderAlbumArtists": "Umělci alba", - "HeaderCameraUploads": "Camera Uploads", + "HeaderCameraUploads": "Nahrané fotografie", "HeaderContinueWatching": "Pokračovat ve sledování", "HeaderFavoriteAlbums": "Oblíbená alba", "HeaderFavoriteArtists": "Oblíbení umělci", "HeaderFavoriteEpisodes": "Oblíbené epizody", "HeaderFavoriteShows": "Oblíbené seriály", "HeaderFavoriteSongs": "Oblíbené písně", - "HeaderLiveTV": "Živá TV", + "HeaderLiveTV": "Live TV", "HeaderNextUp": "Nadcházející", "HeaderRecordingGroups": "Skupiny nahrávek", "HomeVideos": "Domáci videa", @@ -34,17 +34,17 @@ "LabelRunningTimeValue": "Délka média: {0}", "Latest": "Nejnovější", "MessageApplicationUpdated": "Jellyfin Server byl aktualizován", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "Jellyfin server byl aktualizován na verzi {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Konfigurace sekce {0} na serveru byla aktualizována", "MessageServerConfigurationUpdated": "Konfigurace serveru aktualizována", "MixedContent": "Smíšený obsah", "Movies": "Filmy", "Music": "Hudba", "MusicVideos": "Hudební klipy", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "Instalace {0} selhala", "NameSeasonNumber": "Sezóna {0}", "NameSeasonUnknown": "Neznámá sezóna", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NewVersionIsAvailable": "Nová verze Jellyfin serveru je k dispozici ke stažení.", "NotificationOptionApplicationUpdateAvailable": "Dostupná aktualizace aplikace", "NotificationOptionApplicationUpdateInstalled": "Aktualizace aplikace instalována", "NotificationOptionAudioPlayback": "Přehrávání audia zahájeno", @@ -70,12 +70,12 @@ "ProviderValue": "Poskytl: {0}", "ScheduledTaskFailedWithName": "{0} selhalo", "ScheduledTaskStartedWithName": "{0} zahájeno", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "{0} vyžaduje restart", "Shows": "Seriály", "Songs": "Skladby", "StartupEmbyServerIsLoading": "Jellyfin Server je spouštěn. Zkuste to prosím v brzké době znovu.", "SubtitleDownloadFailureForItem": "Stahování titulků selhalo pro {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Stažení titulků pro {1} z {0} selhalo", "SubtitlesDownloadedForItem": "Staženy titulky pro {0}", "Sync": "Synchronizace", "System": "Systém", @@ -88,10 +88,10 @@ "UserOfflineFromDevice": "{0} se odpojil od {1}", "UserOnlineFromDevice": "{0} se připojil z {1}", "UserPasswordChangedWithName": "Provedena změna hesla pro uživatele {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", + "UserPolicyUpdatedWithName": "Zásady uživatele pro {0} byly aktualizovány", "UserStartedPlayingItemWithValues": "{0} spustil přehrávání {1}", "UserStoppedPlayingItemWithValues": "{0} zastavil přehrávání {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueHasBeenAddedToLibrary": "{0} byl přidán do vaší knihovny médií", "ValueSpecialEpisodeName": "Speciál - {0}", "VersionNumber": "Verze {0}" } -- cgit v1.2.3 From d31f5229da7049a6cef56c745ae93168f7df24d5 Mon Sep 17 00:00:00 2001 From: Dan Johansen Date: Tue, 9 Apr 2019 07:39:06 +0000 Subject: Translated using Weblate (Danish) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/da/ --- Emby.Server.Implementations/Localization/Core/da.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 9d4d740996..cb8f4576ad 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -61,8 +61,8 @@ "NotificationOptionUserLockedOut": "Bruger låst ude", "NotificationOptionVideoPlayback": "Videoafspilning påbegyndt", "NotificationOptionVideoPlaybackStopped": "Videoafspilning stoppet", - "Photos": "Fotos", - "Playlists": "Spillelister", + "Photos": "Fotoer", + "Playlists": "Afspilningslister", "Plugin": "Plugin", "PluginInstalledWithName": "{0} blev installeret", "PluginUninstalledWithName": "{0} blev afinstalleret", -- cgit v1.2.3 From 395d2e4917f3c790bb14a1dd43e062b036d98b04 Mon Sep 17 00:00:00 2001 From: Βασίλης Μουρατίδης Date: Thu, 18 Apr 2019 09:05:44 +0000 Subject: Translated using Weblate (Greek) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/ --- Emby.Server.Implementations/Localization/Core/el.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index 91ca34edc2..db7ebb0c01 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -16,7 +16,7 @@ "Folders": "Φάκελοι", "Genres": "Είδη", "HeaderAlbumArtists": "Άλμπουμ Καλλιτεχνών", - "HeaderCameraUploads": "Camera Uploads", + "HeaderCameraUploads": "Μεταφορτώσεις Κάμερας", "HeaderContinueWatching": "Συνεχίστε να παρακολουθείτε", "HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ", "HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες", @@ -34,7 +34,7 @@ "LabelRunningTimeValue": "Διάρκεια: {0}", "Latest": "Πρόσφατα", "MessageApplicationUpdated": "Ο Jellyfin Server έχει ενημερωθεί", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "Ο server Jellyfin αναβαθμίστηκε σε έκδοση {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Η ενότητα {0} ρύθμισης παραμέτρων του server έχει ενημερωθεί", "MessageServerConfigurationUpdated": "Η ρύθμιση παραμέτρων του server έχει ενημερωθεί", "MixedContent": "Ανάμεικτο Περιεχόμενο", @@ -49,7 +49,7 @@ "NotificationOptionApplicationUpdateInstalled": "Η ενημέρωση εφαρμογής εγκαταστάθηκε", "NotificationOptionAudioPlayback": "Η αναπαραγωγή ήχου ξεκίνησε", "NotificationOptionAudioPlaybackStopped": "Η αναπαραγωγή ήχου σταμάτησε", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", + "NotificationOptionCameraImageUploaded": "Μεταφορτώθηκε φωτογραφία απο κάμερα", "NotificationOptionInstallationFailed": "Αποτυχία εγκατάστασης", "NotificationOptionNewLibraryContent": "Προστέθηκε νέο περιεχόμενο", "NotificationOptionPluginError": "Αποτυχία του plugin", @@ -75,7 +75,7 @@ "Songs": "Τραγούδια", "StartupEmbyServerIsLoading": "Ο Jellyfin Server φορτώνει. Παρακαλώ δοκιμάστε σε λίγο.", "SubtitleDownloadFailureForItem": "Οι υπότιτλοι απέτυχαν να κατέβουν για {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Αποτυχίες μεταφόρτωσης υποτίτλων από {0} για {1}", "SubtitlesDownloadedForItem": "Οι υπότιτλοι κατέβηκαν για {0}", "Sync": "Συγχρονισμός", "System": "Σύστημα", -- cgit v1.2.3 From 8e2827cc39388152ae3d86cf5ec57cc1b4b00753 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Tue, 2 Apr 2019 12:41:49 +0000 Subject: Translated using Weblate (Kazakh) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/ --- Emby.Server.Implementations/Localization/Core/kk.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 23841f37d6..cbee711551 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -5,7 +5,7 @@ "Artists": "Oryndaýshylar", "AuthenticationSucceededWithUserName": "{0} túpnusqalyq rastalýy sátti aıaqtaldy", "Books": "Kitaptar", - "CameraImageUploadedFrom": "{0} kamerasynan jańa sýret júktep alyndy", + "CameraImageUploadedFrom": "{0} kamerasynan jańa sýret júktep salyndy", "Channels": "Arnalar", "ChapterNameValue": "{0}-sahna", "Collections": "Jıyntyqtar", @@ -35,8 +35,8 @@ "Latest": "Eń keıingi", "MessageApplicationUpdated": "Jellyfin Serveri jańartyldy", "MessageApplicationUpdatedTo": "Jellyfin Serveri {0} nusqasyna jańartyldy", - "MessageNamedServerConfigurationUpdatedWithValue": "Server teńsheliminiń {0} bólimi jańartyldy", - "MessageServerConfigurationUpdated": "Server teńshelimi jańartyldy", + "MessageNamedServerConfigurationUpdatedWithValue": "Server konfıgýrasýasynyń {0} bólimi jańartyldy", + "MessageServerConfigurationUpdated": "Server konfıgýrasıasy jańartyldy", "MixedContent": "Aralas mazmun", "Movies": "Fılmder", "Music": "Mýzyka", @@ -49,7 +49,7 @@ "NotificationOptionApplicationUpdateInstalled": "Qoldanba jańartýy ornatyldy", "NotificationOptionAudioPlayback": "Dybys oınatýy bastaldy", "NotificationOptionAudioPlaybackStopped": "Dybys oınatýy toqtatyldy", - "NotificationOptionCameraImageUploaded": "Kameradan fotosýret keri qotarylǵan", + "NotificationOptionCameraImageUploaded": "Kameradan fotosýret júktep salynǵan", "NotificationOptionInstallationFailed": "Ornatý sátsizdigi", "NotificationOptionNewLibraryContent": "Jańa mazmun ústelgen", "NotificationOptionPluginError": "Plagın sátsizdigi", -- cgit v1.2.3 From 4886fc467c8cd07fbee2cfc43a2f2c6c71216da1 Mon Sep 17 00:00:00 2001 From: SaddFox Date: Tue, 16 Apr 2019 14:53:26 +0000 Subject: Translated using Weblate (Slovenian) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sl/ --- .../Localization/Core/sl-SI.json | 66 +++++++++++----------- 1 file changed, 33 insertions(+), 33 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index b50ff4706e..531dfe51f0 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -9,7 +9,7 @@ "Channels": "Kanali", "ChapterNameValue": "Poglavje {0}", "Collections": "Zbirke", - "DeviceOfflineWithName": "{0} has disconnected", + "DeviceOfflineWithName": "{0} je prekinil povezavo", "DeviceOnlineWithName": "{0} je povezan", "FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}", "Favorites": "Priljubljeni", @@ -33,9 +33,9 @@ "LabelIpAddressValue": "IP naslov: {0}", "LabelRunningTimeValue": "Čas trajanja: {0}", "Latest": "Najnovejše", - "MessageApplicationUpdated": "Jellyfin strežnik je bil posodobljen", - "MessageApplicationUpdatedTo": "Jellyfin strežnik je bil posodobljen na {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", + "MessageApplicationUpdated": "Jellyfin Server je bil posodobljen", + "MessageApplicationUpdatedTo": "Jellyfin Server je bil posodobljen na {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Oddelek nastavitve strežnika {0} je bil posodobljen", "MessageServerConfigurationUpdated": "Nastavitve strežnika so bile posodobljene", "MixedContent": "Razne vsebine", "Movies": "Filmi", @@ -57,41 +57,41 @@ "NotificationOptionPluginUninstalled": "Dodatek odstranjen", "NotificationOptionPluginUpdateInstalled": "Posodobitev dodatka nameščena", "NotificationOptionServerRestartRequired": "Potreben je ponovni zagon strežnika", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "Photos": "Photos", - "Playlists": "Playlists", + "NotificationOptionTaskFailed": "Razporejena naloga neuspešna", + "NotificationOptionUserLockedOut": "Uporabnik zaklenjen", + "NotificationOptionVideoPlayback": "Predvajanje videa se je začelo", + "NotificationOptionVideoPlaybackStopped": "Predvajanje videa se je ustavilo", + "Photos": "Fotografije", + "Playlists": "Seznami predvajanja", "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", + "PluginInstalledWithName": "{0} je bil nameščen", + "PluginUninstalledWithName": "{0} je bil odstranjen", + "PluginUpdatedWithName": "{0} je bil posodobljen", "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ScheduledTaskFailedWithName": "{0} ni uspelo", + "ScheduledTaskStartedWithName": "{0} začeto", + "ServerNameNeedsToBeRestarted": "{0} mora biti ponovno zagnan", "Shows": "Serije", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "Songs": "Pesmi", + "StartupEmbyServerIsLoading": "Jellyfin Server se nalaga. Poskusi ponovno kasneje.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", - "Sync": "Sync", + "SubtitleDownloadFailureFromForItem": "Neuspešen prenos podnapisov iz {0} za {1}", + "SubtitlesDownloadedForItem": "Podnapisi preneseni za {0}", + "Sync": "Sinhroniziraj", "System": "System", - "TvShows": "TV Shows", + "TvShows": "TV serije", "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", - "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", - "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "UserCreatedWithName": "Uporabnik {0} je bil ustvarjen", + "UserDeletedWithName": "Uporabnik {0} je bil izbrisan", + "UserDownloadingItemWithValues": "{0} prenaša {1}", + "UserLockedOutWithName": "Uporabnik {0} je bil zaklenjen", + "UserOfflineFromDevice": "{0} je prekinil povezavo z {1}", + "UserOnlineFromDevice": "{0} je aktiven iz {1}", + "UserPasswordChangedWithName": "Geslo za uporabnika {0} je bilo spremenjeno", + "UserPolicyUpdatedWithName": "Pravilnik uporabe je bil posodobljen za uporabnika {0}", + "UserStartedPlayingItemWithValues": "{0} predvaja {1} na {2}", + "UserStoppedPlayingItemWithValues": "{0} je nehal predvajati {1} na {2}", + "ValueHasBeenAddedToLibrary": "{0} je bil dodan vaši knjižnici", "ValueSpecialEpisodeName": "Special - {0}", "VersionNumber": "Version {0}" } -- cgit v1.2.3 From 10cbdc8e8e624301c2edb1598491cd6493b1644a Mon Sep 17 00:00:00 2001 From: Heldenkrieger01 Date: Tue, 9 Apr 2019 17:16:35 +0000 Subject: Translated using Weblate (Swiss German) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/gsw/ --- .../Localization/Core/gsw.json | 166 ++++++++++----------- 1 file changed, 83 insertions(+), 83 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json index 728002a560..69c1574014 100644 --- a/Emby.Server.Implementations/Localization/Core/gsw.json +++ b/Emby.Server.Implementations/Localization/Core/gsw.json @@ -1,97 +1,97 @@ { - "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", - "Artists": "Artists", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", + "Albums": "Albom", + "AppDeviceValues": "App: {0}, Grät: {1}", + "Application": "Aawändig", + "Artists": "Könstler", + "AuthenticationSucceededWithUserName": "{0} het sech aagmäudet", "Books": "Büecher", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", - "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", - "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", + "CameraImageUploadedFrom": "Es nöis Foti esch ufeglade worde vo {0}", + "Channels": "Kanäu", + "ChapterNameValue": "Kapitu {0}", + "Collections": "Sammlige", + "DeviceOfflineWithName": "{0} esch offline gange", + "DeviceOnlineWithName": "{0} esch online cho", + "FailedLoginAttemptWithUserName": "Fäugschlagne Aamäudeversuech vo {0}", + "Favorites": "Favorite", + "Folders": "Ordner", "Genres": "Genres", - "HeaderAlbumArtists": "Albuminterprete", - "HeaderCameraUploads": "Camera Uploads", + "HeaderAlbumArtists": "Albom-Könstler", + "HeaderCameraUploads": "Kamera-Uploads", "HeaderContinueWatching": "Wiiterluege", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteArtists": "Besti Interpret", - "HeaderFavoriteEpisodes": "Favorite Episodes", - "HeaderFavoriteShows": "Favorite Shows", - "HeaderFavoriteSongs": "Besti Lieder", - "HeaderLiveTV": "Live TV", - "HeaderNextUp": "Next Up", + "HeaderFavoriteAlbums": "Lieblingsalbe", + "HeaderFavoriteArtists": "Lieblings-Interprete", + "HeaderFavoriteEpisodes": "Lieblingsepisode", + "HeaderFavoriteShows": "Lieblingsserie", + "HeaderFavoriteSongs": "Lieblingslieder", + "HeaderLiveTV": "Live-Färnseh", + "HeaderNextUp": "Als nächts", "HeaderRecordingGroups": "Ufnahmegruppe", "HomeVideos": "Heimfilmli", "Inherit": "Hinzuefüege", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", - "Latest": "Letschte", - "MessageApplicationUpdated": "Jellyfin Server has been updated", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MixedContent": "Gmischte Inhalt", - "Movies": "Movies", + "ItemAddedWithName": "{0} esch de Bibliothek dezuegfüegt worde", + "ItemRemovedWithName": "{0} esch vo de Bibliothek entfärnt worde", + "LabelIpAddressValue": "IP-Adrässe: {0}", + "LabelRunningTimeValue": "Loufziit: {0}", + "Latest": "Nöischti", + "MessageApplicationUpdated": "Jellyfin Server esch aktualisiert worde", + "MessageApplicationUpdatedTo": "Jellyfin Server esch of Version {0} aktualisiert worde", + "MessageNamedServerConfigurationUpdatedWithValue": "De Serveriistöuigsberiich {0} esch aktualisiert worde", + "MessageServerConfigurationUpdated": "Serveriistöuige send aktualisiert worde", + "MixedContent": "Gmeschti Inhäut", + "Movies": "Film", "Music": "Musig", - "MusicVideos": "Musigfilm", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", + "MusicVideos": "Musigvideos", + "NameInstallFailed": "Installation vo {0} fäugschlage", + "NameSeasonNumber": "Staffle {0}", + "NameSeasonUnknown": "Staffle unbekannt", + "NewVersionIsAvailable": "E nöi Version vo Jellyfin Server esch zom Download parat.", + "NotificationOptionApplicationUpdateAvailable": "Aawändigsupdate verfüegbar", + "NotificationOptionApplicationUpdateInstalled": "Aawändigsupdate installiert", + "NotificationOptionAudioPlayback": "Audiowedergab gstartet", + "NotificationOptionAudioPlaybackStopped": "Audiwedergab gstoppt", + "NotificationOptionCameraImageUploaded": "Foti ueglade", + "NotificationOptionInstallationFailed": "Installationsfäuer", + "NotificationOptionNewLibraryContent": "Nöie Inhaut hinzuegfüegt", + "NotificationOptionPluginError": "Plugin-Fäuer", + "NotificationOptionPluginInstalled": "Plugin installiert", + "NotificationOptionPluginUninstalled": "Plugin deinstalliert", + "NotificationOptionPluginUpdateInstalled": "Pluginupdate installiert", + "NotificationOptionServerRestartRequired": "Serverneustart notwändig", + "NotificationOptionTaskFailed": "Planti Uufgab fäugschlage", + "NotificationOptionUserLockedOut": "Benotzer usgschlosse", + "NotificationOptionVideoPlayback": "Videowedergab gstartet", + "NotificationOptionVideoPlaybackStopped": "Videowedergab gstoppt", "Photos": "Fotis", - "Playlists": "Abspielliste", + "Playlists": "Wedergabeliste", "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", - "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", - "Shows": "Shows", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "PluginInstalledWithName": "{0} esch installiert worde", + "PluginUninstalledWithName": "{0} esch deinstalliert worde", + "PluginUpdatedWithName": "{0} esch updated worde", + "ProviderValue": "Aabieter: {0}", + "ScheduledTaskFailedWithName": "{0} esch fäugschlage", + "ScheduledTaskStartedWithName": "{0} het gstartet", + "ServerNameNeedsToBeRestarted": "{0} mues nöi gstartet wärde", + "Shows": "Serie", + "Songs": "Lieder", + "StartupEmbyServerIsLoading": "Jellyfin Server ladt. Bitte grad noeinisch probiere.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", - "Sync": "Sync", + "SubtitleDownloadFailureFromForItem": "Ondertetle vo {0} för {1} hend ned chönne abeglade wärde", + "SubtitlesDownloadedForItem": "Ondertetle abeglade för {0}", + "Sync": "Synchronisation", "System": "System", - "TvShows": "TV Shows", - "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", - "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", - "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", - "ValueSpecialEpisodeName": "Spezial - {0}", + "TvShows": "Färnsehserie", + "User": "Benotzer", + "UserCreatedWithName": "Benotzer {0} esch erstöut worde", + "UserDeletedWithName": "Benotzer {0} esch glösche worde", + "UserDownloadingItemWithValues": "{0} ladt {1} abe", + "UserLockedOutWithName": "Benotzer {0} esch usgschlosse worde", + "UserOfflineFromDevice": "{0} esch vo {1} trennt worde", + "UserOnlineFromDevice": "{0} esch online vo {1}", + "UserPasswordChangedWithName": "S'Passwort för Benotzer {0} esch gänderet worde", + "UserPolicyUpdatedWithName": "Benotzerrechtlinie för {0} esch aktualisiert worde", + "UserStartedPlayingItemWithValues": "{0} hed d'Wedergab vo {1} of {2} gstartet", + "UserStoppedPlayingItemWithValues": "{0} het d'Wedergab vo {1} of {2} gstoppt", + "ValueHasBeenAddedToLibrary": "{0} esch dinnere Biblithek hinzuegfüegt worde", + "ValueSpecialEpisodeName": "Extra - {0}", "VersionNumber": "Version {0}" } -- cgit v1.2.3 From 4a6243096afa9215392c4b2671c84e76eaf0d0b0 Mon Sep 17 00:00:00 2001 From: Libor Filípek Date: Fri, 19 Apr 2019 17:30:29 +0000 Subject: Translated using Weblate (Czech) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/ --- Emby.Server.Implementations/Localization/Core/cs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index c2a101a4da..c19148921b 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -19,10 +19,10 @@ "HeaderCameraUploads": "Nahrané fotografie", "HeaderContinueWatching": "Pokračovat ve sledování", "HeaderFavoriteAlbums": "Oblíbená alba", - "HeaderFavoriteArtists": "Oblíbení umělci", + "HeaderFavoriteArtists": "Oblíbení interpreti", "HeaderFavoriteEpisodes": "Oblíbené epizody", "HeaderFavoriteShows": "Oblíbené seriály", - "HeaderFavoriteSongs": "Oblíbené písně", + "HeaderFavoriteSongs": "Oblíbená hudba", "HeaderLiveTV": "Live TV", "HeaderNextUp": "Nadcházející", "HeaderRecordingGroups": "Skupiny nahrávek", -- cgit v1.2.3 From 0794a3edf48eb232030560c4a03587cd77f38b65 Mon Sep 17 00:00:00 2001 From: bugfixin Date: Thu, 18 Apr 2019 21:47:19 +0000 Subject: Adjust detection of 'sample' in filenames to use regex boundaries --- .../Library/CoreResolutionIgnoreRule.cs | 11 +++-------- .../Library/Resolvers/Movies/MovieResolver.cs | 13 +++---------- 2 files changed, 6 insertions(+), 18 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index c644d13eab..c9d4e43422 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; @@ -148,15 +149,9 @@ namespace Emby.Server.Implementations.Library } // Ignore samples - var sampleFilename = " " + filename.Replace(".", " ", StringComparison.OrdinalIgnoreCase) - .Replace("-", " ", StringComparison.OrdinalIgnoreCase) - .Replace("_", " ", StringComparison.OrdinalIgnoreCase) - .Replace("!", " ", StringComparison.OrdinalIgnoreCase); + Match m = Regex.Match(filename,"\bsample\b",RegexOptions.IgnoreCase); - if (sampleFilename.IndexOf(" sample ", StringComparison.OrdinalIgnoreCase) != -1) - { - return true; - } + return m.Success; } return false; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 8485636794..47c3e71d72 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using Emby.Naming.Video; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; @@ -167,17 +168,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private static bool IsIgnored(string filename) { // Ignore samples - var sampleFilename = " " + filename.Replace(".", " ", StringComparison.OrdinalIgnoreCase) - .Replace("-", " ", StringComparison.OrdinalIgnoreCase) - .Replace("_", " ", StringComparison.OrdinalIgnoreCase) - .Replace("!", " ", StringComparison.OrdinalIgnoreCase); + Match m = Regex.Match(filename,@"\bsample\b",RegexOptions.IgnoreCase); - if (sampleFilename.IndexOf(" sample ", StringComparison.OrdinalIgnoreCase) != -1) - { - return true; - } - - return false; + return m.Success; } private bool ContainsFile(List result, FileSystemMetadata file) -- cgit v1.2.3 From 9d60cc8c660c47df110d8118975410f86a5a7484 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 19 Apr 2019 13:59:04 -0400 Subject: Rename Chinese (Traditional) file --- .../Localization/Core/zh-TW.json | 93 ++++++++++++++++++++++ .../Localization/Core/zh_Hant.json | 93 ---------------------- 2 files changed, 93 insertions(+), 93 deletions(-) create mode 100644 Emby.Server.Implementations/Localization/Core/zh-TW.json delete mode 100644 Emby.Server.Implementations/Localization/Core/zh_Hant.json (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json new file mode 100644 index 0000000000..effff5566e --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -0,0 +1,93 @@ +{ + "Albums": "專輯", + "AppDeviceValues": "應用: {0}, 裝置: {1}", + "Application": "應用程式", + "Artists": "演出者", + "AuthenticationSucceededWithUserName": "{0} 成功授權", + "Books": "圖書", + "CameraImageUploadedFrom": "{0} 已經成功上傳一張相片", + "Channels": "頻道", + "ChapterNameValue": "章節 {0}", + "Collections": "合輯", + "DeviceOfflineWithName": "{0} 已經斷線", + "DeviceOnlineWithName": "{0} 已經連線", + "FailedLoginAttemptWithUserName": "來自 {0} 的失敗登入嘗試", + "Favorites": "我的最愛", + "Folders": "資料夾", + "Genres": "風格", + "HeaderAlbumArtists": "專輯演出者", + "HeaderCameraUploads": "相機上傳", + "HeaderContinueWatching": "繼續觀賞", + "HeaderFavoriteAlbums": "最愛專輯", + "HeaderFavoriteArtists": "最愛演出者", + "HeaderFavoriteEpisodes": "最愛級數", + "HeaderFavoriteShows": "最愛節目", + "HeaderFavoriteSongs": "最愛歌曲", + "HeaderLiveTV": "電視直播", + "HeaderNextUp": "接下來", + "HomeVideos": "自製影片", + "ItemAddedWithName": "{0} 已新增至媒體庫", + "ItemRemovedWithName": "{0} 已從媒體庫移除", + "LabelIpAddressValue": "IP 位置: {0}", + "LabelRunningTimeValue": "運行時間: {0}", + "Latest": "最新", + "MessageApplicationUpdated": "Jellyfin Server 已經更新", + "MessageApplicationUpdatedTo": "Jellyfin Server 已經更新至 {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 部分已經更新", + "MessageServerConfigurationUpdated": "伺服器設定已經更新", + "MixedContent": "混合內容", + "Movies": "電影", + "Music": "音樂", + "MusicVideos": "音樂MV", + "NameInstallFailed": "{0} 安裝失敗", + "NameSeasonNumber": "第 {0} 季", + "NameSeasonUnknown": "未知季數", + "NewVersionIsAvailable": "新版本的Jellyfin Server 軟體已經推出可供下載。", + "NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新", + "NotificationOptionApplicationUpdateInstalled": "應用程式已更新", + "NotificationOptionAudioPlayback": "音樂開始播放", + "NotificationOptionAudioPlaybackStopped": "音樂停止播放", + "NotificationOptionCameraImageUploaded": "相機相片已上傳", + "NotificationOptionInstallationFailed": "安裝失敗", + "NotificationOptionNewLibraryContent": "已新增新內容", + "NotificationOptionPluginError": "外掛失敗", + "NotificationOptionPluginInstalled": "外掛已安裝", + "NotificationOptionPluginUninstalled": "外掛已移除", + "NotificationOptionPluginUpdateInstalled": "已更新外掛", + "NotificationOptionServerRestartRequired": "伺服器需要重新啟動", + "NotificationOptionTaskFailed": "排程任務失敗", + "NotificationOptionUserLockedOut": "使用者已鎖定", + "NotificationOptionVideoPlayback": "影片開始播放", + "NotificationOptionVideoPlaybackStopped": "影片停止播放", + "Photos": "相片", + "Playlists": "播放清單", + "Plugin": "外掛", + "PluginInstalledWithName": "{0} 已安裝", + "PluginUninstalledWithName": "{0} 已移除", + "PluginUpdatedWithName": "{0} 已更新", + "ProviderValue": "提供商: {0}", + "ScheduledTaskFailedWithName": "{0} 已失敗", + "ScheduledTaskStartedWithName": "{0} 已開始", + "ServerNameNeedsToBeRestarted": "{0} 需要重新啟動", + "Shows": "節目", + "Songs": "歌曲", + "StartupEmbyServerIsLoading": "Jellyfin Server正在啟動,請稍後再試一次。", + "SubtitlesDownloadedForItem": "已為 {0} 下載字幕", + "Sync": "同步", + "System": "系統", + "TvShows": "電視節目", + "User": "使用者", + "UserCreatedWithName": "使用者 {0} 已建立", + "UserDeletedWithName": "使用者 {0} 已移除", + "UserDownloadingItemWithValues": "{0} 正在下載 {1}", + "UserLockedOutWithName": "使用者 {0} 已鎖定", + "UserOfflineFromDevice": "{0} 已從 {1} 斷線", + "UserOnlineFromDevice": "{0} 已連線,來自 {1}", + "UserPasswordChangedWithName": "使用者 {0} 的密碼已變更", + "UserPolicyUpdatedWithName": "使用者條約已更新為 {0}", + "UserStartedPlayingItemWithValues": "{0}正在使用 {2} 播放 {1}", + "UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 播放 {1}", + "ValueHasBeenAddedToLibrary": "{0} 已新增至您的媒體庫", + "ValueSpecialEpisodeName": "特典 - {0}", + "VersionNumber": "版本 {0}" +} diff --git a/Emby.Server.Implementations/Localization/Core/zh_Hant.json b/Emby.Server.Implementations/Localization/Core/zh_Hant.json deleted file mode 100644 index effff5566e..0000000000 --- a/Emby.Server.Implementations/Localization/Core/zh_Hant.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "Albums": "專輯", - "AppDeviceValues": "應用: {0}, 裝置: {1}", - "Application": "應用程式", - "Artists": "演出者", - "AuthenticationSucceededWithUserName": "{0} 成功授權", - "Books": "圖書", - "CameraImageUploadedFrom": "{0} 已經成功上傳一張相片", - "Channels": "頻道", - "ChapterNameValue": "章節 {0}", - "Collections": "合輯", - "DeviceOfflineWithName": "{0} 已經斷線", - "DeviceOnlineWithName": "{0} 已經連線", - "FailedLoginAttemptWithUserName": "來自 {0} 的失敗登入嘗試", - "Favorites": "我的最愛", - "Folders": "資料夾", - "Genres": "風格", - "HeaderAlbumArtists": "專輯演出者", - "HeaderCameraUploads": "相機上傳", - "HeaderContinueWatching": "繼續觀賞", - "HeaderFavoriteAlbums": "最愛專輯", - "HeaderFavoriteArtists": "最愛演出者", - "HeaderFavoriteEpisodes": "最愛級數", - "HeaderFavoriteShows": "最愛節目", - "HeaderFavoriteSongs": "最愛歌曲", - "HeaderLiveTV": "電視直播", - "HeaderNextUp": "接下來", - "HomeVideos": "自製影片", - "ItemAddedWithName": "{0} 已新增至媒體庫", - "ItemRemovedWithName": "{0} 已從媒體庫移除", - "LabelIpAddressValue": "IP 位置: {0}", - "LabelRunningTimeValue": "運行時間: {0}", - "Latest": "最新", - "MessageApplicationUpdated": "Jellyfin Server 已經更新", - "MessageApplicationUpdatedTo": "Jellyfin Server 已經更新至 {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 部分已經更新", - "MessageServerConfigurationUpdated": "伺服器設定已經更新", - "MixedContent": "混合內容", - "Movies": "電影", - "Music": "音樂", - "MusicVideos": "音樂MV", - "NameInstallFailed": "{0} 安裝失敗", - "NameSeasonNumber": "第 {0} 季", - "NameSeasonUnknown": "未知季數", - "NewVersionIsAvailable": "新版本的Jellyfin Server 軟體已經推出可供下載。", - "NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新", - "NotificationOptionApplicationUpdateInstalled": "應用程式已更新", - "NotificationOptionAudioPlayback": "音樂開始播放", - "NotificationOptionAudioPlaybackStopped": "音樂停止播放", - "NotificationOptionCameraImageUploaded": "相機相片已上傳", - "NotificationOptionInstallationFailed": "安裝失敗", - "NotificationOptionNewLibraryContent": "已新增新內容", - "NotificationOptionPluginError": "外掛失敗", - "NotificationOptionPluginInstalled": "外掛已安裝", - "NotificationOptionPluginUninstalled": "外掛已移除", - "NotificationOptionPluginUpdateInstalled": "已更新外掛", - "NotificationOptionServerRestartRequired": "伺服器需要重新啟動", - "NotificationOptionTaskFailed": "排程任務失敗", - "NotificationOptionUserLockedOut": "使用者已鎖定", - "NotificationOptionVideoPlayback": "影片開始播放", - "NotificationOptionVideoPlaybackStopped": "影片停止播放", - "Photos": "相片", - "Playlists": "播放清單", - "Plugin": "外掛", - "PluginInstalledWithName": "{0} 已安裝", - "PluginUninstalledWithName": "{0} 已移除", - "PluginUpdatedWithName": "{0} 已更新", - "ProviderValue": "提供商: {0}", - "ScheduledTaskFailedWithName": "{0} 已失敗", - "ScheduledTaskStartedWithName": "{0} 已開始", - "ServerNameNeedsToBeRestarted": "{0} 需要重新啟動", - "Shows": "節目", - "Songs": "歌曲", - "StartupEmbyServerIsLoading": "Jellyfin Server正在啟動,請稍後再試一次。", - "SubtitlesDownloadedForItem": "已為 {0} 下載字幕", - "Sync": "同步", - "System": "系統", - "TvShows": "電視節目", - "User": "使用者", - "UserCreatedWithName": "使用者 {0} 已建立", - "UserDeletedWithName": "使用者 {0} 已移除", - "UserDownloadingItemWithValues": "{0} 正在下載 {1}", - "UserLockedOutWithName": "使用者 {0} 已鎖定", - "UserOfflineFromDevice": "{0} 已從 {1} 斷線", - "UserOnlineFromDevice": "{0} 已連線,來自 {1}", - "UserPasswordChangedWithName": "使用者 {0} 的密碼已變更", - "UserPolicyUpdatedWithName": "使用者條約已更新為 {0}", - "UserStartedPlayingItemWithValues": "{0}正在使用 {2} 播放 {1}", - "UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 播放 {1}", - "ValueHasBeenAddedToLibrary": "{0} 已新增至您的媒體庫", - "ValueSpecialEpisodeName": "特典 - {0}", - "VersionNumber": "版本 {0}" -} -- cgit v1.2.3 From da842d5a730ea8db2fab8e4c71a501191d54e46c Mon Sep 17 00:00:00 2001 From: bugfixin Date: Fri, 19 Apr 2019 18:35:28 +0000 Subject: Fix incorrect escaping in regex pattern --- Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index c9d4e43422..a70077163e 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Library } // Ignore samples - Match m = Regex.Match(filename,"\bsample\b",RegexOptions.IgnoreCase); + Match m = Regex.Match(filename,@"\bsample\b",RegexOptions.IgnoreCase); return m.Success; } -- cgit v1.2.3 From f62af07381b633d8e7ddf5787d9048fbbf4e3c85 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 20 Apr 2019 12:18:44 +0200 Subject: Handle exception when loading unsupported assembly Fixes #1256 --- Emby.Server.Implementations/ApplicationHost.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 82042f5ca7..0295f10981 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1167,7 +1167,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - Logger.LogError(ex, "Error loading plugin {pluginName}", plugin.GetType().FullName); + Logger.LogError(ex, "Error loading plugin {PluginName}", plugin.GetType().FullName); return null; } @@ -1348,8 +1348,19 @@ namespace Emby.Server.Implementations { foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.AllDirectories)) { - Logger.LogInformation("Loading assembly {Path}", file); - yield return Assembly.LoadFrom(file); + Assembly plugAss; + try + { + plugAss = Assembly.LoadFrom(file); + } + catch (TypeLoadException ex) + { + Logger.LogError(ex, "Failed to load assembly {Path}", file); + continue; + } + + Logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file); + yield return plugAss; } } -- cgit v1.2.3 From 6973182ade7af9173abaf835608327be30b6b162 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 20 Apr 2019 14:02:00 +0200 Subject: Fix more possible exceptions --- Emby.Server.Implementations/ApplicationHost.cs | 32 ++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0295f10981..5d5a63a635 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1181,10 +1181,32 @@ namespace Emby.Server.Implementations { Logger.LogInformation("Loading assemblies"); - AllConcreteTypes = GetComposablePartAssemblies() - .SelectMany(x => x.ExportedTypes) - .Where(type => type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType) - .ToArray(); + AllConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray(); + } + + private IEnumerable GetTypes(IEnumerable assemblies) + { + foreach (var ass in assemblies) + { + Type[] exportedTypes; + try + { + exportedTypes = ass.GetExportedTypes(); + } + catch (TypeLoadException ex) + { + Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName); + continue; + } + + foreach (Type type in exportedTypes) + { + if (type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType) + { + yield return type; + } + } + } } private CertificateInfo CertificateInfo { get; set; } @@ -1353,7 +1375,7 @@ namespace Emby.Server.Implementations { plugAss = Assembly.LoadFrom(file); } - catch (TypeLoadException ex) + catch (FileLoadException ex) { Logger.LogError(ex, "Failed to load assembly {Path}", file); continue; -- cgit v1.2.3 From 764c6d5461ff359f50ab76a60589d871545eec2a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 20 Apr 2019 17:42:11 +0200 Subject: Fix comparison for empty password migration --- Emby.Server.Implementations/Data/SqliteUserRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index 182df0edc9..5957b29031 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.Data { // If the user password is the sha1 hash of the empty string, remove it if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) - || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) + && !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) { continue; } -- cgit v1.2.3 From 08d3a5d2feafe9d54c354028f38d7cfa4e94293c Mon Sep 17 00:00:00 2001 From: bugfixin Date: Sun, 21 Apr 2019 19:29:05 +0000 Subject: Fix null reference when request content type is application/x-www-form-urlencoded --- Emby.Server.Implementations/Services/ServiceHandler.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 621be4fcb5..d32fce1c77 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -26,7 +26,10 @@ namespace Emby.Server.Implementations.Services if (!string.IsNullOrEmpty(contentType) && httpReq.ContentLength > 0) { var deserializer = RequestHelper.GetRequestReader(host, contentType); - return deserializer?.Invoke(requestType, httpReq.InputStream); + if (deserializer != null) + { + return deserializer.Invoke(requestType, httpReq.InputStream); + } } return Task.FromResult(host.CreateInstance(requestType)); -- cgit v1.2.3 From 28c2ac528d46ba97b920d37300fa814bd6f4a51a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 24 Apr 2019 14:06:54 +0200 Subject: Re-add content length, semi revert of changes in #1010 (#1287) * Re-add content length, semi revert of changes in #1010 --- .../HttpServer/FileWriter.cs | 6 +- .../HttpServer/HttpListenerHost.cs | 1 + .../HttpServer/HttpResultFactory.cs | 13 ++-- .../HttpServer/RangeRequestWriter.cs | 1 + .../HttpServer/ResponseFilter.cs | 3 +- .../HttpServer/StreamWriter.cs | 11 ++- Emby.Server.Implementations/Services/HttpResult.cs | 5 ++ .../Services/ResponseHelper.cs | 81 +++++++++------------- .../Progressive/BaseProgressiveStreamingService.cs | 53 ++++++++++++-- 9 files changed, 114 insertions(+), 60 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index c4d2a70e23..c6b7d31a83 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -67,6 +67,7 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(rangeHeader)) { + Headers[HeaderNames.ContentLength] = TotalContentLength.ToString(CultureInfo.InvariantCulture); StatusCode = HttpStatusCode.OK; } else @@ -99,10 +100,13 @@ namespace Emby.Server.Implementations.HttpServer RangeStart = requestedRange.Key; RangeLength = 1 + RangeEnd - RangeStart; + // Content-Length is the length of what we're serving, not the original content + var lengthString = RangeLength.ToString(CultureInfo.InvariantCulture); + Headers[HeaderNames.ContentLength] = lengthString; var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; Headers[HeaderNames.ContentRange] = rangeString; - Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Range: {2}", Path, RangeHeader, rangeString); + Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); } /// diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 831391cee6..1fd27a7e36 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -638,6 +638,7 @@ namespace Emby.Server.Implementations.HttpServer private static Task Write(IResponse response, string text) { var bOutput = Encoding.UTF8.GetBytes(text); + response.OriginalResponse.ContentLength = bOutput.Length; return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length); } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 134f3c8418..5c29988c76 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.HttpServer content = Array.Empty(); } - result = new StreamWriter(content, contentType); + result = new StreamWriter(content, contentType, contentLength); } else { @@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.HttpServer bytes = Array.Empty(); } - result = new StreamWriter(bytes, contentType); + result = new StreamWriter(bytes, contentType, contentLength); } else { @@ -335,13 +335,13 @@ namespace Emby.Server.Implementations.HttpServer if (isHeadRequest) { - var result = new StreamWriter(Array.Empty(), contentType); + var result = new StreamWriter(Array.Empty(), contentType, contentLength); AddResponseHeaders(result, responseHeaders); return result; } else { - var result = new StreamWriter(content, contentType); + var result = new StreamWriter(content, contentType, contentLength); AddResponseHeaders(result, responseHeaders); return result; } @@ -581,6 +581,11 @@ namespace Emby.Server.Implementations.HttpServer } else { + if (totalContentLength.HasValue) + { + responseHeaders["Content-Length"] = totalContentLength.Value.ToString(CultureInfo.InvariantCulture); + } + if (isHeadRequest) { using (stream) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 449159834a..e27f794ba6 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -96,6 +96,7 @@ namespace Emby.Server.Implementations.HttpServer RangeStart = requestedRange.Key; RangeLength = 1 + RangeEnd - RangeStart; + Headers[HeaderNames.ContentLength] = RangeLength.ToString(CultureInfo.InvariantCulture); Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; if (RangeStart > 0 && SourceStream.CanSeek) diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index a53d9bf0b9..08f4247577 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.HttpServer public void FilterResponse(IRequest req, IResponse res, object dto) { // Try to prevent compatibility view - res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); + res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); res.AddHeader("Access-Control-Allow-Origin", "*"); @@ -58,6 +58,7 @@ namespace Emby.Server.Implementations.HttpServer if (length > 0) { + res.OriginalResponse.ContentLength = length; res.SendChunked = false; } } diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 324f9085e5..194d04441a 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -49,6 +50,13 @@ namespace Emby.Server.Implementations.HttpServer SourceStream = source; + Headers["Content-Type"] = contentType; + + if (source.CanSeek) + { + Headers[HeaderNames.ContentLength] = source.Length.ToString(CultureInfo.InvariantCulture); + } + Headers[HeaderNames.ContentType] = contentType; } @@ -57,7 +65,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// The source. /// Type of the content. - public StreamWriter(byte[] source, string contentType) + public StreamWriter(byte[] source, string contentType, int contentLength) { if (string.IsNullOrEmpty(contentType)) { @@ -66,6 +74,7 @@ namespace Emby.Server.Implementations.HttpServer SourceBytes = source; + Headers[HeaderNames.ContentLength] = contentLength.ToString(CultureInfo.InvariantCulture); Headers[HeaderNames.ContentType] = contentType; } diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs index b6758486ce..2b5963a770 100644 --- a/Emby.Server.Implementations/Services/HttpResult.cs +++ b/Emby.Server.Implementations/Services/HttpResult.cs @@ -43,6 +43,11 @@ namespace Emby.Server.Implementations.Services { var contentLength = bytesResponse.Length; + if (response != null) + { + response.OriginalResponse.ContentLength = contentLength; + } + if (contentLength > 0) { await responseStream.WriteAsync(bytesResponse, 0, contentLength, cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index 0301ff335f..251ba3529a 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Net; using System.Text; @@ -20,6 +21,8 @@ namespace Emby.Server.Implementations.Services { response.StatusCode = (int)HttpStatusCode.NoContent; } + + response.OriginalResponse.ContentLength = 0; return Task.CompletedTask; } @@ -39,11 +42,6 @@ namespace Emby.Server.Implementations.Services response.StatusCode = httpResult.Status; response.StatusDescription = httpResult.StatusCode.ToString(); - //if (string.IsNullOrEmpty(httpResult.ContentType)) - //{ - // httpResult.ContentType = defaultContentType; - //} - //response.ContentType = httpResult.ContentType; } var responseOptions = result as IHasHeaders; @@ -53,6 +51,7 @@ namespace Emby.Server.Implementations.Services { if (string.Equals(responseHeaders.Key, "Content-Length", StringComparison.OrdinalIgnoreCase)) { + response.OriginalResponse.ContentLength = long.Parse(responseHeaders.Value, CultureInfo.InvariantCulture); continue; } @@ -72,52 +71,37 @@ namespace Emby.Server.Implementations.Services response.ContentType += "; charset=utf-8"; } - var asyncStreamWriter = result as IAsyncStreamWriter; - if (asyncStreamWriter != null) - { - return asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken); - } - - var streamWriter = result as IStreamWriter; - if (streamWriter != null) + switch (result) { - streamWriter.WriteTo(response.OutputStream); - return Task.CompletedTask; - } - - var fileWriter = result as FileWriter; - if (fileWriter != null) - { - return fileWriter.WriteToAsync(response, cancellationToken); - } - - var stream = result as Stream; - if (stream != null) - { - return CopyStream(stream, response.OutputStream); - } + case IAsyncStreamWriter asyncStreamWriter: + return asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken); + case IStreamWriter streamWriter: + streamWriter.WriteTo(response.OutputStream); + return Task.CompletedTask; + case FileWriter fileWriter: + return fileWriter.WriteToAsync(response, cancellationToken); + case Stream stream: + return CopyStream(stream, response.OutputStream); + case byte[] bytes: + response.ContentType = "application/octet-stream"; + response.OriginalResponse.ContentLength = bytes.Length; + + if (bytes.Length > 0) + { + return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); + } - var bytes = result as byte[]; - if (bytes != null) - { - response.ContentType = "application/octet-stream"; + return Task.CompletedTask; + case string responseText: + var responseTextAsBytes = Encoding.UTF8.GetBytes(responseText); + response.OriginalResponse.ContentLength = responseTextAsBytes.Length; - if (bytes.Length > 0) - { - return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); - } - return Task.CompletedTask; - } + if (responseTextAsBytes.Length > 0) + { + return response.OutputStream.WriteAsync(responseTextAsBytes, 0, responseTextAsBytes.Length, cancellationToken); + } - var responseText = result as string; - if (responseText != null) - { - bytes = Encoding.UTF8.GetBytes(responseText); - if (bytes.Length > 0) - { - return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); - } - return Task.CompletedTask; + return Task.CompletedTask; } return WriteObject(request, result, response); @@ -143,14 +127,13 @@ namespace Emby.Server.Implementations.Services ms.Position = 0; var contentLength = ms.Length; + response.OriginalResponse.ContentLength = contentLength; if (contentLength > 0) { await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false); } } - - //serializer(result, outputStream); } } } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index a2c20e38fd..1c36289c5b 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -13,6 +14,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Services; using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Playback.Progressive @@ -279,10 +281,9 @@ namespace MediaBrowser.Api.Playback.Progressive /// Task{System.Object}. private async Task GetStaticRemoteStreamResult(StreamState state, Dictionary responseHeaders, bool isHeadRequest, CancellationTokenSource cancellationTokenSource) { - string useragent = null; - state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); + state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent); - var trySupportSeek = false; + const bool trySupportSeek = false; var options = new HttpRequestOptions { @@ -317,6 +318,12 @@ namespace MediaBrowser.Api.Playback.Progressive responseHeaders[HeaderNames.AcceptRanges] = "none"; } + // Seeing cases of -1 here + if (response.ContentLength.HasValue && response.ContentLength.Value >= 0) + { + responseHeaders[HeaderNames.ContentLength] = response.ContentLength.Value.ToString(CultureInfo.InvariantCulture); + } + if (isHeadRequest) { using (response) @@ -356,10 +363,31 @@ namespace MediaBrowser.Api.Playback.Progressive var contentType = state.GetMimeType(outputPath); // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response + var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null; + + if (contentLength.HasValue) + { + responseHeaders[HeaderNames.ContentLength] = contentLength.Value.ToString(CultureInfo.InvariantCulture); + } + // Headers only if (isHeadRequest) { - return ResultFactory.GetResult(null, Array.Empty(), contentType, responseHeaders); + var streamResult = ResultFactory.GetResult(null, Array.Empty(), contentType, responseHeaders); + + if (streamResult is IHasHeaders hasHeaders) + { + if (contentLength.HasValue) + { + hasHeaders.Headers[HeaderNames.ContentLength] = contentLength.Value.ToString(CultureInfo.InvariantCulture); + } + else + { + hasHeaders.Headers.Remove(HeaderNames.ContentLength); + } + } + + return streamResult; } var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath); @@ -397,5 +425,22 @@ namespace MediaBrowser.Api.Playback.Progressive transcodingLock.Release(); } } + + /// + /// Gets the length of the estimated content. + /// + /// The state. + /// System.Nullable{System.Int64}. + private long? GetEstimatedContentLength(StreamState state) + { + var totalBitrate = state.TotalOutputBitrate ?? 0; + + if (totalBitrate > 0 && state.RunTimeTicks.HasValue) + { + return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8); + } + + return null; + } } } -- cgit v1.2.3 From a9337033c1d95d7238e9411abbc7255ef2456f35 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 24 Apr 2019 15:25:22 +0200 Subject: Fix query time logging --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 088a6694b0..8841a9a504 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2741,15 +2741,16 @@ namespace Emby.Server.Implementations.Data { var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds; - int slowThreshold = 100; - #if DEBUG - slowThreshold = 10; + const int SlowThreshold = 100; +#else + const int SlowThreshold = 10; #endif - if (elapsed >= slowThreshold) + if (elapsed >= SlowThreshold) { - Logger.LogWarning("{0} query time (slow): {1:g}. Query: {2}", + Logger.LogWarning( + "{Method} query time (slow): {ElapsedMs}ms. Query: {Query}", methodName, elapsed, commandText); -- cgit v1.2.3 From 71479286e9b35cd43b78037d551da13cc19e49bd Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 24 Apr 2019 19:38:14 +0200 Subject: Fix #1234 --- Emby.Server.Implementations/HttpServer/HttpResultFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 5c29988c76..0b2924a3ba 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -629,7 +629,7 @@ namespace Emby.Server.Implementations.HttpServer if (lastModifiedDate.HasValue) { - responseHeaders[HeaderNames.LastModified] = lastModifiedDate.ToString(); + responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToString(CultureInfo.InvariantCulture); } } -- cgit v1.2.3 From 844ea9d77ed24bb5b3bc5ec5b23df8a045933759 Mon Sep 17 00:00:00 2001 From: bugfixin Date: Thu, 25 Apr 2019 04:36:28 +0000 Subject: Don't coalesce empty strings to null in StringMapTypeDeserializer --- Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index f835aa1b5b..6a522fbef3 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.Services string propertyName = pair.Key; string propertyTextValue = pair.Value; - if (string.IsNullOrEmpty(propertyTextValue) + if (propertyTextValue == null || !propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry) || propertySerializerEntry.PropertySetFn == null) { -- cgit v1.2.3 From 1df73fdeba0aca5ff2835080659877f0a6722f17 Mon Sep 17 00:00:00 2001 From: bugfixin Date: Tue, 30 Apr 2019 19:16:53 +0000 Subject: Fix incorrect hasPassword flag when easy pin set --- Emby.Server.Implementations/Library/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 952cc6896b..c33bb77409 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -596,7 +596,7 @@ namespace Emby.Server.Implementations.Library } bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; - bool hasConfiguredEasyPassword = string.IsNullOrEmpty(GetLocalPasswordHash(user)); + bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetLocalPasswordHash(user)); bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : -- cgit v1.2.3 From 35d7e97258347cf60cc9062402093cc53ff0e922 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 11 May 2019 11:55:41 +0200 Subject: Ignore casing photo extensions --- Emby.Photos/PhotoProvider.cs | 78 ++++++++-------------- .../Library/Resolvers/PhotoResolver.cs | 52 +++++++-------- 2 files changed, 52 insertions(+), 78 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs index a4179e6609..99a635e608 100644 --- a/Emby.Photos/PhotoProvider.cs +++ b/Emby.Photos/PhotoProvider.cs @@ -20,7 +20,10 @@ namespace Emby.Photos public class PhotoProvider : ICustomMetadataProvider, IForcedProvider, IHasItemChangeMonitor { private readonly ILogger _logger; - private IImageProcessor _imageProcessor; + private readonly IImageProcessor _imageProcessor; + + // These are causing taglib to hang + private string[] _includextensions = new string[] { ".jpg", ".jpeg", ".png", ".tiff", ".cr2" }; public PhotoProvider(ILogger logger, IImageProcessor imageProcessor) { @@ -28,75 +31,55 @@ namespace Emby.Photos _imageProcessor = imageProcessor; } + public string Name => "Embedded Information"; + public bool HasChanged(BaseItem item, IDirectoryService directoryService) { if (item.IsFileProtocol) { var file = directoryService.GetFile(item.Path); - if (file != null && file.LastWriteTimeUtc != item.DateModified) - { - return true; - } + return (file != null && file.LastWriteTimeUtc != item.DateModified); } return false; } - // These are causing taglib to hang - private string[] _includextensions = new string[] { ".jpg", ".jpeg", ".png", ".tiff", ".cr2" }; - public Task FetchAsync(Photo item, MetadataRefreshOptions options, CancellationToken cancellationToken) { item.SetImagePath(ImageType.Primary, item.Path); // Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs - if (_includextensions.Contains(Path.GetExtension(item.Path) ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (_includextensions.Contains(Path.GetExtension(item.Path), StringComparer.OrdinalIgnoreCase)) { try { using (var file = TagLib.File.Create(item.Path)) { - var image = file as TagLib.Image.File; - - var tag = file.GetTag(TagTypes.TiffIFD) as IFDTag; - - if (tag != null) + if (file.GetTag(TagTypes.TiffIFD) is IFDTag tag) { var structure = tag.Structure; - - if (structure != null) + if (structure != null + && structure.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) is SubIFDEntry exif) { - var exif = structure.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) as SubIFDEntry; - - if (exif != null) + var exifStructure = exif.Structure; + if (exifStructure != null) { - var exifStructure = exif.Structure; + var entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) as RationalIFDEntry; + if (entry != null) + { + item.Aperture = (double)entry.Value.Numerator / entry.Value.Denominator; + } - if (exifStructure != null) + entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) as RationalIFDEntry; + if (entry != null) { - var entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) as RationalIFDEntry; - - if (entry != null) - { - double val = entry.Value.Numerator; - val /= entry.Value.Denominator; - item.Aperture = val; - } - - entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) as RationalIFDEntry; - - if (entry != null) - { - double val = entry.Value.Numerator; - val /= entry.Value.Denominator; - item.ShutterSpeed = val; - } + item.ShutterSpeed = (double)entry.Value.Numerator / entry.Value.Denominator; } } } } - if (image != null) + if (file is TagLib.Image.File image) { item.CameraMake = image.ImageTag.Make; item.CameraModel = image.ImageTag.Model; @@ -116,12 +99,10 @@ namespace Emby.Photos item.Overview = image.ImageTag.Comment; - if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)) + if (!string.IsNullOrWhiteSpace(image.ImageTag.Title) + && !item.LockedFields.Contains(MetadataFields.Name)) { - if (!item.LockedFields.Contains(MetadataFields.Name)) - { - item.Name = image.ImageTag.Title; - } + item.Name = image.ImageTag.Title; } var dateTaken = image.ImageTag.DateTime; @@ -140,12 +121,9 @@ namespace Emby.Photos { item.Orientation = null; } - else + else if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out ImageOrientation orientation)) { - if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out ImageOrientation orientation)) - { - item.Orientation = orientation; - } + item.Orientation = orientation; } item.ExposureTime = image.ImageTag.ExposureTime; @@ -195,7 +173,5 @@ namespace Emby.Photos const ItemUpdateType result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport; return Task.FromResult(result); } - - public string Name => "Embedded Information"; } } diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index db270c398b..8171c010bd 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -14,6 +14,18 @@ namespace Emby.Server.Implementations.Library.Resolvers { private readonly IImageProcessor _imageProcessor; private readonly ILibraryManager _libraryManager; + private static readonly HashSet _ignoreFiles = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "folder", + "thumb", + "landscape", + "fanart", + "backdrop", + "poster", + "cover", + "logo", + "default" + }; public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager) { @@ -31,10 +43,10 @@ namespace Emby.Server.Implementations.Library.Resolvers if (!args.IsDirectory) { // Must be an image file within a photo collection - var collectionType = args.GetCollectionType(); + var collectionType = args.CollectionType; - if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) || - (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) + if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) + || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) { if (IsImageFile(args.Path, _imageProcessor)) { @@ -74,43 +86,29 @@ namespace Emby.Server.Implementations.Library.Resolvers } internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename) + => imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase); + + internal static bool IsImageFile(string path, IImageProcessor imageProcessor) { - if (imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase)) + if (path == null) { - return true; + throw new ArgumentNullException(nameof(path)); } - return false; - } - - private static readonly HashSet IgnoreFiles = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "folder", - "thumb", - "landscape", - "fanart", - "backdrop", - "poster", - "cover", - "logo", - "default" - }; - - internal static bool IsImageFile(string path, IImageProcessor imageProcessor) - { - var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty; + var filename = Path.GetFileNameWithoutExtension(path); - if (IgnoreFiles.Contains(filename)) + if (_ignoreFiles.Contains(filename)) { return false; } - if (IgnoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1)) + if (_ignoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1)) { return false; } - return imageProcessor.SupportedInputFormats.Contains(Path.GetExtension(path).TrimStart('.'), StringComparer.Ordinal); + string extension = Path.GetExtension(path).TrimStart('.'); + return imageProcessor.SupportedInputFormats.Contains(extension, StringComparer.OrdinalIgnoreCase); } } } -- cgit v1.2.3 From c22068d6b1b84e54521d7ce31b3dac43eeb1e92e Mon Sep 17 00:00:00 2001 From: DrPandemic Date: Sat, 11 May 2019 19:32:20 -0400 Subject: Fix pin bug introduced in 10.3.z. The issue is that the new easyPassword format prepends the hash function. This PR extract the hash from "$SHA1$_hash_". --- .../Library/DefaultAuthenticationProvider.cs | 6 +++--- Emby.Server.Implementations/Library/UserManager.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 3d15a8afbb..0527464ffc 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.Library public string Name => "Default"; public bool IsEnabled => true; - + // This is dumb and an artifact of the backwards way auth providers were designed. // This version of authenticate was never meant to be called, but needs to be here for interface compat // Only the providers that don't provide local user support use this @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library { throw new NotImplementedException(); } - + // This is the verson that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) { @@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library string hash = user.Password; user.Password = string.Format("$SHA1${0}", hash); } - + if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) { string hash = user.EasyPassword; diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index c33bb77409..b396ee51a3 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -550,7 +550,7 @@ namespace Emby.Server.Implementations.Library { return string.IsNullOrEmpty(user.EasyPassword) ? null - : user.EasyPassword; + : (new PasswordHash(user.EasyPassword)).Hash; } /// -- cgit v1.2.3 From 4a9b349c0463b46a3208b949f75d951f092aa735 Mon Sep 17 00:00:00 2001 From: dkanada Date: Thu, 16 May 2019 16:45:56 -0700 Subject: only return useful drives --- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 0dea5041a1..f0bea0455a 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -667,13 +667,13 @@ namespace Emby.Server.Implementations.IO public virtual List GetDrives() { - // Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout - return DriveInfo.GetDrives().Where(d => d.IsReady).Select(d => new FileSystemMetadata + // check for ready state to avoid waiting for drives to timeout + // some drives on linux have no actual size or are used for other purposes + return DriveInfo.GetDrives().Where(d => d.IsReady && d.TotalSize != 0 && d.DriveType != DriveType.Ram).Select(d => new FileSystemMetadata { Name = d.Name, FullName = d.RootDirectory.FullName, IsDirectory = true - }).ToList(); } -- cgit v1.2.3 From 2aed2d164b110ecafa88b5036e0875627908bde2 Mon Sep 17 00:00:00 2001 From: erikasne6152 Date: Wed, 22 May 2019 11:05:08 +0000 Subject: Translated using Weblate (Lithuanian) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lt/ --- Emby.Server.Implementations/Localization/Core/lt-LT.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index 558904f060..e2f3ba3dc8 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -1,21 +1,21 @@ { - "Albums": "Albums", + "Albums": "Albumai", "AppDeviceValues": "App: {0}, Device: {1}", "Application": "Application", - "Artists": "Artists", + "Artists": "Atlikėjai", "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", + "Books": "Knygos", "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", + "Channels": "Kanalai", "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", + "Collections": "Kolekcijos", "DeviceOfflineWithName": "{0} has disconnected", "DeviceOnlineWithName": "{0} is connected", "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", + "Favorites": "Mėgstami", + "Folders": "Katalogai", "Genres": "Žanrai", - "HeaderAlbumArtists": "Album Artists", + "HeaderAlbumArtists": "Albumo atlikėjai", "HeaderCameraUploads": "Camera Uploads", "HeaderContinueWatching": "Žiūrėti toliau", "HeaderFavoriteAlbums": "Favorite Albums", -- cgit v1.2.3 From 69ee49bee607d716a857a1525f503575ebf6db7f Mon Sep 17 00:00:00 2001 From: DrPandemic Date: Sat, 25 May 2019 13:46:55 -0400 Subject: Format correctly the PIN when updating it --- .../Library/DefaultAuthenticationProvider.cs | 28 ++++++++++++++++++++++ Emby.Server.Implementations/Library/UserManager.cs | 27 ++++----------------- .../Authentication/IAuthenticationProvider.cs | 3 +++ 3 files changed, 36 insertions(+), 22 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 0527464ffc..fe09b07ff6 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -165,6 +165,34 @@ namespace Emby.Server.Implementations.Library return user.Password; } + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) + { + ConvertPasswordFormat(user); + + if (newPassword != null) + { + newPasswordHash = string.Format("$SHA1${0}", GetHashedString(user, newPassword)); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException(nameof(newPasswordHash)); + } + + user.EasyPassword = newPasswordHash; + } + + public string GetEasyPasswordHash(User user) + { + // This should be removed in the future. This was added to let user login after + // Jellyfin 10.3.3 failed to save a well formatted PIN. + ConvertPasswordFormat(user); + + return string.IsNullOrEmpty(user.EasyPassword) + ? null + : (new PasswordHash(user.EasyPassword)).Hash; + } + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) { passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index b396ee51a3..a0b8d4ba42 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -471,7 +471,7 @@ namespace Emby.Server.Implementations.Library if (password == null) { // legacy - success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + success = string.Equals(GetAuthenticationProvider(user).GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); } else { @@ -497,11 +497,11 @@ namespace Emby.Server.Implementations.Library if (password == null) { // legacy - success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); } else { - success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); } } } @@ -546,13 +546,6 @@ namespace Emby.Server.Implementations.Library } } - private string GetLocalPasswordHash(User user) - { - return string.IsNullOrEmpty(user.EasyPassword) - ? null - : (new PasswordHash(user.EasyPassword)).Hash; - } - /// /// Loads the users from the repository /// @@ -596,7 +589,7 @@ namespace Emby.Server.Implementations.Library } bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; - bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetLocalPasswordHash(user)); + bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user)); bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : @@ -884,17 +877,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(user)); } - if (newPassword != null) - { - newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword); - } - - if (string.IsNullOrWhiteSpace(newPasswordHash)) - { - throw new ArgumentNullException(nameof(newPasswordHash)); - } - - user.EasyPassword = newPasswordHash; + GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordHash); UpdateUser(user); diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs index b9f282bd2a..2cf531eed5 100644 --- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs +++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs @@ -11,6 +11,9 @@ namespace MediaBrowser.Controller.Authentication Task Authenticate(string username, string password); Task HasPassword(User user); Task ChangePassword(User user, string newPassword); + void ChangeEasyPassword(User user, string newPassword, string newPasswordHash); + string GetPasswordHash(User user); + string GetEasyPasswordHash(User user); } public interface IRequiresResolvedUser -- cgit v1.2.3 From b1f764984f7098ee1164efee77f1bcb3de9fd08a Mon Sep 17 00:00:00 2001 From: exveria1015 Date: Thu, 30 May 2019 22:28:11 +0000 Subject: Added translation using Weblate (Japanese) --- Emby.Server.Implementations/Localization/Core/ja.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/ja.json (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -0,0 +1 @@ +{} -- cgit v1.2.3 From a6f9ceedd82fe34a8d1f088d91f7c217ee070aad Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 10 May 2019 20:37:42 +0200 Subject: Fix more warnings --- Emby.Naming/Audio/AlbumParser.cs | 26 ++++---- Emby.Naming/Audio/MultiPartResult.cs | 2 + Emby.Naming/AudioBook/AudioBookFileInfo.cs | 29 +++++++-- Emby.Naming/AudioBook/AudioBookFilePathParser.cs | 13 ++-- .../AudioBook/AudioBookFilePathParserResult.cs | 2 + Emby.Naming/AudioBook/AudioBookInfo.cs | 21 ++++-- Emby.Naming/AudioBook/AudioBookListResolver.cs | 2 +- Emby.Naming/AudioBook/AudioBookResolver.cs | 11 ++-- Emby.Naming/Common/EpisodeExpression.cs | 17 ++++- Emby.Naming/Common/MediaType.cs | 2 + Emby.Naming/Common/NamingOptions.cs | 75 +++++++++++----------- Emby.Naming/Emby.Naming.csproj | 18 +++++- Emby.Naming/Extensions/StringExtensions.cs | 1 + Emby.Naming/StringExtensions.cs | 30 --------- Emby.Naming/Subtitles/SubtitleInfo.cs | 3 + Emby.Naming/TV/EpisodeInfo.cs | 11 ++++ Emby.Naming/TV/EpisodePathParser.cs | 52 +++++++-------- Emby.Naming/TV/EpisodePathParserResult.cs | 7 ++ Emby.Naming/TV/EpisodeResolver.cs | 12 +++- Emby.Naming/TV/SeasonPathParser.cs | 42 ++++++------ Emby.Naming/TV/SeasonPathParserResult.cs | 2 + Emby.Naming/Video/CleanDateTimeParser.cs | 20 +++--- Emby.Naming/Video/ExtraResolver.cs | 2 - Emby.Naming/Video/FileStack.cs | 4 +- Emby.Naming/Video/Format3DParser.cs | 10 ++- Emby.Naming/Video/Format3DResult.cs | 12 ++-- Emby.Naming/Video/StackResolver.cs | 19 ++++-- Emby.Naming/Video/StubResolver.cs | 32 +++++---- Emby.Naming/Video/StubResult.cs | 1 + Emby.Naming/Video/StubTypeRule.cs | 1 + Emby.Naming/Video/VideoFileInfo.cs | 12 +++- Emby.Naming/Video/VideoInfo.cs | 4 ++ Emby.Naming/Video/VideoListResolver.cs | 40 ++++++------ Emby.Naming/Video/VideoResolver.cs | 12 ++-- .../Emby.Server.Implementations.csproj | 4 +- .../Library/LibraryManager.cs | 2 +- .../Library/Resolvers/TV/SeasonResolver.cs | 2 +- .../Library/Resolvers/TV/SeriesResolver.cs | 4 +- Jellyfin.Server/Jellyfin.Server.csproj | 6 +- Jellyfin.Server/Program.cs | 6 +- jellyfin.ruleset | 5 ++ 41 files changed, 339 insertions(+), 237 deletions(-) delete mode 100644 Emby.Naming/StringExtensions.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Naming/Audio/AlbumParser.cs b/Emby.Naming/Audio/AlbumParser.cs index 7d029a9f49..e8d7655525 100644 --- a/Emby.Naming/Audio/AlbumParser.cs +++ b/Emby.Naming/Audio/AlbumParser.cs @@ -33,27 +33,29 @@ namespace Emby.Naming.Audio // Normalize // Remove whitespace - filename = filename.Replace("-", " "); - filename = filename.Replace(".", " "); - filename = filename.Replace("(", " "); - filename = filename.Replace(")", " "); + filename = filename.Replace('-', ' '); + filename = filename.Replace('.', ' '); + filename = filename.Replace('(', ' '); + filename = filename.Replace(')', ' '); filename = Regex.Replace(filename, @"\s+", " "); filename = filename.TrimStart(); foreach (var prefix in _options.AlbumStackingPrefixes) { - if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) == 0) + if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0) { - var tmp = filename.Substring(prefix.Length); + continue; + } + + var tmp = filename.Substring(prefix.Length); - tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty; + tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty; - if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) - { - result.IsMultiPart = true; - break; - } + if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _)) + { + result.IsMultiPart = true; + break; } } diff --git a/Emby.Naming/Audio/MultiPartResult.cs b/Emby.Naming/Audio/MultiPartResult.cs index b1fa6e5639..00e4a9eb2e 100644 --- a/Emby.Naming/Audio/MultiPartResult.cs +++ b/Emby.Naming/Audio/MultiPartResult.cs @@ -7,11 +7,13 @@ namespace Emby.Naming.Audio /// /// The name. public string Name { get; set; } + /// /// Gets or sets the part. /// /// The part. public string Part { get; set; } + /// /// Gets or sets a value indicating whether this instance is multi part. /// diff --git a/Emby.Naming/AudioBook/AudioBookFileInfo.cs b/Emby.Naming/AudioBook/AudioBookFileInfo.cs index de66a54022..326ea05ef3 100644 --- a/Emby.Naming/AudioBook/AudioBookFileInfo.cs +++ b/Emby.Naming/AudioBook/AudioBookFileInfo.cs @@ -12,35 +12,56 @@ namespace Emby.Naming.AudioBook /// /// The path. public string Path { get; set; } + /// /// Gets or sets the container. /// /// The container. public string Container { get; set; } + /// /// Gets or sets the part number. /// /// The part number. public int? PartNumber { get; set; } + /// /// Gets or sets the chapter number. /// /// The chapter number. public int? ChapterNumber { get; set; } + /// /// Gets or sets the type. /// /// The type. public bool IsDirectory { get; set; } + /// public int CompareTo(AudioBookFileInfo other) { - if (ReferenceEquals(this, other)) return 0; - if (ReferenceEquals(null, other)) return 1; + if (ReferenceEquals(this, other)) + { + return 0; + } + + if (ReferenceEquals(null, other)) + { + return 1; + } + var chapterNumberComparison = Nullable.Compare(ChapterNumber, other.ChapterNumber); - if (chapterNumberComparison != 0) return chapterNumberComparison; + if (chapterNumberComparison != 0) + { + return chapterNumberComparison; + } + var partNumberComparison = Nullable.Compare(PartNumber, other.PartNumber); - if (partNumberComparison != 0) return partNumberComparison; + if (partNumberComparison != 0) + { + return partNumberComparison; + } + return string.Compare(Path, other.Path, StringComparison.Ordinal); } } diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs index 5909797947..ea7f06c8cb 100644 --- a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs +++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs @@ -1,3 +1,4 @@ +using System; using System.Globalization; using System.IO; using System.Text.RegularExpressions; @@ -14,14 +15,13 @@ namespace Emby.Naming.AudioBook _options = options; } - public AudioBookFilePathParserResult Parse(string path, bool IsDirectory) + public AudioBookFilePathParserResult Parse(string path) { - var result = Parse(path); - return !result.Success ? new AudioBookFilePathParserResult() : result; - } + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } - private AudioBookFilePathParserResult Parse(string path) - { var result = new AudioBookFilePathParserResult(); var fileName = Path.GetFileNameWithoutExtension(path); foreach (var expression in _options.AudioBookPartsExpressions) @@ -40,6 +40,7 @@ namespace Emby.Naming.AudioBook } } } + if (!result.PartNumber.HasValue) { var value = match.Groups["part"]; diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs index 3a8e3c31f2..f845e82435 100644 --- a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs +++ b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs @@ -3,7 +3,9 @@ namespace Emby.Naming.AudioBook public class AudioBookFilePathParserResult { public int? PartNumber { get; set; } + public int? ChapterNumber { get; set; } + public bool Success { get; set; } } } diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs index f6e1d5be43..600d3f05da 100644 --- a/Emby.Naming/AudioBook/AudioBookInfo.cs +++ b/Emby.Naming/AudioBook/AudioBookInfo.cs @@ -7,33 +7,40 @@ namespace Emby.Naming.AudioBook /// public class AudioBookInfo { + public AudioBookInfo() + { + Files = new List(); + Extras = new List(); + AlternateVersions = new List(); + } + /// /// Gets or sets the name. /// /// The name. public string Name { get; set; } + + /// + /// Gets or sets the year. + /// public int? Year { get; set; } + /// /// Gets or sets the files. /// /// The files. public List Files { get; set; } + /// /// Gets or sets the extras. /// /// The extras. public List Extras { get; set; } + /// /// Gets or sets the alternate versions. /// /// The alternate versions. public List AlternateVersions { get; set; } - - public AudioBookInfo() - { - Files = new List(); - Extras = new List(); - AlternateVersions = new List(); - } } } diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index 4e3ad7cac8..414ef11830 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -15,7 +15,7 @@ namespace Emby.Naming.AudioBook _options = options; } - public IEnumerable Resolve(List files) + public IEnumerable Resolve(IEnumerable files) { var audioBookResolver = new AudioBookResolver(_options); diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs index 67ab62e801..4a2b516d0e 100644 --- a/Emby.Naming/AudioBook/AudioBookResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -24,19 +24,21 @@ namespace Emby.Naming.AudioBook return Resolve(path, true); } - public AudioBookFileInfo Resolve(string path, bool IsDirectory = false) + public AudioBookFileInfo Resolve(string path, bool isDirectory = false) { if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); } - if (IsDirectory) // TODO + // TODO + if (isDirectory) { return null; } var extension = Path.GetExtension(path); + // Check supported extensions if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { @@ -45,8 +47,7 @@ namespace Emby.Naming.AudioBook var container = extension.TrimStart('.'); - var parsingResult = new AudioBookFilePathParser(_options) - .Parse(path, IsDirectory); + var parsingResult = new AudioBookFilePathParser(_options).Parse(path); return new AudioBookFileInfo { @@ -54,7 +55,7 @@ namespace Emby.Naming.AudioBook Container = container, PartNumber = parsingResult.PartNumber, ChapterNumber = parsingResult.ChapterNumber, - IsDirectory = IsDirectory + IsDirectory = isDirectory }; } } diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs index fd85bf76ad..136d8189dd 100644 --- a/Emby.Naming/Common/EpisodeExpression.cs +++ b/Emby.Naming/Common/EpisodeExpression.cs @@ -6,17 +6,28 @@ namespace Emby.Naming.Common public class EpisodeExpression { private string _expression; - public string Expression { get => _expression; - set { _expression = value; _regex = null; } } + private Regex _regex; + + public string Expression + { + get => _expression; + set + { + _expression = value; + _regex = null; + } + } public bool IsByDate { get; set; } + public bool IsOptimistic { get; set; } + public bool IsNamed { get; set; } + public bool SupportsAbsoluteEpisodeNumbers { get; set; } public string[] DateTimeFormats { get; set; } - private Regex _regex; public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled)); public EpisodeExpression(string expression, bool byDate) diff --git a/Emby.Naming/Common/MediaType.cs b/Emby.Naming/Common/MediaType.cs index 49cc9ee39b..a7b08bf793 100644 --- a/Emby.Naming/Common/MediaType.cs +++ b/Emby.Naming/Common/MediaType.cs @@ -6,10 +6,12 @@ namespace Emby.Naming.Common /// The audio /// Audio = 0, + /// /// The photo /// Photo = 1, + /// /// The video /// diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 2ef0208baf..88a9b46e63 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -8,19 +8,25 @@ namespace Emby.Naming.Common public class NamingOptions { public string[] AudioFileExtensions { get; set; } + public string[] AlbumStackingPrefixes { get; set; } public string[] SubtitleFileExtensions { get; set; } + public char[] SubtitleFlagDelimiters { get; set; } public string[] SubtitleForcedFlags { get; set; } + public string[] SubtitleDefaultFlags { get; set; } public EpisodeExpression[] EpisodeExpressions { get; set; } + public string[] EpisodeWithoutSeasonExpressions { get; set; } + public string[] EpisodeMultiPartExpressions { get; set; } public string[] VideoFileExtensions { get; set; } + public string[] StubFileExtensions { get; set; } public string[] AudioBookPartsExpressions { get; set; } @@ -28,12 +34,14 @@ namespace Emby.Naming.Common public StubTypeRule[] StubTypes { get; set; } public char[] VideoFlagDelimiters { get; set; } + public Format3DRule[] Format3DRules { get; set; } public string[] VideoFileStackingExpressions { get; set; } + public string[] CleanDateTimes { get; set; } - public string[] CleanStrings { get; set; } + public string[] CleanStrings { get; set; } public EpisodeExpression[] MultipleEpisodeExpressions { get; set; } @@ -41,7 +49,7 @@ namespace Emby.Naming.Common public NamingOptions() { - VideoFileExtensions = new string[] + VideoFileExtensions = new[] { ".m4v", ".3gp", @@ -106,53 +114,53 @@ namespace Emby.Naming.Common { new StubTypeRule { - StubType = "dvd", - Token = "dvd" + StubType = "dvd", + Token = "dvd" }, new StubTypeRule { - StubType = "hddvd", - Token = "hddvd" + StubType = "hddvd", + Token = "hddvd" }, new StubTypeRule { - StubType = "bluray", - Token = "bluray" + StubType = "bluray", + Token = "bluray" }, new StubTypeRule { - StubType = "bluray", - Token = "brrip" + StubType = "bluray", + Token = "brrip" }, new StubTypeRule { - StubType = "bluray", - Token = "bd25" + StubType = "bluray", + Token = "bd25" }, new StubTypeRule { - StubType = "bluray", - Token = "bd50" + StubType = "bluray", + Token = "bd50" }, new StubTypeRule { - StubType = "vhs", - Token = "vhs" + StubType = "vhs", + Token = "vhs" }, new StubTypeRule { - StubType = "tv", - Token = "HDTV" + StubType = "tv", + Token = "HDTV" }, new StubTypeRule { - StubType = "tv", - Token = "PDTV" + StubType = "tv", + Token = "PDTV" }, new StubTypeRule { - StubType = "tv", - Token = "DSR" + StubType = "tv", + Token = "DSR" } }; @@ -286,7 +294,7 @@ namespace Emby.Naming.Common new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"), new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true) { - DateTimeFormats = new [] + DateTimeFormats = new[] { "yyyy.MM.dd", "yyyy-MM-dd", @@ -295,7 +303,7 @@ namespace Emby.Naming.Common }, new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true) { - DateTimeFormats = new [] + DateTimeFormats = new[] { "dd.MM.yyyy", "dd-MM-yyyy", @@ -348,9 +356,7 @@ namespace Emby.Naming.Common }, // "1-12 episode title" - new EpisodeExpression(@"([0-9]+)-([0-9]+)") - { - }, + new EpisodeExpression(@"([0-9]+)-([0-9]+)"), // "01 - blah.avi", "01-blah.avi" new EpisodeExpression(@".*(\\|\/)(?\d{1,3})(-(?\d{2,3}))*\s?-\s?[^\\\/]*$") @@ -427,7 +433,7 @@ namespace Emby.Naming.Common Token = "_trailer", MediaType = MediaType.Video }, - new ExtraRule + new ExtraRule { ExtraType = "trailer", RuleType = ExtraRuleType.Suffix, @@ -462,7 +468,7 @@ namespace Emby.Naming.Common Token = "_sample", MediaType = MediaType.Video }, - new ExtraRule + new ExtraRule { ExtraType = "sample", RuleType = ExtraRuleType.Suffix, @@ -476,7 +482,6 @@ namespace Emby.Naming.Common Token = "theme", MediaType = MediaType.Audio }, - new ExtraRule { ExtraType = "scene", @@ -526,8 +531,8 @@ namespace Emby.Naming.Common Token = "-short", MediaType = MediaType.Video } - }; + Format3DRules = new[] { // Kodi rules: @@ -648,12 +653,10 @@ namespace Emby.Naming.Common @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))(-[xX]?[eE]?(?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})(-[xX]?[eE]?(?\d{1,3}))+[^\\\/]*$" - }.Select(i => new EpisodeExpression(i) - { - IsNamed = true - - }).ToArray(); + { + IsNamed = true + }).ToArray(); VideoFileExtensions = extensions .Distinct(StringComparer.OrdinalIgnoreCase) diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index c448ec0ce6..699d893256 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -18,6 +18,22 @@ Jellyfin.Naming https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt https://github.com/jellyfin/jellyfin + true + + + + true + + + + + + + + + + + ../jellyfin.ruleset diff --git a/Emby.Naming/Extensions/StringExtensions.cs b/Emby.Naming/Extensions/StringExtensions.cs index 26c09aeb40..5512127a80 100644 --- a/Emby.Naming/Extensions/StringExtensions.cs +++ b/Emby.Naming/Extensions/StringExtensions.cs @@ -5,6 +5,7 @@ namespace Emby.Naming.Extensions { public static class StringExtensions { + // TODO: @bond remove this when moving to netstandard2.1 public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison) { var sb = new StringBuilder(); diff --git a/Emby.Naming/StringExtensions.cs b/Emby.Naming/StringExtensions.cs deleted file mode 100644 index 7c61922af3..0000000000 --- a/Emby.Naming/StringExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Text; - -namespace Emby.Naming -{ - internal static class StringExtensions - { - public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison) - { - var sb = new StringBuilder(); - - var previousIndex = 0; - var index = str.IndexOf(oldValue, comparison); - - while (index != -1) - { - sb.Append(str.Substring(previousIndex, index - previousIndex)); - sb.Append(newValue); - index += oldValue.Length; - - previousIndex = index; - index = str.IndexOf(oldValue, index, comparison); - } - - sb.Append(str.Substring(previousIndex)); - - return sb.ToString(); - } - } -} diff --git a/Emby.Naming/Subtitles/SubtitleInfo.cs b/Emby.Naming/Subtitles/SubtitleInfo.cs index e4709dfbb6..96fce04d76 100644 --- a/Emby.Naming/Subtitles/SubtitleInfo.cs +++ b/Emby.Naming/Subtitles/SubtitleInfo.cs @@ -7,16 +7,19 @@ namespace Emby.Naming.Subtitles /// /// The path. public string Path { get; set; } + /// /// Gets or sets the language. /// /// The language. public string Language { get; set; } + /// /// Gets or sets a value indicating whether this instance is default. /// /// true if this instance is default; otherwise, false. public bool IsDefault { get; set; } + /// /// Gets or sets a value indicating whether this instance is forced. /// diff --git a/Emby.Naming/TV/EpisodeInfo.cs b/Emby.Naming/TV/EpisodeInfo.cs index c8aca7a6f3..de79b8bbaf 100644 --- a/Emby.Naming/TV/EpisodeInfo.cs +++ b/Emby.Naming/TV/EpisodeInfo.cs @@ -7,31 +7,37 @@ namespace Emby.Naming.TV /// /// The path. public string Path { get; set; } + /// /// Gets or sets the container. /// /// The container. public string Container { get; set; } + /// /// Gets or sets the name of the series. /// /// The name of the series. public string SeriesName { get; set; } + /// /// Gets or sets the format3 d. /// /// The format3 d. public string Format3D { get; set; } + /// /// Gets or sets a value indicating whether [is3 d]. /// /// true if [is3 d]; otherwise, false. public bool Is3D { get; set; } + /// /// Gets or sets a value indicating whether this instance is stub. /// /// true if this instance is stub; otherwise, false. public bool IsStub { get; set; } + /// /// Gets or sets the type of the stub. /// @@ -39,12 +45,17 @@ namespace Emby.Naming.TV public string StubType { get; set; } public int? SeasonNumber { get; set; } + public int? EpisodeNumber { get; set; } + public int? EndingEpsiodeNumber { get; set; } public int? Year { get; set; } + public int? Month { get; set; } + public int? Day { get; set; } + public bool IsByDate { get; set; } } } diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs index a8f81a3b85..812bc970ed 100644 --- a/Emby.Naming/TV/EpisodePathParser.cs +++ b/Emby.Naming/TV/EpisodePathParser.cs @@ -15,12 +15,12 @@ namespace Emby.Naming.TV _options = options; } - public EpisodePathParserResult Parse(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true) + public EpisodePathParserResult Parse(string path, bool isDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true) { // Added to be able to use regex patterns which require a file extension. // There were no failed tests without this block, but to be safe, we can keep it until // the regex which require file extensions are modified so that they don't need them. - if (IsDirectory) + if (isDirectory) { path += ".mp4"; } @@ -29,28 +29,20 @@ namespace Emby.Naming.TV foreach (var expression in _options.EpisodeExpressions) { - if (supportsAbsoluteNumbers.HasValue) + if (supportsAbsoluteNumbers.HasValue + && expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value) { - if (expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value) - { - continue; - } + continue; } - if (isNamed.HasValue) + if (isNamed.HasValue && expression.IsNamed != isNamed.Value) { - if (expression.IsNamed != isNamed.Value) - { - continue; - } + continue; } - if (isOptimistic.HasValue) + if (isOptimistic.HasValue && expression.IsOptimistic != isOptimistic.Value) { - if (expression.IsOptimistic != isOptimistic.Value) - { - continue; - } + continue; } var currentResult = Parse(path, expression); @@ -97,7 +89,8 @@ namespace Emby.Naming.TV DateTime date; if (expression.DateTimeFormats.Length > 0) { - if (DateTime.TryParseExact(match.Groups[0].Value, + if (DateTime.TryParseExact( + match.Groups[0].Value, expression.DateTimeFormats, CultureInfo.InvariantCulture, DateTimeStyles.None, @@ -109,17 +102,15 @@ namespace Emby.Naming.TV result.Success = true; } } - else + else if (DateTime.TryParse(match.Groups[0].Value, out date)) { - if (DateTime.TryParse(match.Groups[0].Value, out date)) - { - result.Year = date.Year; - result.Month = date.Month; - result.Day = date.Day; - result.Success = true; - } + result.Year = date.Year; + result.Month = date.Month; + result.Day = date.Day; + result.Success = true; } + // TODO: Only consider success if date successfully parsed? result.Success = true; } @@ -142,7 +133,8 @@ namespace Emby.Naming.TV // or a 'p' or 'i' as what you would get with a pixel resolution specification. // It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108 int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length; - if (nextIndex >= name.Length || "0123456789iIpP".IndexOf(name[nextIndex]) == -1) + if (nextIndex >= name.Length + || "0123456789iIpP".IndexOf(name[nextIndex]) == -1) { if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)) { @@ -160,6 +152,7 @@ namespace Emby.Naming.TV { result.SeasonNumber = num; } + if (int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)) { result.EpisodeNumber = num; @@ -171,8 +164,11 @@ namespace Emby.Naming.TV // Invalidate match when the season is 200 through 1927 or above 2500 // because it is an error unless the TV show is intentionally using false season numbers. // It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 episode 1080. - if (result.SeasonNumber >= 200 && result.SeasonNumber < 1928 || result.SeasonNumber > 2500) + if ((result.SeasonNumber >= 200 && result.SeasonNumber < 1928) + || result.SeasonNumber > 2500) + { result.Success = false; + } result.IsByDate = expression.IsByDate; } diff --git a/Emby.Naming/TV/EpisodePathParserResult.cs b/Emby.Naming/TV/EpisodePathParserResult.cs index e1a48bfbc9..996edfc506 100644 --- a/Emby.Naming/TV/EpisodePathParserResult.cs +++ b/Emby.Naming/TV/EpisodePathParserResult.cs @@ -3,14 +3,21 @@ namespace Emby.Naming.TV public class EpisodePathParserResult { public int? SeasonNumber { get; set; } + public int? EpisodeNumber { get; set; } + public int? EndingEpsiodeNumber { get; set; } + public string SeriesName { get; set; } + public bool Success { get; set; } public bool IsByDate { get; set; } + public int? Year { get; set; } + public int? Month { get; set; } + public int? Day { get; set; } } } diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs index fccf9bdecb..2d7bcb6382 100644 --- a/Emby.Naming/TV/EpisodeResolver.cs +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -15,7 +15,13 @@ namespace Emby.Naming.TV _options = options; } - public EpisodeInfo Resolve(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true) + public EpisodeInfo Resolve( + string path, + bool isDirectory, + bool? isNamed = null, + bool? isOptimistic = null, + bool? supportsAbsoluteNumbers = null, + bool fillExtendedInfo = true) { if (string.IsNullOrEmpty(path)) { @@ -26,7 +32,7 @@ namespace Emby.Naming.TV string container = null; string stubType = null; - if (!IsDirectory) + if (!isDirectory) { var extension = Path.GetExtension(path); // Check supported extensions @@ -52,7 +58,7 @@ namespace Emby.Naming.TV var format3DResult = new Format3DParser(_options).Parse(flags); var parsingResult = new EpisodePathParser(_options) - .Parse(path, IsDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo); + .Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo); return new EpisodeInfo { diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs index f1dcc50b89..e81b2bb349 100644 --- a/Emby.Naming/TV/SeasonPathParser.cs +++ b/Emby.Naming/TV/SeasonPathParser.cs @@ -3,30 +3,24 @@ using System.Globalization; using System.IO; using System.Linq; using Emby.Naming.Common; +using Emby.Naming.Extensions; namespace Emby.Naming.TV { public class SeasonPathParser { - private readonly NamingOptions _options; - - public SeasonPathParser(NamingOptions options) - { - _options = options; - } - public SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders) { var result = new SeasonPathParserResult(); var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders); - result.SeasonNumber = seasonNumberInfo.Item1; + result.SeasonNumber = seasonNumberInfo.seasonNumber; if (result.SeasonNumber.HasValue) { result.Success = true; - result.IsSeasonFolder = seasonNumberInfo.Item2; + result.IsSeasonFolder = seasonNumberInfo.isSeasonFolder; } return result; @@ -35,7 +29,7 @@ namespace Emby.Naming.TV /// /// A season folder must contain one of these somewhere in the name /// - private static readonly string[] SeasonFolderNames = + private static readonly string[] _seasonFolderNames = { "season", "sæson", @@ -54,19 +48,23 @@ namespace Emby.Naming.TV /// if set to true [support special aliases]. /// if set to true [support numeric season folders]. /// System.Nullable{System.Int32}. - private Tuple GetSeasonNumberFromPath(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders) + private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath( + string path, + bool supportSpecialAliases, + bool supportNumericSeasonFolders) { - var filename = Path.GetFileName(path); + var filename = Path.GetFileName(path) ?? string.Empty; if (supportSpecialAliases) { if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase)) { - return new Tuple(0, true); + return (0, true); } + if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase)) { - return new Tuple(0, true); + return (0, true); } } @@ -74,7 +72,7 @@ namespace Emby.Naming.TV { if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { - return new Tuple(val, true); + return (val, true); } } @@ -84,12 +82,12 @@ namespace Emby.Naming.TV if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { - return new Tuple(val, true); + return (val, true); } } // Look for one of the season folder names - foreach (var name in SeasonFolderNames) + foreach (var name in _seasonFolderNames) { var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase); @@ -107,10 +105,10 @@ namespace Emby.Naming.TV var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries); var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue); - return new Tuple(resultNumber, true); + return (resultNumber, true); } - private int? GetSeasonNumberFromPart(string part) + private static int? GetSeasonNumberFromPart(string part) { if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase)) { @@ -132,7 +130,7 @@ namespace Emby.Naming.TV /// /// The path. /// System.Nullable{System.Int32}. - private Tuple GetSeasonNumberFromPathSubstring(string path) + private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(string path) { var numericStart = -1; var length = 0; @@ -174,10 +172,10 @@ namespace Emby.Naming.TV if (numericStart == -1) { - return new Tuple(null, isSeasonFolder); + return (null, isSeasonFolder); } - return new Tuple(int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder); + return (int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder); } } } diff --git a/Emby.Naming/TV/SeasonPathParserResult.cs b/Emby.Naming/TV/SeasonPathParserResult.cs index eab27a4a5f..548dbd5d22 100644 --- a/Emby.Naming/TV/SeasonPathParserResult.cs +++ b/Emby.Naming/TV/SeasonPathParserResult.cs @@ -7,11 +7,13 @@ namespace Emby.Naming.TV /// /// The season number. public int? SeasonNumber { get; set; } + /// /// Gets or sets a value indicating whether this is success. /// /// true if success; otherwise, false. public bool Success { get; set; } + public bool IsSeasonFolder { get; set; } } } diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs index 74807ef53f..25fa09c488 100644 --- a/Emby.Naming/Video/CleanDateTimeParser.cs +++ b/Emby.Naming/Video/CleanDateTimeParser.cs @@ -27,8 +27,8 @@ namespace Emby.Naming.Video { var extension = Path.GetExtension(name) ?? string.Empty; // Check supported extensions - if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase) && - !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase) + && !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { // Dummy up a file extension because the expressions will fail without one // This is tricky because we can't just check Path.GetExtension for empty @@ -38,7 +38,6 @@ namespace Emby.Naming.Video } catch (ArgumentException) { - } var result = _options.CleanDateTimeRegexes.Select(i => Clean(name, i)) @@ -69,14 +68,15 @@ namespace Emby.Naming.Video var match = expression.Match(name); - if (match.Success && match.Groups.Count == 4) + if (match.Success + && match.Groups.Count == 4 + && match.Groups[1].Success + && match.Groups[2].Success + && int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year)) { - if (match.Groups[1].Success && match.Groups[2].Success && int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year)) - { - name = match.Groups[1].Value; - result.Year = year; - result.HasChanged = true; - } + name = match.Groups[1].Value; + result.Year = year; + result.HasChanged = true; } result.Name = name; diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index 3459b689ac..9f70494d01 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -56,7 +56,6 @@ namespace Emby.Naming.Video result.Rule = rule; } } - else if (rule.RuleType == ExtraRuleType.Suffix) { var filename = Path.GetFileNameWithoutExtension(path); @@ -67,7 +66,6 @@ namespace Emby.Naming.Video result.Rule = rule; } } - else if (rule.RuleType == ExtraRuleType.Regex) { var filename = Path.GetFileName(path); diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs index 2df1e9aed1..584bdf2d2f 100644 --- a/Emby.Naming/Video/FileStack.cs +++ b/Emby.Naming/Video/FileStack.cs @@ -15,9 +15,9 @@ namespace Emby.Naming.Video Files = new List(); } - public bool ContainsFile(string file, bool IsDirectory) + public bool ContainsFile(string file, bool isDirectory) { - if (IsDirectoryStack == IsDirectory) + if (IsDirectoryStack == isDirectory) { return Files.Contains(file, StringComparer.OrdinalIgnoreCase); } diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs index e6f830c583..333a48641e 100644 --- a/Emby.Naming/Video/Format3DParser.cs +++ b/Emby.Naming/Video/Format3DParser.cs @@ -15,10 +15,12 @@ namespace Emby.Naming.Video public Format3DResult Parse(string path) { - var delimeters = _options.VideoFlagDelimiters.ToList(); - delimeters.Add(' '); + int oldLen = _options.VideoFlagDelimiters.Length; + var delimeters = new char[oldLen + 1]; + _options.VideoFlagDelimiters.CopyTo(delimeters, 0); + delimeters[oldLen] = ' '; - return Parse(new FlagParser(_options).GetFlags(path, delimeters.ToArray())); + return Parse(new FlagParser(_options).GetFlags(path, delimeters)); } internal Format3DResult Parse(string[] videoFlags) @@ -66,8 +68,10 @@ namespace Emby.Naming.Video format = flag; result.Tokens.Add(rule.Token); } + break; } + foundPrefix = string.Equals(flag, rule.PreceedingToken, StringComparison.OrdinalIgnoreCase); } diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs index e12494079c..40fc31e082 100644 --- a/Emby.Naming/Video/Format3DResult.cs +++ b/Emby.Naming/Video/Format3DResult.cs @@ -4,25 +4,27 @@ namespace Emby.Naming.Video { public class Format3DResult { + public Format3DResult() + { + Tokens = new List(); + } + /// /// Gets or sets a value indicating whether [is3 d]. /// /// true if [is3 d]; otherwise, false. public bool Is3D { get; set; } + /// /// Gets or sets the format3 d. /// /// The format3 d. public string Format3D { get; set; } + /// /// Gets or sets the tokens. /// /// The tokens. public List Tokens { get; set; } - - public Format3DResult() - { - Tokens = new List(); - } } } diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index 4893002c1b..b8ba42da4f 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -40,17 +40,24 @@ namespace Emby.Naming.Video var result = new StackResult(); foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName))) { - var stack = new FileStack(); - stack.Name = Path.GetFileName(directory.Key); - stack.IsDirectoryStack = false; + var stack = new FileStack() + { + Name = Path.GetFileName(directory.Key), + IsDirectoryStack = false + }; foreach (var file in directory) { if (file.IsDirectory) + { continue; + } + stack.Files.Add(file.FullName); } + result.Stacks.Add(stack); } + return result; } @@ -114,16 +121,16 @@ namespace Emby.Naming.Video { if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase) && - string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase) + && string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase)) { if (stack.Files.Count == 0) { stack.Name = title1 + ignore1; stack.IsDirectoryStack = file1.IsDirectory; - //stack.Name = title1 + ignore1 + extension1; stack.Files.Add(file1.FullName); } + stack.Files.Add(file2.FullName); } else diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs index f86bcbdf04..b78244cb33 100644 --- a/Emby.Naming/Video/StubResolver.cs +++ b/Emby.Naming/Video/StubResolver.cs @@ -9,24 +9,32 @@ namespace Emby.Naming.Video { public static StubResult ResolveFile(string path, NamingOptions options) { - var result = new StubResult(); - var extension = Path.GetExtension(path) ?? string.Empty; + if (path == null) + { + return default(StubResult); + } + + var extension = Path.GetExtension(path); - if (options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { - result.IsStub = true; + return default(StubResult); + } - path = Path.GetFileNameWithoutExtension(path); + var result = new StubResult() + { + IsStub = true + }; - var token = (Path.GetExtension(path) ?? string.Empty).TrimStart('.'); + path = Path.GetFileNameWithoutExtension(path); + var token = Path.GetExtension(path).TrimStart('.'); - foreach (var rule in options.StubTypes) + foreach (var rule in options.StubTypes) + { + if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase)) - { - result.StubType = rule.StubType; - break; - } + result.StubType = rule.StubType; + break; } } diff --git a/Emby.Naming/Video/StubResult.cs b/Emby.Naming/Video/StubResult.cs index 7f9509ca56..7a62e7b981 100644 --- a/Emby.Naming/Video/StubResult.cs +++ b/Emby.Naming/Video/StubResult.cs @@ -7,6 +7,7 @@ namespace Emby.Naming.Video /// /// true if this instance is stub; otherwise, false. public bool IsStub { get; set; } + /// /// Gets or sets the type of the stub. /// diff --git a/Emby.Naming/Video/StubTypeRule.cs b/Emby.Naming/Video/StubTypeRule.cs index b46050085d..d765321504 100644 --- a/Emby.Naming/Video/StubTypeRule.cs +++ b/Emby.Naming/Video/StubTypeRule.cs @@ -7,6 +7,7 @@ namespace Emby.Naming.Video /// /// The token. public string Token { get; set; } + /// /// Gets or sets the type of the stub. /// diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs index 6a29ada7ea..78f688ca8a 100644 --- a/Emby.Naming/Video/VideoFileInfo.cs +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -1,4 +1,3 @@ - namespace Emby.Naming.Video { /// @@ -11,56 +10,67 @@ namespace Emby.Naming.Video /// /// The path. public string Path { get; set; } + /// /// Gets or sets the container. /// /// The container. public string Container { get; set; } + /// /// Gets or sets the name. /// /// The name. public string Name { get; set; } + /// /// Gets or sets the year. /// /// The year. public int? Year { get; set; } + /// /// Gets or sets the type of the extra, e.g. trailer, theme song, behing the scenes, etc. /// /// The type of the extra. public string ExtraType { get; set; } + /// /// Gets or sets the extra rule. /// /// The extra rule. public ExtraRule ExtraRule { get; set; } + /// /// Gets or sets the format3 d. /// /// The format3 d. public string Format3D { get; set; } + /// /// Gets or sets a value indicating whether [is3 d]. /// /// true if [is3 d]; otherwise, false. public bool Is3D { get; set; } + /// /// Gets or sets a value indicating whether this instance is stub. /// /// true if this instance is stub; otherwise, false. public bool IsStub { get; set; } + /// /// Gets or sets the type of the stub. /// /// The type of the stub. public string StubType { get; set; } + /// /// Gets or sets the type. /// /// The type. public bool IsDirectory { get; set; } + /// /// Gets the file name without extension. /// diff --git a/Emby.Naming/Video/VideoInfo.cs b/Emby.Naming/Video/VideoInfo.cs index d96d0e7571..2e456bda2b 100644 --- a/Emby.Naming/Video/VideoInfo.cs +++ b/Emby.Naming/Video/VideoInfo.cs @@ -12,21 +12,25 @@ namespace Emby.Naming.Video /// /// The name. public string Name { get; set; } + /// /// Gets or sets the year. /// /// The year. public int? Year { get; set; } + /// /// Gets or sets the files. /// /// The files. public List Files { get; set; } + /// /// Gets or sets the extras. /// /// The extras. public List Extras { get; set; } + /// /// Gets or sets the alternate versions. /// diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index afedc30ef2..5fa0041e07 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -53,7 +53,7 @@ namespace Emby.Naming.Video Name = stack.Name }; - info.Year = info.Files.First().Year; + info.Year = info.Files[0].Year; var extraBaseNames = new List { @@ -87,7 +87,7 @@ namespace Emby.Naming.Video Name = media.Name }; - info.Year = info.Files.First().Year; + info.Year = info.Files[0].Year; var extras = GetExtras(remainingFiles, new List { media.FileNameWithoutExtension }); @@ -115,7 +115,7 @@ namespace Emby.Naming.Video if (!string.IsNullOrEmpty(parentPath)) { - var folderName = Path.GetFileName(Path.GetDirectoryName(videoPath)); + var folderName = Path.GetFileName(parentPath); if (!string.IsNullOrEmpty(folderName)) { var extras = GetExtras(remainingFiles, new List { folderName }); @@ -163,9 +163,7 @@ namespace Emby.Naming.Video Year = i.Year })); - var orderedList = list.OrderBy(i => i.Name); - - return orderedList; + return list.OrderBy(i => i.Name); } private IEnumerable GetVideosGroupedByVersion(List videos) @@ -179,23 +177,21 @@ namespace Emby.Naming.Video var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path)); - if (!string.IsNullOrEmpty(folderName) && folderName.Length > 1) + if (!string.IsNullOrEmpty(folderName) + && folderName.Length > 1 + && videos.All(i => i.Files.Count == 1 + && IsEligibleForMultiVersion(folderName, i.Files[0].Path)) + && HaveSameYear(videos)) { - if (videos.All(i => i.Files.Count == 1 && IsEligibleForMultiVersion(folderName, i.Files[0].Path))) - { - if (HaveSameYear(videos)) - { - var ordered = videos.OrderBy(i => i.Name).ToList(); + var ordered = videos.OrderBy(i => i.Name).ToList(); - list.Add(ordered[0]); + list.Add(ordered[0]); - list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList(); - list[0].Name = folderName; - list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras)); + list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList(); + list[0].Name = folderName; + list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras)); - return list; - } - } + return list; } return videos; @@ -213,9 +209,9 @@ namespace Emby.Naming.Video if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) { testFilename = testFilename.Substring(folderName.Length).Trim(); - return string.IsNullOrEmpty(testFilename) || - testFilename.StartsWith("-") || - string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)) ; + return string.IsNullOrEmpty(testFilename) + || testFilename[0] == '-' + || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); } return false; diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index a673156517..02a25c4b5b 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -38,10 +38,11 @@ namespace Emby.Naming.Video /// Resolves the specified path. /// /// The path. - /// if set to true [is folder]. + /// if set to true [is folder]. + /// Whether or not the name should be parsed for info /// VideoFileInfo. /// path - public VideoFileInfo Resolve(string path, bool IsDirectory, bool parseName = true) + public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true) { if (string.IsNullOrEmpty(path)) { @@ -52,9 +53,10 @@ namespace Emby.Naming.Video string container = null; string stubType = null; - if (!IsDirectory) + if (!isDirectory) { var extension = Path.GetExtension(path); + // Check supported extensions if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { @@ -79,7 +81,7 @@ namespace Emby.Naming.Video var extraResult = new ExtraResolver(_options).GetExtraInfo(path); - var name = IsDirectory + var name = isDirectory ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path); @@ -108,7 +110,7 @@ namespace Emby.Naming.Video Is3D = format3DResult.Is3D, Format3D = format3DResult.Format3D, ExtraType = extraResult.ExtraType, - IsDirectory = IsDirectory, + IsDirectory = isDirectory, ExtraRule = extraResult.Rule }; } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 2c79624524..d4e17c42ae 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -52,8 +52,8 @@ - - + + diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 1673e37776..4b5063ada6 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2368,7 +2368,7 @@ namespace Emby.Server.Implementations.Library public int? GetSeasonNumberFromPath(string path) { - return new SeasonPathParser(GetNamingOptions()).Parse(path, true, true).SeasonNumber; + return new SeasonPathParser().Parse(path, true, true).SeasonNumber; } public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index ce1386e91a..3b9e48d978 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var path = args.Path; - var seasonParserResult = new SeasonPathParser(namingOptions).Parse(path, true, true); + var seasonParserResult = new SeasonPathParser().Parse(path, true, true); var season = new Season { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 5c95534ec1..1f873d7c6d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -194,9 +194,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// true if [is season folder] [the specified path]; otherwise, false. private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager) { - var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions(); - - var seasonNumber = new SeasonPathParser(namingOptions).Parse(path, isTvContentType, isTvContentType).SeasonNumber; + var seasonNumber = new SeasonPathParser().Parse(path, isTvContentType, isTvContentType).SeasonNumber; return seasonNumber.HasValue; } diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 9346a2d254..81f145abff 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -12,7 +12,7 @@ latest - SA1600;SA1601;CS1591 + SA1600;SA1601;SA1629;CS1591 true @@ -26,8 +26,8 @@ - - + + diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index fab584befc..d8ca121176 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -122,8 +122,12 @@ namespace Jellyfin.Server // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others. ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); +// CA5359: Do Not Disable Certificate Validation +#pragma warning disable CA5359 + // Allow all https requests ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); +#pragma warning restore CA5359 var fileSystem = new ManagedFileSystem(_loggerFactory, appPaths); @@ -368,7 +372,7 @@ namespace Jellyfin.Server } catch (Exception ex) { - _logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder. {0}"); + _logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder."); } return new NullImageEncoder(); diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 262121a325..0a60c8c7a2 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -14,12 +14,17 @@ + + + + + -- cgit v1.2.3 From 430483c7a1206e1e4fa6402f67c291931c55c4f0 Mon Sep 17 00:00:00 2001 From: dracocephalum Date: Thu, 30 May 2019 06:09:49 +0000 Subject: Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant/ --- Emby.Server.Implementations/Localization/Core/zh-TW.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index effff5566e..293fc28a86 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -89,5 +89,8 @@ "UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 播放 {1}", "ValueHasBeenAddedToLibrary": "{0} 已新增至您的媒體庫", "ValueSpecialEpisodeName": "特典 - {0}", - "VersionNumber": "版本 {0}" + "VersionNumber": "版本 {0}", + "HeaderRecordingGroups": "錄製組", + "Inherit": "繼承", + "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕" } -- cgit v1.2.3 From 3431a85adf9a5f9fbcd2ce4551af384c050051e1 Mon Sep 17 00:00:00 2001 From: exveria1015 Date: Thu, 30 May 2019 22:28:51 +0000 Subject: Translated using Weblate (Japanese) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/ --- .../Localization/Core/ja.json | 97 +++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index 0967ef424b..53a43a1252 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -1 +1,96 @@ -{} +{ + "Albums": "アルバム", + "AppDeviceValues": "アプリ: {0}, デバイス: {1}", + "Application": "アプリケーション", + "Artists": "アーティスト", + "AuthenticationSucceededWithUserName": "{0} 認証に成功しました", + "Books": "ブック", + "CameraImageUploadedFrom": "新しいカメライメージが {0}からアップロードされました", + "Channels": "チャンネル", + "ChapterNameValue": "チャプター {0}", + "Collections": "コレクション", + "DeviceOfflineWithName": "{0} が切断されました", + "DeviceOnlineWithName": "{0} が接続されました", + "FailedLoginAttemptWithUserName": "ログインを試行しましたが {0}によって失敗しました", + "Favorites": "お気に入り", + "Folders": "フォルダ", + "Genres": "ジャンル", + "HeaderAlbumArtists": "アルバムアーティスト", + "HeaderCameraUploads": "カメラアップロード", + "HeaderContinueWatching": "視聴中", + "HeaderFavoriteAlbums": "お気に入りのアルバム", + "HeaderFavoriteArtists": "お気に入りのアーティスト", + "HeaderFavoriteEpisodes": "お気に入りのエピソード", + "HeaderFavoriteShows": "お気に入りの番組", + "HeaderFavoriteSongs": "お気に入りの曲", + "HeaderLiveTV": "ライブ テレビ", + "HeaderNextUp": "次", + "HeaderRecordingGroups": "レコーディンググループ", + "HomeVideos": "ホームビデオ", + "Inherit": "継承", + "ItemAddedWithName": "{0} をライブラリに追加しました", + "ItemRemovedWithName": "{0} をライブラリから削除しました", + "LabelIpAddressValue": "IPアドレス: {0}", + "LabelRunningTimeValue": "稼働時間: {0}", + "Latest": "最新", + "MessageApplicationUpdated": "Jellyfin Server が更新されました", + "MessageApplicationUpdatedTo": "Jellyfin Server が {0}に更新されました", + "MessageNamedServerConfigurationUpdatedWithValue": "サーバー設定項目の {0} が更新されました", + "MessageServerConfigurationUpdated": "サーバー設定が更新されました", + "MixedContent": "ミックスコンテンツ", + "Movies": "ムービー", + "Music": "ミュージック", + "MusicVideos": "ミュージックビデオ", + "NameInstallFailed": "{0}のインストールに失敗しました", + "NameSeasonNumber": "シーズン {0}", + "NameSeasonUnknown": "不明なシーズン", + "NewVersionIsAvailable": "新しいバージョンの Jellyfin Server がダウンロード可能です。", + "NotificationOptionApplicationUpdateAvailable": "アプリケーションの更新があります", + "NotificationOptionApplicationUpdateInstalled": "アプリケーションは最新です", + "NotificationOptionAudioPlayback": "オーディオの再生を開始", + "NotificationOptionAudioPlaybackStopped": "オーディオの再生をストップしました", + "NotificationOptionCameraImageUploaded": "カメライメージがアップロードされました", + "NotificationOptionInstallationFailed": "インストール失敗", + "NotificationOptionNewLibraryContent": "新しいコンテンツを追加しました", + "NotificationOptionPluginError": "プラグインに障害が発生しました", + "NotificationOptionPluginInstalled": "プラグインがインストールされました", + "NotificationOptionPluginUninstalled": "プラグインがアンインストールされました", + "NotificationOptionPluginUpdateInstalled": "プラグインのアップデートをインストールしました", + "NotificationOptionServerRestartRequired": "サーバーを再起動してください", + "NotificationOptionTaskFailed": "スケジュールされていたタスクの失敗", + "NotificationOptionUserLockedOut": "ユーザーはロックされています", + "NotificationOptionVideoPlayback": "ビデオの再生を開始しました", + "NotificationOptionVideoPlaybackStopped": "ビデオを停止しました", + "Photos": "フォト", + "Playlists": "プレイリスト", + "Plugin": "プラグイン", + "PluginInstalledWithName": "{0} がインストールされました", + "PluginUninstalledWithName": "{0} がアンインストールされました", + "PluginUpdatedWithName": "{0} が更新されました", + "ProviderValue": "プロバイダ: {0}", + "ScheduledTaskFailedWithName": "{0} が失敗しました", + "ScheduledTaskStartedWithName": "{0} が開始されました", + "ServerNameNeedsToBeRestarted": "{0} を再起動してください", + "Shows": "番組", + "Songs": "曲", + "StartupEmbyServerIsLoading": "Jellyfin Server は現在読み込み中です。しばらくしてからもう一度お試しください。", + "SubtitleDownloadFailureFromForItem": "{0} から {1}の字幕のダウンロードに失敗しました", + "SubtitlesDownloadedForItem": "{0} の字幕がダウンロードされました", + "Sync": "同期", + "System": "システム", + "TvShows": "テレビ番組", + "User": "ユーザー", + "UserCreatedWithName": "ユーザー {0} が作成されました", + "UserDeletedWithName": "User {0} を削除しました", + "UserDownloadingItemWithValues": "{0} が {1} をダウンロードしています", + "UserLockedOutWithName": "ユーザー {0} はロックされています", + "UserOfflineFromDevice": "{0} は {1} から切断しました", + "UserOnlineFromDevice": "{0} は {1} からオンラインになりました", + "UserPasswordChangedWithName": "ユーザー {0} のパスワードは変更されました", + "UserPolicyUpdatedWithName": "ユーザーポリシーが{0}に更新されました", + "UserStartedPlayingItemWithValues": "{0} は {2}で{1} を再生しています", + "UserStoppedPlayingItemWithValues": "{0} は{2}で{1} の再生が終わりました", + "ValueHasBeenAddedToLibrary": "{0}はあなたのメディアライブラリに追加されました", + "ValueSpecialEpisodeName": "スペシャル - {0}", + "VersionNumber": "バージョン {0}" +} -- cgit v1.2.3 From a2d9420139639ff339daf77c6658bf2f22502110 Mon Sep 17 00:00:00 2001 From: Hyunsu Nam Date: Fri, 31 May 2019 01:10:13 +0000 Subject: Translated using Weblate (Korean) Currently translated at 97.8% (92 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/ --- .../Localization/Core/ko.json | 156 ++++++++++----------- 1 file changed, 78 insertions(+), 78 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index 21808fd18a..5a7ba8ba7b 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -1,88 +1,88 @@ { - "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", - "Artists": "Artists", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", - "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", - "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", - "Genres": "Genres", + "Albums": "앨범", + "AppDeviceValues": "앱: {0}, 디바이스: {1}", + "Application": "애플리케이션", + "Artists": "아티스트", + "AuthenticationSucceededWithUserName": "{0} 인증에 성공했습니다.", + "Books": "책", + "CameraImageUploadedFrom": "새로운 카메라 이미지가 {0}에서 업로드되었습니다.", + "Channels": "채널", + "ChapterNameValue": "챕터 {0}", + "Collections": "컬렉션", + "DeviceOfflineWithName": "{0}가 접속이 끊어졌습니다.", + "DeviceOnlineWithName": "{0}가 접속되었습니다.", + "FailedLoginAttemptWithUserName": "{0}에서 로그인이 실패했습니다.", + "Favorites": "즐겨찾기", + "Folders": "폴더", + "Genres": "장르", "HeaderAlbumArtists": "앨범 아티스트", - "HeaderCameraUploads": "Camera Uploads", + "HeaderCameraUploads": "카메라 업로드", "HeaderContinueWatching": "계속 시청하기", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteArtists": "Favorite Artists", + "HeaderFavoriteAlbums": "좋아하는 앨범", + "HeaderFavoriteArtists": "좋아하는 아티스트", "HeaderFavoriteEpisodes": "Favorite Episodes", "HeaderFavoriteShows": "즐겨찾는 쇼", - "HeaderFavoriteSongs": "Favorite Songs", - "HeaderLiveTV": "Live TV", - "HeaderNextUp": "Next Up", - "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", - "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", - "Latest": "Latest", - "MessageApplicationUpdated": "Jellyfin Server has been updated", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MixedContent": "Mixed content", - "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "Photos": "Photos", - "Playlists": "Playlists", - "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", - "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", - "Shows": "Shows", - "Songs": "Songs", + "HeaderFavoriteSongs": "좋아하는 노래", + "HeaderLiveTV": "TV 방송", + "HeaderNextUp": "다음으로", + "HeaderRecordingGroups": "녹화 그룹", + "HomeVideos": "홈 비디오", + "Inherit": "상속", + "ItemAddedWithName": "{0} 라이브러리에 추가됨", + "ItemRemovedWithName": "{0} 라이브러리에서 제거됨", + "LabelIpAddressValue": "IP 주소: {0}", + "LabelRunningTimeValue": "상영 시간: {0}", + "Latest": "최근", + "MessageApplicationUpdated": "Jellyfin 서버 업데이트됨", + "MessageApplicationUpdatedTo": "Jellyfin 서버가 {0}로 업데이트됨", + "MessageNamedServerConfigurationUpdatedWithValue": "서버 환경 설정 {0} 섹션 업데이트 됨", + "MessageServerConfigurationUpdated": "서버 환경 설정 업데이드됨", + "MixedContent": "혼합 콘텐츠", + "Movies": "영화", + "Music": "음악", + "MusicVideos": "뮤직 비디오", + "NameInstallFailed": "{0} 설치 실패.", + "NameSeasonNumber": "시즌 {0}", + "NameSeasonUnknown": "알 수 없는 시즌", + "NewVersionIsAvailable": "새 버전의 Jellyfin 서버를 사용할 수 있습니다.", + "NotificationOptionApplicationUpdateAvailable": "애플리케이션 업데이트 사용 가능", + "NotificationOptionApplicationUpdateInstalled": "애플리케이션 업데이트가 설치됨", + "NotificationOptionAudioPlayback": "오디오 재생을 시작함", + "NotificationOptionAudioPlaybackStopped": "오디오 재생이 중지됨", + "NotificationOptionCameraImageUploaded": "카메라 이미지가 업로드됨", + "NotificationOptionInstallationFailed": "설치 실패", + "NotificationOptionNewLibraryContent": "새 콘텐트가 추가됨", + "NotificationOptionPluginError": "플러그인 실패", + "NotificationOptionPluginInstalled": "플러그인이 설치됨", + "NotificationOptionPluginUninstalled": "플러그인이 설치 제거됨", + "NotificationOptionPluginUpdateInstalled": "플러그인 업데이트가 설치됨", + "NotificationOptionServerRestartRequired": "서버를 다시 시작하십시오", + "NotificationOptionTaskFailed": "예약 작업 실패", + "NotificationOptionUserLockedOut": "사용자가 잠겼습니다", + "NotificationOptionVideoPlayback": "비디오 재생을 시작함", + "NotificationOptionVideoPlaybackStopped": "비디오 재생이 중지됨", + "Photos": "사진", + "Playlists": "재생목록", + "Plugin": "플러그인", + "PluginInstalledWithName": "{0} 설치됨", + "PluginUninstalledWithName": "{0} 설치 제거됨", + "PluginUpdatedWithName": "{0} 업데이트됨", + "ProviderValue": "제공자: {0}", + "ScheduledTaskFailedWithName": "{0} 실패", + "ScheduledTaskStartedWithName": "{0} 시작", + "ServerNameNeedsToBeRestarted": "{0} 를 재시작하십시오", + "Shows": "프로그램", + "Songs": "노래", "StartupEmbyServerIsLoading": "Jellyfin 서버를 불러오고 있습니다. 잠시후 다시시도 해주세요.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", - "Sync": "Sync", - "System": "System", - "TvShows": "TV Shows", - "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", + "SubtitleDownloadFailureFromForItem": "{0}에서 {1} 자막 다운로드에 실패했습니다", + "SubtitlesDownloadedForItem": "{0} 자막을 다운로드했습니다", + "Sync": "동기화", + "System": "시스템", + "TvShows": "TV 쇼", + "User": "사용자", + "UserCreatedWithName": "사용자 {0} 생성됨", + "UserDeletedWithName": "사용자 {0} 삭제됨", "UserDownloadingItemWithValues": "{0} is downloading {1}", "UserLockedOutWithName": "User {0} has been locked out", "UserOfflineFromDevice": "{0} has disconnected from {1}", -- cgit v1.2.3 From 4adaeee0547bf67ebaebb200dcdd7b34af964051 Mon Sep 17 00:00:00 2001 From: Julio García Date: Sat, 1 Jun 2019 11:29:04 +0000 Subject: Translated using Weblate (Spanish) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/ --- Emby.Server.Implementations/Localization/Core/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index 1850b8f255..f03184d5ba 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -21,7 +21,7 @@ "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos", - "HeaderFavoriteShows": "Programas favoritos", + "HeaderFavoriteShows": "Series favoritas", "HeaderFavoriteSongs": "Canciones favoritas", "HeaderLiveTV": "TV en directo", "HeaderNextUp": "Siguiendo", -- cgit v1.2.3 From 6b6776042c0c456c128734056e28765f1717ffc7 Mon Sep 17 00:00:00 2001 From: Juvenal Yescas Date: Tue, 28 May 2019 14:56:25 +0000 Subject: Translated using Weblate (Spanish (Mexico)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_MX/ --- Emby.Server.Implementations/Localization/Core/es-MX.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index 2285f2808a..0036329689 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -18,11 +18,11 @@ "HeaderAlbumArtists": "Artistas del Álbum", "HeaderCameraUploads": "Subidos desde Camara", "HeaderContinueWatching": "Continuar Viendo", - "HeaderFavoriteAlbums": "Álbumes Favoritos", - "HeaderFavoriteArtists": "Artistas Favoritos", - "HeaderFavoriteEpisodes": "Episodios Preferidos", - "HeaderFavoriteShows": "Programas Preferidos", - "HeaderFavoriteSongs": "Canciones Favoritas", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteShows": "Programas favoritos", + "HeaderFavoriteSongs": "Canciones favoritas", "HeaderLiveTV": "TV en Vivo", "HeaderNextUp": "A Continuación", "HeaderRecordingGroups": "Grupos de Grabaciones", -- cgit v1.2.3 From b768ad978efdb653022dc490055688d321f2bf34 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 2 Jun 2019 21:49:12 -0700 Subject: split the new command to more than one line --- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index f0bea0455a..7c2ea50e2a 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -647,7 +647,6 @@ namespace Emby.Server.Implementations.IO public virtual bool IsPathFile(string path) { // Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\ - if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 && !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) { @@ -655,8 +654,6 @@ namespace Emby.Server.Implementations.IO } return true; - - //return Path.IsPathRooted(path); } public virtual void DeleteFile(string path) @@ -669,7 +666,8 @@ namespace Emby.Server.Implementations.IO { // check for ready state to avoid waiting for drives to timeout // some drives on linux have no actual size or are used for other purposes - return DriveInfo.GetDrives().Where(d => d.IsReady && d.TotalSize != 0 && d.DriveType != DriveType.Ram).Select(d => new FileSystemMetadata + return DriveInfo.GetDrives().Where(d => d.IsReady && d.TotalSize != 0 && d.DriveType != DriveType.Ram) + .Select(d => new FileSystemMetadata { Name = d.Name, FullName = d.RootDirectory.FullName, -- cgit v1.2.3 From d78a55adb4f66b8a82449216a11657da1388ab12 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sat, 8 Jun 2019 22:54:31 -0400 Subject: Implement InvalidAuthProvider Implements the InvalidAuthProvider, which acts as a fallback if a configured authentication provider, e.g. LDAP, is unavailable due to a load failure or removal. Until the user or the authentication plugin is corrected, this will cause users with the missing provider to be locked out, while throwing errors in the logs about the issue. Fixes #1445 part 2 --- .../Library/InvalidAuthProvider.cs | 46 ++++++++++++++++++++++ Emby.Server.Implementations/Library/UserManager.cs | 20 +++++++--- 2 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 Emby.Server.Implementations/Library/InvalidAuthProvider.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs new file mode 100644 index 0000000000..ee25695621 --- /dev/null +++ b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Entities; + +namespace Emby.Server.Implementations.Library +{ + public class InvalidAuthProvider : IAuthenticationProvider + { + public string Name => "InvalidorMissingAuthenticationProvider"; + + public bool IsEnabled => true; + + public Task Authenticate(string username, string password) + { + throw new Exception("User Account cannot login with this provider. The Normal provider for this user cannot be found"); + } + + public Task HasPassword(User user) + { + return Task.FromResult(true); + } + + public Task ChangePassword(User user, string newPassword) + { + return Task.FromResult(true); + } + + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) + { + // Nothing here + } + + public string GetPasswordHash(User user) + { + return ""; + } + + public string GetEasyPasswordHash(User user) + { + return ""; + } + } +} diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index a0b8d4ba42..ca43f7aaa1 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -79,6 +79,8 @@ namespace Emby.Server.Implementations.Library private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; + private InvalidAuthProvider _invalidAuthProvider; + private IPasswordResetProvider[] _passwordResetProviders; private DefaultPasswordResetProvider _defaultPasswordResetProvider; @@ -141,6 +143,8 @@ namespace Emby.Server.Implementations.Library _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); + _invalidAuthProvider = _authenticationProviders.OfType().First(); + _passwordResetProviders = passwordResetProviders.ToArray(); _defaultPasswordResetProvider = passwordResetProviders.OfType().First(); @@ -307,11 +311,14 @@ namespace Emby.Server.Implementations.Library user = Users .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; - if (hasNewUserPolicy != null) + if (authenticationProvider.GetType() != typeof(InvalidAuthProvider)) { - var policy = hasNewUserPolicy.GetNewUserPolicy(); - UpdateUserPolicy(user, policy, true); + var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; + if (hasNewUserPolicy != null) + { + var policy = hasNewUserPolicy.GetNewUserPolicy(); + UpdateUserPolicy(user, policy, true); + } } } } @@ -400,7 +407,10 @@ namespace Emby.Server.Implementations.Library if (providers.Length == 0) { - providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; + // this function used to assign any user without an auth provider to the default. + // we're going to have it use a new function now. + _logger.LogWarning($"The user {user.Name} was found but no Authentication Provider with ID: {user.Policy.AuthenticationProviderId} was found. Assigning user to InvalidAuthProvider temporarily"); + providers = new IAuthenticationProvider[] { _invalidAuthProvider }; } return providers; -- cgit v1.2.3 From 74ef3898798033a7cad987c4a869e7e72f57b229 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 9 Jun 2019 11:07:35 -0400 Subject: Add nicer log message and comment --- Emby.Server.Implementations/Library/UserManager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index ca43f7aaa1..83584acf3d 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -407,9 +407,8 @@ namespace Emby.Server.Implementations.Library if (providers.Length == 0) { - // this function used to assign any user without an auth provider to the default. - // we're going to have it use a new function now. - _logger.LogWarning($"The user {user.Name} was found but no Authentication Provider with ID: {user.Policy.AuthenticationProviderId} was found. Assigning user to InvalidAuthProvider temporarily"); + // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found + _logger.LogWarning("User {0} was found with invalid/missing Authentication Provider {1}. Assigning user to InvalidAuthProvider until this is corrected", user.Name, user.Policy.AuthenticationProviderId); providers = new IAuthenticationProvider[] { _invalidAuthProvider }; } -- cgit v1.2.3 From b70083f3b370055b2942e450291ce42345732cb7 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 9 Jun 2019 13:41:14 -0400 Subject: Apply suggestions from code review Co-Authored-By: Claus Vium Co-Authored-By: Bond-009 --- Emby.Server.Implementations/Library/InvalidAuthProvider.cs | 8 ++++---- Emby.Server.Implementations/Library/UserManager.cs | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs index ee25695621..133864708d 100644 --- a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs +++ b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Library { public class InvalidAuthProvider : IAuthenticationProvider { - public string Name => "InvalidorMissingAuthenticationProvider"; + public string Name => "InvalidOrMissingAuthenticationProvider"; public bool IsEnabled => true; @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Library public Task ChangePassword(User user, string newPassword) { - return Task.FromResult(true); + return Task.CompletedTask; } public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) @@ -35,12 +35,12 @@ namespace Emby.Server.Implementations.Library public string GetPasswordHash(User user) { - return ""; + return string.Empty; } public string GetEasyPasswordHash(User user) { - return ""; + return string.Empty; } } } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 83584acf3d..04abfc3156 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -313,8 +313,7 @@ namespace Emby.Server.Implementations.Library if (authenticationProvider.GetType() != typeof(InvalidAuthProvider)) { - var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; - if (hasNewUserPolicy != null) + if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) { var policy = hasNewUserPolicy.GetNewUserPolicy(); UpdateUserPolicy(user, policy, true); @@ -408,7 +407,7 @@ namespace Emby.Server.Implementations.Library if (providers.Length == 0) { // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found - _logger.LogWarning("User {0} was found with invalid/missing Authentication Provider {1}. Assigning user to InvalidAuthProvider until this is corrected", user.Name, user.Policy.AuthenticationProviderId); + _logger.LogWarning("User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", user.Name, user.Policy.AuthenticationProviderId); providers = new IAuthenticationProvider[] { _invalidAuthProvider }; } -- cgit v1.2.3 From 20e2cb2d8693e3f53749bcde5b5fd6367bd15007 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 9 Jun 2019 13:45:51 -0400 Subject: Use SecurityException for auth failure --- Emby.Server.Implementations/Library/InvalidAuthProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs index 133864708d..25d2331373 100644 --- a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs +++ b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Net; namespace Emby.Server.Implementations.Library { @@ -15,7 +16,7 @@ namespace Emby.Server.Implementations.Library public Task Authenticate(string username, string password) { - throw new Exception("User Account cannot login with this provider. The Normal provider for this user cannot be found"); + throw new SecurityException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); } public Task HasPassword(User user) -- cgit v1.2.3 From c230d49d7c37d4fbe77676b835c3afd6c8cb56e7 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 9 Jun 2019 13:46:53 -0400 Subject: Don't set a default reset provider --- Emby.Server.Implementations/Library/UserManager.cs | 5 ----- 1 file changed, 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 04abfc3156..4233ea8f44 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -425,11 +425,6 @@ namespace Emby.Server.Implementations.Library providers = providers.Where(i => string.Equals(passwordResetProviderId, GetPasswordResetProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); } - if (providers.Length == 0) - { - providers = new IPasswordResetProvider[] { _defaultPasswordResetProvider }; - } - return providers; } -- cgit v1.2.3 From 4b8f735cb89901bd1004d590f4f2820c23e2493c Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 9 Jun 2019 13:57:49 -0400 Subject: Remove superfluous conditional This wasn't needed to prevent updating the policy on-disk from my tests and can be removed as suggested by @Bond-009 --- Emby.Server.Implementations/Library/UserManager.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 4233ea8f44..16becbd526 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -311,13 +311,10 @@ namespace Emby.Server.Implementations.Library user = Users .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - if (authenticationProvider.GetType() != typeof(InvalidAuthProvider)) + if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) { - if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) - { - var policy = hasNewUserPolicy.GetNewUserPolicy(); - UpdateUserPolicy(user, policy, true); - } + var policy = hasNewUserPolicy.GetNewUserPolicy(); + UpdateUserPolicy(user, policy, true); } } } -- cgit v1.2.3 From 2946ae10092cddadade4c84cfa000129bf117e03 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 9 Jun 2019 15:27:38 -0400 Subject: Revert "Don't set a default reset provider" This reverts commit c230d49d7c37d4fbe77676b835c3afd6c8cb56e7. This reenables an edge case where an admin might want to reset, with the default auth provider, the password of an externally-provided user so they could "unlock" the account while it was failing. There might be minor security implications to this, but the malicious actor would need FS access to do it (as they would with any password resets) so it's probably best to keep it as-is. Removing this in the first place was due to a misunderstanding anyways so no harm. --- Emby.Server.Implementations/Library/UserManager.cs | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 16becbd526..ff375e5900 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -422,6 +422,11 @@ namespace Emby.Server.Implementations.Library providers = providers.Where(i => string.Equals(passwordResetProviderId, GetPasswordResetProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); } + if (providers.Length == 0) + { + providers = new IPasswordResetProvider[] { _defaultPasswordResetProvider }; + } + return providers; } -- cgit v1.2.3 From aa3022754527759d307138f606136561b4151d2a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 9 Jun 2019 23:51:52 +0200 Subject: Improve main code flow Improved the way how some parts of the code depend on eachother Fixed some style issues --- .../IO/ManagedFileSystem.cs | 6 +- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 72 ++++++++++------------ Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 48 +++++++-------- Jellyfin.Server/Program.cs | 56 ++++++++++------- 4 files changed, 92 insertions(+), 90 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 7c2ea50e2a..8517abed6c 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -20,16 +20,14 @@ namespace Emby.Server.Implementations.IO protected ILogger Logger; private readonly List _shortcutHandlers = new List(); - private readonly string _tempPath; - private readonly bool _isEnvironmentCaseInsensitive; public ManagedFileSystem( - ILoggerFactory loggerFactory, + ILogger logger, IApplicationPaths applicationPaths) { - Logger = loggerFactory.CreateLogger("FileSystem"); + Logger = logger; _tempPath = applicationPaths.TempDirectory; _isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows; diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 5060476bad..f6c1208014 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -2,14 +2,11 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; -using System.Reflection; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; using SkiaSharp; @@ -18,24 +15,28 @@ namespace Jellyfin.Drawing.Skia public class SkiaEncoder : IImageEncoder { private readonly ILogger _logger; - private static IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - private static ILocalizationManager _localizationManager; + private readonly IApplicationPaths _appPaths; + private readonly ILocalizationManager _localizationManager; + + private static readonly HashSet _transparentImageTypes + = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; public SkiaEncoder( - ILoggerFactory loggerFactory, + ILogger logger, IApplicationPaths appPaths, - IFileSystem fileSystem, ILocalizationManager localizationManager) { - _logger = loggerFactory.CreateLogger("ImageEncoder"); + _logger = logger; _appPaths = appPaths; - _fileSystem = fileSystem; _localizationManager = localizationManager; - - LogVersion(); } + public string Name => "Skia"; + + public bool SupportsImageCollageCreation => true; + + public bool SupportsImageEncoding => true; + public IReadOnlyCollection SupportedInputFormats => new HashSet(StringComparer.OrdinalIgnoreCase) { @@ -66,17 +67,15 @@ namespace Jellyfin.Drawing.Skia public IReadOnlyCollection SupportedOutputFormats => new HashSet() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png }; - private void LogVersion() + /// + /// Test to determine if the native lib is available + /// + public static void TestSkia() { // test an operation that requires the native library SKPMColor.PreMultiply(SKColors.Black); - - _logger.LogInformation("SkiaSharp version: " + GetVersion()); } - public static Version GetVersion() - => typeof(SKBitmap).GetTypeInfo().Assembly.GetName().Version; - private static bool IsTransparent(SKColor color) => (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0; @@ -106,6 +105,7 @@ namespace Jellyfin.Drawing.Skia return false; } } + return true; } @@ -118,6 +118,7 @@ namespace Jellyfin.Drawing.Skia return false; } } + return true; } @@ -197,7 +198,7 @@ namespace Jellyfin.Drawing.Skia private static bool HasDiacritics(string text) => !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal); - private static bool RequiresSpecialCharacterHack(string path) + private bool RequiresSpecialCharacterHack(string path) { if (_localizationManager.HasUnicodeCategory(path, UnicodeCategory.OtherLetter)) { @@ -212,7 +213,7 @@ namespace Jellyfin.Drawing.Skia return false; } - private static string NormalizePath(string path, IFileSystem fileSystem) + private string NormalizePath(string path) { if (!RequiresSpecialCharacterHack(path)) { @@ -255,21 +256,18 @@ namespace Jellyfin.Drawing.Skia } } - private static readonly HashSet TransparentImageTypes - = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; - - internal static SKBitmap Decode(string path, bool forceCleanBitmap, IFileSystem fileSystem, ImageOrientation? orientation, out SKEncodedOrigin origin) + internal SKBitmap Decode(string path, bool forceCleanBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin) { if (!File.Exists(path)) { throw new FileNotFoundException("File not found", path); } - var requiresTransparencyHack = TransparentImageTypes.Contains(Path.GetExtension(path)); + var requiresTransparencyHack = _transparentImageTypes.Contains(Path.GetExtension(path)); if (requiresTransparencyHack || forceCleanBitmap) { - using (var stream = new SKFileStream(NormalizePath(path, fileSystem))) + using (var stream = new SKFileStream(NormalizePath(path))) using (var codec = SKCodec.Create(stream)) { if (codec == null) @@ -290,11 +288,11 @@ namespace Jellyfin.Drawing.Skia } } - var resultBitmap = SKBitmap.Decode(NormalizePath(path, fileSystem)); + var resultBitmap = SKBitmap.Decode(NormalizePath(path)); if (resultBitmap == null) { - return Decode(path, true, fileSystem, orientation, out origin); + return Decode(path, true, orientation, out origin); } // If we have to resize these they often end up distorted @@ -302,7 +300,7 @@ namespace Jellyfin.Drawing.Skia { using (resultBitmap) { - return Decode(path, true, fileSystem, orientation, out origin); + return Decode(path, true, orientation, out origin); } } @@ -314,13 +312,13 @@ namespace Jellyfin.Drawing.Skia { if (cropWhitespace) { - using (var bitmap = Decode(path, forceAnalyzeBitmap, _fileSystem, orientation, out origin)) + using (var bitmap = Decode(path, forceAnalyzeBitmap, orientation, out origin)) { return CropWhiteSpace(bitmap); } } - return Decode(path, forceAnalyzeBitmap, _fileSystem, orientation, out origin); + return Decode(path, forceAnalyzeBitmap, orientation, out origin); } private SKBitmap GetBitmap(string path, bool cropWhitespace, bool autoOrient, ImageOrientation? orientation) @@ -607,16 +605,16 @@ namespace Jellyfin.Drawing.Skia if (ratio >= 1.4) { - new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); + new StripCollageBuilder(this).BuildThumbCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); } else if (ratio >= .9) { - new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); + new StripCollageBuilder(this).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); } else { // TODO: Create Poster collage capability - new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); + new StripCollageBuilder(this).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); } } @@ -645,11 +643,5 @@ namespace Jellyfin.Drawing.Skia _logger.LogError(ex, "Error drawing indicator overlay"); } } - - public string Name => "Skia"; - - public bool SupportsImageCollageCreation => true; - - public bool SupportsImageEncoding => true; } } diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 7d404ce644..1f2a6e81a4 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -1,21 +1,17 @@ using System; using System.Collections.Generic; using System.IO; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.IO; using SkiaSharp; namespace Jellyfin.Drawing.Skia { public class StripCollageBuilder { - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; + private readonly SkiaEncoder _skiaEncoder; - public StripCollageBuilder(IApplicationPaths appPaths, IFileSystem fileSystem) + public StripCollageBuilder(SkiaEncoder skiaEncoder) { - _appPaths = appPaths; - _fileSystem = fileSystem; + _skiaEncoder = skiaEncoder; } public static SKEncodedImageFormat GetEncodedFormat(string outputPath) @@ -25,19 +21,28 @@ namespace Jellyfin.Drawing.Skia throw new ArgumentNullException(nameof(outputPath)); } - var ext = Path.GetExtension(outputPath).ToLowerInvariant(); + var ext = Path.GetExtension(outputPath); - if (ext == ".jpg" || ext == ".jpeg") + if (string.Equals(ext, ".jpg", StringComparison.OrdinalIgnoreCase) + || string.Equals(ext, ".jpeg", StringComparison.OrdinalIgnoreCase)) + { return SKEncodedImageFormat.Jpeg; + } - if (ext == ".webp") + if (string.Equals(ext, ".webp", StringComparison.OrdinalIgnoreCase)) + { return SKEncodedImageFormat.Webp; + } - if (ext == ".gif") + if (string.Equals(ext, ".gif", StringComparison.OrdinalIgnoreCase)) + { return SKEncodedImageFormat.Gif; + } - if (ext == ".bmp") + if (string.Equals(ext, ".bmp", StringComparison.OrdinalIgnoreCase)) + { return SKEncodedImageFormat.Bmp; + } // default to png return SKEncodedImageFormat.Png; @@ -47,25 +52,19 @@ namespace Jellyfin.Drawing.Skia { using (var bitmap = BuildSquareCollageBitmap(paths, width, height)) using (var outputStream = new SKFileWStream(outputPath)) + using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels())) { - using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels())) - { - pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); - } + pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); } } public void BuildThumbCollage(string[] paths, string outputPath, int width, int height) { using (var bitmap = BuildThumbCollageBitmap(paths, width, height)) + using (var outputStream = new SKFileWStream(outputPath)) + using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels())) { - using (var outputStream = new SKFileWStream(outputPath)) - { - using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels())) - { - pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); - } - } + pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); } } @@ -127,7 +126,7 @@ namespace Jellyfin.Drawing.Skia currentIndex = 0; } - bitmap = SkiaEncoder.Decode(paths[currentIndex], false, _fileSystem, null, out var origin); + bitmap = _skiaEncoder.Decode(paths[currentIndex], false, null, out var origin); imagesTested[currentIndex] = 0; @@ -156,7 +155,6 @@ namespace Jellyfin.Drawing.Skia { for (var y = 0; y < 2; y++) { - using (var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex)) { imageIndex = newIndex; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 049dd761bb..91752a16df 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -34,7 +34,6 @@ namespace Jellyfin.Server private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); private static ILogger _logger; private static bool _restartOnShutdown; - private static IConfiguration appConfig; public static Task Main(string[] args) { @@ -76,7 +75,7 @@ namespace Jellyfin.Server // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath); - appConfig = await CreateConfiguration(appPaths).ConfigureAwait(false); + IConfiguration appConfig = await CreateConfiguration(appPaths).ConfigureAwait(false); CreateLogger(appConfig, appPaths); @@ -116,8 +115,6 @@ namespace Jellyfin.Server ApplicationHost.LogEnvironmentInfo(_logger, appPaths); - SQLitePCL.Batteries_V2.Init(); - // Increase the max http request limit // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others. ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); @@ -129,20 +126,20 @@ namespace Jellyfin.Server ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); #pragma warning restore CA5359 - var fileSystem = new ManagedFileSystem(_loggerFactory, appPaths); + SQLitePCL.Batteries_V2.Init(); using (var appHost = new CoreAppHost( appPaths, _loggerFactory, options, - fileSystem, + new ManagedFileSystem(_loggerFactory.CreateLogger(), appPaths), new NullImageEncoder(), new NetworkManager(_loggerFactory), appConfig)) { await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false); - appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); + appHost.ImageProcessor.ImageEncoder = GetImageEncoder(appPaths, appHost.LocalizationManager); await appHost.RunStartupTasksAsync().ConfigureAwait(false); @@ -165,7 +162,7 @@ namespace Jellyfin.Server /// /// Create the data, config and log paths from the variety of inputs(command line args, - /// environment variables) or decide on what default to use. For Windows it's %AppPath% + /// environment variables) or decide on what default to use. For Windows it's %AppPath% /// for everything else the XDG approach is followed: /// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html /// @@ -187,7 +184,9 @@ namespace Jellyfin.Server if (string.IsNullOrEmpty(dataDir)) { // LocalApplicationData follows the XDG spec on unix machines - dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "jellyfin"); + dataDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "jellyfin"); } } @@ -206,20 +205,26 @@ namespace Jellyfin.Server if (string.IsNullOrEmpty(configDir)) { - if (options.DataDir != null || Directory.Exists(Path.Combine(dataDir, "config")) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (options.DataDir != null + || Directory.Exists(Path.Combine(dataDir, "config")) + || RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // Hang config folder off already set dataDir configDir = Path.Combine(dataDir, "config"); } else { - // $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored. + // $XDG_CONFIG_HOME defines the base directory relative to which + // user specific configuration files should be stored. configDir = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); - // If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME /.config should be used. + // If $XDG_CONFIG_HOME is either not set or empty, + // a default equal to $HOME /.config should be used. if (string.IsNullOrEmpty(configDir)) { - configDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config"); + configDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".config"); } configDir = Path.Combine(configDir, "jellyfin"); @@ -247,13 +252,17 @@ namespace Jellyfin.Server } else { - // $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored. + // $XDG_CACHE_HOME defines the base directory relative to which + // user specific non-essential data files should be stored. cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME"); - // If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used. + // If $XDG_CACHE_HOME is either not set or empty, + // a default equal to $HOME/.cache should be used. if (string.IsNullOrEmpty(cacheDir)) { - cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache"); + cacheDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".cache"); } cacheDir = Path.Combine(cacheDir, "jellyfin"); @@ -362,17 +371,22 @@ namespace Jellyfin.Server } private static IImageEncoder GetImageEncoder( - IFileSystem fileSystem, IApplicationPaths appPaths, ILocalizationManager localizationManager) { try { - return new SkiaEncoder(_loggerFactory, appPaths, fileSystem, localizationManager); + // Test if the native lib is available + SkiaEncoder.TestSkia(); + + return new SkiaEncoder( + _loggerFactory.CreateLogger(), + appPaths, + localizationManager); } catch (Exception ex) { - _logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder."); + _logger.LogWarning(ex, "Skia not available. Will fallback to NullIMageEncoder."); } return new NullImageEncoder(); @@ -386,7 +400,7 @@ namespace Jellyfin.Server if (string.IsNullOrWhiteSpace(module)) { - module = Environment.GetCommandLineArgs().First(); + module = Environment.GetCommandLineArgs()[0]; } string commandLineArgsString; @@ -398,7 +412,7 @@ namespace Jellyfin.Server else { commandLineArgsString = string.Join( - " ", + ' ', Environment.GetCommandLineArgs().Skip(1).Select(NormalizeCommandLineArgument)); } -- cgit v1.2.3 From 253e72f66720c2f590c443f8347e2187f4f36db0 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 10 Jun 2019 00:53:16 +0200 Subject: Simplify file serving code --- Emby.Server.Implementations/ApplicationHost.cs | 7 +- Emby.Server.Implementations/ResourceFileManager.cs | 30 +---- Jellyfin.Server/CoreAppHost.cs | 7 +- MediaBrowser.Controller/IResourceFileManager.cs | 11 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 13 +- MediaBrowser.WebDashboard/Api/PackageCreator.cs | 148 ++++++++------------- 6 files changed, 67 insertions(+), 149 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 53bc85b284..a4e54af2ca 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -231,11 +231,6 @@ namespace Emby.Server.Implementations /// The server configuration manager. public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager; - protected virtual IResourceFileManager CreateResourceFileManager() - { - return new ResourceFileManager(HttpResultFactory, LoggerFactory, FileSystemManager); - } - /// /// Gets or sets the user manager. /// @@ -886,7 +881,7 @@ namespace Emby.Server.Implementations SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory); serviceCollection.AddSingleton(SubtitleEncoder); - serviceCollection.AddSingleton(CreateResourceFileManager()); + serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager)); displayPreferencesRepo.Initialize(); diff --git a/Emby.Server.Implementations/ResourceFileManager.cs b/Emby.Server.Implementations/ResourceFileManager.cs index 890d848f4b..6eda2b5032 100644 --- a/Emby.Server.Implementations/ResourceFileManager.cs +++ b/Emby.Server.Implementations/ResourceFileManager.cs @@ -1,10 +1,8 @@ using System; using System.IO; -using System.Threading.Tasks; using MediaBrowser.Controller; using MediaBrowser.Controller.Net; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations @@ -13,34 +11,14 @@ namespace Emby.Server.Implementations { private readonly IFileSystem _fileSystem; private readonly ILogger _logger; - private readonly IHttpResultFactory _resultFactory; - public ResourceFileManager( - IHttpResultFactory resultFactory, - ILoggerFactory loggerFactory, - IFileSystem fileSystem) + public ResourceFileManager(ILogger logger, IFileSystem fileSystem) { - _resultFactory = resultFactory; - _logger = loggerFactory.CreateLogger("ResourceManager"); + _logger = logger; _fileSystem = fileSystem; } - public Stream GetResourceFileStream(string basePath, string virtualPath) - { - return _fileSystem.GetFileStream(GetResourcePath(basePath, virtualPath), FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true); - } - - public Task GetStaticFileResult(IRequest request, string basePath, string virtualPath, string contentType, TimeSpan? cacheDuration) - { - return _resultFactory.GetStaticFileResult(request, GetResourcePath(basePath, virtualPath)); - } - - public string ReadAllText(string basePath, string virtualPath) - { - return File.ReadAllText(GetResourcePath(basePath, virtualPath)); - } - - private string GetResourcePath(string basePath, string virtualPath) + public string GetResourcePath(string basePath, string virtualPath) { var fullPath = Path.Combine(basePath, virtualPath.Replace('/', Path.DirectorySeparatorChar)); @@ -50,7 +28,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - _logger.LogError(ex, "Error in Path.GetFullPath"); + _logger.LogError(ex, "Error retrieving full path"); } // Don't allow file system access outside of the source folder diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 8e6ed7a7e5..b9b0cc3825 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; using System.Reflection; using Emby.Server.Implementations; -using Emby.Server.Implementations.HttpServer; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -15,8 +16,8 @@ namespace Jellyfin.Server ILoggerFactory loggerFactory, StartupOptions options, IFileSystem fileSystem, - MediaBrowser.Controller.Drawing.IImageEncoder imageEncoder, - MediaBrowser.Common.Net.INetworkManager networkManager, + IImageEncoder imageEncoder, + INetworkManager networkManager, IConfiguration configuration) : base( applicationPaths, diff --git a/MediaBrowser.Controller/IResourceFileManager.cs b/MediaBrowser.Controller/IResourceFileManager.cs index f70ea6a17d..69a51cec83 100644 --- a/MediaBrowser.Controller/IResourceFileManager.cs +++ b/MediaBrowser.Controller/IResourceFileManager.cs @@ -1,16 +1,7 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - namespace MediaBrowser.Controller { public interface IResourceFileManager { - Task GetStaticFileResult(IRequest request, string basePath, string virtualPath, string contentType, TimeSpan? cacheDuration); - - Stream GetResourceFileStream(string basePath, string virtualPath); - - string ReadAllText(string basePath, string virtualPath); + string GetResourcePath(string basePath, string virtualPath); } } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 58ab2d27b5..d2ffd5efcd 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -114,8 +114,6 @@ namespace MediaBrowser.WebDashboard.Api private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IFileSystem _fileSystem; - private readonly ILocalizationManager _localization; - private readonly IJsonSerializer _jsonSerializer; private IResourceFileManager _resourceFileManager; /// @@ -126,16 +124,12 @@ namespace MediaBrowser.WebDashboard.Api IResourceFileManager resourceFileManager, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, - ILocalizationManager localization, - IJsonSerializer jsonSerializer, ILogger logger, IHttpResultFactory resultFactory) { _appHost = appHost; _serverConfigurationManager = serverConfigurationManager; _fileSystem = fileSystem; - _localization = localization; - _jsonSerializer = jsonSerializer; _logger = logger; _resultFactory = resultFactory; _resourceFileManager = resourceFileManager; @@ -205,6 +199,7 @@ namespace MediaBrowser.WebDashboard.Api { return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.js"), () => Task.FromResult(stream)); } + if (isTemplate) { return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream)); @@ -316,7 +311,7 @@ namespace MediaBrowser.WebDashboard.Api // Bounce them to the startup wizard if it hasn't been completed yet if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted && Request.RawUrl.IndexOf("wizard", StringComparison.OrdinalIgnoreCase) == -1 && - GetPackageCreator(basePath).IsCoreHtml(path)) + PackageCreator.IsCoreHtml(path)) { // But don't redirect if an html import is being requested. if (path.IndexOf("bower_components", StringComparison.OrdinalIgnoreCase) == -1) @@ -355,7 +350,7 @@ namespace MediaBrowser.WebDashboard.Api return await _resultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(basePath, path, localizationCulture)).ConfigureAwait(false); } - return await _resourceFileManager.GetStaticFileResult(Request, basePath, path, contentType, cacheDuration); + return await _resultFactory.GetStaticFileResult(Request, _resourceFileManager.GetResourcePath(basePath, path)); } private string GetLocalizationCulture() @@ -374,7 +369,7 @@ namespace MediaBrowser.WebDashboard.Api private PackageCreator GetPackageCreator(string basePath) { - return new PackageCreator(basePath, _fileSystem, _logger, _serverConfigurationManager, _resourceFileManager); + return new PackageCreator(basePath, _resourceFileManager); } public async Task Get(GetDashboardPackage request) diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 2d0e0e1880..133bf61e8c 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -1,139 +1,108 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using System.Threading.Tasks; using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; namespace MediaBrowser.WebDashboard.Api { public class PackageCreator { - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; - private readonly IServerConfigurationManager _config; private readonly string _basePath; - private IResourceFileManager _resourceFileManager; + private readonly IResourceFileManager _resourceFileManager; - public PackageCreator(string basePath, IFileSystem fileSystem, ILogger logger, IServerConfigurationManager config, IResourceFileManager resourceFileManager) + public PackageCreator(string basePath, IResourceFileManager resourceFileManager) { - _fileSystem = fileSystem; - _logger = logger; - _config = config; _basePath = basePath; _resourceFileManager = resourceFileManager; } - public async Task GetResource(string virtualPath, + public async Task GetResource( + string virtualPath, string mode, string localizationCulture, string appVersion) { - var resourceStream = GetRawResourceStream(virtualPath); + var resourcePath = _resourceFileManager.GetResourcePath(_basePath, virtualPath); + Stream resourceStream = File.OpenRead(resourcePath); - if (resourceStream != null) + if (resourceStream != null && IsCoreHtml(virtualPath)) { - if (IsFormat(virtualPath, "html")) - { - if (IsCoreHtml(virtualPath)) - { - resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); - } - } + resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); } return resourceStream; } - /// - /// Determines whether the specified path is HTML. - /// - /// The path. - /// The format. - /// true if the specified path is HTML; otherwise, false. - private static bool IsFormat(string path, string format) - { - return Path.GetExtension(path).EndsWith(format, StringComparison.OrdinalIgnoreCase); - } - - public bool IsCoreHtml(string path) + public static bool IsCoreHtml(string path) { if (path.IndexOf(".template.html", StringComparison.OrdinalIgnoreCase) != -1) { return false; } - return IsFormat(path, "html"); + return string.Equals(Path.GetExtension(path), ".html", StringComparison.OrdinalIgnoreCase); } /// /// Modifies the HTML by adding common meta tags, css and js. /// /// Task{Stream}. - public async Task ModifyHtml(string path, Stream sourceStream, string mode, string appVersion, string localizationCulture) + public async Task ModifyHtml( + string path, + Stream sourceStream, + string mode, + string appVersion, + string localizationCulture) { var isMainIndexPage = string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase); - using (sourceStream) + string html; + using (var reader = new StreamReader(sourceStream, Encoding.UTF8)) { - string html; - - using (var memoryStream = new MemoryStream()) - { - await sourceStream.CopyToAsync(memoryStream).ConfigureAwait(false); - - var originalBytes = memoryStream.ToArray(); + html = await reader.ReadToEndAsync().ConfigureAwait(false); + } - html = Encoding.UTF8.GetString(originalBytes, 0, originalBytes.Length); + if (isMainIndexPage && !string.IsNullOrWhiteSpace(localizationCulture)) + { + var lang = localizationCulture.Split('-')[0]; - if (isMainIndexPage) - { - if (!string.IsNullOrWhiteSpace(localizationCulture)) - { - var lang = localizationCulture.Split('-').FirstOrDefault(); + html = html.Replace("", "" + GetMetaTags(mode)); + } - if (isMainIndexPage) - { - html = html.Replace("", "" + GetMetaTags(mode)); - } + // Disable embedded scripts from plugins. We'll run them later once resources have loaded + if (html.IndexOf("", "-->"); + } - // Disable embedded scripts from plugins. We'll run them later once resources have loaded - if (html.IndexOf("", "-->"); - } + if (isMainIndexPage) + { + html = html.Replace("", GetCommonJavascript(mode, appVersion) + ""); + } - if (isMainIndexPage) - { - html = html.Replace("", GetCommonJavascript(mode, appVersion) + ""); - } + var bytes = Encoding.UTF8.GetBytes(html); - var bytes = Encoding.UTF8.GetBytes(html); + return new MemoryStream(bytes); - return new MemoryStream(bytes); - } } /// /// Gets the meta tags. /// /// System.String. - private string GetMetaTags(string mode) + private static string GetMetaTags(string mode) { var sb = new StringBuilder(); - if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) || - string.Equals(mode, "android", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) + || string.Equals(mode, "android", StringComparison.OrdinalIgnoreCase)) { sb.Append(""); } @@ -147,7 +116,7 @@ namespace MediaBrowser.WebDashboard.Api /// The mode. /// The version. /// System.String. - private string GetCommonJavascript(string mode, string version) + private static string GetCommonJavascript(string mode, string version) { var builder = new StringBuilder(); @@ -156,7 +125,6 @@ namespace MediaBrowser.WebDashboard.Api { builder.AppendFormat("window.appMode='{0}';", mode); } - else { builder.AppendFormat("window.dashboardVersion='{0}';", version); @@ -164,31 +132,21 @@ namespace MediaBrowser.WebDashboard.Api builder.Append(""); - var versionString = string.IsNullOrWhiteSpace(mode) ? "?v=" + version : string.Empty; - - var files = new List(); - - files.Add("scripts/apploader.js" + versionString); - if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) { - files.Insert(0, "cordova.js"); + builder.Append(""); } - var tags = files.Select(s => string.Format("", s)).ToArray(); + builder.Append(""); return builder.ToString(); } - - /// - /// Gets the raw resource stream. - /// - private Stream GetRawResourceStream(string virtualPath) - { - return _resourceFileManager.GetResourceFileStream(_basePath, virtualPath); - } - } } -- cgit v1.2.3 From b136f1408481661df7666652e75625e877873299 Mon Sep 17 00:00:00 2001 From: ferferga Date: Mon, 10 Jun 2019 11:31:38 +0200 Subject: Vacuum databases at startup --- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index fba81306b3..821c4b448a 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -203,6 +203,7 @@ namespace Emby.Server.Implementations.Data { var queries = new List { + "VACUUM", "PRAGMA journal_mode=WAL", "PRAGMA page_size=4096", "PRAGMA synchronous=Normal" -- cgit v1.2.3 From 6ebac0e5002ee79ac4acc5ac2114384436ea0f9a Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 14 Jun 2019 08:20:52 +0200 Subject: Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Jean-Samuel Aubry-Guzzi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 4d5fc585d1..4fc2d0d9cf 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1643,7 +1643,7 @@ namespace Emby.Server.Implementations { Url = apiUrl, LogErrorResponseBody = false, - LogErrors = logPing, + LogErrors = LogPing, LogRequest = logPing, BufferContent = false, -- cgit v1.2.3 From af099a9b53616fdc38392c86236d00413cdc14c5 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 14 Jun 2019 08:21:06 +0200 Subject: Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Jean-Samuel Aubry-Guzzi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 4fc2d0d9cf..f35b322a25 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1644,7 +1644,7 @@ namespace Emby.Server.Implementations Url = apiUrl, LogErrorResponseBody = false, LogErrors = LogPing, - LogRequest = logPing, + LogRequest = LogPing, BufferContent = false, CancellationToken = cancellationToken -- cgit v1.2.3 From 65a0ca2f32e2eba640fbfead46ffb5bfd02e2c88 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 14 Jun 2019 18:38:14 +0200 Subject: Improvements to InstallationManager --- .../Activity/ActivityLogEntryPoint.cs | 2 +- .../Emby.Server.Implementations.csproj | 5 + .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 21 +-- .../Updates/InstallationManager.cs | 177 +++++++++------------ MediaBrowser.Api/PackageService.cs | 10 +- .../Updates/IInstallationManager.cs | 16 +- MediaBrowser.Model/Updates/PackageVersionInfo.cs | 22 +-- MediaBrowser.Providers/Omdb/OmdbProvider.cs | 3 +- 8 files changed, 110 insertions(+), 146 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 0530a251cc..cd3af90644 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -346,7 +346,7 @@ namespace Emby.Server.Implementations.Activity }); } - private void OnPluginUpdated(object sender, GenericEventArgs> e) + private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e) { CreateLogEntry(new ActivityLogEntry { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d4e17c42ae..6c50698b2a 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -46,6 +46,11 @@ false + + + latest + + true diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index c6431c311c..bde7d5c81d 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -1,4 +1,3 @@ -using MediaBrowser.Common; using MediaBrowser.Common.Updates; using MediaBrowser.Model.Net; using System; @@ -25,13 +24,10 @@ namespace Emby.Server.Implementations.ScheduledTasks private readonly IInstallationManager _installationManager; - private readonly IApplicationHost _appHost; - - public PluginUpdateTask(ILogger logger, IInstallationManager installationManager, IApplicationHost appHost) + public PluginUpdateTask(ILogger logger, IInstallationManager installationManager) { _logger = logger; _installationManager = installationManager; - _appHost = appHost; } /// @@ -40,14 +36,11 @@ namespace Emby.Server.Implementations.ScheduledTasks /// IEnumerable{BaseTaskTrigger}. public IEnumerable GetDefaultTriggers() { - return new[] { - - // At startup - new TaskTriggerInfo {Type = TaskTriggerInfo.TriggerStartup}, - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} - }; + // At startup + yield return new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerStartup }; + + // Every so often + yield return new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }; } /// @@ -72,7 +65,7 @@ namespace Emby.Server.Implementations.ScheduledTasks try { - await _installationManager.InstallPackage(package, true, new SimpleProgress(), cancellationToken).ConfigureAwait(false); + await _installationManager.InstallPackage(package, new SimpleProgress(), cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 6833c20c3b..b3d76124bd 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; @@ -33,7 +34,7 @@ namespace Emby.Server.Implementations.Updates /// /// The current installations /// - public List> CurrentInstallations { get; set; } + private List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations { get; set; } /// /// The completed installations @@ -47,49 +48,15 @@ namespace Emby.Server.Implementations.Updates /// public event EventHandler> PluginUninstalled; - /// - /// Called when [plugin uninstalled]. - /// - /// The plugin. - private void OnPluginUninstalled(IPlugin plugin) - { - PluginUninstalled?.Invoke(this, new GenericEventArgs { Argument = plugin }); - } - /// /// Occurs when [plugin updated]. /// - public event EventHandler>> PluginUpdated; - /// - /// Called when [plugin updated]. - /// - /// The plugin. - /// The new version. - private void OnPluginUpdated(IPlugin plugin, PackageVersionInfo newVersion) - { - _logger.LogInformation("Plugin updated: {0} {1} {2}", newVersion.name, newVersion.versionStr ?? string.Empty, newVersion.classification); - - PluginUpdated?.Invoke(this, new GenericEventArgs> { Argument = new Tuple(plugin, newVersion) }); - - _applicationHost.NotifyPendingRestart(); - } + public event EventHandler> PluginUpdated; /// /// Occurs when [plugin updated]. /// public event EventHandler> PluginInstalled; - /// - /// Called when [plugin installed]. - /// - /// The package. - private void OnPluginInstalled(PackageVersionInfo package) - { - _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification); - - PluginInstalled?.Invoke(this, new GenericEventArgs { Argument = package }); - - _applicationHost.NotifyPendingRestart(); - } /// /// The _logger @@ -111,7 +78,7 @@ namespace Emby.Server.Implementations.Updates private readonly IZipClient _zipClient; public InstallationManager( - ILoggerFactory loggerFactory, + ILogger logger, IApplicationHost appHost, IApplicationPaths appPaths, IHttpClient httpClient, @@ -120,15 +87,15 @@ namespace Emby.Server.Implementations.Updates IFileSystem fileSystem, IZipClient zipClient) { - if (loggerFactory == null) + if (logger == null) { - throw new ArgumentNullException(nameof(loggerFactory)); + throw new ArgumentNullException(nameof(logger)); } - CurrentInstallations = new List>(); + _currentInstallations = new List<(InstallationInfo, CancellationTokenSource)>(); _completedInstallationsInternal = new ConcurrentBag(); - _logger = loggerFactory.CreateLogger(nameof(InstallationManager)); + _logger = logger; _applicationHost = appHost; _appPaths = appPaths; _httpClient = httpClient; @@ -138,21 +105,12 @@ namespace Emby.Server.Implementations.Updates _zipClient = zipClient; } - private static Version GetPackageVersion(PackageVersionInfo version) - { - return new Version(ValueOrDefault(version.versionStr, "0.0.0.1")); - } - - private static string ValueOrDefault(string str, string def) - { - return string.IsNullOrEmpty(str) ? def : str; - } - /// /// Gets all available packages. /// /// Task{List{PackageInfo}}. - public async Task> GetAvailablePackages(CancellationToken cancellationToken, + public async Task> GetAvailablePackages( + CancellationToken cancellationToken, bool withRegistration = true, string packageType = null, Version applicationVersion = null) @@ -172,22 +130,14 @@ namespace Emby.Server.Implementations.Updates { Url = "https://repo.jellyfin.org/releases/plugin/manifest.json", CancellationToken = cancellationToken, - Progress = new SimpleProgress(), CacheLength = GetCacheLength() - }, "GET").ConfigureAwait(false)) + }, HttpMethod.Get).ConfigureAwait(false)) + using (var stream = response.Content) { - using (var stream = response.Content) - { - return FilterPackages(await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false)); - } + return FilterPackages(await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false)); } } - private PackageVersionClass GetSystemUpdateLevel() - { - return _applicationHost.SystemUpdateLevel; - } - private static TimeSpan GetCacheLength() { return TimeSpan.FromMinutes(3); @@ -211,7 +161,7 @@ namespace Emby.Server.Implementations.Updates } package.versions = versions - .OrderByDescending(GetPackageVersion) + .OrderByDescending(x => x.Version) .ToArray(); if (package.versions.Length == 0) @@ -294,7 +244,7 @@ namespace Emby.Server.Implementations.Updates return null; } - return package.versions.FirstOrDefault(v => GetPackageVersion(v).Equals(version) && v.classification == classification); + return package.versions.FirstOrDefault(v => v.Version == version && v.classification == classification); } /// @@ -331,7 +281,7 @@ namespace Emby.Server.Implementations.Updates } return package.versions - .OrderByDescending(GetPackageVersion) + .OrderByDescending(x => x.Version) .FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, currentServerVersion)); } @@ -346,14 +296,14 @@ namespace Emby.Server.Implementations.Updates { var catalog = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false); - var systemUpdateLevel = GetSystemUpdateLevel(); + var systemUpdateLevel = _applicationHost.SystemUpdateLevel; // Figure out what needs to be installed return _applicationHost.Plugins.Select(p => { var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, systemUpdateLevel); - return latestPluginInfo != null && GetPackageVersion(latestPluginInfo) > p.Version ? latestPluginInfo : null; + return latestPluginInfo != null && latestPluginInfo.Version > p.Version ? latestPluginInfo : null; }).Where(i => i != null) .Where(p => !string.IsNullOrEmpty(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase))); @@ -368,7 +318,7 @@ namespace Emby.Server.Implementations.Updates /// The cancellation token. /// Task. /// package - public async Task InstallPackage(PackageVersionInfo package, bool isPlugin, IProgress progress, CancellationToken cancellationToken) + public async Task InstallPackage(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken) { if (package == null) { @@ -391,12 +341,12 @@ namespace Emby.Server.Implementations.Updates var innerCancellationTokenSource = new CancellationTokenSource(); - var tuple = new Tuple(installationInfo, innerCancellationTokenSource); + var tuple = (installationInfo, innerCancellationTokenSource); // Add it to the in-progress list - lock (CurrentInstallations) + lock (_currentInstallations) { - CurrentInstallations.Add(tuple); + _currentInstallations.Add(tuple); } var innerProgress = new ActionableProgress(); @@ -421,11 +371,11 @@ namespace Emby.Server.Implementations.Updates try { - await InstallPackageInternal(package, isPlugin, innerProgress, linkedToken).ConfigureAwait(false); + await InstallPackageInternal(package, innerProgress, linkedToken).ConfigureAwait(false); - lock (CurrentInstallations) + lock (_currentInstallations) { - CurrentInstallations.Remove(tuple); + _currentInstallations.Remove(tuple); } _completedInstallationsInternal.Add(installationInfo); @@ -434,9 +384,9 @@ namespace Emby.Server.Implementations.Updates } catch (OperationCanceledException) { - lock (CurrentInstallations) + lock (_currentInstallations) { - CurrentInstallations.Remove(tuple); + _currentInstallations.Remove(tuple); } _logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.versionStr); @@ -449,9 +399,9 @@ namespace Emby.Server.Implementations.Updates { _logger.LogError(ex, "Package installation failed"); - lock (CurrentInstallations) + lock (_currentInstallations) { - CurrentInstallations.Remove(tuple); + _currentInstallations.Remove(tuple); } PackageInstallationFailed?.Invoke(this, new InstallationFailedEventArgs @@ -477,16 +427,12 @@ namespace Emby.Server.Implementations.Updates /// The progress. /// The cancellation token. /// Task. - private async Task InstallPackageInternal(PackageVersionInfo package, bool isPlugin, IProgress progress, CancellationToken cancellationToken) + private async Task InstallPackageInternal(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken) { - IPlugin plugin = null; - - if (isPlugin) - { - // Set last update time if we were installed before - plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase)) + // Set last update time if we were installed before + IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase)) ?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase)); - } + string targetPath = plugin == null ? null : plugin.AssemblyFilePath; @@ -494,17 +440,20 @@ namespace Emby.Server.Implementations.Updates await PerformPackageInstallation(progress, targetPath, package, cancellationToken).ConfigureAwait(false); // Do plugin-specific processing - if (isPlugin) + if (plugin == null) { - if (plugin == null) - { - OnPluginInstalled(package); - } - else - { - OnPluginUpdated(plugin, package); - } + _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification); + + PluginInstalled?.Invoke(this, new GenericEventArgs(package)); } + else + { + _logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification); + + PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, PackageVersionInfo)>((plugin, package))); + } + + _applicationHost.NotifyPendingRestart(); } private async Task PerformPackageInstallation(IProgress progress, string target, PackageVersionInfo package, CancellationToken cancellationToken) @@ -622,11 +571,34 @@ namespace Emby.Server.Implementations.Updates _config.SaveConfiguration(); } - OnPluginUninstalled(plugin); + PluginUninstalled?.Invoke(this, new GenericEventArgs { Argument = plugin }); _applicationHost.NotifyPendingRestart(); } + /// + public bool CancelInstallation(Guid id) + { + lock (_currentInstallations) + { + var install = _currentInstallations.Find(x => x.Item1.Id == id); + if (install == default((InstallationInfo, CancellationTokenSource))) + { + return false; + } + + install.Item2.Cancel(); + _currentInstallations.Remove(install); + return true; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + /// /// Releases unmanaged and - optionally - managed resources. /// @@ -635,21 +607,16 @@ namespace Emby.Server.Implementations.Updates { if (dispose) { - lock (CurrentInstallations) + lock (_currentInstallations) { - foreach (var tuple in CurrentInstallations) + foreach (var tuple in _currentInstallations) { tuple.Item2.Dispose(); } - CurrentInstallations.Clear(); + _currentInstallations.Clear(); } } } - - public void Dispose() - { - Dispose(true); - } } } diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index fbb876deae..cf1e08d53a 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -197,7 +197,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException(string.Format("Package not found: {0}", request.Name)); } - await _installationManager.InstallPackage(package, true, new SimpleProgress(), CancellationToken.None); + await _installationManager.InstallPackage(package, new SimpleProgress(), CancellationToken.None); } /// @@ -206,13 +206,7 @@ namespace MediaBrowser.Api /// The request. public void Delete(CancelPackageInstallation request) { - var info = _installationManager.CurrentInstallations.FirstOrDefault(i => i.Item1.Id.Equals(request.Id)); - - if (info != null) - { - info.Item2.Cancel(); - } + _installationManager.CancelInstallation(new Guid(request.Id)); } } - } diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index a263be35f9..3472a5692f 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -15,11 +15,6 @@ namespace MediaBrowser.Common.Updates event EventHandler PackageInstallationFailed; event EventHandler PackageInstallationCancelled; - /// - /// The current installations - /// - List> CurrentInstallations { get; set; } - /// /// The completed installations /// @@ -33,7 +28,7 @@ namespace MediaBrowser.Common.Updates /// /// Occurs when [plugin updated]. /// - event EventHandler>> PluginUpdated; + event EventHandler> PluginUpdated; /// /// Occurs when [plugin updated]. @@ -107,7 +102,7 @@ namespace MediaBrowser.Common.Updates /// The cancellation token. /// Task. /// package - Task InstallPackage(PackageVersionInfo package, bool isPlugin, IProgress progress, CancellationToken cancellationToken); + Task InstallPackage(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken); /// /// Uninstalls a plugin @@ -115,5 +110,12 @@ namespace MediaBrowser.Common.Updates /// The plugin. /// void UninstallPlugin(IPlugin plugin); + + /// + /// Cancels the installation + /// + /// The id of the package that is being installed + /// Returns true if the install was cancelled + bool CancelInstallation(Guid id); } } diff --git a/MediaBrowser.Model/Updates/PackageVersionInfo.cs b/MediaBrowser.Model/Updates/PackageVersionInfo.cs index be531770d4..7ef07c0dfd 100644 --- a/MediaBrowser.Model/Updates/PackageVersionInfo.cs +++ b/MediaBrowser.Model/Updates/PackageVersionInfo.cs @@ -30,23 +30,25 @@ namespace MediaBrowser.Model.Updates /// The _version /// private Version _version; + /// /// Gets or sets the version. /// Had to make this an interpreted property since Protobuf can't handle Version /// /// The version. [IgnoreDataMember] - public Version version => _version ?? (_version = new Version(ValueOrDefault(versionStr, "0.0.0.1"))); - - /// - /// Values the or default. - /// - /// The STR. - /// The def. - /// System.String. - private static string ValueOrDefault(string str, string def) + public Version Version { - return string.IsNullOrEmpty(str) ? def : str; + get + { + if (_version == null) + { + var ver = versionStr; + _version = new Version(string.IsNullOrEmpty(ver) ? "0.0.0.1" : ver); + } + + return _version; + } } /// diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs index 19dce34d69..f8b8765802 100644 --- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -347,7 +348,7 @@ namespace MediaBrowser.Providers.Omdb CancellationToken = cancellationToken, BufferContent = true, EnableDefaultUserAgent = true - }, "GET"); + }, HttpMethod.Get); } internal string GetDataFilePath(string imdbId) -- cgit v1.2.3 From 555459525515edf9f506465cefce0c6a6aca326d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 14 Jun 2019 18:49:57 +0200 Subject: Update deps --- Emby.Naming/Emby.Naming.csproj | 2 +- .../Emby.Server.Implementations.csproj | 7 +++---- Jellyfin.Server/Jellyfin.Server.csproj | 14 +++++++------- .../MediaBrowser.MediaEncoding.csproj | 2 +- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 2 +- jellyfin.ruleset | 4 ++++ 6 files changed, 17 insertions(+), 14 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 6e05eb7952..9e2a4950f9 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -23,7 +23,7 @@ - + diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d4e17c42ae..49015a07ec 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -31,10 +31,9 @@ - - + + - @@ -52,7 +51,7 @@ - + diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 81f145abff..641b3f1827 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -26,7 +26,7 @@ - + @@ -36,17 +36,17 @@ - - + + - - + + - - + + diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index e4757543ea..c0f92ac4a4 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -18,7 +18,7 @@ - + diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index cfbb85ea6b..5941ed4369 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -14,7 +14,7 @@ - + diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 0a60c8c7a2..1249a60c05 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -25,10 +25,14 @@ + + + + -- cgit v1.2.3 From 067200be83ff220c0aae501d138b0ab284370b60 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 8 Mar 2019 20:17:17 +0100 Subject: Remove usage of depricated 'WebRequest' Ref: https://docs.microsoft.com/en-us/dotnet/api/system.net.webrequest?view=netframework-4.7.2 --- .../HttpClientManager/HttpClientInfo.cs | 18 - .../HttpClientManager/HttpClientManager.cs | 432 +++++++-------------- MediaBrowser.Common/Net/HttpRequestOptions.cs | 1 - MediaBrowser.Common/Net/IHttpClient.cs | 11 + 4 files changed, 161 insertions(+), 301 deletions(-) delete mode 100644 Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs deleted file mode 100644 index f747b01b93..0000000000 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Net.Http; - -namespace Emby.Server.Implementations.HttpClientManager -{ - /// - /// Class HttpClientInfo - /// - public class HttpClientInfo - { - /// - /// Gets or sets the last timeout. - /// - /// The last timeout. - public DateTime LastTimeout { get; set; } - public HttpClient HttpClient { get; set; } - } -} diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 1bebdd1637..581d6bafd1 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Net; -using System.Net.Cache; +using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -55,12 +54,13 @@ namespace Emby.Server.Implementations.HttpClientManager { throw new ArgumentNullException(nameof(appPaths)); } + if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); } - _logger = loggerFactory.CreateLogger("HttpClient"); + _logger = loggerFactory.CreateLogger(nameof(HttpClientManager)); _fileSystem = fileSystem; _appPaths = appPaths; _defaultUserAgentFn = defaultUserAgentFn; @@ -74,27 +74,26 @@ namespace Emby.Server.Implementations.HttpClientManager /// DON'T dispose it after use. /// /// The HTTP clients. - private readonly ConcurrentDictionary _httpClients = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _httpClients = new ConcurrentDictionary(); /// /// Gets /// - /// The host. + /// The host. /// if set to true [enable HTTP compression]. /// HttpClient. /// host - private HttpClientInfo GetHttpClient(string host, bool enableHttpCompression) + private HttpClient GetHttpClient(string url, bool enableHttpCompression) { - if (string.IsNullOrEmpty(host)) - { - throw new ArgumentNullException(nameof(host)); - } - - var key = host + enableHttpCompression; + var key = GetHostFromUrl(url) + enableHttpCompression; if (!_httpClients.TryGetValue(key, out var client)) { - client = new HttpClientInfo(); + + client = new HttpClient() + { + BaseAddress = new Uri(url) + }; _httpClients.TryAdd(key, client); } @@ -102,110 +101,87 @@ namespace Emby.Server.Implementations.HttpClientManager return client; } - private WebRequest GetRequest(HttpRequestOptions options, string method) + private HttpRequestMessage GetRequestMessage(HttpRequestOptions options, HttpMethod method) { string url = options.Url; - var uriAddress = new Uri(url); string userInfo = uriAddress.UserInfo; if (!string.IsNullOrWhiteSpace(userInfo)) { - _logger.LogInformation("Found userInfo in url: {0} ... url: {1}", userInfo, url); + _logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url); url = url.Replace(userInfo + "@", string.Empty); } - var request = WebRequest.Create(url); + var request = new HttpRequestMessage(method, url); - if (request is HttpWebRequest httpWebRequest) - { - AddRequestHeaders(httpWebRequest, options); + AddRequestHeaders(request, options); - if (options.EnableHttpCompression) + if (options.EnableHttpCompression) + { + if (options.DecompressionMethod.HasValue + && options.DecompressionMethod.Value == CompressionMethod.Gzip) { - httpWebRequest.AutomaticDecompression = DecompressionMethods.Deflate; - if (options.DecompressionMethod.HasValue - && options.DecompressionMethod.Value == CompressionMethod.Gzip) - { - httpWebRequest.AutomaticDecompression = DecompressionMethods.GZip; - } + request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" }); } else { - httpWebRequest.AutomaticDecompression = DecompressionMethods.None; + request.Headers.Add(HeaderNames.AcceptEncoding, "deflate"); } + } - httpWebRequest.KeepAlive = options.EnableKeepAlive; + if (options.EnableKeepAlive) + { + request.Headers.Add(HeaderNames.Connection, "Keep-Alive"); + } - if (!string.IsNullOrEmpty(options.Host)) - { - httpWebRequest.Host = options.Host; - } + if (!string.IsNullOrEmpty(options.Host)) + { + request.Headers.Add(HeaderNames.Host, options.Host); + } - if (!string.IsNullOrEmpty(options.Referer)) - { - httpWebRequest.Referer = options.Referer; - } + if (!string.IsNullOrEmpty(options.Referer)) + { + request.Headers.Add(HeaderNames.Referer, options.Referer); } - request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); + //request.Headers.Add(HeaderNames.CacheControl, "no-cache"); - request.Method = method; - request.Timeout = options.TimeoutMs; + //request.Headers.Add(HeaderNames., options.TimeoutMs; + /* if (!string.IsNullOrWhiteSpace(userInfo)) { var parts = userInfo.Split(':'); if (parts.Length == 2) { - request.Credentials = GetCredential(url, parts[0], parts[1]); - // TODO: .net core ?? - request.PreAuthenticate = true; + request.Headers.Add(HeaderNames., GetCredential(url, parts[0], parts[1]); } } + */ return request; } - private static CredentialCache GetCredential(string url, string username, string password) - { - //ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3; - var credentialCache = new CredentialCache(); - credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password)); - return credentialCache; - } - - private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options) + private void AddRequestHeaders(HttpRequestMessage request, HttpRequestOptions options) { var hasUserAgent = false; foreach (var header in options.RequestHeaders) { - if (string.Equals(header.Key, HeaderNames.Accept, StringComparison.OrdinalIgnoreCase)) - { - request.Accept = header.Value; - } - else if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) { - SetUserAgent(request, header.Value); hasUserAgent = true; } - else - { - request.Headers.Set(header.Key, header.Value); - } + + request.Headers.Add(header.Key, header.Value); } if (!hasUserAgent && options.EnableDefaultUserAgent) { - SetUserAgent(request, _defaultUserAgentFn()); + request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn()); } } - private static void SetUserAgent(HttpWebRequest request, string userAgent) - { - request.UserAgent = userAgent; - } - /// /// Gets the response internal. /// @@ -213,7 +189,7 @@ namespace Emby.Server.Implementations.HttpClientManager /// Task{HttpResponseInfo}. public Task GetResponse(HttpRequestOptions options) { - return SendAsync(options, "GET"); + return SendAsync(options, HttpMethod.Get); } /// @@ -235,7 +211,21 @@ namespace Emby.Server.Implementations.HttpClientManager /// Task{HttpResponseInfo}. /// /// - public async Task SendAsync(HttpRequestOptions options, string httpMethod) + public Task SendAsync(HttpRequestOptions options, string httpMethod) + { + var httpMethod2 = GetHttpMethod(httpMethod); + return SendAsync(options, httpMethod2); + } + + /// + /// send as an asynchronous operation. + /// + /// The options. + /// The HTTP method. + /// Task{HttpResponseInfo}. + /// + /// + public async Task SendAsync(HttpRequestOptions options, HttpMethod httpMethod) { if (options.CacheMode == CacheMode.None) { @@ -263,6 +253,40 @@ namespace Emby.Server.Implementations.HttpClientManager return response; } + private HttpMethod GetHttpMethod(string httpMethod) + { + if (httpMethod.Equals("DELETE", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Delete; + } + else if (httpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Get; + } + else if (httpMethod.Equals("HEAD", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Head; + } + else if (httpMethod.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Options; + } + else if (httpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Post; + } + else if (httpMethod.Equals("PUT", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Put; + } + else if (httpMethod.Equals("TRACE", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Trace; + } + + throw new ArgumentException("Invalid HTTP method", nameof(httpMethod)); + } + private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url) { if (File.Exists(responseCachePath) @@ -294,31 +318,23 @@ namespace Emby.Server.Implementations.HttpClientManager } } - private async Task SendAsyncInternal(HttpRequestOptions options, string httpMethod) + private async Task SendAsyncInternal(HttpRequestOptions options, HttpMethod httpMethod) { ValidateParams(options); options.CancellationToken.ThrowIfCancellationRequested(); - var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); - - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds) - { - throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url)) - { - IsTimedOut = true - }; - } + var client = GetHttpClient(options.Url, options.EnableHttpCompression); - var httpWebRequest = GetRequest(options, httpMethod); + var httpWebRequest = GetRequestMessage(options, httpMethod); if (options.RequestContentBytes != null || !string.IsNullOrEmpty(options.RequestContent) || - string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase)) + httpMethod == HttpMethod.Post) { try { - var bytes = options.RequestContentBytes ?? Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty); + httpWebRequest.Content = new StringContent(Encoding.UTF8.GetString(options.RequestContentBytes) ?? options.RequestContent ?? string.Empty); var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded"; @@ -327,8 +343,8 @@ namespace Emby.Server.Implementations.HttpClientManager contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\""; } - httpWebRequest.ContentType = contentType; - (await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length); + httpWebRequest.Headers.Add(HeaderNames.ContentType, contentType); + await client.SendAsync(httpWebRequest).ConfigureAwait(false); } catch (Exception ex) { @@ -341,68 +357,45 @@ namespace Emby.Server.Implementations.HttpClientManager await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); } - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds) - { - options.ResourcePool?.Release(); - - throw new HttpException($"Connection to {options.Url} timed out") { IsTimedOut = true }; - } - if (options.LogRequest) { - if (options.LogRequestAsDebug) - { - _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); - } - else - { - _logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); - } + _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToString(), options.Url); } try { options.CancellationToken.ThrowIfCancellationRequested(); - if (!options.BufferContent) + /*if (!options.BufferContent) { - var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false); - - var httpResponse = (HttpWebResponse)response; + var response = await client.HttpClient.SendAsync(httpWebRequest).ConfigureAwait(false); - EnsureSuccessStatusCode(client, httpResponse, options); + await EnsureSuccessStatusCode(client, response, options).ConfigureAwait(false); options.CancellationToken.ThrowIfCancellationRequested(); - return GetResponseInfo(httpResponse, httpResponse.GetResponseStream(), GetContentLength(httpResponse), httpResponse); - } + return GetResponseInfo(response, await response.Content.ReadAsStreamAsync().ConfigureAwait(false), response.Content.Headers.ContentLength, response); + }*/ - using (var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false)) + using (var response = await client.SendAsync(httpWebRequest).ConfigureAwait(false)) { - var httpResponse = (HttpWebResponse)response; - - EnsureSuccessStatusCode(client, httpResponse, options); + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); options.CancellationToken.ThrowIfCancellationRequested(); - using (var stream = httpResponse.GetResponseStream()) + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; - return GetResponseInfo(httpResponse, memoryStream, memoryStream.Length, null); + return GetResponseInfo(response, memoryStream, memoryStream.Length, null); } } } catch (OperationCanceledException ex) { - throw GetCancellationException(options, client, options.CancellationToken, ex); - } - catch (Exception ex) - { - throw GetException(ex, options, client); + throw GetCancellationException(options, options.CancellationToken, ex); } finally { @@ -410,69 +403,54 @@ namespace Emby.Server.Implementations.HttpClientManager } } - private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, Stream content, long? contentLength, IDisposable disposable) + private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, Stream content, long? contentLength, IDisposable disposable) { var responseInfo = new HttpResponseInfo(disposable) { Content = content, StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.ContentType, + ContentType = httpResponse.Content.Headers.ContentType?.MediaType, ContentLength = contentLength, - ResponseUrl = httpResponse.ResponseUri.ToString() + ResponseUrl = httpResponse.Content.Headers.ContentLocation?.ToString() }; if (httpResponse.Headers != null) { - SetHeaders(httpResponse.Headers, responseInfo); + SetHeaders(httpResponse.Content.Headers, responseInfo); } return responseInfo; } - private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, string tempFile, long? contentLength) + private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, string tempFile, long? contentLength) { var responseInfo = new HttpResponseInfo { TempFilePath = tempFile, StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.ContentType, + ContentType = httpResponse.Content.Headers.ContentType?.MediaType, ContentLength = contentLength }; if (httpResponse.Headers != null) { - SetHeaders(httpResponse.Headers, responseInfo); + SetHeaders(httpResponse.Content.Headers, responseInfo); } return responseInfo; } - private static void SetHeaders(WebHeaderCollection headers, HttpResponseInfo responseInfo) + private static void SetHeaders(HttpContentHeaders headers, HttpResponseInfo responseInfo) { - foreach (var key in headers.AllKeys) + foreach (var key in headers) { - responseInfo.Headers[key] = headers[key]; + responseInfo.Headers[key.Key] = string.Join(", ", key.Value); } } public Task Post(HttpRequestOptions options) { - return SendAsync(options, "POST"); - } - - /// - /// Performs a POST request - /// - /// The options. - /// Params to add to the POST data. - /// stream on success, null on failure - public async Task Post(HttpRequestOptions options, Dictionary postData) - { - options.SetPostData(postData); - - var response = await Post(options).ConfigureAwait(false); - - return response.Content; + return SendAsync(options, HttpMethod.Post); } /// @@ -482,9 +460,10 @@ namespace Emby.Server.Implementations.HttpClientManager /// Task{System.String}. public async Task GetTempFile(HttpRequestOptions options) { - var response = await GetTempFileResponse(options).ConfigureAwait(false); - - return response.TempFilePath; + using (var response = await GetTempFileResponse(options).ConfigureAwait(false)) + { + return response.TempFilePath; + } } public async Task GetTempFileResponse(HttpRequestOptions options) @@ -502,7 +481,7 @@ namespace Emby.Server.Implementations.HttpClientManager options.CancellationToken.ThrowIfCancellationRequested(); - var httpWebRequest = GetRequest(options, "GET"); + var httpWebRequest = GetRequestMessage(options, HttpMethod.Get); if (options.ResourcePool != null) { @@ -513,33 +492,22 @@ namespace Emby.Server.Implementations.HttpClientManager if (options.LogRequest) { - if (options.LogRequestAsDebug) - { - _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url); - } - else - { - _logger.LogInformation("HttpClientManager.GetTempFileResponse url: {0}", options.Url); - } + _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url); } - var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); + var client = GetHttpClient(options.Url, options.EnableHttpCompression); try { options.CancellationToken.ThrowIfCancellationRequested(); - using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) + using (var response = (await client.SendAsync(httpWebRequest).ConfigureAwait(false))) { - var httpResponse = (HttpWebResponse)response; - - EnsureSuccessStatusCode(client, httpResponse, options); + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); options.CancellationToken.ThrowIfCancellationRequested(); - var contentLength = GetContentLength(httpResponse); - - using (var stream = httpResponse.GetResponseStream()) + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) { await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); @@ -547,13 +515,18 @@ namespace Emby.Server.Implementations.HttpClientManager options.Progress.Report(100); - return GetResponseInfo(httpResponse, tempFile, contentLength); + var contentLength = response.Content.Headers.ContentLength; + return GetResponseInfo(response, tempFile, contentLength); } } catch (Exception ex) { - DeleteTempFile(tempFile); - throw GetException(ex, options, client); + if (File.Exists(tempFile)) + { + File.Delete(tempFile); + } + + throw GetException(ex, options); } finally { @@ -561,21 +534,7 @@ namespace Emby.Server.Implementations.HttpClientManager } } - private static long? GetContentLength(HttpWebResponse response) - { - var length = response.ContentLength; - - if (length == 0) - { - return null; - } - - return length; - } - - protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - private Exception GetException(Exception ex, HttpRequestOptions options, HttpClientInfo client) + private Exception GetException(Exception ex, HttpRequestOptions options) { if (ex is HttpException) { @@ -599,11 +558,6 @@ namespace Emby.Server.Implementations.HttpClientManager if (response != null) { exception.StatusCode = response.StatusCode; - - if ((int)response.StatusCode == 429) - { - client.LastTimeout = DateTime.UtcNow; - } } } @@ -624,7 +578,7 @@ namespace Emby.Server.Implementations.HttpClientManager if (operationCanceledException != null) { - return GetCancellationException(options, client, options.CancellationToken, operationCanceledException); + return GetCancellationException(options, options.CancellationToken, operationCanceledException); } if (options.LogErrors) @@ -635,18 +589,6 @@ namespace Emby.Server.Implementations.HttpClientManager return ex; } - private void DeleteTempFile(string file) - { - try - { - _fileSystem.DeleteFile(file); - } - catch (IOException) - { - // Might not have been created at all. No need to worry. - } - } - private void ValidateParams(HttpRequestOptions options) { if (string.IsNullOrEmpty(options.Url)) @@ -682,11 +624,10 @@ namespace Emby.Server.Implementations.HttpClientManager /// Throws the cancellation exception. /// /// The options. - /// The client. /// The cancellation token. /// The exception. /// Exception. - private Exception GetCancellationException(HttpRequestOptions options, HttpClientInfo client, CancellationToken cancellationToken, OperationCanceledException exception) + private Exception GetCancellationException(HttpRequestOptions options, CancellationToken cancellationToken, OperationCanceledException exception) { // If the HttpClient's timeout is reached, it will cancel the Task internally if (!cancellationToken.IsCancellationRequested) @@ -698,8 +639,6 @@ namespace Emby.Server.Implementations.HttpClientManager _logger.LogError(msg); } - client.LastTimeout = DateTime.UtcNow; - // Throw an HttpException so that the caller doesn't think it was cancelled by user code return new HttpException(msg, exception) { @@ -710,91 +649,20 @@ namespace Emby.Server.Implementations.HttpClientManager return exception; } - private void EnsureSuccessStatusCode(HttpClientInfo client, HttpWebResponse response, HttpRequestOptions options) + private async Task EnsureSuccessStatusCode(HttpResponseMessage response, HttpRequestOptions options) { - var statusCode = response.StatusCode; - - var isSuccessful = statusCode >= HttpStatusCode.OK && statusCode <= (HttpStatusCode)299; - - if (isSuccessful) + if (response.IsSuccessStatusCode) { return; } - if (options.LogErrorResponseBody) - { - try - { - using (var stream = response.GetResponseStream()) - { - if (stream != null) - { - using (var reader = new StreamReader(stream)) - { - var msg = reader.ReadToEnd(); - - _logger.LogError(msg); - } - } - } - } - catch - { + var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + _logger.LogError(msg); - } - } - - throw new HttpException(response.StatusDescription) + throw new HttpException(response.ReasonPhrase) { StatusCode = response.StatusCode }; } - - private static Task GetResponseAsync(WebRequest request, TimeSpan timeout) - { - var taskCompletion = new TaskCompletionSource(); - - var asyncTask = Task.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null); - - ThreadPool.RegisterWaitForSingleObject((asyncTask as IAsyncResult).AsyncWaitHandle, TimeoutCallback, request, timeout, true); - var callback = new TaskCallback { taskCompletion = taskCompletion }; - asyncTask.ContinueWith(callback.OnSuccess, TaskContinuationOptions.NotOnFaulted); - - // Handle errors - asyncTask.ContinueWith(callback.OnError, TaskContinuationOptions.OnlyOnFaulted); - - return taskCompletion.Task; - } - - private static void TimeoutCallback(object state, bool timedOut) - { - if (timedOut && state != null) - { - var request = (WebRequest)state; - request.Abort(); - } - } - - private class TaskCallback - { - public TaskCompletionSource taskCompletion; - - public void OnSuccess(Task task) - { - taskCompletion.TrySetResult(task.Result); - } - - public void OnError(Task task) - { - if (task.Exception == null) - { - taskCompletion.TrySetException(Enumerable.Empty()); - } - else - { - taskCompletion.TrySetException(task.Exception); - } - } - } } } diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index bea178517b..874383fedf 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -99,7 +99,6 @@ namespace MediaBrowser.Common.Net public bool EnableDefaultUserAgent { get; set; } public bool AppendCharsetToMimeType { get; set; } - public string DownloadFilePath { get; set; } private string GetHeaderValue(string name) { diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs index 5aaf7e0be6..d332ab2000 100644 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ b/MediaBrowser.Common/Net/IHttpClient.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading.Tasks; +using System.Net.Http; namespace MediaBrowser.Common.Net { @@ -23,6 +24,8 @@ namespace MediaBrowser.Common.Net Task Get(HttpRequestOptions options); /// + /// Warning: Depricated function, + /// use 'Task SendAsync(HttpRequestOptions options, HttpMethod httpMethod);' instead /// Sends the asynchronous. /// /// The options. @@ -30,6 +33,14 @@ namespace MediaBrowser.Common.Net /// Task{HttpResponseInfo}. Task SendAsync(HttpRequestOptions options, string httpMethod); + /// + /// Sends the asynchronous. + /// + /// The options. + /// The HTTP method. + /// Task{HttpResponseInfo}. + Task SendAsync(HttpRequestOptions options, HttpMethod httpMethod); + /// /// Posts the specified options. /// -- cgit v1.2.3 From 0a5550b13d3a32ae7158dafb38eca6ed94e17d60 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 8 Mar 2019 20:32:14 +0100 Subject: Remove more unused stuff --- Emby.Server.Implementations/ApplicationHost.cs | 11 ++++++----- .../HttpClientManager/HttpClientManager.cs | 18 ------------------ .../LiveTv/Listings/SchedulesDirect.cs | 10 +--------- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 2 -- .../LiveTv/TunerHosts/SharedHttpStream.cs | 6 ------ MediaBrowser.Common/Net/HttpRequestOptions.cs | 21 +-------------------- 6 files changed, 8 insertions(+), 60 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 5d5a63a635..02d661d15a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; @@ -1560,12 +1561,12 @@ namespace Emby.Server.Implementations LogErrorResponseBody = false, LogErrors = false, LogRequest = false, - TimeoutMs = 10000, BufferContent = false, CancellationToken = cancellationToken }).ConfigureAwait(false)) { - return GetWanApiUrl(response.ReadToEnd().Trim()); + string res = await response.ReadToEndAsync().ConfigureAwait(false); + return GetWanApiUrl(res.Trim()); } } catch (Exception ex) @@ -1717,15 +1718,15 @@ namespace Emby.Server.Implementations LogErrorResponseBody = false, LogErrors = LogPing, LogRequest = LogPing, - TimeoutMs = 5000, BufferContent = false, CancellationToken = cancellationToken - }, "POST").ConfigureAwait(false)) + }, HttpMethod.Post).ConfigureAwait(false)) + { using (var reader = new StreamReader(response.Content)) { - var result = reader.ReadToEnd(); + var result = await reader.ReadToEndAsync().ConfigureAwait(false); var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase); _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid); diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 581d6bafd1..b82d55d0e6 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -352,11 +352,6 @@ namespace Emby.Server.Implementations.HttpClientManager } } - if (options.ResourcePool != null) - { - await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); - } - if (options.LogRequest) { _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToString(), options.Url); @@ -397,10 +392,6 @@ namespace Emby.Server.Implementations.HttpClientManager { throw GetCancellationException(options, options.CancellationToken, ex); } - finally - { - options.ResourcePool?.Release(); - } } private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, Stream content, long? contentLength, IDisposable disposable) @@ -483,11 +474,6 @@ namespace Emby.Server.Implementations.HttpClientManager var httpWebRequest = GetRequestMessage(options, HttpMethod.Get); - if (options.ResourcePool != null) - { - await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); - } - options.Progress.Report(0); if (options.LogRequest) @@ -528,10 +514,6 @@ namespace Emby.Server.Implementations.HttpClientManager throw GetException(ex, options); } - finally - { - options.ResourcePool?.Release(); - } } private Exception GetException(Exception ex, HttpRequestOptions options) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 4137760d07..f3f7477180 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -96,8 +96,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings Url = ApiUrl + "/schedules", UserAgent = UserAgent, CancellationToken = cancellationToken, - // The data can be large so give it some extra time - TimeoutMs = 60000, LogErrorResponseBody = true, RequestContent = requestString }; @@ -115,9 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings Url = ApiUrl + "/programs", UserAgent = UserAgent, CancellationToken = cancellationToken, - LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 + LogErrorResponseBody = true }; httpOptions.RequestHeaders["token"] = token; @@ -483,8 +479,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings CancellationToken = cancellationToken, RequestContent = imageIdString, LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 }; try @@ -871,8 +865,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings UserAgent = UserAgent, CancellationToken = cancellationToken, LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 }; httpOptions.RequestHeaders["token"] = token; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 24b100eddc..761275f8f3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -138,7 +138,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { Url = string.Format("{0}/discover.json", GetApiUrl(info)), CancellationToken = cancellationToken, - TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(10).TotalMilliseconds), BufferContent = false }, "GET").ConfigureAwait(false)) @@ -191,7 +190,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { Url = string.Format("{0}/tuners.html", GetApiUrl(info)), CancellationToken = cancellationToken, - TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds), BufferContent = false })) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index d74cf3be2d..e8b34da0ca 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -47,13 +47,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts CancellationToken = CancellationToken.None, BufferContent = false, - // Increase a little bit - TimeoutMs = 30000, - EnableHttpCompression = false, - - LogResponse = true, - LogResponseHeaders = true }; foreach (var header in mediaSource.RequiredHttpHeaders) diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index 874383fedf..38e0ff0f55 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -28,18 +28,13 @@ namespace MediaBrowser.Common.Net get => GetHeaderValue(HeaderNames.Accept); set => RequestHeaders[HeaderNames.Accept] = value; } + /// /// Gets or sets the cancellation token. /// /// The cancellation token. public CancellationToken CancellationToken { get; set; } - /// - /// Gets or sets the resource pool. - /// - /// The resource pool. - public SemaphoreSlim ResourcePool { get; set; } - /// /// Gets or sets the user agent. /// @@ -86,8 +81,6 @@ namespace MediaBrowser.Common.Net public bool LogRequest { get; set; } public bool LogRequestAsDebug { get; set; } public bool LogErrors { get; set; } - public bool LogResponse { get; set; } - public bool LogResponseHeaders { get; set; } public bool LogErrorResponseBody { get; set; } public bool EnableKeepAlive { get; set; } @@ -95,7 +88,6 @@ namespace MediaBrowser.Common.Net public CacheMode CacheMode { get; set; } public TimeSpan CacheLength { get; set; } - public int TimeoutMs { get; set; } public bool EnableDefaultUserAgent { get; set; } public bool AppendCharsetToMimeType { get; set; } @@ -119,17 +111,6 @@ namespace MediaBrowser.Common.Net LogRequest = true; LogErrors = true; CacheMode = CacheMode.None; - - TimeoutMs = 20000; - } - - public void SetPostData(IDictionary values) - { - var strings = values.Keys.Select(key => string.Format("{0}={1}", key, values[key])); - var postContent = string.Join("&", strings.ToArray()); - - RequestContent = postContent; - RequestContentType = "application/x-www-form-urlencoded"; } } -- cgit v1.2.3 From cec22ad10daf7abef2f27f846e4022d5a35faccf Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 20 Feb 2019 14:26:49 +0100 Subject: Simplify db code --- .../Activity/ActivityRepository.cs | 5 +- .../Data/BaseSqliteRepository.cs | 239 ++----- .../Data/ManagedConnection.cs | 79 --- .../Data/SqliteDisplayPreferencesRepository.cs | 70 +- .../Data/SqliteExtensions.cs | 2 +- .../Data/SqliteItemRepository.cs | 733 ++++++++++----------- .../Data/SqliteUserDataRepository.cs | 76 +-- .../Data/SqliteUserRepository.cs | 96 ++- .../Security/AuthenticationRepository.cs | 216 +++--- 9 files changed, 589 insertions(+), 927 deletions(-) delete mode 100644 Emby.Server.Implementations/Data/ManagedConnection.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index aeed8b6f1a..495d969776 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.Activity } } - private void TryMigrate(ManagedConnection connection) + private void TryMigrate(SQLiteDatabaseConnection connection) { try { @@ -85,7 +85,6 @@ namespace Emby.Server.Implementations.Activity throw new ArgumentNullException(nameof(entry)); } - using (WriteLock.Write()) using (var connection = CreateConnection()) { connection.RunInTransaction(db => @@ -124,7 +123,6 @@ namespace Emby.Server.Implementations.Activity throw new ArgumentNullException(nameof(entry)); } - using (WriteLock.Write()) using (var connection = CreateConnection()) { connection.RunInTransaction(db => @@ -159,7 +157,6 @@ namespace Emby.Server.Implementations.Activity public QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit) { - using (WriteLock.Read()) using (var connection = CreateConnection(true)) { var commandText = BaseActivitySelectText; diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index fba81306b3..29edddb3a7 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SQLitePCL; using SQLitePCL.pretty; @@ -12,15 +13,12 @@ namespace Emby.Server.Implementations.Data public abstract class BaseSqliteRepository : IDisposable { protected string DbFilePath { get; set; } - protected ReaderWriterLockSlim WriteLock; protected ILogger Logger { get; private set; } protected BaseSqliteRepository(ILogger logger) { Logger = logger; - - WriteLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); } protected TransactionMode TransactionMode => TransactionMode.Deferred; @@ -31,130 +29,96 @@ namespace Emby.Server.Implementations.Data static BaseSqliteRepository() { - SQLite3.EnableSharedCache = false; - - int rc = raw.sqlite3_config(raw.SQLITE_CONFIG_MEMSTATUS, 0); - //CheckOk(rc); - - rc = raw.sqlite3_config(raw.SQLITE_CONFIG_MULTITHREAD, 1); - //rc = raw.sqlite3_config(raw.SQLITE_CONFIG_SINGLETHREAD, 1); - //rc = raw.sqlite3_config(raw.SQLITE_CONFIG_SERIALIZED, 1); - //CheckOk(rc); - - rc = raw.sqlite3_enable_shared_cache(1); - ThreadSafeMode = raw.sqlite3_threadsafe(); } private static bool _versionLogged; private string _defaultWal; - protected ManagedConnection _connection; - - protected virtual bool EnableSingleConnection => true; - protected ManagedConnection CreateConnection(bool isReadOnly = false) + protected SQLiteDatabaseConnection CreateConnection(bool isReadOnly = false) { - if (_connection != null) + if (!_versionLogged) { - return _connection; + _versionLogged = true; + Logger.LogInformation("Sqlite version: " + SQLite3.Version); + Logger.LogInformation("Sqlite compiler options: " + string.Join(",", SQLite3.CompilerOptions)); } - lock (WriteLock) + ConnectionFlags connectionFlags; + + if (isReadOnly) { - if (!_versionLogged) - { - _versionLogged = true; - Logger.LogInformation("Sqlite version: " + SQLite3.Version); - Logger.LogInformation("Sqlite compiler options: " + string.Join(",", SQLite3.CompilerOptions.ToArray())); - } + //Logger.LogInformation("Opening read connection"); + //connectionFlags = ConnectionFlags.Create; + connectionFlags = ConnectionFlags.ReadOnly; + } + else + { + //Logger.LogInformation("Opening write connection"); + connectionFlags = ConnectionFlags.Create; + connectionFlags |= ConnectionFlags.ReadWrite; + } + + connectionFlags |= ConnectionFlags.SharedCached; + connectionFlags |= ConnectionFlags.FullMutex; - ConnectionFlags connectionFlags; + var db = SQLite3.Open(DbFilePath, connectionFlags, null); - if (isReadOnly) + try + { + if (string.IsNullOrWhiteSpace(_defaultWal)) { - //Logger.LogInformation("Opening read connection"); - //connectionFlags = ConnectionFlags.ReadOnly; - connectionFlags = ConnectionFlags.Create; - connectionFlags |= ConnectionFlags.ReadWrite; + _defaultWal = db.Query("PRAGMA journal_mode").SelectScalarString().First(); + + Logger.LogInformation("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal); } - else + + var queries = new List + { + //"PRAGMA cache size=-10000" + //"PRAGMA read_uncommitted = true", + //"PRAGMA synchronous=Normal" + }; + + if (CacheSize.HasValue) { - //Logger.LogInformation("Opening write connection"); - connectionFlags = ConnectionFlags.Create; - connectionFlags |= ConnectionFlags.ReadWrite; + queries.Add("PRAGMA cache_size=" + CacheSize.Value.ToString(CultureInfo.InvariantCulture)); } - if (EnableSingleConnection) + if (EnableTempStoreMemory) { - connectionFlags |= ConnectionFlags.PrivateCache; + queries.Add("PRAGMA temp_store = memory"); } else { - connectionFlags |= ConnectionFlags.SharedCached; + queries.Add("PRAGMA temp_store = file"); } - connectionFlags |= ConnectionFlags.NoMutex; - - var db = SQLite3.Open(DbFilePath, connectionFlags, null); - - try + foreach (var query in queries) { - if (string.IsNullOrWhiteSpace(_defaultWal)) - { - _defaultWal = db.Query("PRAGMA journal_mode").SelectScalarString().First(); - - Logger.LogInformation("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal); - } - - var queries = new List - { - //"PRAGMA cache size=-10000" - //"PRAGMA read_uncommitted = true", - "PRAGMA synchronous=Normal" - }; - - if (CacheSize.HasValue) - { - queries.Add("PRAGMA cache_size=" + CacheSize.Value.ToString(CultureInfo.InvariantCulture)); - } - - if (EnableTempStoreMemory) - { - queries.Add("PRAGMA temp_store = memory"); - } - else - { - queries.Add("PRAGMA temp_store = file"); - } - - foreach (var query in queries) - { - db.Execute(query); - } + db.Execute(query); } - catch + } + catch + { + using (db) { - using (db) - { - } - - throw; } - _connection = new ManagedConnection(db, false); - - return _connection; + throw; } + + return db; } - public IStatement PrepareStatement(ManagedConnection connection, string sql) + public IStatement PrepareStatement(SQLiteDatabaseConnection connection, string sql) { return connection.PrepareStatement(sql); } - public IStatement PrepareStatementSafe(ManagedConnection connection, string sql) + public IStatement PrepareStatementSafe(SQLiteDatabaseConnection connection, string sql) { return connection.PrepareStatement(sql); } @@ -179,7 +143,7 @@ namespace Emby.Server.Implementations.Data return sql.Select(connection.PrepareStatement).ToList(); } - protected bool TableExists(ManagedConnection connection, string name) + protected bool TableExists(SQLiteDatabaseConnection connection, string name) { return connection.RunInTransaction(db => { @@ -199,7 +163,7 @@ namespace Emby.Server.Implementations.Data }, ReadTransactionMode); } - protected void RunDefaultInitialization(ManagedConnection db) + protected void RunDefaultInitialization(SQLiteDatabaseConnection db) { var queries = new List { @@ -243,7 +207,7 @@ namespace Emby.Server.Implementations.Data public void Dispose() { - _disposed = true; + Dispose(true); } @@ -255,42 +219,13 @@ namespace Emby.Server.Implementations.Data /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { - if (dispose) - { - DisposeConnection(); - } - } - - private void DisposeConnection() - { - try - { - lock (_disposeLock) - { - using (WriteLock.Write()) - { - if (_connection != null) - { - using (_connection) - { - _connection.Close(); - } - _connection = null; - } - - CloseConnection(); - } - } - } - catch (Exception ex) + if (_disposed) { - Logger.LogError(ex, "Error disposing database"); + return; } - } - protected virtual void CloseConnection() - { + _disposed = true; } protected List GetColumnNames(IDatabaseConnection connection, string table) @@ -320,60 +255,4 @@ namespace Emby.Server.Implementations.Data connection.Execute("alter table " + table + " add column " + columnName + " " + type + " NULL"); } } - - public static class ReaderWriterLockSlimExtensions - { - private sealed class ReadLockToken : IDisposable - { - private ReaderWriterLockSlim _sync; - public ReadLockToken(ReaderWriterLockSlim sync) - { - _sync = sync; - sync.EnterReadLock(); - } - public void Dispose() - { - if (_sync != null) - { - _sync.ExitReadLock(); - _sync = null; - } - } - } - private sealed class WriteLockToken : IDisposable - { - private ReaderWriterLockSlim _sync; - public WriteLockToken(ReaderWriterLockSlim sync) - { - _sync = sync; - sync.EnterWriteLock(); - } - public void Dispose() - { - if (_sync != null) - { - _sync.ExitWriteLock(); - _sync = null; - } - } - } - - public static IDisposable Read(this ReaderWriterLockSlim obj) - { - //if (BaseSqliteRepository.ThreadSafeMode > 0) - //{ - // return new DummyToken(); - //} - return new WriteLockToken(obj); - } - - public static IDisposable Write(this ReaderWriterLockSlim obj) - { - //if (BaseSqliteRepository.ThreadSafeMode > 0) - //{ - // return new DummyToken(); - //} - return new WriteLockToken(obj); - } - } } diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs deleted file mode 100644 index b8f1e581ab..0000000000 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Collections.Generic; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - public class ManagedConnection : IDisposable - { - private SQLiteDatabaseConnection db; - private readonly bool _closeOnDispose; - - public ManagedConnection(SQLiteDatabaseConnection db, bool closeOnDispose) - { - this.db = db; - _closeOnDispose = closeOnDispose; - } - - public IStatement PrepareStatement(string sql) - { - return db.PrepareStatement(sql); - } - - public IEnumerable PrepareAll(string sql) - { - return db.PrepareAll(sql); - } - - public void ExecuteAll(string sql) - { - db.ExecuteAll(sql); - } - - public void Execute(string sql, params object[] values) - { - db.Execute(sql, values); - } - - public void RunQueries(string[] sql) - { - db.RunQueries(sql); - } - - public void RunInTransaction(Action action, TransactionMode mode) - { - db.RunInTransaction(action, mode); - } - - public T RunInTransaction(Func action, TransactionMode mode) - { - return db.RunInTransaction(action, mode); - } - - public IEnumerable> Query(string sql) - { - return db.Query(sql); - } - - public IEnumerable> Query(string sql, params object[] values) - { - return db.Query(sql, values); - } - - public void Close() - { - using (db) - { - - } - } - - public void Dispose() - { - if (_closeOnDispose) - { - Close(); - } - } - } -} diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 47552806d4..86a7c1551f 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -98,15 +98,12 @@ namespace Emby.Server.Implementations.Data cancellationToken.ThrowIfCancellationRequested(); - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => - { - SaveDisplayPreferences(displayPreferences, userId, client, db); - }, TransactionMode); - } + SaveDisplayPreferences(displayPreferences, userId, client, db); + }, TransactionMode); } } @@ -142,18 +139,15 @@ namespace Emby.Server.Implementations.Data cancellationToken.ThrowIfCancellationRequested(); - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + foreach (var displayPreference in displayPreferences) { - foreach (var displayPreference in displayPreferences) - { - SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db); - } - }, TransactionMode); - } + SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db); + } + }, TransactionMode); } } @@ -174,27 +168,24 @@ namespace Emby.Server.Implementations.Data var guidId = displayPreferencesId.GetMD5(); - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client")) { - using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client")) - { - statement.TryBind("@id", guidId.ToGuidBlob()); - statement.TryBind("@userId", userId.ToGuidBlob()); - statement.TryBind("@client", client); - - foreach (var row in statement.ExecuteQuery()) - { - return Get(row); - } - } + statement.TryBind("@id", guidId.ToGuidBlob()); + statement.TryBind("@userId", userId.ToGuidBlob()); + statement.TryBind("@client", client); - return new DisplayPreferences + foreach (var row in statement.ExecuteQuery()) { - Id = guidId.ToString("N") - }; + return Get(row); + } } + + return new DisplayPreferences + { + Id = guidId.ToString("N") + }; } } @@ -208,18 +199,15 @@ namespace Emby.Server.Implementations.Data { var list = new List(); - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId")) { - using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId")) - { - statement.TryBind("@userId", userId.ToGuidBlob()); + statement.TryBind("@userId", userId.ToGuidBlob()); - foreach (var row in statement.ExecuteQuery()) - { - list.Add(Get(row)); - } + foreach (var row in statement.ExecuteQuery()) + { + list.Add(Get(row)); } } } diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index a486cb1a06..c0f27b25aa 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -141,7 +141,7 @@ namespace Emby.Server.Implementations.Data } } - public static void Attach(ManagedConnection db, string path, string alias) + public static void Attach(SQLiteDatabaseConnection db, string path, string alias) { var commandText = string.Format("attach @path as {0};", alias); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 8841a9a504..baa5b713a2 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -319,7 +319,7 @@ namespace Emby.Server.Implementations.Data connection.RunQueries(postQueries); } - userDataRepo.Initialize(WriteLock, _connection, userManager); + userDataRepo.Initialize(userManager); } private static readonly string[] _retriveItemColumns = @@ -551,21 +551,18 @@ namespace Emby.Server.Implementations.Data CheckDisposed(); - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + using (var saveImagesStatement = PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) { - using (var saveImagesStatement = PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) - { - saveImagesStatement.TryBind("@Id", item.Id.ToGuidBlob()); - saveImagesStatement.TryBind("@Images", SerializeImages(item)); + saveImagesStatement.TryBind("@Id", item.Id.ToGuidBlob()); + saveImagesStatement.TryBind("@Images", SerializeImages(item)); - saveImagesStatement.MoveNext(); - } - }, TransactionMode); - } + saveImagesStatement.MoveNext(); + } + }, TransactionMode); } } @@ -605,16 +602,12 @@ namespace Emby.Server.Implementations.Data tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags)); } - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => - { - SaveItemsInTranscation(db, tuples); - - }, TransactionMode); - } + SaveItemsInTranscation(db, tuples); + }, TransactionMode); } } @@ -1193,23 +1186,20 @@ namespace Emby.Server.Implementations.Data CheckDisposed(); - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + using (var statement = PrepareStatementSafe(connection, "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems where guid = @guid")) { - using (var statement = PrepareStatementSafe(connection, "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems where guid = @guid")) - { - statement.TryBind("@guid", id); + statement.TryBind("@guid", id); - foreach (var row in statement.ExecuteQuery()) - { - return GetItem(row, new InternalItemsQuery()); - } + foreach (var row in statement.ExecuteQuery()) + { + return GetItem(row, new InternalItemsQuery()); } - - return null; } } + + return null; } private bool TypeRequiresDeserialization(Type type) @@ -1909,24 +1899,21 @@ namespace Emby.Server.Implementations.Data { CheckDisposed(); - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + var list = new List(); + + using (var statement = PrepareStatementSafe(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc")) { - var list = new List(); + statement.TryBind("@ItemId", item.Id); - using (var statement = PrepareStatementSafe(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc")) + foreach (var row in statement.ExecuteQuery()) { - statement.TryBind("@ItemId", item.Id); - - foreach (var row in statement.ExecuteQuery()) - { - list.Add(GetChapter(row, item)); - } + list.Add(GetChapter(row, item)); } - - return list; } + + return list; } } @@ -1941,22 +1928,20 @@ namespace Emby.Server.Implementations.Data { CheckDisposed(); - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + using (var statement = PrepareStatementSafe(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex")) { - using (var statement = PrepareStatementSafe(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex")) - { - statement.TryBind("@ItemId", item.Id); - statement.TryBind("@ChapterIndex", index); + statement.TryBind("@ItemId", item.Id); + statement.TryBind("@ChapterIndex", index); - foreach (var row in statement.ExecuteQuery()) - { - return GetChapter(row, item); - } + foreach (var row in statement.ExecuteQuery()) + { + return GetChapter(row, item); } } } + return null; } @@ -2012,21 +1997,18 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(chapters)); } - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => - { - var idBlob = id.ToGuidBlob(); + var idBlob = id.ToGuidBlob(); // First delete chapters db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob); - InsertChapters(idBlob, chapters, db); + InsertChapters(idBlob, chapters, db); - }, TransactionMode); - } + }, TransactionMode); } } @@ -2551,29 +2533,25 @@ namespace Emby.Server.Implementations.Data commandText += " where " + string.Join(" AND ", whereClauses); } - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + using (var statement = PrepareStatementSafe(connection, commandText)) { - using (var statement = PrepareStatementSafe(connection, commandText)) + if (EnableJoinUserData(query)) { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } + statement.TryBind("@UserId", query.User.InternalId); + } - BindSimilarParams(query, statement); - BindSearchParams(query, statement); + BindSimilarParams(query, statement); + BindSearchParams(query, statement); - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); - var count = statement.ExecuteQuery().SelectScalarInt().First(); - LogQueryTime("GetCount", commandText, now); - return count; - } + var count = statement.ExecuteQuery().SelectScalarInt().First(); + LogQueryTime("GetCount", commandText, now); + return count; } - } } @@ -2624,67 +2602,64 @@ namespace Emby.Server.Implementations.Data } } - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) - { - var list = new List(); + var list = new List(); - using (var statement = PrepareStatementSafe(connection, commandText)) + using (var statement = PrepareStatementSafe(connection, commandText)) + { + if (EnableJoinUserData(query)) { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } + statement.TryBind("@UserId", query.User.InternalId); + } - BindSimilarParams(query, statement); - BindSearchParams(query, statement); + BindSimilarParams(query, statement); + BindSearchParams(query, statement); - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); - var hasEpisodeAttributes = HasEpisodeAttributes(query); - var hasServiceName = HasServiceName(query); - var hasProgramAttributes = HasProgramAttributes(query); - var hasStartDate = HasStartDate(query); - var hasTrailerTypes = HasTrailerTypes(query); - var hasArtistFields = HasArtistFields(query); - var hasSeriesFields = HasSeriesFields(query); + var hasEpisodeAttributes = HasEpisodeAttributes(query); + var hasServiceName = HasServiceName(query); + var hasProgramAttributes = HasProgramAttributes(query); + var hasStartDate = HasStartDate(query); + var hasTrailerTypes = HasTrailerTypes(query); + var hasArtistFields = HasArtistFields(query); + var hasSeriesFields = HasSeriesFields(query); - foreach (var row in statement.ExecuteQuery()) + foreach (var row in statement.ExecuteQuery()) + { + var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); + if (item != null) { - var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); - if (item != null) - { - list.Add(item); - } + list.Add(item); } } + } - // Hack for right now since we currently don't support filtering out these duplicates within a query - if (query.EnableGroupByMetadataKey) + // Hack for right now since we currently don't support filtering out these duplicates within a query + if (query.EnableGroupByMetadataKey) + { + var limit = query.Limit ?? int.MaxValue; + limit -= 4; + var newList = new List(); + + foreach (var item in list) { - var limit = query.Limit ?? int.MaxValue; - limit -= 4; - var newList = new List(); + AddItem(newList, item); - foreach (var item in list) + if (newList.Count >= limit) { - AddItem(newList, item); - - if (newList.Count >= limit) - { - break; - } + break; } - - list = newList; } - LogQueryTime("GetItemList", commandText, now); - - return list; + list = newList; } + + LogQueryTime("GetItemList", commandText, now); + + return list; } } @@ -2845,75 +2820,72 @@ namespace Emby.Server.Implementations.Data statementTexts.Add(commandText); } - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + return connection.RunInTransaction(db => { - return connection.RunInTransaction(db => - { - var result = new QueryResult(); - var statements = PrepareAllSafe(db, statementTexts); + var result = new QueryResult(); + var statements = PrepareAllSafe(db, statementTexts); - if (!isReturningZeroItems) + if (!isReturningZeroItems) + { + using (var statement = statements[0]) { - using (var statement = statements[0]) + if (EnableJoinUserData(query)) { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } + statement.TryBind("@UserId", query.User.InternalId); + } - BindSimilarParams(query, statement); - BindSearchParams(query, statement); + BindSimilarParams(query, statement); + BindSearchParams(query, statement); // Running this again will bind the params GetWhereClauses(query, statement); - var hasEpisodeAttributes = HasEpisodeAttributes(query); - var hasServiceName = HasServiceName(query); - var hasProgramAttributes = HasProgramAttributes(query); - var hasStartDate = HasStartDate(query); - var hasTrailerTypes = HasTrailerTypes(query); - var hasArtistFields = HasArtistFields(query); - var hasSeriesFields = HasSeriesFields(query); + var hasEpisodeAttributes = HasEpisodeAttributes(query); + var hasServiceName = HasServiceName(query); + var hasProgramAttributes = HasProgramAttributes(query); + var hasStartDate = HasStartDate(query); + var hasTrailerTypes = HasTrailerTypes(query); + var hasArtistFields = HasArtistFields(query); + var hasSeriesFields = HasSeriesFields(query); - foreach (var row in statement.ExecuteQuery()) + foreach (var row in statement.ExecuteQuery()) + { + var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); + if (item != null) { - var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); - if (item != null) - { - list.Add(item); - } + list.Add(item); } } } + } - if (query.EnableTotalRecordCount) + if (query.EnableTotalRecordCount) + { + using (var statement = statements[statements.Count - 1]) { - using (var statement = statements[statements.Count - 1]) + if (EnableJoinUserData(query)) { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } + statement.TryBind("@UserId", query.User.InternalId); + } - BindSimilarParams(query, statement); - BindSearchParams(query, statement); + BindSimilarParams(query, statement); + BindSearchParams(query, statement); // Running this again will bind the params GetWhereClauses(query, statement); - result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); - } + result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } + } - LogQueryTime("GetItems", commandText, now); + LogQueryTime("GetItems", commandText, now); - result.Items = list.ToArray(); - return result; + result.Items = list.ToArray(); + return result; - }, ReadTransactionMode); - } + }, ReadTransactionMode); } } @@ -3080,35 +3052,32 @@ namespace Emby.Server.Implementations.Data } } - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) - { - var list = new List(); + var list = new List(); - using (var statement = PrepareStatementSafe(connection, commandText)) + using (var statement = PrepareStatementSafe(connection, commandText)) + { + if (EnableJoinUserData(query)) { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } + statement.TryBind("@UserId", query.User.InternalId); + } - BindSimilarParams(query, statement); - BindSearchParams(query, statement); + BindSimilarParams(query, statement); + BindSearchParams(query, statement); - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); - foreach (var row in statement.ExecuteQuery()) - { - list.Add(row[0].ReadGuidFromBlob()); - } + foreach (var row in statement.ExecuteQuery()) + { + list.Add(row[0].ReadGuidFromBlob()); } + } - LogQueryTime("GetItemList", commandText, now); + LogQueryTime("GetItemList", commandText, now); - return list; - } + return list; } } @@ -3149,39 +3118,36 @@ namespace Emby.Server.Implementations.Data } } - using (WriteLock.Read()) + var list = new List>(); + using (var connection = CreateConnection(true)) { - var list = new List>(); - using (var connection = CreateConnection(true)) + using (var statement = PrepareStatementSafe(connection, commandText)) { - using (var statement = PrepareStatementSafe(connection, commandText)) + if (EnableJoinUserData(query)) { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } + statement.TryBind("@UserId", query.User.InternalId); + } - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); - foreach (var row in statement.ExecuteQuery()) - { - var id = row.GetGuid(0); - string path = null; + foreach (var row in statement.ExecuteQuery()) + { + var id = row.GetGuid(0); + string path = null; - if (!row.IsDBNull(1)) - { - path = row.GetString(1); - } - list.Add(new Tuple(id, path)); + if (!row.IsDBNull(1)) + { + path = row.GetString(1); } + list.Add(new Tuple(id, path)); } } + } - LogQueryTime("GetItemIdsWithPath", commandText, now); + LogQueryTime("GetItemIdsWithPath", commandText, now); - return list; - } + return list; } public QueryResult GetItemIds(InternalItemsQuery query) @@ -3265,64 +3231,61 @@ namespace Emby.Server.Implementations.Data statementTexts.Add(commandText); } - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + return connection.RunInTransaction(db => { - return connection.RunInTransaction(db => - { - var result = new QueryResult(); + var result = new QueryResult(); - var statements = PrepareAllSafe(db, statementTexts); + var statements = PrepareAllSafe(db, statementTexts); - if (!isReturningZeroItems) + if (!isReturningZeroItems) + { + using (var statement = statements[0]) { - using (var statement = statements[0]) + if (EnableJoinUserData(query)) { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } + statement.TryBind("@UserId", query.User.InternalId); + } - BindSimilarParams(query, statement); - BindSearchParams(query, statement); + BindSimilarParams(query, statement); + BindSearchParams(query, statement); // Running this again will bind the params GetWhereClauses(query, statement); - foreach (var row in statement.ExecuteQuery()) - { - list.Add(row[0].ReadGuidFromBlob()); - } + foreach (var row in statement.ExecuteQuery()) + { + list.Add(row[0].ReadGuidFromBlob()); } } + } - if (query.EnableTotalRecordCount) + if (query.EnableTotalRecordCount) + { + using (var statement = statements[statements.Count - 1]) { - using (var statement = statements[statements.Count - 1]) + if (EnableJoinUserData(query)) { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } + statement.TryBind("@UserId", query.User.InternalId); + } - BindSimilarParams(query, statement); - BindSearchParams(query, statement); + BindSimilarParams(query, statement); + BindSearchParams(query, statement); // Running this again will bind the params GetWhereClauses(query, statement); - result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); - } + result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } + } - LogQueryTime("GetItemIds", commandText, now); + LogQueryTime("GetItemIds", commandText, now); - result.Items = list.ToArray(); - return result; + result.Items = list.ToArray(); + return result; - }, ReadTransactionMode); - } + }, ReadTransactionMode); } } @@ -4899,14 +4862,12 @@ namespace Emby.Server.Implementations.Data private void UpdateInheritedTags(CancellationToken cancellationToken) { - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + connection.ExecuteAll(string.Join(";", new string[] { - connection.ExecuteAll(string.Join(";", new string[] - { "delete from itemvalues where type = 6", "insert into itemvalues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4", @@ -4916,10 +4877,9 @@ FROM AncestorIds LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId) where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4 " - })); + })); - }, TransactionMode); - } + }, TransactionMode); } } @@ -4965,13 +4925,11 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type CheckDisposed(); - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => - { - var idBlob = id.ToGuidBlob(); + var idBlob = id.ToGuidBlob(); // Delete people ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob); @@ -4990,8 +4948,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type // Delete the item ExecuteWithSingleParam(db, "delete from TypedBaseItems where guid=@Id", idBlob); - }, TransactionMode); - } + }, TransactionMode); } } @@ -5025,23 +4982,20 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type commandText += " order by ListOrder"; - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + var list = new List(); + using (var statement = PrepareStatementSafe(connection, commandText)) { - var list = new List(); - using (var statement = PrepareStatementSafe(connection, commandText)) - { - // Run this again to bind the params - GetPeopleWhereClauses(query, statement); + // Run this again to bind the params + GetPeopleWhereClauses(query, statement); - foreach (var row in statement.ExecuteQuery()) - { - list.Add(row.GetString(0)); - } + foreach (var row in statement.ExecuteQuery()) + { + list.Add(row.GetString(0)); } - return list; } + return list; } } @@ -5065,25 +5019,22 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type commandText += " order by ListOrder"; - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + var list = new List(); + + using (var statement = PrepareStatementSafe(connection, commandText)) { - var list = new List(); + // Run this again to bind the params + GetPeopleWhereClauses(query, statement); - using (var statement = PrepareStatementSafe(connection, commandText)) + foreach (var row in statement.ExecuteQuery()) { - // Run this again to bind the params - GetPeopleWhereClauses(query, statement); - - foreach (var row in statement.ExecuteQuery()) - { - list.Add(GetPerson(row)); - } + list.Add(GetPerson(row)); } - - return list; } + + return list; } } @@ -5294,27 +5245,24 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type commandText += " Group By CleanValue"; - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) - { - var list = new List(); + var list = new List(); - using (var statement = PrepareStatementSafe(connection, commandText)) + using (var statement = PrepareStatementSafe(connection, commandText)) + { + foreach (var row in statement.ExecuteQuery()) { - foreach (var row in statement.ExecuteQuery()) + if (!row.IsDBNull(0)) { - if (!row.IsDBNull(0)) - { - list.Add(row.GetString(0)); - } + list.Add(row.GetString(0)); } } + } - LogQueryTime("GetItemValueNames", commandText, now); + LogQueryTime("GetItemValueNames", commandText, now); - return list; - } + return list; } } @@ -5483,100 +5431,97 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statementTexts.Add(countText); } - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + return connection.RunInTransaction(db => { - return connection.RunInTransaction(db => - { - var list = new List<(BaseItem, ItemCounts)>(); - var result = new QueryResult<(BaseItem, ItemCounts)>(); + var list = new List<(BaseItem, ItemCounts)>(); + var result = new QueryResult<(BaseItem, ItemCounts)>(); - var statements = PrepareAllSafe(db, statementTexts); + var statements = PrepareAllSafe(db, statementTexts); - if (!isReturningZeroItems) + if (!isReturningZeroItems) + { + using (var statement = statements[0]) { - using (var statement = statements[0]) + statement.TryBind("@SelectType", returnType); + if (EnableJoinUserData(query)) { - statement.TryBind("@SelectType", returnType); - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } + statement.TryBind("@UserId", query.User.InternalId); + } - if (typeSubQuery != null) - { - GetWhereClauses(typeSubQuery, null); - } - BindSimilarParams(query, statement); - BindSearchParams(query, statement); - GetWhereClauses(innerQuery, statement); - GetWhereClauses(outerQuery, statement); - - var hasEpisodeAttributes = HasEpisodeAttributes(query); - var hasProgramAttributes = HasProgramAttributes(query); - var hasServiceName = HasServiceName(query); - var hasStartDate = HasStartDate(query); - var hasTrailerTypes = HasTrailerTypes(query); - var hasArtistFields = HasArtistFields(query); - var hasSeriesFields = HasSeriesFields(query); - - foreach (var row in statement.ExecuteQuery()) + if (typeSubQuery != null) + { + GetWhereClauses(typeSubQuery, null); + } + BindSimilarParams(query, statement); + BindSearchParams(query, statement); + GetWhereClauses(innerQuery, statement); + GetWhereClauses(outerQuery, statement); + + var hasEpisodeAttributes = HasEpisodeAttributes(query); + var hasProgramAttributes = HasProgramAttributes(query); + var hasServiceName = HasServiceName(query); + var hasStartDate = HasStartDate(query); + var hasTrailerTypes = HasTrailerTypes(query); + var hasArtistFields = HasArtistFields(query); + var hasSeriesFields = HasSeriesFields(query); + + foreach (var row in statement.ExecuteQuery()) + { + var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); + if (item != null) { - var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); - if (item != null) - { - var countStartColumn = columns.Count - 1; + var countStartColumn = columns.Count - 1; - list.Add((item, GetItemCounts(row, countStartColumn, typesToCount))); - } + list.Add((item, GetItemCounts(row, countStartColumn, typesToCount))); } - - LogQueryTime("GetItemValues", commandText, now); } + + LogQueryTime("GetItemValues", commandText, now); } + } - if (query.EnableTotalRecordCount) - { - commandText = "select " - + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) - + GetFromText() - + GetJoinUserDataText(query) - + whereText; + if (query.EnableTotalRecordCount) + { + commandText = "select " + + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + + GetFromText() + + GetJoinUserDataText(query) + + whereText; - using (var statement = statements[statements.Count - 1]) + using (var statement = statements[statements.Count - 1]) + { + statement.TryBind("@SelectType", returnType); + if (EnableJoinUserData(query)) { - statement.TryBind("@SelectType", returnType); - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } + statement.TryBind("@UserId", query.User.InternalId); + } - if (typeSubQuery != null) - { - GetWhereClauses(typeSubQuery, null); - } - BindSimilarParams(query, statement); - BindSearchParams(query, statement); - GetWhereClauses(innerQuery, statement); - GetWhereClauses(outerQuery, statement); + if (typeSubQuery != null) + { + GetWhereClauses(typeSubQuery, null); + } + BindSimilarParams(query, statement); + BindSearchParams(query, statement); + GetWhereClauses(innerQuery, statement); + GetWhereClauses(outerQuery, statement); - result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); + result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); - LogQueryTime("GetItemValues", commandText, now); - } + LogQueryTime("GetItemValues", commandText, now); } + } - if (result.TotalRecordCount == 0) - { - result.TotalRecordCount = list.Count; - } - result.Items = list.ToArray(); + if (result.TotalRecordCount == 0) + { + result.TotalRecordCount = list.Count; + } + result.Items = list.ToArray(); - return result; + return result; - }, ReadTransactionMode); - } + }, ReadTransactionMode); } } @@ -5753,22 +5698,18 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type CheckDisposed(); - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => - { - var itemIdBlob = itemId.ToGuidBlob(); + var itemIdBlob = itemId.ToGuidBlob(); // First delete chapters db.Execute("delete from People where ItemId=@ItemId", itemIdBlob); - InsertPeople(itemIdBlob, people, db); - - }, TransactionMode); + InsertPeople(itemIdBlob, people, db); - } + }, TransactionMode); } } @@ -5874,34 +5815,31 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type cmdText += " order by StreamIndex ASC"; - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + var list = new List(); + + using (var statement = PrepareStatementSafe(connection, cmdText)) { - var list = new List(); + statement.TryBind("@ItemId", query.ItemId.ToGuidBlob()); - using (var statement = PrepareStatementSafe(connection, cmdText)) + if (query.Type.HasValue) { - statement.TryBind("@ItemId", query.ItemId.ToGuidBlob()); - - if (query.Type.HasValue) - { - statement.TryBind("@StreamType", query.Type.Value.ToString()); - } - - if (query.Index.HasValue) - { - statement.TryBind("@StreamIndex", query.Index.Value); - } + statement.TryBind("@StreamType", query.Type.Value.ToString()); + } - foreach (var row in statement.ExecuteQuery()) - { - list.Add(GetMediaStream(row)); - } + if (query.Index.HasValue) + { + statement.TryBind("@StreamIndex", query.Index.Value); } - return list; + foreach (var row in statement.ExecuteQuery()) + { + list.Add(GetMediaStream(row)); + } } + + return list; } } @@ -5921,21 +5859,18 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type cancellationToken.ThrowIfCancellationRequested(); - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => - { - var itemIdBlob = id.ToGuidBlob(); + var itemIdBlob = id.ToGuidBlob(); // First delete chapters db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob); - InsertMediaStreams(itemIdBlob, streams, db); + InsertMediaStreams(itemIdBlob, streams, db); - }, TransactionMode); - } + }, TransactionMode); } } diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 4109b7ad1f..f544dfac4c 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -7,7 +7,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; using SQLitePCL.pretty; @@ -33,13 +32,8 @@ namespace Emby.Server.Implementations.Data /// Opens the connection to the database /// /// Task. - public void Initialize(ReaderWriterLockSlim writeLock, ManagedConnection managedConnection, IUserManager userManager) + public void Initialize(IUserManager userManager) { - _connection = managedConnection; - - WriteLock.Dispose(); - WriteLock = writeLock; - using (var connection = CreateConnection()) { var userDatasTableExists = TableExists(connection, "UserDatas"); @@ -178,15 +172,12 @@ namespace Emby.Server.Implementations.Data { cancellationToken.ThrowIfCancellationRequested(); - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => - { - SaveUserData(db, internalUserId, key, userData); - }, TransactionMode); - } + SaveUserData(db, internalUserId, key, userData); + }, TransactionMode); } } @@ -249,18 +240,15 @@ namespace Emby.Server.Implementations.Data { cancellationToken.ThrowIfCancellationRequested(); - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + foreach (var userItemData in userDataList) { - foreach (var userItemData in userDataList) - { - SaveUserData(db, internalUserId, userItemData.Key, userItemData); - } - }, TransactionMode); - } + SaveUserData(db, internalUserId, userItemData.Key, userItemData); + } + }, TransactionMode); } } @@ -281,28 +269,26 @@ namespace Emby.Server.Implementations.Data { throw new ArgumentNullException(nameof(internalUserId)); } + if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); } - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId")) { - using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId")) - { - statement.TryBind("@UserId", internalUserId); - statement.TryBind("@Key", key); + statement.TryBind("@UserId", internalUserId); + statement.TryBind("@Key", key); - foreach (var row in statement.ExecuteQuery()) - { - return ReadRow(row); - } + foreach (var row in statement.ExecuteQuery()) + { + return ReadRow(row); } - - return null; } + + return null; } } @@ -335,18 +321,15 @@ namespace Emby.Server.Implementations.Data var list = new List(); - using (WriteLock.Read()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId")) { - using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId")) - { - statement.TryBind("@UserId", internalUserId); + statement.TryBind("@UserId", internalUserId); - foreach (var row in statement.ExecuteQuery()) - { - list.Add(ReadRow(row)); - } + foreach (var row in statement.ExecuteQuery()) + { + list.Add(ReadRow(row)); } } } @@ -397,10 +380,5 @@ namespace Emby.Server.Implementations.Data { // handled by library database } - - protected override void CloseConnection() - { - // handled by library database - } } } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index 5957b29031..785452ad39 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.Data } } - private void TryMigrateToLocalUsersTable(ManagedConnection connection) + private void TryMigrateToLocalUsersTable(SQLiteDatabaseConnection connection) { try { @@ -119,31 +119,28 @@ namespace Emby.Server.Implementations.Data var serialized = _jsonSerializer.SerializeToBytes(user); - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) { - using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) - { - statement.TryBind("@guid", user.Id.ToGuidBlob()); - statement.TryBind("@data", serialized); + statement.TryBind("@guid", user.Id.ToGuidBlob()); + statement.TryBind("@data", serialized); - statement.MoveNext(); - } + statement.MoveNext(); + } - var createdUser = GetUser(user.Id, false); + var createdUser = GetUser(user.Id, connection); - if (createdUser == null) - { - throw new ApplicationException("created user should never be null"); - } + if (createdUser == null) + { + throw new ApplicationException("created user should never be null"); + } - user.InternalId = createdUser.InternalId; + user.InternalId = createdUser.InternalId; - }, TransactionMode); - } + }, TransactionMode); } } @@ -156,39 +153,30 @@ namespace Emby.Server.Implementations.Data var serialized = _jsonSerializer.SerializeToBytes(user); - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } + statement.TryBind("@InternalId", user.InternalId); + statement.TryBind("@data", serialized); + statement.MoveNext(); + } - }, TransactionMode); - } + }, TransactionMode); } } - private User GetUser(Guid guid, bool openLock) + private User GetUser(Guid guid, SQLiteDatabaseConnection connection) { - using (openLock ? WriteLock.Read() : null) + using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) { - using (var connection = CreateConnection(true)) - { - using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) - { - statement.TryBind("@guid", guid); + statement.TryBind("@guid", guid); - foreach (var row in statement.ExecuteQuery()) - { - return GetUser(row); - } - } + foreach (var row in statement.ExecuteQuery()) + { + return GetUser(row); } } @@ -218,14 +206,11 @@ namespace Emby.Server.Implementations.Data { var list = new List(); - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) - { - list.Add(GetUser(row)); - } + list.Add(GetUser(row)); } } @@ -245,19 +230,16 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(user)); } - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) { - using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) - { - statement.TryBind("@id", user.InternalId); - statement.MoveNext(); - } - }, TransactionMode); - } + statement.TryBind("@id", user.InternalId); + statement.MoveNext(); + } + }, TransactionMode); } } } diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 29b8dfd3d7..abc23239e2 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.Security } } - private void TryMigrate(ManagedConnection connection, bool tableNewlyCreated) + private void TryMigrate(SQLiteDatabaseConnection connection, bool tableNewlyCreated) { try { @@ -87,31 +87,28 @@ namespace Emby.Server.Implementations.Security throw new ArgumentNullException(nameof(info)); } - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + using (var statement = db.PrepareStatement("insert into Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) values (@AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @UserName, @IsActive, @DateCreated, @DateLastActivity)")) { - using (var statement = db.PrepareStatement("insert into Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) values (@AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @UserName, @IsActive, @DateCreated, @DateLastActivity)")) - { - statement.TryBind("@AccessToken", info.AccessToken); - - statement.TryBind("@DeviceId", info.DeviceId); - statement.TryBind("@AppName", info.AppName); - statement.TryBind("@AppVersion", info.AppVersion); - statement.TryBind("@DeviceName", info.DeviceName); - statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N"))); - statement.TryBind("@UserName", info.UserName); - statement.TryBind("@IsActive", true); - statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); - statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); - - statement.MoveNext(); - } - - }, TransactionMode); - } + statement.TryBind("@AccessToken", info.AccessToken); + + statement.TryBind("@DeviceId", info.DeviceId); + statement.TryBind("@AppName", info.AppName); + statement.TryBind("@AppVersion", info.AppVersion); + statement.TryBind("@DeviceName", info.DeviceName); + statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N"))); + statement.TryBind("@UserName", info.UserName); + statement.TryBind("@IsActive", true); + statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); + statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); + + statement.MoveNext(); + } + + }, TransactionMode); } } @@ -122,31 +119,28 @@ namespace Emby.Server.Implementations.Security throw new ArgumentNullException(nameof(info)); } - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + using (var statement = db.PrepareStatement("Update Tokens set AccessToken=@AccessToken, DeviceId=@DeviceId, AppName=@AppName, AppVersion=@AppVersion, DeviceName=@DeviceName, UserId=@UserId, UserName=@UserName, DateCreated=@DateCreated, DateLastActivity=@DateLastActivity where Id=@Id")) { - using (var statement = db.PrepareStatement("Update Tokens set AccessToken=@AccessToken, DeviceId=@DeviceId, AppName=@AppName, AppVersion=@AppVersion, DeviceName=@DeviceName, UserId=@UserId, UserName=@UserName, DateCreated=@DateCreated, DateLastActivity=@DateLastActivity where Id=@Id")) - { - statement.TryBind("@Id", info.Id); - - statement.TryBind("@AccessToken", info.AccessToken); - - statement.TryBind("@DeviceId", info.DeviceId); - statement.TryBind("@AppName", info.AppName); - statement.TryBind("@AppVersion", info.AppVersion); - statement.TryBind("@DeviceName", info.DeviceName); - statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N"))); - statement.TryBind("@UserName", info.UserName); - statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); - statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); - - statement.MoveNext(); - } - }, TransactionMode); - } + statement.TryBind("@Id", info.Id); + + statement.TryBind("@AccessToken", info.AccessToken); + + statement.TryBind("@DeviceId", info.DeviceId); + statement.TryBind("@AppName", info.AppName); + statement.TryBind("@AppVersion", info.AppVersion); + statement.TryBind("@DeviceName", info.DeviceName); + statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N"))); + statement.TryBind("@UserName", info.UserName); + statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); + statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); + + statement.MoveNext(); + } + }, TransactionMode); } } @@ -157,20 +151,17 @@ namespace Emby.Server.Implementations.Security throw new ArgumentNullException(nameof(info)); } - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + using (var statement = db.PrepareStatement("Delete from Tokens where Id=@Id")) { - using (var statement = db.PrepareStatement("Delete from Tokens where Id=@Id")) - { - statement.TryBind("@Id", info.Id); + statement.TryBind("@Id", info.Id); - statement.MoveNext(); - } - }, TransactionMode); - } + statement.MoveNext(); + } + }, TransactionMode); } } @@ -257,45 +248,42 @@ namespace Emby.Server.Implementations.Security var list = new List(); - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + return connection.RunInTransaction(db => { - return connection.RunInTransaction(db => - { - var result = new QueryResult(); + var result = new QueryResult(); - var statementTexts = new List(); - statementTexts.Add(commandText); - statementTexts.Add("select count (Id) from Tokens" + whereTextWithoutPaging); + var statementTexts = new List(); + statementTexts.Add(commandText); + statementTexts.Add("select count (Id) from Tokens" + whereTextWithoutPaging); - var statements = PrepareAllSafe(db, statementTexts) - .ToList(); + var statements = PrepareAllSafe(db, statementTexts) + .ToList(); - using (var statement = statements[0]) - { - BindAuthenticationQueryParams(query, statement); + using (var statement = statements[0]) + { + BindAuthenticationQueryParams(query, statement); - foreach (var row in statement.ExecuteQuery()) - { - list.Add(Get(row)); - } + foreach (var row in statement.ExecuteQuery()) + { + list.Add(Get(row)); + } - using (var totalCountStatement = statements[1]) - { - BindAuthenticationQueryParams(query, totalCountStatement); + using (var totalCountStatement = statements[1]) + { + BindAuthenticationQueryParams(query, totalCountStatement); - result.TotalRecordCount = totalCountStatement.ExecuteQuery() - .SelectScalarInt() - .First(); - } + result.TotalRecordCount = totalCountStatement.ExecuteQuery() + .SelectScalarInt() + .First(); } + } - result.Items = list.ToArray(); - return result; + result.Items = list.ToArray(); + return result; - }, ReadTransactionMode); - } + }, ReadTransactionMode); } } @@ -358,31 +346,28 @@ namespace Emby.Server.Implementations.Security public DeviceOptions GetDeviceOptions(string deviceId) { - using (WriteLock.Read()) + using (var connection = CreateConnection(true)) { - using (var connection = CreateConnection(true)) + return connection.RunInTransaction(db => { - return connection.RunInTransaction(db => + using (var statement = PrepareStatementSafe(db, "select CustomName from Devices where Id=@DeviceId")) { - using (var statement = PrepareStatementSafe(db, "select CustomName from Devices where Id=@DeviceId")) - { - statement.TryBind("@DeviceId", deviceId); + statement.TryBind("@DeviceId", deviceId); - var result = new DeviceOptions(); + var result = new DeviceOptions(); - foreach (var row in statement.ExecuteQuery()) + foreach (var row in statement.ExecuteQuery()) + { + if (row[0].SQLiteType != SQLiteType.Null) { - if (row[0].SQLiteType != SQLiteType.Null) - { - result.CustomName = row[0].ToString(); - } + result.CustomName = row[0].ToString(); } - - return result; } - }, ReadTransactionMode); - } + return result; + } + + }, ReadTransactionMode); } } @@ -393,30 +378,27 @@ namespace Emby.Server.Implementations.Security throw new ArgumentNullException(nameof(options)); } - using (WriteLock.Write()) + using (var connection = CreateConnection()) { - using (var connection = CreateConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + using (var statement = db.PrepareStatement("replace into devices (Id, CustomName, Capabilities) VALUES (@Id, @CustomName, (Select Capabilities from Devices where Id=@Id))")) { - using (var statement = db.PrepareStatement("replace into devices (Id, CustomName, Capabilities) VALUES (@Id, @CustomName, (Select Capabilities from Devices where Id=@Id))")) - { - statement.TryBind("@Id", deviceId); - - if (string.IsNullOrWhiteSpace(options.CustomName)) - { - statement.TryBindNull("@CustomName"); - } - else - { - statement.TryBind("@CustomName", options.CustomName); - } + statement.TryBind("@Id", deviceId); - statement.MoveNext(); + if (string.IsNullOrWhiteSpace(options.CustomName)) + { + statement.TryBindNull("@CustomName"); + } + else + { + statement.TryBind("@CustomName", options.CustomName); } - }, TransactionMode); - } + statement.MoveNext(); + } + + }, TransactionMode); } } } -- cgit v1.2.3 From c30ba14c1f9701638bbb47e81d3e7027cb778135 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 Feb 2019 18:30:13 +0100 Subject: Use a connection pool instead of creating new connections --- .../Activity/ActivityRepository.cs | 11 +- .../Data/BaseSqliteRepository.cs | 114 ++++++++++----------- .../Data/ManagedConnection.cs | 87 ++++++++++++++++ .../Data/SqliteDisplayPreferencesRepository.cs | 11 +- .../Data/SqliteItemRepository.cs | 47 +++++---- .../Data/SqliteUserDataRepository.cs | 11 +- .../Data/SqliteUserRepository.cs | 15 +-- .../Security/AuthenticationRepository.cs | 17 +-- 8 files changed, 199 insertions(+), 114 deletions(-) create mode 100644 Emby.Server.Implementations/Data/ManagedConnection.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index 495d969776..a38cb38d7e 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -43,7 +43,8 @@ namespace Emby.Server.Implementations.Activity private void InitializeInternal() { - using (var connection = CreateConnection()) + CreateConnections().GetAwaiter().GetResult(); + using (var connection = GetConnection()) { RunDefaultInitialization(connection); @@ -57,7 +58,7 @@ namespace Emby.Server.Implementations.Activity } } - private void TryMigrate(SQLiteDatabaseConnection connection) + private void TryMigrate(ManagedConnection connection) { try { @@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Activity throw new ArgumentNullException(nameof(entry)); } - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -123,7 +124,7 @@ namespace Emby.Server.Implementations.Activity throw new ArgumentNullException(nameof(entry)); } - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -157,7 +158,7 @@ namespace Emby.Server.Implementations.Activity public QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit) { - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { var commandText = BaseActivitySelectText; var whereClauses = new List(); diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 29edddb3a7..6a19ea373e 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -27,98 +28,93 @@ namespace Emby.Server.Implementations.Data internal static int ThreadSafeMode { get; set; } + protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.SharedCached | ConnectionFlags.FullMutex; + + private readonly SemaphoreSlim WriteLock = new SemaphoreSlim(1, 1); + + private SQLiteDatabaseConnection WriteConnection; + + private readonly BlockingCollection ReadConnectionPool = new BlockingCollection(); + static BaseSqliteRepository() { ThreadSafeMode = raw.sqlite3_threadsafe(); } - private static bool _versionLogged; - private string _defaultWal; - protected SQLiteDatabaseConnection CreateConnection(bool isReadOnly = false) + protected async Task CreateConnections() { - if (!_versionLogged) - { - _versionLogged = true; - Logger.LogInformation("Sqlite version: " + SQLite3.Version); - Logger.LogInformation("Sqlite compiler options: " + string.Join(",", SQLite3.CompilerOptions)); - } - - ConnectionFlags connectionFlags; - - if (isReadOnly) - { - //Logger.LogInformation("Opening read connection"); - //connectionFlags = ConnectionFlags.Create; - connectionFlags = ConnectionFlags.ReadOnly; - } - else - { - //Logger.LogInformation("Opening write connection"); - connectionFlags = ConnectionFlags.Create; - connectionFlags |= ConnectionFlags.ReadWrite; - } - - connectionFlags |= ConnectionFlags.SharedCached; - connectionFlags |= ConnectionFlags.FullMutex; - - var db = SQLite3.Open(DbFilePath, connectionFlags, null); + await WriteLock.WaitAsync().ConfigureAwait(false); try { - if (string.IsNullOrWhiteSpace(_defaultWal)) + if (WriteConnection == null) { - _defaultWal = db.Query("PRAGMA journal_mode").SelectScalarString().First(); - - Logger.LogInformation("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal); + WriteConnection = SQLite3.Open( + DbFilePath, + DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite, + null); } - var queries = new List + if (string.IsNullOrWhiteSpace(_defaultWal)) { - //"PRAGMA cache size=-10000" - //"PRAGMA read_uncommitted = true", - //"PRAGMA synchronous=Normal" - }; + _defaultWal = WriteConnection.Query("PRAGMA journal_mode").SelectScalarString().First(); - if (CacheSize.HasValue) - { - queries.Add("PRAGMA cache_size=" + CacheSize.Value.ToString(CultureInfo.InvariantCulture)); + Logger.LogInformation("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal); } if (EnableTempStoreMemory) { - queries.Add("PRAGMA temp_store = memory"); + WriteConnection.Execute("PRAGMA temp_store = memory"); } else { - queries.Add("PRAGMA temp_store = file"); - } - - foreach (var query in queries) - { - db.Execute(query); + WriteConnection.Execute("PRAGMA temp_store = file"); } } catch { - using (db) - { - - } throw; } + finally + { + WriteLock.Release(); + } - return db; + // Add one reading connection for each thread + int threads = System.Environment.ProcessorCount; + for (int i = 0; i <= threads; i++) + { + ReadConnectionPool.Add(SQLite3.Open(DbFilePath, DefaultConnectionFlags | ConnectionFlags.ReadOnly, null)); + } } - public IStatement PrepareStatement(SQLiteDatabaseConnection connection, string sql) + protected ManagedConnection GetConnection(bool isReadOnly = false) + { + if (isReadOnly) + { + return new ManagedConnection(ReadConnectionPool.Take(), ReadConnectionPool); + } + else + { + if (WriteConnection == null) + { + throw new InvalidOperationException("Can't access the write connection at this time."); + } + + WriteLock.Wait(); + return new ManagedConnection(WriteConnection, WriteLock); + } + } + + public IStatement PrepareStatement(ManagedConnection connection, string sql) { return connection.PrepareStatement(sql); } - public IStatement PrepareStatementSafe(SQLiteDatabaseConnection connection, string sql) + public IStatement PrepareStatementSafe(ManagedConnection connection, string sql) { return connection.PrepareStatement(sql); } @@ -143,7 +139,7 @@ namespace Emby.Server.Implementations.Data return sql.Select(connection.PrepareStatement).ToList(); } - protected bool TableExists(SQLiteDatabaseConnection connection, string name) + protected bool TableExists(ManagedConnection connection, string name) { return connection.RunInTransaction(db => { @@ -163,7 +159,7 @@ namespace Emby.Server.Implementations.Data }, ReadTransactionMode); } - protected void RunDefaultInitialization(SQLiteDatabaseConnection db) + protected void RunDefaultInitialization(ManagedConnection db) { var queries = new List { @@ -192,9 +188,7 @@ namespace Emby.Server.Implementations.Data Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First()); } - protected virtual bool EnableTempStoreMemory => false; - - protected virtual int? CacheSize => null; + protected virtual bool EnableTempStoreMemory => true; private bool _disposed; protected void CheckDisposed() diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs new file mode 100644 index 0000000000..71b934a9bb --- /dev/null +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + public class ManagedConnection : IDisposable + { + private SQLiteDatabaseConnection _db; + private SemaphoreSlim _writeLock; + private BlockingCollection _readConPool; + private bool _disposed = false; + + public ManagedConnection(SQLiteDatabaseConnection db, SemaphoreSlim writeLock) + { + _db = db; + _writeLock = writeLock; + } + + public ManagedConnection(SQLiteDatabaseConnection db, BlockingCollection queue) + { + _db = db; + _readConPool = queue; + } + + public IStatement PrepareStatement(string sql) + { + return _db.PrepareStatement(sql); + } + + public IEnumerable PrepareAll(string sql) + { + return _db.PrepareAll(sql); + } + + public void ExecuteAll(string sql) + { + _db.ExecuteAll(sql); + } + + public void Execute(string sql, params object[] values) + { + _db.Execute(sql, values); + } + + public void RunQueries(string[] sql) + { + _db.RunQueries(sql); + } + + public void RunInTransaction(Action action, TransactionMode mode) + { + _db.RunInTransaction(action, mode); + } + + public T RunInTransaction(Func action, TransactionMode mode) + { + return _db.RunInTransaction(action, mode); + } + + public IEnumerable> Query(string sql) + { + return _db.Query(sql); + } + + public IEnumerable> Query(string sql, params object[] values) + { + return _db.Query(sql, values); + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _writeLock?.Release(); + _readConPool?.Add(_db); + + _db = null; // Don't dispose it + _disposed = true; + } + } +} diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 86a7c1551f..d620f3962f 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -61,7 +61,8 @@ namespace Emby.Server.Implementations.Data /// Task. private void InitializeInternal() { - using (var connection = CreateConnection()) + CreateConnections().GetAwaiter().GetResult(); + using (var connection = GetConnection()) { RunDefaultInitialization(connection); @@ -98,7 +99,7 @@ namespace Emby.Server.Implementations.Data cancellationToken.ThrowIfCancellationRequested(); - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -139,7 +140,7 @@ namespace Emby.Server.Implementations.Data cancellationToken.ThrowIfCancellationRequested(); - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -168,7 +169,7 @@ namespace Emby.Server.Implementations.Data var guidId = displayPreferencesId.GetMD5(); - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client")) { @@ -199,7 +200,7 @@ namespace Emby.Server.Implementations.Data { var list = new List(); - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId")) { diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index baa5b713a2..e96b6ce3a0 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -92,8 +92,6 @@ namespace Emby.Server.Implementations.Data private const string ChaptersTableName = "Chapters2"; - protected override int? CacheSize => 20000; - protected override bool EnableTempStoreMemory => true; /// @@ -101,7 +99,8 @@ namespace Emby.Server.Implementations.Data /// public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager) { - using (var connection = CreateConnection()) + CreateConnections().GetAwaiter().GetResult(); + using (var connection = GetConnection()) { RunDefaultInitialization(connection); @@ -551,7 +550,7 @@ namespace Emby.Server.Implementations.Data CheckDisposed(); - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -602,7 +601,7 @@ namespace Emby.Server.Implementations.Data tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags)); } - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -1186,7 +1185,7 @@ namespace Emby.Server.Implementations.Data CheckDisposed(); - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { using (var statement = PrepareStatementSafe(connection, "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems where guid = @guid")) { @@ -1899,7 +1898,7 @@ namespace Emby.Server.Implementations.Data { CheckDisposed(); - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { var list = new List(); @@ -1928,7 +1927,7 @@ namespace Emby.Server.Implementations.Data { CheckDisposed(); - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { using (var statement = PrepareStatementSafe(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex")) { @@ -1997,7 +1996,7 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(chapters)); } - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -2533,7 +2532,7 @@ namespace Emby.Server.Implementations.Data commandText += " where " + string.Join(" AND ", whereClauses); } - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { using (var statement = PrepareStatementSafe(connection, commandText)) { @@ -2602,7 +2601,7 @@ namespace Emby.Server.Implementations.Data } } - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { var list = new List(); @@ -2820,7 +2819,7 @@ namespace Emby.Server.Implementations.Data statementTexts.Add(commandText); } - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { return connection.RunInTransaction(db => { @@ -3052,7 +3051,7 @@ namespace Emby.Server.Implementations.Data } } - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { var list = new List(); @@ -3119,7 +3118,7 @@ namespace Emby.Server.Implementations.Data } var list = new List>(); - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { using (var statement = PrepareStatementSafe(connection, commandText)) { @@ -3231,7 +3230,7 @@ namespace Emby.Server.Implementations.Data statementTexts.Add(commandText); } - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { return connection.RunInTransaction(db => { @@ -4862,7 +4861,7 @@ namespace Emby.Server.Implementations.Data private void UpdateInheritedTags(CancellationToken cancellationToken) { - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -4925,7 +4924,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type CheckDisposed(); - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -4982,7 +4981,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type commandText += " order by ListOrder"; - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { var list = new List(); using (var statement = PrepareStatementSafe(connection, commandText)) @@ -5019,7 +5018,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type commandText += " order by ListOrder"; - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { var list = new List(); @@ -5245,7 +5244,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type commandText += " Group By CleanValue"; - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { var list = new List(); @@ -5431,7 +5430,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statementTexts.Add(countText); } - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { return connection.RunInTransaction(db => { @@ -5698,7 +5697,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type CheckDisposed(); - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -5815,7 +5814,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type cmdText += " order by StreamIndex ASC"; - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { var list = new List(); @@ -5859,7 +5858,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type cancellationToken.ThrowIfCancellationRequested(); - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index f544dfac4c..9dc31d5972 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -34,7 +34,8 @@ namespace Emby.Server.Implementations.Data /// Task. public void Initialize(IUserManager userManager) { - using (var connection = CreateConnection()) + CreateConnections().GetAwaiter().GetResult(); + using (var connection = GetConnection()) { var userDatasTableExists = TableExists(connection, "UserDatas"); var userDataTableExists = TableExists(connection, "userdata"); @@ -172,7 +173,7 @@ namespace Emby.Server.Implementations.Data { cancellationToken.ThrowIfCancellationRequested(); - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -240,7 +241,7 @@ namespace Emby.Server.Implementations.Data { cancellationToken.ThrowIfCancellationRequested(); - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -275,7 +276,7 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(key)); } - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId")) { @@ -321,7 +322,7 @@ namespace Emby.Server.Implementations.Data var list = new List(); - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId")) { diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index 785452ad39..ef8ae60b3c 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -40,7 +40,8 @@ namespace Emby.Server.Implementations.Data /// Task. public void Initialize() { - using (var connection = CreateConnection()) + CreateConnections().GetAwaiter().GetResult(); + using (var connection = GetConnection()) { RunDefaultInitialization(connection); @@ -60,7 +61,7 @@ namespace Emby.Server.Implementations.Data } } - private void TryMigrateToLocalUsersTable(SQLiteDatabaseConnection connection) + private void TryMigrateToLocalUsersTable(ManagedConnection connection) { try { @@ -119,7 +120,7 @@ namespace Emby.Server.Implementations.Data var serialized = _jsonSerializer.SerializeToBytes(user); - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -153,7 +154,7 @@ namespace Emby.Server.Implementations.Data var serialized = _jsonSerializer.SerializeToBytes(user); - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -168,7 +169,7 @@ namespace Emby.Server.Implementations.Data } } - private User GetUser(Guid guid, SQLiteDatabaseConnection connection) + private User GetUser(Guid guid, ManagedConnection connection) { using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) { @@ -206,7 +207,7 @@ namespace Emby.Server.Implementations.Data { var list = new List(); - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) { @@ -230,7 +231,7 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(user)); } - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index abc23239e2..dfcd6af0df 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -23,7 +23,8 @@ namespace Emby.Server.Implementations.Security public void Initialize() { - using (var connection = CreateConnection()) + CreateConnections().GetAwaiter().GetResult(); + using (var connection = GetConnection()) { RunDefaultInitialization(connection); @@ -48,7 +49,7 @@ namespace Emby.Server.Implementations.Security } } - private void TryMigrate(SQLiteDatabaseConnection connection, bool tableNewlyCreated) + private void TryMigrate(ManagedConnection connection, bool tableNewlyCreated) { try { @@ -87,7 +88,7 @@ namespace Emby.Server.Implementations.Security throw new ArgumentNullException(nameof(info)); } - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -119,7 +120,7 @@ namespace Emby.Server.Implementations.Security throw new ArgumentNullException(nameof(info)); } - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -151,7 +152,7 @@ namespace Emby.Server.Implementations.Security throw new ArgumentNullException(nameof(info)); } - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { @@ -248,7 +249,7 @@ namespace Emby.Server.Implementations.Security var list = new List(); - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { return connection.RunInTransaction(db => { @@ -346,7 +347,7 @@ namespace Emby.Server.Implementations.Security public DeviceOptions GetDeviceOptions(string deviceId) { - using (var connection = CreateConnection(true)) + using (var connection = GetConnection(true)) { return connection.RunInTransaction(db => { @@ -378,7 +379,7 @@ namespace Emby.Server.Implementations.Security throw new ArgumentNullException(nameof(options)); } - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { -- cgit v1.2.3 From e5248cfaa25a4aaa00e9cc8ba7d23a2b3c690b6e Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 Feb 2019 18:58:33 +0100 Subject: Properly dispose --- .../Data/BaseSqliteRepository.cs | 53 ++++++++++++---------- 1 file changed, 30 insertions(+), 23 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 6a19ea373e..33bbbbfe4f 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -28,7 +27,7 @@ namespace Emby.Server.Implementations.Data internal static int ThreadSafeMode { get; set; } - protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.SharedCached | ConnectionFlags.FullMutex; + protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.SharedCached | ConnectionFlags.NoMutex; private readonly SemaphoreSlim WriteLock = new SemaphoreSlim(1, 1); @@ -39,6 +38,7 @@ namespace Emby.Server.Implementations.Data static BaseSqliteRepository() { ThreadSafeMode = raw.sqlite3_threadsafe(); + raw.sqlite3_enable_shared_cache(1); } private string _defaultWal; @@ -55,6 +55,7 @@ namespace Emby.Server.Implementations.Data DbFilePath, DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite, null); + SQLiteDatabaseConnectionBuilder.InMemory. } if (string.IsNullOrWhiteSpace(_defaultWal)) @@ -110,34 +111,22 @@ namespace Emby.Server.Implementations.Data } public IStatement PrepareStatement(ManagedConnection connection, string sql) - { - return connection.PrepareStatement(sql); - } + => connection.PrepareStatement(sql); public IStatement PrepareStatementSafe(ManagedConnection connection, string sql) - { - return connection.PrepareStatement(sql); - } + => connection.PrepareStatement(sql); public IStatement PrepareStatement(IDatabaseConnection connection, string sql) - { - return connection.PrepareStatement(sql); - } + => connection.PrepareStatement(sql); public IStatement PrepareStatementSafe(IDatabaseConnection connection, string sql) - { - return connection.PrepareStatement(sql); - } + => connection.PrepareStatement(sql); - public List PrepareAll(IDatabaseConnection connection, IEnumerable sql) - { - return PrepareAllSafe(connection, sql); - } + public IEnumerable PrepareAll(IDatabaseConnection connection, IEnumerable sql) + => PrepareAllSafe(connection, sql); - public List PrepareAllSafe(IDatabaseConnection connection, IEnumerable sql) - { - return sql.Select(connection.PrepareStatement).ToList(); - } + public IEnumerable PrepareAllSafe(IDatabaseConnection connection, IEnumerable sql) + => sql.Select(connection.PrepareStatement); protected bool TableExists(ManagedConnection connection, string name) { @@ -201,7 +190,6 @@ namespace Emby.Server.Implementations.Data public void Dispose() { - Dispose(true); } @@ -218,6 +206,25 @@ namespace Emby.Server.Implementations.Data return; } + if (dispose) + { + WriteLock.Wait(); + try + { + WriteConnection.Dispose(); + } + finally + { + WriteLock.Release(); + } + + foreach (var i in ReadConnectionPool) + { + i.Dispose(); + } + + ReadConnectionPool.Dispose(); + } _disposed = true; } -- cgit v1.2.3 From 30842656a7fb1d23eef245dcfb57287ebdb960a0 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 Feb 2019 19:01:18 +0100 Subject: Properly dispose --- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 1 - Emby.Server.Implementations/Data/SqliteItemRepository.cs | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 33bbbbfe4f..db63f68d0f 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -55,7 +55,6 @@ namespace Emby.Server.Implementations.Data DbFilePath, DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite, null); - SQLiteDatabaseConnectionBuilder.InMemory. } if (string.IsNullOrWhiteSpace(_defaultWal)) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index e96b6ce3a0..a2f490c4a6 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -616,7 +616,7 @@ namespace Emby.Server.Implementations.Data { GetSaveItemCommandText(), "delete from AncestorIds where ItemId=@ItemId" - }); + }).ToList(); using (var saveItemStatement = statements[0]) using (var deleteAncestorsStatement = statements[1]) @@ -2824,7 +2824,7 @@ namespace Emby.Server.Implementations.Data return connection.RunInTransaction(db => { var result = new QueryResult(); - var statements = PrepareAllSafe(db, statementTexts); + var statements = PrepareAllSafe(db, statementTexts).ToList(); if (!isReturningZeroItems) { @@ -3236,7 +3236,7 @@ namespace Emby.Server.Implementations.Data { var result = new QueryResult(); - var statements = PrepareAllSafe(db, statementTexts); + var statements = PrepareAllSafe(db, statementTexts).ToList(); if (!isReturningZeroItems) { @@ -5437,7 +5437,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type var list = new List<(BaseItem, ItemCounts)>(); var result = new QueryResult<(BaseItem, ItemCounts)>(); - var statements = PrepareAllSafe(db, statementTexts); + var statements = PrepareAllSafe(db, statementTexts).ToList(); if (!isReturningZeroItems) { -- cgit v1.2.3 From 27c29bbb4c75d5fc5c9111c5552c37a1f137dd58 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 11 Mar 2019 22:33:27 +0100 Subject: Back to a single connection --- .../Activity/ActivityRepository.cs | 1 - .../Data/BaseSqliteRepository.cs | 80 ++++++---------------- .../Data/ManagedConnection.cs | 13 +--- .../Data/SqliteDisplayPreferencesRepository.cs | 1 - .../Data/SqliteItemRepository.cs | 1 - .../Data/SqliteUserDataRepository.cs | 1 - .../Data/SqliteUserRepository.cs | 4 +- .../Security/AuthenticationRepository.cs | 1 - .../ScheduledTasksWebSocketListener.cs | 1 - .../Session/SessionInfoWebSocketListener.cs | 1 - 10 files changed, 23 insertions(+), 81 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index a38cb38d7e..63931e1348 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -43,7 +43,6 @@ namespace Emby.Server.Implementations.Activity private void InitializeInternal() { - CreateConnections().GetAwaiter().GetResult(); using (var connection = GetConnection()) { RunDefaultInitialization(connection); diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index db63f68d0f..33a0b7ddfa 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SQLitePCL; using SQLitePCL.pretty; @@ -33,8 +31,6 @@ namespace Emby.Server.Implementations.Data private SQLiteDatabaseConnection WriteConnection; - private readonly BlockingCollection ReadConnectionPool = new BlockingCollection(); - static BaseSqliteRepository() { ThreadSafeMode = raw.sqlite3_threadsafe(); @@ -43,70 +39,37 @@ namespace Emby.Server.Implementations.Data private string _defaultWal; - protected async Task CreateConnections() + protected ManagedConnection GetConnection(bool isReadOnly = false) { - await WriteLock.WaitAsync().ConfigureAwait(false); - - try + WriteLock.Wait(); + if (WriteConnection != null) { - if (WriteConnection == null) - { - WriteConnection = SQLite3.Open( - DbFilePath, - DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite, - null); - } + return new ManagedConnection(WriteConnection, WriteLock); + } - if (string.IsNullOrWhiteSpace(_defaultWal)) - { - _defaultWal = WriteConnection.Query("PRAGMA journal_mode").SelectScalarString().First(); + WriteConnection = SQLite3.Open( + DbFilePath, + DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite, + null); - Logger.LogInformation("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal); - } - if (EnableTempStoreMemory) - { - WriteConnection.Execute("PRAGMA temp_store = memory"); - } - else - { - WriteConnection.Execute("PRAGMA temp_store = file"); - } - } - catch + if (string.IsNullOrWhiteSpace(_defaultWal)) { + _defaultWal = WriteConnection.Query("PRAGMA journal_mode").SelectScalarString().First(); - throw; - } - finally - { - WriteLock.Release(); + Logger.LogInformation("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal); } - // Add one reading connection for each thread - int threads = System.Environment.ProcessorCount; - for (int i = 0; i <= threads; i++) - { - ReadConnectionPool.Add(SQLite3.Open(DbFilePath, DefaultConnectionFlags | ConnectionFlags.ReadOnly, null)); - } - } - - protected ManagedConnection GetConnection(bool isReadOnly = false) - { - if (isReadOnly) + if (EnableTempStoreMemory) { - return new ManagedConnection(ReadConnectionPool.Take(), ReadConnectionPool); + WriteConnection.Execute("PRAGMA temp_store = memory"); } else { - if (WriteConnection == null) - { - throw new InvalidOperationException("Can't access the write connection at this time."); - } - - WriteLock.Wait(); - return new ManagedConnection(WriteConnection, WriteLock); + WriteConnection.Execute("PRAGMA temp_store = file"); } + + return new ManagedConnection(WriteConnection, WriteLock); } public IStatement PrepareStatement(ManagedConnection connection, string sql) @@ -217,14 +180,11 @@ namespace Emby.Server.Implementations.Data WriteLock.Release(); } - foreach (var i in ReadConnectionPool) - { - i.Dispose(); - } - - ReadConnectionPool.Dispose(); + WriteLock.Dispose(); } + WriteConnection = null; + _disposed = true; } diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index 71b934a9bb..4c34244100 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using SQLitePCL.pretty; @@ -9,8 +8,7 @@ namespace Emby.Server.Implementations.Data public class ManagedConnection : IDisposable { private SQLiteDatabaseConnection _db; - private SemaphoreSlim _writeLock; - private BlockingCollection _readConPool; + private readonly SemaphoreSlim _writeLock; private bool _disposed = false; public ManagedConnection(SQLiteDatabaseConnection db, SemaphoreSlim writeLock) @@ -19,12 +17,6 @@ namespace Emby.Server.Implementations.Data _writeLock = writeLock; } - public ManagedConnection(SQLiteDatabaseConnection db, BlockingCollection queue) - { - _db = db; - _readConPool = queue; - } - public IStatement PrepareStatement(string sql) { return _db.PrepareStatement(sql); @@ -77,8 +69,7 @@ namespace Emby.Server.Implementations.Data return; } - _writeLock?.Release(); - _readConPool?.Add(_db); + _writeLock.Release(); _db = null; // Don't dispose it _disposed = true; diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index d620f3962f..7f8df76262 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.Data /// Task. private void InitializeInternal() { - CreateConnections().GetAwaiter().GetResult(); using (var connection = GetConnection()) { RunDefaultInitialization(connection); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index a2f490c4a6..9e96d77451 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -99,7 +99,6 @@ namespace Emby.Server.Implementations.Data /// public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager) { - CreateConnections().GetAwaiter().GetResult(); using (var connection = GetConnection()) { RunDefaultInitialization(connection); diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 9dc31d5972..355755014c 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -34,7 +34,6 @@ namespace Emby.Server.Implementations.Data /// Task. public void Initialize(IUserManager userManager) { - CreateConnections().GetAwaiter().GetResult(); using (var connection = GetConnection()) { var userDatasTableExists = TableExists(connection, "UserDatas"); diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index ef8ae60b3c..e79b3d6015 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -40,7 +40,6 @@ namespace Emby.Server.Implementations.Data /// Task. public void Initialize() { - CreateConnections().GetAwaiter().GetResult(); using (var connection = GetConnection()) { RunDefaultInitialization(connection); @@ -90,8 +89,7 @@ namespace Emby.Server.Implementations.Data user.Password = null; var serialized = _jsonSerializer.SerializeToBytes(user); - using (WriteLock.Write()) - using (var connection = CreateConnection()) + using (var connection = GetConnection()) { connection.RunInTransaction(db => { diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index dfcd6af0df..efe56c0816 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -23,7 +23,6 @@ namespace Emby.Server.Implementations.Security public void Initialize() { - CreateConnections().GetAwaiter().GetResult(); using (var connection = GetConnection()) { RunDefaultInitialization(connection); diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs index d24a187430..d9530ffb79 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Events; diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs index b79e9f84b1..f1a6622fb5 100644 --- a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; -- cgit v1.2.3 From b6954f3bfd68c87923348444a5923406cf672f9b Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 11 Mar 2019 23:07:38 +0100 Subject: More --- .../Activity/ActivityRepository.cs | 8 ++--- .../Data/BaseSqliteRepository.cs | 42 +++++++++------------- .../Data/SqliteDisplayPreferencesRepository.cs | 6 ++-- Jellyfin.Server/Program.cs | 7 +++- 4 files changed, 30 insertions(+), 33 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index 63931e1348..cac1a9feb4 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -15,14 +15,14 @@ namespace Emby.Server.Implementations.Activity { public class ActivityRepository : BaseSqliteRepository, IActivityRepository { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - protected IFileSystem FileSystem { get; private set; } + private static readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly IFileSystem _fileSystem; public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem) : base(loggerFactory.CreateLogger(nameof(ActivityRepository))) { DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db"); - FileSystem = fileSystem; + _fileSystem = fileSystem; } public void Initialize() @@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.Activity { Logger.LogError(ex, "Error loading database file. Will reset and retry."); - FileSystem.DeleteFile(DbFilePath); + _fileSystem.DeleteFile(DbFilePath); InitializeInternal(); } diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 33a0b7ddfa..f3bd07bb09 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Data { protected string DbFilePath { get; set; } - protected ILogger Logger { get; private set; } + protected ILogger Logger { get; } protected BaseSqliteRepository(ILogger logger) { @@ -23,31 +23,23 @@ namespace Emby.Server.Implementations.Data protected TransactionMode ReadTransactionMode => TransactionMode.Deferred; - internal static int ThreadSafeMode { get; set; } - protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.SharedCached | ConnectionFlags.NoMutex; - private readonly SemaphoreSlim WriteLock = new SemaphoreSlim(1, 1); - - private SQLiteDatabaseConnection WriteConnection; + private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); - static BaseSqliteRepository() - { - ThreadSafeMode = raw.sqlite3_threadsafe(); - raw.sqlite3_enable_shared_cache(1); - } + private SQLiteDatabaseConnection _writeConnection; private string _defaultWal; - protected ManagedConnection GetConnection(bool isReadOnly = false) + protected ManagedConnection GetConnection(bool _ = false) { - WriteLock.Wait(); - if (WriteConnection != null) + _writeLock.Wait(); + if (_writeConnection != null) { - return new ManagedConnection(WriteConnection, WriteLock); + return new ManagedConnection(_writeConnection, _writeLock); } - WriteConnection = SQLite3.Open( + _writeConnection = SQLite3.Open( DbFilePath, DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite, null); @@ -55,21 +47,21 @@ namespace Emby.Server.Implementations.Data if (string.IsNullOrWhiteSpace(_defaultWal)) { - _defaultWal = WriteConnection.Query("PRAGMA journal_mode").SelectScalarString().First(); + _defaultWal = _writeConnection.Query("PRAGMA journal_mode").SelectScalarString().First(); Logger.LogInformation("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal); } if (EnableTempStoreMemory) { - WriteConnection.Execute("PRAGMA temp_store = memory"); + _writeConnection.Execute("PRAGMA temp_store = memory"); } else { - WriteConnection.Execute("PRAGMA temp_store = file"); + _writeConnection.Execute("PRAGMA temp_store = file"); } - return new ManagedConnection(WriteConnection, WriteLock); + return new ManagedConnection(_writeConnection, _writeLock); } public IStatement PrepareStatement(ManagedConnection connection, string sql) @@ -170,20 +162,20 @@ namespace Emby.Server.Implementations.Data if (dispose) { - WriteLock.Wait(); + _writeLock.Wait(); try { - WriteConnection.Dispose(); + _writeConnection.Dispose(); } finally { - WriteLock.Release(); + _writeLock.Release(); } - WriteLock.Dispose(); + _writeLock.Dispose(); } - WriteConnection = null; + _writeConnection = null; _disposed = true; } diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 7f8df76262..1d44b0b292 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -18,13 +18,13 @@ namespace Emby.Server.Implementations.Data /// public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository { - protected IFileSystem FileSystem { get; private set; } + private readonly IFileSystem _fileSystem; public SqliteDisplayPreferencesRepository(ILoggerFactory loggerFactory, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IFileSystem fileSystem) : base(loggerFactory.CreateLogger(nameof(SqliteDisplayPreferencesRepository))) { _jsonSerializer = jsonSerializer; - FileSystem = fileSystem; + _fileSystem = fileSystem; DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db"); } @@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Data { Logger.LogError(ex, "Error loading database file. Will reset and retry."); - FileSystem.DeleteFile(DbFilePath); + _fileSystem.DeleteFile(DbFilePath); InitializeInternal(); } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 91752a16df..11c09db983 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -24,6 +24,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.AspNetCore; +using SQLitePCL; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Jellyfin.Server @@ -126,7 +127,11 @@ namespace Jellyfin.Server ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); #pragma warning restore CA5359 - SQLitePCL.Batteries_V2.Init(); + Batteries_V2.Init(); + if (raw.sqlite3_enable_shared_cache(1) != raw.SQLITE_OK) + { + Console.WriteLine("WARN: Failed to enable shared cache for SQLite"); + } using (var appHost = new CoreAppHost( appPaths, -- cgit v1.2.3 From e88ebd748d98cf9bd2a3978d36254f1644ce751a Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 2 Apr 2019 22:15:18 +0200 Subject: Final fixes --- .../Data/BaseSqliteRepository.cs | 44 +++++++++------------- .../Data/SqliteItemRepository.cs | 40 ++++++++++---------- .../Data/SqliteUserDataRepository.cs | 7 +++- .../Security/AuthenticationRepository.cs | 6 +-- Jellyfin.Server/Program.cs | 4 -- 5 files changed, 47 insertions(+), 54 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index f3bd07bb09..63cef80b04 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using Microsoft.Extensions.Logging; -using SQLitePCL; using SQLitePCL.pretty; namespace Emby.Server.Implementations.Data @@ -23,23 +22,23 @@ namespace Emby.Server.Implementations.Data protected TransactionMode ReadTransactionMode => TransactionMode.Deferred; - protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.SharedCached | ConnectionFlags.NoMutex; + protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.NoMutex; - private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); + protected SemaphoreSlim WriteLock = new SemaphoreSlim(1, 1); - private SQLiteDatabaseConnection _writeConnection; + protected SQLiteDatabaseConnection WriteConnection; private string _defaultWal; protected ManagedConnection GetConnection(bool _ = false) { - _writeLock.Wait(); - if (_writeConnection != null) + WriteLock.Wait(); + if (WriteConnection != null) { - return new ManagedConnection(_writeConnection, _writeLock); + return new ManagedConnection(WriteConnection, WriteLock); } - _writeConnection = SQLite3.Open( + WriteConnection = SQLite3.Open( DbFilePath, DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite, null); @@ -47,38 +46,29 @@ namespace Emby.Server.Implementations.Data if (string.IsNullOrWhiteSpace(_defaultWal)) { - _defaultWal = _writeConnection.Query("PRAGMA journal_mode").SelectScalarString().First(); + _defaultWal = WriteConnection.Query("PRAGMA journal_mode").SelectScalarString().First(); Logger.LogInformation("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal); } if (EnableTempStoreMemory) { - _writeConnection.Execute("PRAGMA temp_store = memory"); + WriteConnection.Execute("PRAGMA temp_store = memory"); } else { - _writeConnection.Execute("PRAGMA temp_store = file"); + WriteConnection.Execute("PRAGMA temp_store = file"); } - return new ManagedConnection(_writeConnection, _writeLock); + return new ManagedConnection(WriteConnection, WriteLock); } public IStatement PrepareStatement(ManagedConnection connection, string sql) => connection.PrepareStatement(sql); - public IStatement PrepareStatementSafe(ManagedConnection connection, string sql) - => connection.PrepareStatement(sql); - public IStatement PrepareStatement(IDatabaseConnection connection, string sql) => connection.PrepareStatement(sql); - public IStatement PrepareStatementSafe(IDatabaseConnection connection, string sql) - => connection.PrepareStatement(sql); - - public IEnumerable PrepareAll(IDatabaseConnection connection, IEnumerable sql) - => PrepareAllSafe(connection, sql); - public IEnumerable PrepareAllSafe(IDatabaseConnection connection, IEnumerable sql) => sql.Select(connection.PrepareStatement); @@ -145,6 +135,7 @@ namespace Emby.Server.Implementations.Data public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } private readonly object _disposeLock = new object(); @@ -162,20 +153,21 @@ namespace Emby.Server.Implementations.Data if (dispose) { - _writeLock.Wait(); + WriteLock.Wait(); try { - _writeConnection.Dispose(); + WriteConnection.Dispose(); } finally { - _writeLock.Release(); + WriteLock.Release(); } - _writeLock.Dispose(); + WriteLock.Dispose(); } - _writeConnection = null; + WriteConnection = null; + WriteLock = null; _disposed = true; } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 9e96d77451..462d91e41f 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -317,7 +317,7 @@ namespace Emby.Server.Implementations.Data connection.RunQueries(postQueries); } - userDataRepo.Initialize(userManager); + userDataRepo.Initialize(userManager, WriteLock, WriteConnection); } private static readonly string[] _retriveItemColumns = @@ -551,16 +551,16 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction((Action)(db => { - using (var saveImagesStatement = PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) + using (var saveImagesStatement = base.PrepareStatement((IDatabaseConnection)db, (string)"Update TypedBaseItems set Images=@Images where guid=@Id")) { saveImagesStatement.TryBind("@Id", item.Id.ToGuidBlob()); saveImagesStatement.TryBind("@Images", SerializeImages(item)); saveImagesStatement.MoveNext(); } - }, TransactionMode); + }), TransactionMode); } } @@ -1186,7 +1186,7 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection(true)) { - using (var statement = PrepareStatementSafe(connection, "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems where guid = @guid")) + using (var statement = PrepareStatement(connection, "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems where guid = @guid")) { statement.TryBind("@guid", id); @@ -1901,7 +1901,7 @@ namespace Emby.Server.Implementations.Data { var list = new List(); - using (var statement = PrepareStatementSafe(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc")) + using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc")) { statement.TryBind("@ItemId", item.Id); @@ -1928,7 +1928,7 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection(true)) { - using (var statement = PrepareStatementSafe(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex")) + using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex")) { statement.TryBind("@ItemId", item.Id); statement.TryBind("@ChapterIndex", index); @@ -2028,7 +2028,7 @@ namespace Emby.Server.Implementations.Data } insertText.Length -= 1; // Remove last , - using (var statement = PrepareStatementSafe(db, insertText.ToString())) + using (var statement = PrepareStatement(db, insertText.ToString())) { statement.TryBind("@ItemId", idBlob); @@ -2533,7 +2533,7 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection(true)) { - using (var statement = PrepareStatementSafe(connection, commandText)) + using (var statement = PrepareStatement(connection, commandText)) { if (EnableJoinUserData(query)) { @@ -2604,7 +2604,7 @@ namespace Emby.Server.Implementations.Data { var list = new List(); - using (var statement = PrepareStatementSafe(connection, commandText)) + using (var statement = PrepareStatement(connection, commandText)) { if (EnableJoinUserData(query)) { @@ -3054,7 +3054,7 @@ namespace Emby.Server.Implementations.Data { var list = new List(); - using (var statement = PrepareStatementSafe(connection, commandText)) + using (var statement = PrepareStatement(connection, commandText)) { if (EnableJoinUserData(query)) { @@ -3119,7 +3119,7 @@ namespace Emby.Server.Implementations.Data var list = new List>(); using (var connection = GetConnection(true)) { - using (var statement = PrepareStatementSafe(connection, commandText)) + using (var statement = PrepareStatement(connection, commandText)) { if (EnableJoinUserData(query)) { @@ -4983,7 +4983,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type using (var connection = GetConnection(true)) { var list = new List(); - using (var statement = PrepareStatementSafe(connection, commandText)) + using (var statement = PrepareStatement(connection, commandText)) { // Run this again to bind the params GetPeopleWhereClauses(query, statement); @@ -5021,7 +5021,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { var list = new List(); - using (var statement = PrepareStatementSafe(connection, commandText)) + using (var statement = PrepareStatement(connection, commandText)) { // Run this again to bind the params GetPeopleWhereClauses(query, statement); @@ -5146,7 +5146,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type insertText.AppendFormat("(@ItemId, @AncestorId{0}, @AncestorIdText{0})", i.ToString(CultureInfo.InvariantCulture)); } - using (var statement = PrepareStatementSafe(db, insertText.ToString())) + using (var statement = PrepareStatement(db, insertText.ToString())) { statement.TryBind("@ItemId", itemIdBlob); @@ -5247,7 +5247,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { var list = new List(); - using (var statement = PrepareStatementSafe(connection, commandText)) + using (var statement = PrepareStatement(connection, commandText)) { foreach (var row in statement.ExecuteQuery()) { @@ -5651,7 +5651,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type isSubsequentRow = true; } - using (var statement = PrepareStatementSafe(db, insertText.ToString())) + using (var statement = PrepareStatement(db, insertText.ToString())) { statement.TryBind("@ItemId", idBlob); @@ -5735,7 +5735,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type isSubsequentRow = true; } - using (var statement = PrepareStatementSafe(db, insertText.ToString())) + using (var statement = PrepareStatement(db, insertText.ToString())) { statement.TryBind("@ItemId", idBlob); @@ -5817,7 +5817,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { var list = new List(); - using (var statement = PrepareStatementSafe(connection, cmdText)) + using (var statement = PrepareStatement(connection, cmdText)) { statement.TryBind("@ItemId", query.ItemId.ToGuidBlob()); @@ -5902,7 +5902,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type insertText.Append(")"); } - using (var statement = PrepareStatementSafe(db, insertText.ToString())) + using (var statement = PrepareStatement(db, insertText.ToString())) { statement.TryBind("@ItemId", idBlob); diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 355755014c..6ac3989372 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -32,8 +32,13 @@ namespace Emby.Server.Implementations.Data /// Opens the connection to the database /// /// Task. - public void Initialize(IUserManager userManager) + public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection) { + WriteLock.Dispose(); + WriteLock = dbLock; + WriteConnection?.Dispose(); + WriteConnection = dbConnection; + using (var connection = GetConnection()) { var userDatasTableExists = TableExists(connection, "UserDatas"); diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index efe56c0816..29afb9f646 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -348,9 +348,9 @@ namespace Emby.Server.Implementations.Security { using (var connection = GetConnection(true)) { - return connection.RunInTransaction(db => + return connection.RunInTransaction((Func)(db => { - using (var statement = PrepareStatementSafe(db, "select CustomName from Devices where Id=@DeviceId")) + using (var statement = base.PrepareStatement((IDatabaseConnection)db, (string)"select CustomName from Devices where Id=@DeviceId")) { statement.TryBind("@DeviceId", deviceId); @@ -367,7 +367,7 @@ namespace Emby.Server.Implementations.Security return result; } - }, ReadTransactionMode); + }), ReadTransactionMode); } } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 11c09db983..9454b28d6d 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -128,10 +128,6 @@ namespace Jellyfin.Server #pragma warning restore CA5359 Batteries_V2.Init(); - if (raw.sqlite3_enable_shared_cache(1) != raw.SQLITE_OK) - { - Console.WriteLine("WARN: Failed to enable shared cache for SQLite"); - } using (var appHost = new CoreAppHost( appPaths, -- cgit v1.2.3 From 02b864e41b80c07941d262d9d3723c0e7967d557 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 2 Apr 2019 22:18:13 +0200 Subject: Last line? --- Emby.Server.Implementations/Activity/ActivityRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index cac1a9feb4..e45add355f 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Activity { public class ActivityRepository : BaseSqliteRepository, IActivityRepository { - private static readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); private readonly IFileSystem _fileSystem; public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem) -- cgit v1.2.3 From d00ad28efd10e2bb312c9a08055f83df26065494 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 3 Apr 2019 13:46:07 +0200 Subject: Address comments --- .../Activity/ActivityRepository.cs | 2 +- .../Data/BaseSqliteRepository.cs | 2 +- .../Data/SqliteItemRepository.cs | 24 ++++++++++++++-------- .../Data/SqliteUserDataRepository.cs | 5 ----- .../Security/AuthenticationRepository.cs | 8 ++++---- 5 files changed, 21 insertions(+), 20 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index e45add355f..f8a1b32afe 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.Activity var list = new List(); var result = new QueryResult(); - var statements = PrepareAllSafe(db, statementTexts).ToList(); + var statements = PrepareAll(db, statementTexts).ToList(); using (var statement = statements[0]) { diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 63cef80b04..c5af156bbd 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.Data public IStatement PrepareStatement(IDatabaseConnection connection, string sql) => connection.PrepareStatement(sql); - public IEnumerable PrepareAllSafe(IDatabaseConnection connection, IEnumerable sql) + public IEnumerable PrepareAll(IDatabaseConnection connection, IEnumerable sql) => sql.Select(connection.PrepareStatement); protected bool TableExists(ManagedConnection connection, string name) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 462d91e41f..5dc104347a 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -518,10 +518,11 @@ namespace Emby.Server.Implementations.Data { saveItemCommandCommandText += ","; } + saveItemCommandCommandText += "@" + saveColumns[i]; } - saveItemCommandCommandText += ")"; - return saveItemCommandCommandText; + + return saveItemCommandCommandText + ")"; } /// @@ -551,16 +552,16 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection()) { - connection.RunInTransaction((Action)(db => + connection.RunInTransaction(db => { - using (var saveImagesStatement = base.PrepareStatement((IDatabaseConnection)db, (string)"Update TypedBaseItems set Images=@Images where guid=@Id")) + using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) { saveImagesStatement.TryBind("@Id", item.Id.ToGuidBlob()); saveImagesStatement.TryBind("@Images", SerializeImages(item)); saveImagesStatement.MoveNext(); } - }), TransactionMode); + }, TransactionMode); } } @@ -611,7 +612,7 @@ namespace Emby.Server.Implementations.Data private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List, BaseItem, string, List)> tuples) { - var statements = PrepareAllSafe(db, new string[] + var statements = PrepareAll(db, new string[] { GetSaveItemCommandText(), "delete from AncestorIds where ItemId=@ItemId" @@ -990,6 +991,7 @@ namespace Emby.Server.Implementations.Data { albumArtists = string.Join("|", hasAlbumArtists.AlbumArtists); } + saveItemStatement.TryBind("@AlbumArtists", albumArtists); saveItemStatement.TryBind("@ExternalId", item.ExternalId); @@ -1026,6 +1028,7 @@ namespace Emby.Server.Implementations.Data { continue; } + str.Append($"{i.Key}={i.Value}|"); } @@ -1033,6 +1036,7 @@ namespace Emby.Server.Implementations.Data { return null; } + str.Length -= 1; // Remove last | return str.ToString(); } @@ -1070,6 +1074,7 @@ namespace Emby.Server.Implementations.Data { return null; } + StringBuilder str = new StringBuilder(); foreach (var i in images) { @@ -1079,6 +1084,7 @@ namespace Emby.Server.Implementations.Data } str.Append(ToValueString(i) + "|"); } + str.Length -= 1; // Remove last | return str.ToString(); } @@ -2823,7 +2829,7 @@ namespace Emby.Server.Implementations.Data return connection.RunInTransaction(db => { var result = new QueryResult(); - var statements = PrepareAllSafe(db, statementTexts).ToList(); + var statements = PrepareAll(db, statementTexts).ToList(); if (!isReturningZeroItems) { @@ -3235,7 +3241,7 @@ namespace Emby.Server.Implementations.Data { var result = new QueryResult(); - var statements = PrepareAllSafe(db, statementTexts).ToList(); + var statements = PrepareAll(db, statementTexts).ToList(); if (!isReturningZeroItems) { @@ -5436,7 +5442,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type var list = new List<(BaseItem, ItemCounts)>(); var result = new QueryResult<(BaseItem, ItemCounts)>(); - var statements = PrepareAllSafe(db, statementTexts).ToList(); + var statements = PrepareAll(db, statementTexts).ToList(); if (!isReturningZeroItems) { diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 6ac3989372..0580203c56 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -380,10 +380,5 @@ namespace Emby.Server.Implementations.Data return userData; } - - protected override void Dispose(bool dispose) - { - // handled by library database - } } } diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 29afb9f646..c8ecd7e6e6 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Security statementTexts.Add(commandText); statementTexts.Add("select count (Id) from Tokens" + whereTextWithoutPaging); - var statements = PrepareAllSafe(db, statementTexts) + var statements = PrepareAll(db, statementTexts) .ToList(); using (var statement = statements[0]) @@ -348,9 +348,9 @@ namespace Emby.Server.Implementations.Security { using (var connection = GetConnection(true)) { - return connection.RunInTransaction((Func)(db => + return connection.RunInTransaction(db => { - using (var statement = base.PrepareStatement((IDatabaseConnection)db, (string)"select CustomName from Devices where Id=@DeviceId")) + using (var statement = base.PrepareStatement(db, "select CustomName from Devices where Id=@DeviceId")) { statement.TryBind("@DeviceId", deviceId); @@ -367,7 +367,7 @@ namespace Emby.Server.Implementations.Security return result; } - }), ReadTransactionMode); + }, ReadTransactionMode); } } -- cgit v1.2.3 From edfd2d0cd959020cdcd2196320850d28ae10094d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 3 Apr 2019 14:22:17 +0200 Subject: Fix startup --- Emby.Server.Implementations/ApplicationHost.cs | 11 +++++---- .../Data/SqliteUserRepository.cs | 27 ++++++++++------------ 2 files changed, 18 insertions(+), 20 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 62cc6ec477..3aa2dbf9a6 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -754,10 +754,6 @@ namespace Emby.Server.Implementations UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager); serviceCollection.AddSingleton(UserDataManager); - UserRepository = GetUserRepository(); - // This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it - serviceCollection.AddSingleton(UserRepository); - var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager); serviceCollection.AddSingleton(displayPreferencesRepo); @@ -767,6 +763,8 @@ namespace Emby.Server.Implementations AuthenticationRepository = GetAuthenticationRepository(); serviceCollection.AddSingleton(AuthenticationRepository); + UserRepository = GetUserRepository(); + UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager); serviceCollection.AddSingleton(UserManager); @@ -807,7 +805,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(TVSeriesManager); DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager); - serviceCollection.AddSingleton(DeviceManager); MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); @@ -1893,8 +1890,12 @@ namespace Emby.Server.Implementations Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name); } } + + UserRepository.Dispose(); } + UserRepository = null; + _disposed = true; } } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index e79b3d6015..a0c6d2903b 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.Data TryMigrateToLocalUsersTable(connection); } - RemoveEmptyPasswordHashes(); + RemoveEmptyPasswordHashes(connection); } } @@ -75,10 +75,12 @@ namespace Emby.Server.Implementations.Data } } - private void RemoveEmptyPasswordHashes() + private void RemoveEmptyPasswordHashes(ManagedConnection connection) { - foreach (var user in RetrieveAllUsers()) + foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) { + var user = GetUser(row); + // If the user password is the sha1 hash of the empty string, remove it if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) && !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) @@ -89,21 +91,16 @@ namespace Emby.Server.Implementations.Data user.Password = null; var serialized = _jsonSerializer.SerializeToBytes(user); - using (var connection = GetConnection()) + connection.RunInTransaction(db => { - connection.RunInTransaction(db => + using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - - }, TransactionMode); - } + statement.TryBind("@InternalId", user.InternalId); + statement.TryBind("@data", serialized); + statement.MoveNext(); + } + }, TransactionMode); } - } /// -- cgit v1.2.3 From 7898af4cebe58bc11d120552594098041fff56fb Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 3 Apr 2019 17:34:54 +0200 Subject: Reworked PRAGMA statements use --- .../Activity/ActivityRepository.cs | 2 - .../Data/BaseSqliteRepository.cs | 90 +++++++++++----------- .../Data/SqliteDisplayPreferencesRepository.cs | 2 - .../Data/SqliteItemRepository.cs | 4 +- .../Data/SqliteUserDataRepository.cs | 2 - .../Data/SqliteUserRepository.cs | 2 - .../Security/AuthenticationRepository.cs | 2 - 7 files changed, 45 insertions(+), 59 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index f8a1b32afe..de46ab9655 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -45,8 +45,6 @@ namespace Emby.Server.Implementations.Activity { using (var connection = GetConnection()) { - RunDefaultInitialization(connection); - connection.RunQueries(new[] { "create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)", diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index c5af156bbd..4da6665c2c 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -9,27 +9,37 @@ namespace Emby.Server.Implementations.Data { public abstract class BaseSqliteRepository : IDisposable { - protected string DbFilePath { get; set; } - - protected ILogger Logger { get; } + private bool _disposed = false; protected BaseSqliteRepository(ILogger logger) { Logger = logger; } + protected string DbFilePath { get; set; } + + protected ILogger Logger { get; } + + protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.NoMutex; + protected TransactionMode TransactionMode => TransactionMode.Deferred; protected TransactionMode ReadTransactionMode => TransactionMode.Deferred; - protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.NoMutex; + protected virtual int? CacheSize => null; + + protected virtual string JournalMode => "WAL"; + + protected virtual int? PageSize => null; + + protected virtual TempStoreMode TempStore => TempStoreMode.Default; + + protected virtual SynchronousMode? Synchronous => null; protected SemaphoreSlim WriteLock = new SemaphoreSlim(1, 1); protected SQLiteDatabaseConnection WriteConnection; - private string _defaultWal; - protected ManagedConnection GetConnection(bool _ = false) { WriteLock.Wait(); @@ -43,23 +53,28 @@ namespace Emby.Server.Implementations.Data DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite, null); - - if (string.IsNullOrWhiteSpace(_defaultWal)) + if (CacheSize.HasValue) { - _defaultWal = WriteConnection.Query("PRAGMA journal_mode").SelectScalarString().First(); + WriteConnection.Execute("PRAGMA cache_size=" + (int)CacheSize.Value); + } - Logger.LogInformation("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal); + if (!string.IsNullOrWhiteSpace(JournalMode)) + { + WriteConnection.Execute("PRAGMA journal_mode=" + JournalMode); } - if (EnableTempStoreMemory) + if (Synchronous.HasValue) { - WriteConnection.Execute("PRAGMA temp_store = memory"); + WriteConnection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value); } - else + + if (PageSize.HasValue) { - WriteConnection.Execute("PRAGMA temp_store = file"); + WriteConnection.Execute("PRAGMA page_size=" + (int)PageSize.Value); } + WriteConnection.Execute("PRAGMA temp_store=" + (int)TempStore); + return new ManagedConnection(WriteConnection, WriteLock); } @@ -92,38 +107,6 @@ namespace Emby.Server.Implementations.Data }, ReadTransactionMode); } - protected void RunDefaultInitialization(ManagedConnection db) - { - var queries = new List - { - "PRAGMA journal_mode=WAL", - "PRAGMA page_size=4096", - "PRAGMA synchronous=Normal" - }; - - if (EnableTempStoreMemory) - { - queries.AddRange(new List - { - "pragma default_temp_store = memory", - "pragma temp_store = memory" - }); - } - else - { - queries.AddRange(new List - { - "pragma temp_store = file" - }); - } - - db.ExecuteAll(string.Join(";", queries)); - Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First()); - } - - protected virtual bool EnableTempStoreMemory => true; - - private bool _disposed; protected void CheckDisposed() { if (_disposed) @@ -199,4 +182,19 @@ namespace Emby.Server.Implementations.Data connection.Execute("alter table " + table + " add column " + columnName + " " + type + " NULL"); } } + + public enum SynchronousMode + { + Off = 0, + Normal = 1, + Full = 2, + Extra = 3 + } + + public enum TempStoreMode + { + Default = 0, + File = 1, + Memory = 2 + } } diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 1d44b0b292..01ef9851dc 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -63,8 +63,6 @@ namespace Emby.Server.Implementations.Data { using (var connection = GetConnection()) { - RunDefaultInitialization(connection); - string[] queries = { "create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)", diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 5dc104347a..8a56e16bb7 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Data private const string ChaptersTableName = "Chapters2"; - protected override bool EnableTempStoreMemory => true; + protected override TempStoreMode TempStore => TempStoreMode.Memory; /// /// Opens the connection to the database @@ -101,8 +101,6 @@ namespace Emby.Server.Implementations.Data { using (var connection = GetConnection()) { - RunDefaultInitialization(connection); - const string createMediaStreamsTableCommand = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))"; diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 0580203c56..4035bb99d1 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -128,8 +128,6 @@ namespace Emby.Server.Implementations.Data return list; } - protected override bool EnableTempStoreMemory => true; - /// /// Saves the user data. /// diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index a0c6d2903b..cd364e7f44 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -42,8 +42,6 @@ namespace Emby.Server.Implementations.Data { using (var connection = GetConnection()) { - RunDefaultInitialization(connection); - var localUsersTableExists = TableExists(connection, "LocalUsersv2"); connection.RunQueries(new[] { diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index c8ecd7e6e6..545e11bf9f 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -25,8 +25,6 @@ namespace Emby.Server.Implementations.Security { using (var connection = GetConnection()) { - RunDefaultInitialization(connection); - var tableNewlyCreated = !TableExists(connection, "Tokens"); string[] queries = { -- cgit v1.2.3 From db2765aae5556fd4e05e1c310027bdbd699327d2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 3 Apr 2019 18:02:43 +0200 Subject: Last bit of cleanup --- .../Data/BaseSqliteRepository.cs | 56 +++++++++++----------- 1 file changed, 27 insertions(+), 29 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 4da6665c2c..7938d6b7e0 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -107,6 +107,33 @@ namespace Emby.Server.Implementations.Data }, ReadTransactionMode); } + protected List GetColumnNames(IDatabaseConnection connection, string table) + { + var list = new List(); + + foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) + { + if (row[1].SQLiteType != SQLiteType.Null) + { + var name = row[1].ToString(); + + list.Add(name); + } + } + + return list; + } + + protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List existingColumnNames) + { + if (existingColumnNames.Contains(columnName, StringComparer.OrdinalIgnoreCase)) + { + return; + } + + connection.Execute("alter table " + table + " add column " + columnName + " " + type + " NULL"); + } + protected void CheckDisposed() { if (_disposed) @@ -121,8 +148,6 @@ namespace Emby.Server.Implementations.Data GC.SuppressFinalize(this); } - private readonly object _disposeLock = new object(); - /// /// Releases unmanaged and - optionally - managed resources. /// @@ -154,33 +179,6 @@ namespace Emby.Server.Implementations.Data _disposed = true; } - - protected List GetColumnNames(IDatabaseConnection connection, string table) - { - var list = new List(); - - foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) - { - if (row[1].SQLiteType != SQLiteType.Null) - { - var name = row[1].ToString(); - - list.Add(name); - } - } - - return list; - } - - protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List existingColumnNames) - { - if (existingColumnNames.Contains(columnName, StringComparer.OrdinalIgnoreCase)) - { - return; - } - - connection.Execute("alter table " + table + " add column " + columnName + " " + type + " NULL"); - } } public enum SynchronousMode -- cgit v1.2.3 From d961278b3dcab57910b260115cd45d9831e6443e Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 3 Apr 2019 18:15:04 +0200 Subject: Reduce amount of raw sql --- .../Data/SqliteUserRepository.cs | 23 ++++++------ Emby.Server.Implementations/Library/UserManager.cs | 42 +++++++++++----------- 2 files changed, 34 insertions(+), 31 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index cd364e7f44..de2354eefc 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -75,10 +75,8 @@ namespace Emby.Server.Implementations.Data private void RemoveEmptyPasswordHashes(ManagedConnection connection) { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) + foreach (var user in RetrieveAllUsers(connection)) { - var user = GetUser(row); - // If the user password is the sha1 hash of the empty string, remove it if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) && !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) @@ -198,17 +196,22 @@ namespace Emby.Server.Implementations.Data /// IEnumerable{User}. public List RetrieveAllUsers() { - var list = new List(); - using (var connection = GetConnection(true)) { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) - { - list.Add(GetUser(row)); - } + return new List(RetrieveAllUsers(connection)); } + } - return list; + /// + /// Retrieve all users from the database + /// + /// IEnumerable{User}. + private IEnumerable RetrieveAllUsers(ManagedConnection connection) + { + foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) + { + yield return GetUser(row); + } } /// diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index ff375e5900..1701ced426 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -222,9 +222,8 @@ namespace Emby.Server.Implementations.Library public void Initialize() { - _users = LoadUsers(); - - var users = Users.ToList(); + var users = LoadUsers(); + _users = users.ToArray(); // If there are no local users with admin rights, make them all admins if (!users.Any(i => i.Policy.IsAdministrator)) @@ -555,35 +554,36 @@ namespace Emby.Server.Implementations.Library /// Loads the users from the repository /// /// IEnumerable{User}. - private User[] LoadUsers() + private List LoadUsers() { var users = UserRepository.RetrieveAllUsers(); // There always has to be at least one user. - if (users.Count == 0) + if (users.Count != 0) { - var defaultName = Environment.UserName; - if (string.IsNullOrWhiteSpace(defaultName)) - { - defaultName = "MyJellyfinUser"; - } - var name = MakeValidUsername(defaultName); + return users; + } - var user = InstantiateNewUser(name); + var defaultName = Environment.UserName; + if (string.IsNullOrWhiteSpace(defaultName)) + { + defaultName = "MyJellyfinUser"; + } - user.DateLastSaved = DateTime.UtcNow; + var name = MakeValidUsername(defaultName); - UserRepository.CreateUser(user); + var user = InstantiateNewUser(name); + + user.DateLastSaved = DateTime.UtcNow; - users.Add(user); + UserRepository.CreateUser(user); - user.Policy.IsAdministrator = true; - user.Policy.EnableContentDeletion = true; - user.Policy.EnableRemoteControlOfOtherUsers = true; - UpdateUserPolicy(user, user.Policy, false); - } + user.Policy.IsAdministrator = true; + user.Policy.EnableContentDeletion = true; + user.Policy.EnableRemoteControlOfOtherUsers = true; + UpdateUserPolicy(user, user.Policy, false); - return users.ToArray(); + return new List { user }; } public UserDto GetUserDto(User user, string remoteEndPoint = null) -- cgit v1.2.3 From ecb8d8991b1ea4e1e26a69c7c9e0a217927d27d4 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 28 Jun 2019 12:22:33 +0200 Subject: Fix whitespace --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- Emby.Server.Implementations/Updates/InstallationManager.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 6c50698b2a..48c915edee 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -48,7 +48,7 @@ - latest + latest diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index b3d76124bd..9bc85633d4 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -433,7 +433,6 @@ namespace Emby.Server.Implementations.Updates IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase)) ?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase)); - string targetPath = plugin == null ? null : plugin.AssemblyFilePath; // Do the install -- cgit v1.2.3 From 45c13141f9ac3da07f4d15c1fb90442d3eb855e2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 1 Jul 2019 17:59:01 +0200 Subject: Address comments --- .../Data/BaseSqliteRepository.cs | 15 +++++---- .../Data/SqliteItemRepository.cs | 38 +++++++++++----------- 2 files changed, 27 insertions(+), 26 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 7938d6b7e0..6e061c1548 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -36,9 +36,9 @@ namespace Emby.Server.Implementations.Data protected virtual SynchronousMode? Synchronous => null; - protected SemaphoreSlim WriteLock = new SemaphoreSlim(1, 1); + protected SemaphoreSlim WriteLock { get; set; } = new SemaphoreSlim(1, 1); - protected SQLiteDatabaseConnection WriteConnection; + protected SQLiteDatabaseConnection WriteConnection { get; set; } protected ManagedConnection GetConnection(bool _ = false) { @@ -55,7 +55,7 @@ namespace Emby.Server.Implementations.Data if (CacheSize.HasValue) { - WriteConnection.Execute("PRAGMA cache_size=" + (int)CacheSize.Value); + WriteConnection.Execute("PRAGMA cache_size=" + CacheSize.Value); } if (!string.IsNullOrWhiteSpace(JournalMode)) @@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.Data if (PageSize.HasValue) { - WriteConnection.Execute("PRAGMA page_size=" + (int)PageSize.Value); + WriteConnection.Execute("PRAGMA page_size=" + PageSize.Value); } WriteConnection.Execute("PRAGMA temp_store=" + (int)TempStore); @@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Data protected List GetColumnNames(IDatabaseConnection connection, string table) { - var list = new List(); + var columnNames = new List(); foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) { @@ -117,11 +117,11 @@ namespace Emby.Server.Implementations.Data { var name = row[1].ToString(); - list.Add(name); + columnNames.Add(name); } } - return list; + return columnNames; } protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List existingColumnNames) @@ -142,6 +142,7 @@ namespace Emby.Server.Implementations.Data } } + /// public void Dispose() { Dispose(true); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 8a56e16bb7..1cefcec7ca 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -36,13 +36,9 @@ namespace Emby.Server.Implementations.Data /// public class SqliteItemRepository : BaseSqliteRepository, IItemRepository { - private readonly TypeMapper _typeMapper; + private const string ChaptersTableName = "Chapters2"; - /// - /// Gets the name of the repository - /// - /// The name. - public string Name => "SQLite"; + private readonly TypeMapper _typeMapper; /// /// Gets the json serializer. @@ -54,12 +50,9 @@ namespace Emby.Server.Implementations.Data /// The _app paths /// private readonly IServerConfigurationManager _config; - private IServerApplicationHost _appHost; - + private readonly IServerApplicationHost _appHost; private readonly ILocalizationManager _localization; - public IImageProcessor ImageProcessor { get; set; } - /// /// Initializes a new instance of the class. /// @@ -90,10 +83,17 @@ namespace Emby.Server.Implementations.Data DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db"); } - private const string ChaptersTableName = "Chapters2"; + /// + public string Name => "SQLite"; + + /// + protected override int? CacheSize => 20000; + /// protected override TempStoreMode TempStore => TempStoreMode.Memory; + public IImageProcessor ImageProcessor { get; set; } + /// /// Opens the connection to the database /// @@ -1903,7 +1903,7 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection(true)) { - var list = new List(); + var chapters = new List(); using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc")) { @@ -1911,11 +1911,11 @@ namespace Emby.Server.Implementations.Data foreach (var row in statement.ExecuteQuery()) { - list.Add(GetChapter(row, item)); + chapters.Add(GetChapter(row, item)); } } - return list; + return chapters; } } @@ -2606,7 +2606,7 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection(true)) { - var list = new List(); + var items = new List(); using (var statement = PrepareStatement(connection, commandText)) { @@ -2634,7 +2634,7 @@ namespace Emby.Server.Implementations.Data var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); if (item != null) { - list.Add(item); + items.Add(item); } } } @@ -2646,7 +2646,7 @@ namespace Emby.Server.Implementations.Data limit -= 4; var newList = new List(); - foreach (var item in list) + foreach (var item in items) { AddItem(newList, item); @@ -2656,12 +2656,12 @@ namespace Emby.Server.Implementations.Data } } - list = newList; + items = newList; } LogQueryTime("GetItemList", commandText, now); - return list; + return items; } } -- cgit v1.2.3 From 29ae7b9aeb46ac297ba5c3253d73cd4cd325c042 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 1 Jul 2019 18:24:35 +0200 Subject: Add docs --- .../Data/BaseSqliteRepository.cs | 86 ++++++++++++++++++++++ 1 file changed, 86 insertions(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 6e061c1548..9bc0bb9456 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -16,28 +16,78 @@ namespace Emby.Server.Implementations.Data Logger = logger; } + /// + /// Gets or sets the path to the DB file. + /// + /// Path to the DB file. protected string DbFilePath { get; set; } + /// + /// Gets the logger. + /// + /// The logger. protected ILogger Logger { get; } + /// + /// Gets the default connection flags. + /// + /// The default connection flags. protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.NoMutex; + /// + /// Gets the transaction mode. + /// + /// The transaction mode.> protected TransactionMode TransactionMode => TransactionMode.Deferred; + /// + /// Gets the transaction mode for read-only operations. + /// + /// The transaction mode. protected TransactionMode ReadTransactionMode => TransactionMode.Deferred; + /// + /// Gets the cache size. + /// + /// The cache size or null. protected virtual int? CacheSize => null; + /// + /// Gets the journal mode. + /// + /// The journal mode. protected virtual string JournalMode => "WAL"; + /// + /// Gets the page size. + /// + /// The page size or null. protected virtual int? PageSize => null; + /// + /// Gets the temp store mode. + /// + /// The temp store mode. + /// protected virtual TempStoreMode TempStore => TempStoreMode.Default; + /// + /// Gets the synchronous mode. + /// + /// The synchronous mode or null. + /// protected virtual SynchronousMode? Synchronous => null; + /// + /// Gets or sets the write lock. + /// + /// The write lock. protected SemaphoreSlim WriteLock { get; set; } = new SemaphoreSlim(1, 1); + /// + /// Gets or sets the write connection. + /// + /// The write connection. protected SQLiteDatabaseConnection WriteConnection { get; set; } protected ManagedConnection GetConnection(bool _ = false) @@ -182,18 +232,54 @@ namespace Emby.Server.Implementations.Data } } + /// + /// The disk synchronization mode, controls how aggressively SQLite will write data + /// all the way out to physical storage. + /// public enum SynchronousMode { + /// + /// SQLite continues without syncing as soon as it has handed data off to the operating system + /// Off = 0, + + /// + /// SQLite database engine will still sync at the most critical moments + /// Normal = 1, + + /// + /// SQLite database engine will use the xSync method of the VFS + /// to ensure that all content is safely written to the disk surface prior to continuing. + /// Full = 2, + + /// + /// EXTRA synchronous is like FULL with the addition that the directory containing a rollback journal + /// is synced after that journal is unlinked to commit a transaction in DELETE mode. + /// Extra = 3 } + /// + /// Storage mode used by temporary database files. + /// public enum TempStoreMode { + /// + /// The compile-time C preprocessor macro SQLITE_TEMP_STORE + /// is used to determine where temporary tables and indices are stored. + /// Default = 0, + + /// + /// Temporary tables and indices are stored in a file. + /// File = 1, + + /// + /// Temporary tables and indices are kept in as if they were pure in-memory databases memory. + /// Memory = 2 } } -- cgit v1.2.3 From 7587fe56d83fd6b3031cb11a91d94b9348c08846 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Thu, 4 Jul 2019 20:54:57 +0200 Subject: Moved VACUUM down to the end of the list. --- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 821c4b448a..eabe7d120a 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -202,8 +202,7 @@ namespace Emby.Server.Implementations.Data protected void RunDefaultInitialization(ManagedConnection db) { var queries = new List - { - "VACUUM", + { "PRAGMA journal_mode=WAL", "PRAGMA page_size=4096", "PRAGMA synchronous=Normal" @@ -224,6 +223,8 @@ namespace Emby.Server.Implementations.Data "pragma temp_store = file" }); } + // Configuration and pragmas can affect VACUUM so it needs to be last. + queries.Add("VACUUM"); db.ExecuteAll(string.Join(";", queries)); Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First()); -- cgit v1.2.3 From acf52b9b55ef16809b05092eda6aca2b4cafb964 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Thu, 4 Jul 2019 20:55:49 +0200 Subject: Cleanup extra spaces. --- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index eabe7d120a..aea37ffb17 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -202,7 +202,7 @@ namespace Emby.Server.Implementations.Data protected void RunDefaultInitialization(ManagedConnection db) { var queries = new List - { + { "PRAGMA journal_mode=WAL", "PRAGMA page_size=4096", "PRAGMA synchronous=Normal" -- cgit v1.2.3 From 0f897589ed6349bb3c88919b06861daa80aec1be Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 21 May 2019 19:28:34 +0200 Subject: Streamline authentication proccess --- .../Cryptography/CryptographyProvider.cs | 86 ++++--- .../Library/DefaultAuthenticationProvider.cs | 115 +++++---- .../Library/DefaultPasswordResetProvider.cs | 257 ++++++++++----------- .../Library/InvalidAuthProvider.cs | 11 +- Emby.Server.Implementations/Library/UserManager.cs | 49 ++-- MediaBrowser.Api/LiveTv/LiveTvService.cs | 19 -- .../Authentication/AuthenticationException.cs | 28 +++ .../Authentication/IAuthenticationProvider.cs | 3 +- .../Authentication/IPasswordResetProvider.cs | 1 + MediaBrowser.Model/Cryptography/ICryptoProvider.cs | 6 +- MediaBrowser.Model/Cryptography/PasswordHash.cs | 152 ++++++------ 11 files changed, 365 insertions(+), 362 deletions(-) create mode 100644 MediaBrowser.Controller/Authentication/AuthenticationException.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 6d7193ce20..f726dae2ee 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -8,7 +8,7 @@ using MediaBrowser.Model.Cryptography; namespace Emby.Server.Implementations.Cryptography { - public class CryptographyProvider : ICryptoProvider + public class CryptographyProvider : ICryptoProvider, IDisposable { private static readonly HashSet _supportedHashMethods = new HashSet() { @@ -28,26 +28,28 @@ namespace Emby.Server.Implementations.Cryptography "System.Security.Cryptography.SHA512" }; - public string DefaultHashMethod => "PBKDF2"; - private RandomNumberGenerator _randomNumberGenerator; private const int _defaultIterations = 1000; + private bool _disposed = false; + public CryptographyProvider() { - //FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto - //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 - //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one - //Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 + // FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto + // Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 + // there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one + // Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 _randomNumberGenerator = RandomNumberGenerator.Create(); } + public string DefaultHashMethod => "PBKDF2"; + + [Obsolete("Use System.Security.Cryptography.MD5 directly")] public Guid GetMD5(string str) - { - return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); - } + => new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); + [Obsolete("Use System.Security.Cryptography.SHA1 directly")] public byte[] ComputeSHA1(byte[] bytes) { using (var provider = SHA1.Create()) @@ -56,6 +58,7 @@ namespace Emby.Server.Implementations.Cryptography } } + [Obsolete("Use System.Security.Cryptography.MD5 directly")] public byte[] ComputeMD5(Stream str) { using (var provider = MD5.Create()) @@ -64,6 +67,7 @@ namespace Emby.Server.Implementations.Cryptography } } + [Obsolete("Use System.Security.Cryptography.MD5 directly")] public byte[] ComputeMD5(byte[] bytes) { using (var provider = MD5.Create()) @@ -73,9 +77,7 @@ namespace Emby.Server.Implementations.Cryptography } public IEnumerable GetSupportedHashMethods() - { - return _supportedHashMethods; - } + => _supportedHashMethods; private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) { @@ -93,14 +95,10 @@ namespace Emby.Server.Implementations.Cryptography } public byte[] ComputeHash(string hashMethod, byte[] bytes) - { - return ComputeHash(hashMethod, bytes, Array.Empty()); - } + => ComputeHash(hashMethod, bytes, Array.Empty()); public byte[] ComputeHashWithDefaultMethod(byte[] bytes) - { - return ComputeHash(DefaultHashMethod, bytes); - } + => ComputeHash(DefaultHashMethod, bytes); public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt) { @@ -125,37 +123,27 @@ namespace Emby.Server.Implementations.Cryptography } } } - else - { - throw new CryptographicException($"Requested hash method is not supported: {hashMethod}"); - } + + throw new CryptographicException($"Requested hash method is not supported: {hashMethod}"); + } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) - { - return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations); - } + => PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations); public byte[] ComputeHash(PasswordHash hash) { int iterations = _defaultIterations; if (!hash.Parameters.ContainsKey("iterations")) { - hash.Parameters.Add("iterations", _defaultIterations.ToString(CultureInfo.InvariantCulture)); + hash.Parameters.Add("iterations", iterations.ToString(CultureInfo.InvariantCulture)); } - else + else if (!int.TryParse(hash.Parameters["iterations"], out iterations)) { - try - { - iterations = int.Parse(hash.Parameters["iterations"]); - } - catch (Exception e) - { - throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e); - } + throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}"); } - return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations); + return PBKDF2(hash.Id, hash.Hash, hash.Salt, iterations); } public byte[] GenerateSalt() @@ -164,5 +152,29 @@ namespace Emby.Server.Implementations.Cryptography _randomNumberGenerator.GetBytes(salt); return salt; } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _randomNumberGenerator.Dispose(); + } + + _randomNumberGenerator = null; + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index fe09b07ff6..b07244fda1 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -11,9 +11,9 @@ namespace Emby.Server.Implementations.Library public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser { private readonly ICryptoProvider _cryptographyProvider; - public DefaultAuthenticationProvider(ICryptoProvider crypto) + public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider) { - _cryptographyProvider = crypto; + _cryptographyProvider = cryptographyProvider; } public string Name => "Default"; @@ -28,17 +28,17 @@ namespace Emby.Server.Implementations.Library throw new NotImplementedException(); } - // This is the verson that we need to use for local users. Because reasons. + // This is the version that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) { bool success = false; if (resolvedUser == null) { - throw new Exception("Invalid username or password"); + throw new ArgumentNullException(nameof(resolvedUser)); } // As long as jellyfin supports passwordless users, we need this little block here to accomodate - if (IsPasswordEmpty(resolvedUser, password)) + if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password)) { return Task.FromResult(new ProviderAuthenticationResult { @@ -50,37 +50,24 @@ namespace Emby.Server.Implementations.Library byte[] passwordbytes = Encoding.UTF8.GetBytes(password); PasswordHash readyHash = new PasswordHash(resolvedUser.Password); - byte[] calculatedHash; - string calculatedHashString; - if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id) + if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) + || _cryptographyProvider.DefaultHashMethod == readyHash.Id) { - if (string.IsNullOrEmpty(readyHash.Salt)) - { - calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); - calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty); - } - else - { - calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); - calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty); - } + byte[] calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.Salt); - if (calculatedHashString == readyHash.Hash) + if (calculatedHash.SequenceEqual(readyHash.Hash)) { success = true; - // throw new Exception("Invalid username or password"); } } else { - throw new Exception(string.Format($"Requested crypto method not available in provider: {readyHash.Id}")); + throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}"); } - // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); - if (!success) { - throw new Exception("Invalid username or password"); + throw new AuthenticationException("Invalid username or password"); } return Task.FromResult(new ProviderAuthenticationResult @@ -98,29 +85,22 @@ namespace Emby.Server.Implementations.Library return; } - if (!user.Password.Contains("$")) + if (user.Password.IndexOf('$') == -1) { string hash = user.Password; user.Password = string.Format("$SHA1${0}", hash); } - if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) + if (user.EasyPassword != null + && user.EasyPassword.IndexOf('$') == -1) { string hash = user.EasyPassword; user.EasyPassword = string.Format("$SHA1${0}", hash); } } - public Task HasPassword(User user) - { - var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); - return Task.FromResult(hasConfiguredPassword); - } - - private bool IsPasswordEmpty(User user, string password) - { - return (string.IsNullOrEmpty(user.Password) && string.IsNullOrEmpty(password)); - } + public bool HasPassword(User user) + => !string.IsNullOrEmpty(user.Password); public Task ChangePassword(User user, string newPassword) { @@ -129,30 +109,24 @@ namespace Emby.Server.Implementations.Library if (string.IsNullOrEmpty(user.Password)) { PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider); - newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); - newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes); + newPasswordHash.Salt = _cryptographyProvider.GenerateSalt(); newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod; - newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash); + newPasswordHash.Hash = GetHashedChangeAuth(newPassword, newPasswordHash); user.Password = newPasswordHash.ToString(); return Task.CompletedTask; } PasswordHash passwordHash = new PasswordHash(user.Password); - if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) + if (passwordHash.Id == "SHA1" + && passwordHash.Salt.Length == 0) { - passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); - passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); + passwordHash.Salt = _cryptographyProvider.GenerateSalt(); passwordHash.Id = _cryptographyProvider.DefaultHashMethod; - passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); + passwordHash.Hash = GetHashedChangeAuth(newPassword, passwordHash); } else if (newPassword != null) { - passwordHash.Hash = GetHashedString(user, newPassword); - } - - if (string.IsNullOrWhiteSpace(passwordHash.Hash)) - { - throw new ArgumentNullException(nameof(passwordHash.Hash)); + passwordHash.Hash = GetHashed(user, newPassword); } user.Password = passwordHash.ToString(); @@ -160,11 +134,6 @@ namespace Emby.Server.Implementations.Library return Task.CompletedTask; } - public string GetPasswordHash(User user) - { - return user.Password; - } - public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) { ConvertPasswordFormat(user); @@ -190,13 +159,13 @@ namespace Emby.Server.Implementations.Library return string.IsNullOrEmpty(user.EasyPassword) ? null - : (new PasswordHash(user.EasyPassword)).Hash; + : PasswordHash.ConvertToByteString(new PasswordHash(user.EasyPassword).Hash); } - public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) + internal byte[] GetHashedChangeAuth(string newPassword, PasswordHash passwordHash) { - passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); + passwordHash.Hash = Encoding.UTF8.GetBytes(newPassword); + return _cryptographyProvider.ComputeHash(passwordHash); } /// @@ -215,10 +184,10 @@ namespace Emby.Server.Implementations.Library passwordHash = new PasswordHash(user.Password); } - if (passwordHash.SaltBytes != null) + if (passwordHash.Salt != null) { // the password is modern format with PBKDF and we should take advantage of that - passwordHash.HashBytes = Encoding.UTF8.GetBytes(str); + passwordHash.Hash = Encoding.UTF8.GetBytes(str); return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); } else @@ -227,5 +196,31 @@ namespace Emby.Server.Implementations.Library return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))); } } + + public byte[] GetHashed(User user, string str) + { + PasswordHash passwordHash; + if (string.IsNullOrEmpty(user.Password)) + { + passwordHash = new PasswordHash(_cryptographyProvider); + } + else + { + ConvertPasswordFormat(user); + passwordHash = new PasswordHash(user.Password); + } + + if (passwordHash.Salt != null) + { + // the password is modern format with PBKDF and we should take advantage of that + passwordHash.Hash = Encoding.UTF8.GetBytes(str); + return _cryptographyProvider.ComputeHash(passwordHash); + } + else + { + // the password has no salt and should be called with the older method for safety + return _cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str)); + } + } } } diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index e218749d90..c7044820c7 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -1,132 +1,125 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -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; - -namespace Emby.Server.Implementations.Library -{ - public class DefaultPasswordResetProvider : IPasswordResetProvider - { - public string Name => "Default Password Reset Provider"; - - public bool IsEnabled => true; - - 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) - { - _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; - _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName); - _jsonSerializer = jsonSerializer; - _userManager = userManager; - _crypto = cryptoProvider; - } - - public async Task RedeemPasswordResetPin(string pin) - { - SerializablePasswordReset spr; - HashSet usersreset = new HashSet(); - foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) - { - using (var str = File.OpenRead(resetfile)) - { - spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); - } - - if (spr.ExpirationDate < DateTime.Now) - { - File.Delete(resetfile); - } - else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase)) - { - var resetUser = _userManager.GetUserByName(spr.UserName); - if (resetUser == null) - { - throw new Exception($"User with a username of {spr.UserName} not found"); - } - - await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); - File.Delete(resetfile); - } - } - - if (usersreset.Count < 1) - { - throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); - } - else - { - return new PinRedeemResult - { - Success = true, - UsersReset = usersreset.ToArray() - }; - } - } - - public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) - { - string pin = string.Empty; - using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create()) - { - byte[] bytes = new byte[4]; - cryptoRandom.GetBytes(bytes); - pin = BitConverter.ToString(bytes); - } - - DateTime expireTime = DateTime.Now.AddMinutes(30); - string filePath = _passwordResetFileBase + user.InternalId + ".json"; - SerializablePasswordReset spr = new SerializablePasswordReset - { - ExpirationDate = expireTime, - Pin = pin, - PinFile = filePath, - UserName = user.Name - }; - - try - { - using (FileStream fileStream = File.OpenWrite(filePath)) - { - _jsonSerializer.SerializeToStream(spr, fileStream); - await fileStream.FlushAsync().ConfigureAwait(false); - } - } - catch (Exception e) - { - throw new Exception($"Error serializing or writing password reset for {user.Name} to location: {filePath}", e); - } - - return new ForgotPasswordResult - { - Action = ForgotPasswordAction.PinCode, - PinExpirationDate = expireTime, - PinFile = filePath - }; - } - - private class SerializablePasswordReset : PasswordPinCreationResult - { - public string Pin { get; set; } - - public string UserName { get; set; } - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +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; + +namespace Emby.Server.Implementations.Library +{ + public class DefaultPasswordResetProvider : IPasswordResetProvider + { + public string Name => "Default Password Reset Provider"; + + public bool IsEnabled => true; + + 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) + { + _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; + _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName); + _jsonSerializer = jsonSerializer; + _userManager = userManager; + _crypto = cryptoProvider; + } + + public async Task RedeemPasswordResetPin(string pin) + { + SerializablePasswordReset spr; + HashSet usersreset = new HashSet(); + foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) + { + using (var str = File.OpenRead(resetfile)) + { + spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); + } + + if (spr.ExpirationDate < DateTime.Now) + { + File.Delete(resetfile); + } + else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase)) + { + var resetUser = _userManager.GetUserByName(spr.UserName); + if (resetUser == null) + { + throw new Exception($"User with a username of {spr.UserName} not found"); + } + + await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); + usersreset.Add(resetUser.Name); + File.Delete(resetfile); + } + } + + if (usersreset.Count < 1) + { + throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); + } + else + { + return new PinRedeemResult + { + Success = true, + UsersReset = usersreset.ToArray() + }; + } + } + + public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) + { + string pin = string.Empty; + using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create()) + { + byte[] bytes = new byte[4]; + cryptoRandom.GetBytes(bytes); + pin = BitConverter.ToString(bytes); + } + + DateTime expireTime = DateTime.Now.AddMinutes(30); + string filePath = _passwordResetFileBase + user.InternalId + ".json"; + SerializablePasswordReset spr = new SerializablePasswordReset + { + ExpirationDate = expireTime, + Pin = pin, + PinFile = filePath, + UserName = user.Name + }; + + using (FileStream fileStream = File.OpenWrite(filePath)) + { + _jsonSerializer.SerializeToStream(spr, fileStream); + await fileStream.FlushAsync().ConfigureAwait(false); + } + + return new ForgotPasswordResult + { + Action = ForgotPasswordAction.PinCode, + PinExpirationDate = expireTime, + PinFile = filePath + }; + } + + private class SerializablePasswordReset : PasswordPinCreationResult + { + public string Pin { get; set; } + + public string UserName { get; set; } + } + } +} diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs index 25d2331373..6956369dc1 100644 --- a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs +++ b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Entities; @@ -16,12 +13,12 @@ namespace Emby.Server.Implementations.Library public Task Authenticate(string username, string password) { - throw new SecurityException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); + throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); } - public Task HasPassword(User user) + public bool HasPassword(User user) { - return Task.FromResult(true); + return true; } public Task ChangePassword(User user, string newPassword) @@ -31,7 +28,7 @@ namespace Emby.Server.Implementations.Library public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) { - // Nothing here + // Nothing here } public string GetPasswordHash(User user) diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 1701ced426..c8c8a108d5 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -266,6 +266,7 @@ namespace Emby.Server.Implementations.Library builder.Append(c); } } + return builder.ToString(); } @@ -286,17 +287,17 @@ namespace Emby.Server.Implementations.Library if (user != null) { var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.Item1; - updatedUsername = authResult.Item2; - success = authResult.Item3; + authenticationProvider = authResult.authenticationProvider; + updatedUsername = authResult.username; + success = authResult.success; } else { // user is null var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.Item1; - updatedUsername = authResult.Item2; - success = authResult.Item3; + authenticationProvider = authResult.authenticationProvider; + updatedUsername = authResult.username; + success = authResult.success; if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) { @@ -331,22 +332,25 @@ namespace Emby.Server.Implementations.Library if (user == null) { - throw new SecurityException("Invalid username or password entered."); + throw new AuthenticationException("Invalid username or password entered."); } if (user.Policy.IsDisabled) { - throw new SecurityException(string.Format("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)) { - throw new SecurityException("Forbidden."); + throw new AuthenticationException("Forbidden."); } if (!user.IsParentalScheduleAllowed()) { - throw new SecurityException("User is not allowed access at this time."); + throw new AuthenticationException("User is not allowed access at this time."); } // Update LastActivityDate and LastLoginDate, then save @@ -357,6 +361,7 @@ namespace Emby.Server.Implementations.Library user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; UpdateUser(user); } + UpdateInvalidLoginAttemptCount(user, 0); } else @@ -429,7 +434,7 @@ namespace Emby.Server.Implementations.Library return providers; } - private async Task> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) + private async Task<(string username, bool success)> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) { try { @@ -444,23 +449,23 @@ namespace Emby.Server.Implementations.Library authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false); } - if(authenticationResult.Username != username) + if (authenticationResult.Username != username) { _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); username = authenticationResult.Username; } - return new Tuple(username, true); + return (username, true); } - catch (Exception ex) + catch (AuthenticationException ex) { - _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name); + _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name); - return new Tuple(username, false); + return (username, false); } } - private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) + 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; @@ -475,15 +480,15 @@ namespace Emby.Server.Implementations.Library if (password == null) { // legacy - success = string.Equals(GetAuthenticationProvider(user).GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + success = string.Equals(user.Password, hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); } else { foreach (var provider in GetAuthenticationProviders(user)) { var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - updatedUsername = providerAuthResult.Item1; - success = providerAuthResult.Item2; + updatedUsername = providerAuthResult.username; + success = providerAuthResult.success; if (success) { @@ -510,7 +515,7 @@ namespace Emby.Server.Implementations.Library } } - return new Tuple(authenticationProvider, username, success); + return (authenticationProvider, username, success); } private void UpdateInvalidLoginAttemptCount(User user, int newValue) @@ -593,7 +598,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(user)); } - bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; + bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user); bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user)); bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index e41ad540ad..8a4d6e2161 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -599,7 +599,6 @@ namespace MediaBrowser.Api.LiveTv { public bool ValidateLogin { get; set; } public bool ValidateListings { get; set; } - public string Pw { get; set; } } [Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")] @@ -867,28 +866,10 @@ namespace MediaBrowser.Api.LiveTv public async Task Post(AddListingProvider request) { - if (request.Pw != null) - { - request.Password = GetHashedString(request.Pw); - } - - request.Pw = null; - var result = await _liveTvManager.SaveListingProvider(request, request.ValidateLogin, request.ValidateListings).ConfigureAwait(false); return ToOptimizedResult(result); } - /// - /// Gets the hashed string. - /// - private string GetHashedString(string str) - { - // legacy - return BitConverter.ToString( - _cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))) - .Replace("-", string.Empty).ToLowerInvariant(); - } - public void Delete(DeleteListingProvider request) { _liveTvManager.DeleteListingsProvider(request.Id); diff --git a/MediaBrowser.Controller/Authentication/AuthenticationException.cs b/MediaBrowser.Controller/Authentication/AuthenticationException.cs new file mode 100644 index 0000000000..045cbcdaed --- /dev/null +++ b/MediaBrowser.Controller/Authentication/AuthenticationException.cs @@ -0,0 +1,28 @@ +using System; +namespace MediaBrowser.Controller.Authentication +{ + /// + /// The exception that is thrown when an attempt to authenticate fails. + /// + public class AuthenticationException : Exception + { + /// + public AuthenticationException() : base() + { + + } + + /// + public AuthenticationException(string message) : base(message) + { + + } + + /// + public AuthenticationException(string message, Exception innerException) + : base(message, innerException) + { + + } + } +} diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs index 2cf531eed5..f5571065f2 100644 --- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs +++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs @@ -9,10 +9,9 @@ namespace MediaBrowser.Controller.Authentication string Name { get; } bool IsEnabled { get; } Task Authenticate(string username, string password); - Task HasPassword(User user); + bool HasPassword(User user); Task ChangePassword(User user, string newPassword); void ChangeEasyPassword(User user, string newPassword, string newPasswordHash); - string GetPasswordHash(User user); string GetEasyPasswordHash(User user); } diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs index 9e5cd88160..2639960e76 100644 --- a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs +++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs @@ -12,6 +12,7 @@ namespace MediaBrowser.Controller.Authentication Task StartForgotPasswordProcess(User user, bool isInNetwork); Task RedeemPasswordResetPin(string pin); } + public class PasswordPinCreationResult { public string PinFile { get; set; } diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index 5988112c2e..9e85beb43c 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -6,9 +6,14 @@ namespace MediaBrowser.Model.Cryptography { public interface ICryptoProvider { + string DefaultHashMethod { get; } + [Obsolete("Use System.Security.Cryptography.MD5 directly")] Guid GetMD5(string str); + [Obsolete("Use System.Security.Cryptography.MD5 directly")] byte[] ComputeMD5(Stream str); + [Obsolete("Use System.Security.Cryptography.MD5 directly")] byte[] ComputeMD5(byte[] bytes); + [Obsolete("Use System.Security.Cryptography.SHA1 directly")] byte[] ComputeSHA1(byte[] bytes); IEnumerable GetSupportedHashMethods(); byte[] ComputeHash(string HashMethod, byte[] bytes); @@ -17,6 +22,5 @@ namespace MediaBrowser.Model.Cryptography byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); byte[] ComputeHash(PasswordHash hash); byte[] GenerateSalt(); - string DefaultHashMethod { get; } } } diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index f15b27d325..df32fdb003 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Text; namespace MediaBrowser.Model.Cryptography @@ -16,86 +17,71 @@ namespace MediaBrowser.Model.Cryptography private Dictionary _parameters = new Dictionary(); - private string _salt; + private byte[] _salt; - private byte[] _saltBytes; - - private string _hash; - - private byte[] _hashBytes; - - public string Id { get => _id; set => _id = value; } - - public Dictionary Parameters { get => _parameters; set => _parameters = value; } - - public string Salt { get => _salt; set => _salt = value; } - - public byte[] SaltBytes { get => _saltBytes; set => _saltBytes = value; } - - public string Hash { get => _hash; set => _hash = value; } - - public byte[] HashBytes { get => _hashBytes; set => _hashBytes = value; } + private byte[] _hash; public PasswordHash(string storageString) { string[] splitted = storageString.Split('$'); - _id = splitted[1]; - if (splitted[2].Contains("=")) + // The string should at least contain the hash function and the hash itself + if (splitted.Length < 3) + { + throw new ArgumentException("String doesn't contain enough segments", nameof(storageString)); + } + + // Start at 1, the first index shouldn't contain any data + int index = 1; + + // Name of the hash function + _id = splitted[index++]; + + // Optional parameters + if (splitted[index].IndexOf('=') != -1) { - foreach (string paramset in (splitted[2].Split(','))) + foreach (string paramset in splitted[index++].Split(',')) { - if (!string.IsNullOrEmpty(paramset)) + if (string.IsNullOrEmpty(paramset)) { - string[] fields = paramset.Split('='); - if (fields.Length == 2) - { - _parameters.Add(fields[0], fields[1]); - } - else - { - throw new Exception($"Malformed parameter in password hash string {paramset}"); - } + continue; } + + string[] fields = paramset.Split('='); + if (fields.Length != 2) + { + throw new InvalidDataException($"Malformed parameter in password hash string {paramset}"); + } + + _parameters.Add(fields[0], fields[1]); } - if (splitted.Length == 5) - { - _salt = splitted[3]; - _saltBytes = ConvertFromByteString(_salt); - _hash = splitted[4]; - _hashBytes = ConvertFromByteString(_hash); - } - else - { - _salt = string.Empty; - _hash = splitted[3]; - _hashBytes = ConvertFromByteString(_hash); - } + } + + // Check if the string also contains a salt + if (splitted.Length - index == 2) + { + _salt = ConvertFromByteString(splitted[index++]); + _hash = ConvertFromByteString(splitted[index++]); } else { - if (splitted.Length == 4) - { - _salt = splitted[2]; - _saltBytes = ConvertFromByteString(_salt); - _hash = splitted[3]; - _hashBytes = ConvertFromByteString(_hash); - } - else - { - _salt = string.Empty; - _hash = splitted[2]; - _hashBytes = ConvertFromByteString(_hash); - } - + _salt = Array.Empty(); + _hash = ConvertFromByteString(splitted[index++]); } - } + public string Id { get => _id; set => _id = value; } + + public Dictionary Parameters { get => _parameters; set => _parameters = value; } + + public byte[] Salt { get => _salt; set => _salt = value; } + + public byte[] Hash { get => _hash; set => _hash = value; } + public PasswordHash(ICryptoProvider cryptoProvider) { _id = cryptoProvider.DefaultHashMethod; - _saltBytes = cryptoProvider.GenerateSalt(); - _salt = ConvertToByteString(SaltBytes); + _salt = cryptoProvider.GenerateSalt(); + _hash = Array.Empty(); } public static byte[] ConvertFromByteString(string byteString) @@ -111,43 +97,45 @@ namespace MediaBrowser.Model.Cryptography } public static string ConvertToByteString(byte[] bytes) - { - return BitConverter.ToString(bytes).Replace("-", ""); - } + => BitConverter.ToString(bytes).Replace("-", string.Empty); - private string SerializeParameters() + private void SerializeParameters(StringBuilder stringBuilder) { - string returnString = string.Empty; - foreach (var KVP in _parameters) + if (_parameters.Count == 0) { - returnString += $",{KVP.Key}={KVP.Value}"; + return; } - if ((!string.IsNullOrEmpty(returnString)) && returnString[0] == ',') + stringBuilder.Append('$'); + foreach (var pair in _parameters) { - returnString = returnString.Remove(0, 1); + stringBuilder.Append(pair.Key); + stringBuilder.Append('='); + stringBuilder.Append(pair.Value); + stringBuilder.Append(','); } - return returnString; + // Remove last ',' + stringBuilder.Length -= 1; } public override string ToString() { - string outString = "$" + _id; - string paramstring = SerializeParameters(); - if (!string.IsNullOrEmpty(paramstring)) - { - outString += $"${paramstring}"; - } + var str = new StringBuilder(); + str.Append('$'); + str.Append(_id); + SerializeParameters(str); - if (!string.IsNullOrEmpty(_salt)) + if (_salt.Length == 0) { - outString += $"${_salt}"; + str.Append('$'); + str.Append(ConvertToByteString(_salt)); } - outString += $"${_hash}"; - return outString; + str.Append('$'); + str.Append(ConvertToByteString(_hash)); + + return str.ToString(); } } - } -- cgit v1.2.3 From ab7ef9c9cb951111eec5132e9c6a00aa0d1b111b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 6 Jul 2019 16:15:38 +0200 Subject: Fix style issues --- .../Library/CoreResolutionIgnoreRule.cs | 69 +++++----------------- .../Library/Resolvers/Movies/MovieResolver.cs | 27 +++++---- .../Resolvers/IResolverIgnoreRule.cs | 6 ++ 3 files changed, 35 insertions(+), 67 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index a70077163e..f1ae2fc9c0 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -5,7 +5,6 @@ using System.Text.RegularExpressions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library @@ -17,12 +16,10 @@ namespace Emby.Server.Implementations.Library { private readonly ILibraryManager _libraryManager; - private bool _ignoreDotPrefix; - /// - /// Any folder named in this list will be ignored - can be added to at runtime for extensibility + /// Any folder named in this list will be ignored /// - public static readonly string[] IgnoreFolders = + private static readonly string[] _ignoreFolders = { "metadata", "ps3_update", @@ -43,25 +40,14 @@ namespace Emby.Server.Implementations.Library "$RECYCLE.BIN", "System Volume Information", ".grab", - - // macos - ".AppleDouble" - }; public CoreResolutionIgnoreRule(ILibraryManager libraryManager) { _libraryManager = libraryManager; - - _ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT; } - /// - /// Shoulds the ignore. - /// - /// The file information. - /// The parent. - /// true if XXXX, false otherwise + /// public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent) { // Don't ignore top level folders @@ -73,46 +59,17 @@ namespace Emby.Server.Implementations.Library var filename = fileInfo.Name; var path = fileInfo.FullName; - // Handle mac .DS_Store - // https://github.com/MediaBrowser/MediaBrowser/issues/427 - if (_ignoreDotPrefix) + // Ignore hidden files on UNIX + if (Environment.OSVersion.Platform != PlatformID.Win32NT + && filename[0] == '.') { - if (filename.IndexOf('.') == 0) - { - return true; - } + return true; } - // Ignore hidden files and folders - //if (fileInfo.IsHidden) - //{ - // if (parent == null) - // { - // var parentFolderName = Path.GetFileName(_fileSystem.GetDirectoryName(path)); - - // if (string.Equals(parentFolderName, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase)) - // { - // return false; - // } - // if (string.Equals(parentFolderName, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase)) - // { - // return false; - // } - // } - - // // Sometimes these are marked hidden - // if (_fileSystem.IsRootPath(path)) - // { - // return false; - // } - - // return true; - //} - if (fileInfo.IsDirectory) { // Ignore any folders in our list - if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase)) + if (_ignoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase)) { return true; } @@ -120,8 +77,9 @@ namespace Emby.Server.Implementations.Library if (parent != null) { // Ignore trailer folders but allow it at the collection level - if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase) && - !(parent is AggregateFolder) && !(parent is UserRootFolder)) + if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase) + && !(parent is AggregateFolder) + && !(parent is UserRootFolder)) { return true; } @@ -142,14 +100,15 @@ namespace Emby.Server.Implementations.Library if (parent != null) { // Don't resolve these into audio files - if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && _libraryManager.IsAudioFile(filename)) + if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) + && _libraryManager.IsAudioFile(filename)) { return true; } } // Ignore samples - Match m = Regex.Match(filename,@"\bsample\b",RegexOptions.IgnoreCase); + Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase); return m.Success; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 47c3e71d72..1b63b00a34 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -12,7 +12,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library.Resolvers.Movies @@ -28,7 +27,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies /// The priority. public override ResolverPriority Priority => ResolverPriority.Third; - public MultiItemResolverResult ResolveMultiple(Folder parent, + public MultiItemResolverResult ResolveMultiple( + Folder parent, List files, string collectionType, IDirectoryService directoryService) @@ -46,7 +46,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return result; } - private MultiItemResolverResult ResolveMultipleInternal(Folder parent, + private MultiItemResolverResult ResolveMultipleInternal( + Folder parent, List files, string collectionType, IDirectoryService directoryService) @@ -91,7 +92,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } - private MultiItemResolverResult ResolveVideos(Folder parent, IEnumerable fileSystemEntries, IDirectoryService directoryService, bool suppportMultiEditions, string collectionType, bool parseName) + private MultiItemResolverResult ResolveVideos( + Folder parent, + IEnumerable fileSystemEntries, + IDirectoryService directoryService, + bool suppportMultiEditions, + string collectionType, + bool parseName) where T : Video, new() { var files = new List(); @@ -104,8 +111,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies // This is a hack but currently no better way to resolve a sometimes ambiguous situation if (string.IsNullOrEmpty(collectionType)) { - if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase) || - string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase) + || string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase)) { return null; } @@ -115,11 +122,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies { leftOver.Add(child); } - else if (IsIgnored(child.Name)) - { - - } - else + else if (!IsIgnored(child.Name)) { files.Add(child); } @@ -168,7 +171,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private static bool IsIgnored(string filename) { // Ignore samples - Match m = Regex.Match(filename,@"\bsample\b",RegexOptions.IgnoreCase); + Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase); return m.Success; } diff --git a/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs b/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs index b40cc157a8..bb80e60256 100644 --- a/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs +++ b/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs @@ -8,6 +8,12 @@ namespace MediaBrowser.Controller.Resolvers /// public interface IResolverIgnoreRule { + /// + /// Checks whether or not the file should be ignored. + /// + /// The file information. + /// The parent BaseItem. + /// True if the file should be ignored. bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent); } } -- cgit v1.2.3 From d405a400aaa5f9676cc2ce9159b562f94233dcd5 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 14 Jun 2019 16:32:37 +0200 Subject: Fixes issues with HttpClientManager --- Emby.Dlna/PlayTo/SsdpHttpClient.cs | 22 ++-- Emby.Server.Implementations/ApplicationHost.cs | 13 +- .../HttpClientManager/HttpClientManager.cs | 143 ++++++++++----------- .../LiveTv/EmbyTV/DirectRecorder.cs | 2 +- .../LiveTv/Listings/SchedulesDirect.cs | 20 +-- .../LiveTv/Listings/XmlTvListingsProvider.cs | 4 +- .../LiveTv/TunerHosts/SharedHttpStream.cs | 2 +- Jellyfin.Server/Program.cs | 12 ++ MediaBrowser.Common/Net/HttpRequestOptions.cs | 29 +++-- MediaBrowser.Common/Net/HttpResponseInfo.cs | 12 +- 10 files changed, 119 insertions(+), 140 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 1ad99fac5b..780b0a8897 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -34,16 +34,13 @@ namespace Emby.Dlna.PlayTo { var cancellationToken = CancellationToken.None; - using (var response = await PostSoapDataAsync(NormalizeServiceUrl(baseUrl, service.ControlUrl), "\"" + service.ServiceType + "#" + command + "\"", postData, header, logRequest, cancellationToken) + var url = NormalizeServiceUrl(baseUrl, service.ControlUrl); + 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)) { - using (var stream = response.Content) - { - using (var reader = new StreamReader(stream, Encoding.UTF8)) - { - return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace); - } - } + return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace); } } @@ -121,15 +118,18 @@ namespace Emby.Dlna.PlayTo } } - private Task PostSoapDataAsync(string url, + private Task PostSoapDataAsync( + string url, string soapAction, string postData, string header, bool logRequest, CancellationToken cancellationToken) { - if (!soapAction.StartsWith("\"")) - soapAction = "\"" + soapAction + "\""; + if (soapAction[0] != '\"') + { + soapAction = '\"' + soapAction + '\"'; + } var options = new HttpRequestOptions { diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 02d661d15a..c64922a931 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -430,7 +430,7 @@ namespace Emby.Server.Implementations /// Gets the current application user agent /// /// The application user agent. - public string ApplicationUserAgent => Name.Replace(' ','-') + "/" + ApplicationVersion; + public string ApplicationUserAgent => Name.Replace(' ','-') + '/' + ApplicationVersion; /// /// Gets the email address for use within a comment section of a user agent field. @@ -690,11 +690,6 @@ namespace Emby.Server.Implementations await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); } - protected virtual IHttpClient CreateHttpClient() - { - return new HttpClientManager.HttpClientManager(ApplicationPaths, LoggerFactory, FileSystemManager, () => ApplicationUserAgent); - } - public static IStreamHelper StreamHelper { get; set; } /// @@ -720,7 +715,11 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(FileSystemManager); serviceCollection.AddSingleton(); - HttpClient = CreateHttpClient(); + HttpClient = new HttpClientManager.HttpClientManager( + ApplicationPaths, + LoggerFactory.CreateLogger(), + FileSystemManager, + () => ApplicationUserAgent); serviceCollection.AddSingleton(HttpClient); serviceCollection.AddSingleton(NetworkManager); diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index b82d55d0e6..987657bcb1 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -23,11 +23,6 @@ namespace Emby.Server.Implementations.HttpClientManager /// public class HttpClientManager : IHttpClient { - /// - /// When one request to a host times out, we'll ban all other requests for this period of time, to prevent scans from stalling - /// - private const int TimeoutSeconds = 30; - /// /// The _logger /// @@ -46,7 +41,7 @@ namespace Emby.Server.Implementations.HttpClientManager /// public HttpClientManager( IApplicationPaths appPaths, - ILoggerFactory loggerFactory, + ILogger logger, IFileSystem fileSystem, Func defaultUserAgentFn) { @@ -55,18 +50,15 @@ namespace Emby.Server.Implementations.HttpClientManager throw new ArgumentNullException(nameof(appPaths)); } - if (loggerFactory == null) + if (logger == null) { - throw new ArgumentNullException(nameof(loggerFactory)); + throw new ArgumentNullException(nameof(logger)); } - _logger = loggerFactory.CreateLogger(nameof(HttpClientManager)); + _logger = logger; _fileSystem = fileSystem; _appPaths = appPaths; _defaultUserAgentFn = defaultUserAgentFn; - - // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c - ServicePointManager.Expect100Continue = false; } /// @@ -83,13 +75,12 @@ namespace Emby.Server.Implementations.HttpClientManager /// if set to true [enable HTTP compression]. /// HttpClient. /// host - private HttpClient GetHttpClient(string url, bool enableHttpCompression) + private HttpClient GetHttpClient(string url) { - var key = GetHostFromUrl(url) + enableHttpCompression; + var key = GetHostFromUrl(url); if (!_httpClients.TryGetValue(key, out var client)) { - client = new HttpClient() { BaseAddress = new Uri(url) @@ -109,24 +100,27 @@ namespace Emby.Server.Implementations.HttpClientManager if (!string.IsNullOrWhiteSpace(userInfo)) { _logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url); - url = url.Replace(userInfo + "@", string.Empty); + url = url.Replace(userInfo + '@', string.Empty); } var request = new HttpRequestMessage(method, url); AddRequestHeaders(request, options); - if (options.EnableHttpCompression) + switch (options.DecompressionMethod) { - if (options.DecompressionMethod.HasValue - && options.DecompressionMethod.Value == CompressionMethod.Gzip) - { + case CompressionMethod.Deflate | CompressionMethod.Gzip: request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" }); - } - else - { + break; + case CompressionMethod.Deflate: request.Headers.Add(HeaderNames.AcceptEncoding, "deflate"); - } + break; + case CompressionMethod.Gzip: + request.Headers.Add(HeaderNames.AcceptEncoding, "gzip"); + break; + case 0: + default: + break; } if (options.EnableKeepAlive) @@ -134,20 +128,8 @@ namespace Emby.Server.Implementations.HttpClientManager request.Headers.Add(HeaderNames.Connection, "Keep-Alive"); } - if (!string.IsNullOrEmpty(options.Host)) - { - request.Headers.Add(HeaderNames.Host, options.Host); - } - - if (!string.IsNullOrEmpty(options.Referer)) - { - request.Headers.Add(HeaderNames.Referer, options.Referer); - } - //request.Headers.Add(HeaderNames.CacheControl, "no-cache"); - //request.Headers.Add(HeaderNames., options.TimeoutMs; - /* if (!string.IsNullOrWhiteSpace(userInfo)) { @@ -188,9 +170,7 @@ namespace Emby.Server.Implementations.HttpClientManager /// The options. /// Task{HttpResponseInfo}. public Task GetResponse(HttpRequestOptions options) - { - return SendAsync(options, HttpMethod.Get); - } + => SendAsync(options, HttpMethod.Get); /// /// Performs a GET request and returns the resulting stream @@ -324,18 +304,29 @@ namespace Emby.Server.Implementations.HttpClientManager options.CancellationToken.ThrowIfCancellationRequested(); - var client = GetHttpClient(options.Url, options.EnableHttpCompression); + var client = GetHttpClient(options.Url); var httpWebRequest = GetRequestMessage(options, httpMethod); - if (options.RequestContentBytes != null || - !string.IsNullOrEmpty(options.RequestContent) || - httpMethod == HttpMethod.Post) + if (options.RequestContentBytes != null + || !string.IsNullOrEmpty(options.RequestContent) + || httpMethod == HttpMethod.Post) { try { - httpWebRequest.Content = new StringContent(Encoding.UTF8.GetString(options.RequestContentBytes) ?? options.RequestContent ?? string.Empty); - + if (options.RequestContentBytes != null) + { + httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes); + } + else if (options.RequestContent != null) + { + httpWebRequest.Content = new StringContent(options.RequestContent); + } + else + { + httpWebRequest.Content = new ByteArrayContent(Array.Empty()); + } + /* var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded"; if (options.AppendCharsetToMimeType) @@ -343,8 +334,11 @@ namespace Emby.Server.Implementations.HttpClientManager contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\""; } - httpWebRequest.Headers.Add(HeaderNames.ContentType, contentType); - await client.SendAsync(httpWebRequest).ConfigureAwait(false); + httpWebRequest.Headers.Add(HeaderNames.ContentType, contentType);*/ + using (var response = await client.SendAsync(httpWebRequest).ConfigureAwait(false)) + { + return await HandleResponseAsync(response, options).ConfigureAwait(false); + } } catch (Exception ex) { @@ -374,18 +368,7 @@ namespace Emby.Server.Implementations.HttpClientManager using (var response = await client.SendAsync(httpWebRequest).ConfigureAwait(false)) { - await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); - - options.CancellationToken.ThrowIfCancellationRequested(); - - using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) - { - var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; - - return GetResponseInfo(response, memoryStream, memoryStream.Length, null); - } + return await HandleResponseAsync(response, options).ConfigureAwait(false); } } catch (OperationCanceledException ex) @@ -394,9 +377,25 @@ namespace Emby.Server.Implementations.HttpClientManager } } - private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, Stream content, long? contentLength, IDisposable disposable) + private async Task HandleResponseAsync(HttpResponseMessage response, HttpRequestOptions options) + { + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); + + options.CancellationToken.ThrowIfCancellationRequested(); + + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + { + var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream, 81920, options.CancellationToken).ConfigureAwait(false); + memoryStream.Position = 0; + + return GetResponseInfo(response, memoryStream, memoryStream.Length); + } + } + + private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, Stream content, long? contentLength) { - var responseInfo = new HttpResponseInfo(disposable) + var responseInfo = new HttpResponseInfo() { Content = content, StatusCode = httpResponse.StatusCode, @@ -433,16 +432,14 @@ namespace Emby.Server.Implementations.HttpClientManager private static void SetHeaders(HttpContentHeaders headers, HttpResponseInfo responseInfo) { - foreach (var key in headers) + foreach (var header in headers) { - responseInfo.Headers[key.Key] = string.Join(", ", key.Value); + responseInfo.Headers[header.Key] = string.Join(", ", header.Value); } } public Task Post(HttpRequestOptions options) - { - return SendAsync(options, HttpMethod.Post); - } + => SendAsync(options, HttpMethod.Post); /// /// Downloads the contents of a given url into a temporary location @@ -451,10 +448,8 @@ namespace Emby.Server.Implementations.HttpClientManager /// Task{System.String}. public async Task GetTempFile(HttpRequestOptions options) { - using (var response = await GetTempFileResponse(options).ConfigureAwait(false)) - { - return response.TempFilePath; - } + var response = await GetTempFileResponse(options).ConfigureAwait(false); + return response.TempFilePath; } public async Task GetTempFileResponse(HttpRequestOptions options) @@ -481,13 +476,13 @@ namespace Emby.Server.Implementations.HttpClientManager _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url); } - var client = GetHttpClient(options.Url, options.EnableHttpCompression); + var client = GetHttpClient(options.Url); try { options.CancellationToken.ThrowIfCancellationRequested(); - using (var response = (await client.SendAsync(httpWebRequest).ConfigureAwait(false))) + using (var response = (await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false))) { await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); @@ -530,7 +525,7 @@ namespace Emby.Server.Implementations.HttpClientManager { if (options.LogErrors) { - _logger.LogError(webException, "Error {status} getting response from {url}", webException.Status, options.Url); + _logger.LogError(webException, "Error {Status} getting response from {Url}", webException.Status, options.Url); } var exception = new HttpException(webException.Message, webException); @@ -565,7 +560,7 @@ namespace Emby.Server.Implementations.HttpClientManager if (options.LogErrors) { - _logger.LogError(ex, "Error getting response from {url}", options.Url); + _logger.LogError(ex, "Error getting response from {Url}", options.Url); } return ex; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index dd636e6cdf..8dee7046e7 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV UserAgent = "Emby/3.0", // Shouldn't matter but may cause issues - EnableHttpCompression = false + DecompressionMethod = CompressionMethod.None }; using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false)) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index f3f7477180..f5dffc22af 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -627,15 +627,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings ListingsProviderInfo providerInfo) { // Schedules direct requires that the client support compression and will return a 400 response without it - options.EnableHttpCompression = true; - - // On windows 7 under .net core, this header is not getting added -#if NETSTANDARD2_0 - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate"; - } -#endif + options.DecompressionMethod = CompressionMethod.Deflate; try { @@ -665,15 +657,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings ListingsProviderInfo providerInfo) { // Schedules direct requires that the client support compression and will return a 400 response without it - options.EnableHttpCompression = true; - - // On windows 7 under .net core, this header is not getting added -#if NETSTANDARD2_0 - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate"; - } -#endif + options.DecompressionMethod = CompressionMethod.Deflate; try { diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 69b10e6daa..d39c917835 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -73,11 +73,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings CancellationToken = cancellationToken, Url = path, Progress = new SimpleProgress(), - DecompressionMethod = CompressionMethod.Gzip, - // It's going to come back gzipped regardless of this value // So we need to make sure the decompression method is set to gzip - EnableHttpCompression = true, + DecompressionMethod = CompressionMethod.Gzip, UserAgent = "Emby/3.0" diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index e8b34da0ca..60c914898c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts CancellationToken = CancellationToken.None, BufferContent = false, - EnableHttpCompression = false, + DecompressionMethod = CompressionMethod.None, }; foreach (var header in mediaSource.RequiredHttpHeaders) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index d4b10c8c84..95ce08f269 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -118,8 +118,20 @@ namespace Jellyfin.Server SQLitePCL.Batteries_V2.Init(); + // Increase the max http request limit + // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others. + ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); + + // Disable the "Expect: 100-Continue" header by default + // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c + ServicePointManager.Expect100Continue = false; + +// CA5359: Do Not Disable Certificate Validation +#pragma warning disable CA5359 + // Allow all https requests ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); +#pragma warning restore CA5359 var fileSystem = new ManagedFileSystem(_loggerFactory, appPaths); diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index 38e0ff0f55..432e389d3b 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using Microsoft.Net.Http.Headers; @@ -17,7 +16,7 @@ namespace MediaBrowser.Common.Net /// The URL. public string Url { get; set; } - public CompressionMethod? DecompressionMethod { get; set; } + public CompressionMethod DecompressionMethod { get; set; } /// /// Gets or sets the accept header. @@ -49,13 +48,21 @@ namespace MediaBrowser.Common.Net /// Gets or sets the referrer. /// /// The referrer. - public string Referer { get; set; } + public string Referer + { + get => GetHeaderValue(HeaderNames.Referer); + set => RequestHeaders[HeaderNames.Referer] = value; + } /// /// Gets or sets the host. /// /// The host. - public string Host { get; set; } + public string Host + { + get => GetHeaderValue(HeaderNames.Host); + set => RequestHeaders[HeaderNames.Host] = value; + } /// /// Gets or sets the progress. @@ -63,12 +70,6 @@ namespace MediaBrowser.Common.Net /// The progress. public IProgress Progress { get; set; } - /// - /// Gets or sets a value indicating whether [enable HTTP compression]. - /// - /// true if [enable HTTP compression]; otherwise, false. - public bool EnableHttpCompression { get; set; } - public Dictionary RequestHeaders { get; private set; } public string RequestContentType { get; set; } @@ -104,13 +105,12 @@ namespace MediaBrowser.Common.Net /// public HttpRequestOptions() { - EnableHttpCompression = true; - RequestHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); LogRequest = true; LogErrors = true; CacheMode = CacheMode.None; + DecompressionMethod = CompressionMethod.Deflate; } } @@ -122,7 +122,8 @@ namespace MediaBrowser.Common.Net public enum CompressionMethod { - Deflate, - Gzip + None = 0b00000001, + Deflate = 0b00000010, + Gzip = 0b00000100 } } diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs index 1866741676..aa496adaca 100644 --- a/MediaBrowser.Common/Net/HttpResponseInfo.cs +++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs @@ -52,13 +52,6 @@ namespace MediaBrowser.Common.Net /// The headers. public Dictionary Headers { get; set; } - private readonly IDisposable _disposable; - - public HttpResponseInfo(IDisposable disposable) - { - _disposable = disposable; - Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - } public HttpResponseInfo() { Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -66,10 +59,7 @@ namespace MediaBrowser.Common.Net public void Dispose() { - if (_disposable != null) - { - _disposable.Dispose(); - } + // Only IDisposable for backwards compatibility } } } -- cgit v1.2.3 From 3603c64fa6033eca4a92c4f537fddc182817998a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 14 Jun 2019 17:14:42 +0200 Subject: Use HttpResponseHeaders instead of a dictionary --- .../HttpClientManager/HttpClientManager.cs | 67 ++++++---------------- MediaBrowser.Common/Net/HttpResponseInfo.cs | 11 +++- 2 files changed, 26 insertions(+), 52 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 987657bcb1..7af0efc173 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -4,8 +4,6 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; @@ -389,52 +387,16 @@ namespace Emby.Server.Implementations.HttpClientManager await stream.CopyToAsync(memoryStream, 81920, options.CancellationToken).ConfigureAwait(false); memoryStream.Position = 0; - return GetResponseInfo(response, memoryStream, memoryStream.Length); - } - } - - private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, Stream content, long? contentLength) - { - var responseInfo = new HttpResponseInfo() - { - Content = content, - StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.Content.Headers.ContentType?.MediaType, - ContentLength = contentLength, - ResponseUrl = httpResponse.Content.Headers.ContentLocation?.ToString() - }; - - if (httpResponse.Headers != null) - { - SetHeaders(httpResponse.Content.Headers, responseInfo); - } - - return responseInfo; - } - - private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, string tempFile, long? contentLength) - { - var responseInfo = new HttpResponseInfo - { - TempFilePath = tempFile, - StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.Content.Headers.ContentType?.MediaType, - ContentLength = contentLength - }; - - if (httpResponse.Headers != null) - { - SetHeaders(httpResponse.Content.Headers, responseInfo); - } - - return responseInfo; - } + var responseInfo = new HttpResponseInfo(response.Headers) + { + Content = memoryStream, + StatusCode = response.StatusCode, + ContentType = response.Content.Headers.ContentType?.MediaType, + ContentLength = memoryStream.Length, + ResponseUrl = response.Content.Headers.ContentLocation?.ToString() + }; - private static void SetHeaders(HttpContentHeaders headers, HttpResponseInfo responseInfo) - { - foreach (var header in headers) - { - responseInfo.Headers[header.Key] = string.Join(", ", header.Value); + return responseInfo; } } @@ -496,8 +458,15 @@ namespace Emby.Server.Implementations.HttpClientManager options.Progress.Report(100); - var contentLength = response.Content.Headers.ContentLength; - return GetResponseInfo(response, tempFile, contentLength); + var responseInfo = new HttpResponseInfo(response.Headers) + { + TempFilePath = tempFile, + StatusCode = response.StatusCode, + ContentType = response.Content.Headers.ContentType?.MediaType, + ContentLength = response.Content.Headers.ContentLength + }; + + return responseInfo; } } catch (Exception ex) diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs index aa496adaca..cd9feabfe0 100644 --- a/MediaBrowser.Common/Net/HttpResponseInfo.cs +++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs @@ -1,7 +1,7 @@ using System; -using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.Http.Headers; namespace MediaBrowser.Common.Net { @@ -50,11 +50,16 @@ namespace MediaBrowser.Common.Net /// Gets or sets the headers. /// /// The headers. - public Dictionary Headers { get; set; } + public HttpResponseHeaders Headers { get; set; } public HttpResponseInfo() { - Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + + } + + public HttpResponseInfo(HttpResponseHeaders headers) + { + Headers = headers; } public void Dispose() -- cgit v1.2.3 From b117b364f2f60db33a100a46d12ff5f2b2c4193d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 14 Jun 2019 17:31:56 +0200 Subject: Remove duplicate code --- .../HttpClientManager/HttpClientManager.cs | 112 +++++++++------------ 1 file changed, 45 insertions(+), 67 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 7af0efc173..b8c52a53fb 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -310,38 +310,28 @@ namespace Emby.Server.Implementations.HttpClientManager || !string.IsNullOrEmpty(options.RequestContent) || httpMethod == HttpMethod.Post) { - try - { - if (options.RequestContentBytes != null) - { - httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes); - } - else if (options.RequestContent != null) - { - httpWebRequest.Content = new StringContent(options.RequestContent); - } - else - { - httpWebRequest.Content = new ByteArrayContent(Array.Empty()); - } - /* - var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded"; - - if (options.AppendCharsetToMimeType) - { - contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\""; - } - httpWebRequest.Headers.Add(HeaderNames.ContentType, contentType);*/ - using (var response = await client.SendAsync(httpWebRequest).ConfigureAwait(false)) - { - return await HandleResponseAsync(response, options).ConfigureAwait(false); - } + if (options.RequestContentBytes != null) + { + httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes); + } + else if (options.RequestContent != null) + { + httpWebRequest.Content = new StringContent(options.RequestContent); } - catch (Exception ex) + else + { + httpWebRequest.Content = new ByteArrayContent(Array.Empty()); + } + /* + var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded"; + + if (options.AppendCharsetToMimeType) { - throw new HttpException(ex.Message) { IsTimedOut = true }; + contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\""; } + + httpWebRequest.Headers.Add(HeaderNames.ContentType, contentType);*/ } if (options.LogRequest) @@ -349,54 +339,42 @@ namespace Emby.Server.Implementations.HttpClientManager _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToString(), options.Url); } - try - { - options.CancellationToken.ThrowIfCancellationRequested(); + options.CancellationToken.ThrowIfCancellationRequested(); - /*if (!options.BufferContent) - { - var response = await client.HttpClient.SendAsync(httpWebRequest).ConfigureAwait(false); + /*if (!options.BufferContent) + { + var response = await client.HttpClient.SendAsync(httpWebRequest).ConfigureAwait(false); - await EnsureSuccessStatusCode(client, response, options).ConfigureAwait(false); + await EnsureSuccessStatusCode(client, response, options).ConfigureAwait(false); - options.CancellationToken.ThrowIfCancellationRequested(); + options.CancellationToken.ThrowIfCancellationRequested(); - return GetResponseInfo(response, await response.Content.ReadAsStreamAsync().ConfigureAwait(false), response.Content.Headers.ContentLength, response); - }*/ + return GetResponseInfo(response, await response.Content.ReadAsStreamAsync().ConfigureAwait(false), response.Content.Headers.ContentLength, response); + }*/ - using (var response = await client.SendAsync(httpWebRequest).ConfigureAwait(false)) - { - return await HandleResponseAsync(response, options).ConfigureAwait(false); - } - } - catch (OperationCanceledException ex) + using (var response = await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false)) { - throw GetCancellationException(options, options.CancellationToken, ex); - } - } - - private async Task HandleResponseAsync(HttpResponseMessage response, HttpRequestOptions options) - { - await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); - - options.CancellationToken.ThrowIfCancellationRequested(); + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); - using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) - { - var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream, 81920, options.CancellationToken).ConfigureAwait(false); - memoryStream.Position = 0; + options.CancellationToken.ThrowIfCancellationRequested(); - var responseInfo = new HttpResponseInfo(response.Headers) + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { - Content = memoryStream, - StatusCode = response.StatusCode, - ContentType = response.Content.Headers.ContentType?.MediaType, - ContentLength = memoryStream.Length, - ResponseUrl = response.Content.Headers.ContentLocation?.ToString() - }; + var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); + memoryStream.Position = 0; - return responseInfo; + var responseInfo = new HttpResponseInfo(response.Headers) + { + Content = memoryStream, + StatusCode = response.StatusCode, + ContentType = response.Content.Headers.ContentType?.MediaType, + ContentLength = memoryStream.Length, + ResponseUrl = response.Content.Headers.ContentLocation?.ToString() + }; + + return responseInfo; + } } } @@ -603,7 +581,7 @@ namespace Emby.Server.Implementations.HttpClientManager } var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - _logger.LogError(msg); + _logger.LogError("HTTP request failed with message: {Message}", msg); throw new HttpException(response.ReasonPhrase) { -- cgit v1.2.3 From 5fc4ad6c4e9aab8246e70a064c8506d050cf2147 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 1 Jul 2019 19:24:42 +0200 Subject: Address comments --- .../HttpClientManager/HttpClientManager.cs | 58 ++++++++++------------ MediaBrowser.Common/Net/HttpRequestOptions.cs | 1 + 2 files changed, 26 insertions(+), 33 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index b8c52a53fb..ae62f34e06 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -21,19 +21,18 @@ namespace Emby.Server.Implementations.HttpClientManager /// public class HttpClientManager : IHttpClient { - /// - /// The _logger - /// private readonly ILogger _logger; - - /// - /// The _app paths - /// private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; private readonly Func _defaultUserAgentFn; + /// + /// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests. + /// DON'T dispose it after use. + /// + /// The HTTP clients. + private readonly ConcurrentDictionary _httpClients = new ConcurrentDictionary(); + /// /// Initializes a new instance of the class. /// @@ -60,19 +59,10 @@ namespace Emby.Server.Implementations.HttpClientManager } /// - /// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests. - /// DON'T dispose it after use. - /// - /// The HTTP clients. - private readonly ConcurrentDictionary _httpClients = new ConcurrentDictionary(); - - /// - /// Gets + /// Gets the correct http client for the given url. /// - /// The host. - /// if set to true [enable HTTP compression]. + /// The url. /// HttpClient. - /// host private HttpClient GetHttpClient(string url) { var key = GetHostFromUrl(url); @@ -116,7 +106,6 @@ namespace Emby.Server.Implementations.HttpClientManager case CompressionMethod.Gzip: request.Headers.Add(HeaderNames.AcceptEncoding, "gzip"); break; - case 0: default: break; } @@ -187,8 +176,6 @@ namespace Emby.Server.Implementations.HttpClientManager /// The options. /// The HTTP method. /// Task{HttpResponseInfo}. - /// - /// public Task SendAsync(HttpRequestOptions options, string httpMethod) { var httpMethod2 = GetHttpMethod(httpMethod); @@ -201,8 +188,6 @@ namespace Emby.Server.Implementations.HttpClientManager /// The options. /// The HTTP method. /// Task{HttpResponseInfo}. - /// - /// public async Task SendAsync(HttpRequestOptions options, HttpMethod httpMethod) { if (options.CacheMode == CacheMode.None) @@ -310,7 +295,6 @@ namespace Emby.Server.Implementations.HttpClientManager || !string.IsNullOrEmpty(options.RequestContent) || httpMethod == HttpMethod.Post) { - if (options.RequestContentBytes != null) { httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes); @@ -323,6 +307,8 @@ namespace Emby.Server.Implementations.HttpClientManager { httpWebRequest.Content = new ByteArrayContent(Array.Empty()); } + + // TODO: add correct content type /* var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded"; @@ -341,16 +327,24 @@ namespace Emby.Server.Implementations.HttpClientManager options.CancellationToken.ThrowIfCancellationRequested(); - /*if (!options.BufferContent) + if (!options.BufferContent) { - var response = await client.HttpClient.SendAsync(httpWebRequest).ConfigureAwait(false); + var response = await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false); - await EnsureSuccessStatusCode(client, response, options).ConfigureAwait(false); + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); options.CancellationToken.ThrowIfCancellationRequested(); - return GetResponseInfo(response, await response.Content.ReadAsStreamAsync().ConfigureAwait(false), response.Content.Headers.ContentLength, response); - }*/ + var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return new HttpResponseInfo(response.Headers) + { + Content = stream, + StatusCode = response.StatusCode, + ContentType = response.Content.Headers.ContentType?.MediaType, + ContentLength = stream.Length, + ResponseUrl = response.Content.Headers.ContentLocation?.ToString() + }; + } using (var response = await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false)) { @@ -364,7 +358,7 @@ namespace Emby.Server.Implementations.HttpClientManager await stream.CopyToAsync(memoryStream, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); memoryStream.Position = 0; - var responseInfo = new HttpResponseInfo(response.Headers) + return new HttpResponseInfo(response.Headers) { Content = memoryStream, StatusCode = response.StatusCode, @@ -372,8 +366,6 @@ namespace Emby.Server.Implementations.HttpClientManager ContentLength = memoryStream.Length, ResponseUrl = response.Content.Headers.ContentLocation?.ToString() }; - - return responseInfo; } } } diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index 432e389d3b..0576a1a5d9 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -120,6 +120,7 @@ namespace MediaBrowser.Common.Net Unconditional = 1 } + [Flags] public enum CompressionMethod { None = 0b00000001, -- cgit v1.2.3 From 0dbc294836bbf1f11e370efeeae0904d3bb099e0 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Sat, 6 Jul 2019 23:50:06 +0200 Subject: Move VACUUM command to fix merge error This fixes a syntax error. --- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 919453d2a4..010ad63846 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -124,6 +124,9 @@ namespace Emby.Server.Implementations.Data } WriteConnection.Execute("PRAGMA temp_store=" + (int)TempStore); + + // Configuration and pragmas can affect VACUUM so it needs to be last. + WriteConnection.Execute("VACUUM"); return new ManagedConnection(WriteConnection, WriteLock); } @@ -170,8 +173,6 @@ namespace Emby.Server.Implementations.Data columnNames.Add(name); } } - // Configuration and pragmas can affect VACUUM so it needs to be last. - queries.Add("VACUUM"); return columnNames; } -- cgit v1.2.3 From b1bd0627098e67169b9ceaa318a32f4d2a9fba30 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 7 Jul 2019 18:04:06 +0200 Subject: Properly set content type --- Emby.Dlna/PlayTo/SsdpHttpClient.cs | 1 - .../HttpClientManager/HttpClientManager.cs | 16 ++++------------ MediaBrowser.Common/Net/HttpRequestOptions.cs | 2 -- Mono.Nat/Upnp/Messages/UpnpMessage.cs | 5 +---- 4 files changed, 5 insertions(+), 19 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 780b0a8897..22aaa68858 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -155,7 +155,6 @@ namespace Emby.Dlna.PlayTo } options.RequestContentType = "text/xml"; - options.AppendCharsetToMimeType = true; options.RequestContent = postData; return _httpClient.Post(options); diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index ae62f34e06..681859f693 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -301,23 +301,15 @@ namespace Emby.Server.Implementations.HttpClientManager } else if (options.RequestContent != null) { - httpWebRequest.Content = new StringContent(options.RequestContent); + httpWebRequest.Content = new StringContent( + options.RequestContent, + null, + options.RequestContentType); } else { httpWebRequest.Content = new ByteArrayContent(Array.Empty()); } - - // TODO: add correct content type - /* - var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded"; - - if (options.AppendCharsetToMimeType) - { - contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\""; - } - - httpWebRequest.Headers.Add(HeaderNames.ContentType, contentType);*/ } if (options.LogRequest) diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index 0576a1a5d9..76bd35e578 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -91,8 +91,6 @@ namespace MediaBrowser.Common.Net public bool EnableDefaultUserAgent { get; set; } - public bool AppendCharsetToMimeType { get; set; } - private string GetHeaderValue(string name) { RequestHeaders.TryGetValue(name, out var value); diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs index 1151dd997d..ade9df50b5 100644 --- a/Mono.Nat/Upnp/Messages/UpnpMessage.cs +++ b/Mono.Nat/Upnp/Messages/UpnpMessage.cs @@ -57,10 +57,9 @@ namespace Mono.Nat.Upnp req.Url = ss; req.EnableKeepAlive = false; req.RequestContentType = "text/xml"; - req.AppendCharsetToMimeType = true; req.RequestHeaders.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\""); - string bodyString = "" + "" @@ -70,8 +69,6 @@ namespace Mono.Nat.Upnp + "" + "" + "\r\n\r\n"; - - req.RequestContentBytes = System.Text.Encoding.UTF8.GetBytes(bodyString); return req; } -- cgit v1.2.3 From 7bb504d491746d5602b95ab74ed60863cd2e7078 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 7 Jul 2019 18:26:01 +0200 Subject: Create a new HttpMethod from the function name --- .../HttpClientManager/HttpClientManager.cs | 39 +--------------------- 1 file changed, 1 insertion(+), 38 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 681859f693..613545e21d 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -177,10 +177,7 @@ namespace Emby.Server.Implementations.HttpClientManager /// The HTTP method. /// Task{HttpResponseInfo}. public Task SendAsync(HttpRequestOptions options, string httpMethod) - { - var httpMethod2 = GetHttpMethod(httpMethod); - return SendAsync(options, httpMethod2); - } + => SendAsync(options, new HttpMethod(httpMethod)); /// /// send as an asynchronous operation. @@ -216,40 +213,6 @@ namespace Emby.Server.Implementations.HttpClientManager return response; } - private HttpMethod GetHttpMethod(string httpMethod) - { - if (httpMethod.Equals("DELETE", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Delete; - } - else if (httpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Get; - } - else if (httpMethod.Equals("HEAD", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Head; - } - else if (httpMethod.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Options; - } - else if (httpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Post; - } - else if (httpMethod.Equals("PUT", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Put; - } - else if (httpMethod.Equals("TRACE", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Trace; - } - - throw new ArgumentException("Invalid HTTP method", nameof(httpMethod)); - } - private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url) { if (File.Exists(responseCachePath) -- cgit v1.2.3 From b294b802a8bfd80958f8b273895ab430f24cc9d0 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 8 Jul 2019 14:50:15 +0200 Subject: Try to fix XmlTvListingsProvider --- .../HttpClientManager/HttpClientManager.cs | 6 +- .../LiveTv/Listings/XmlTvListingsProvider.cs | 66 ++++++++++++---------- MediaBrowser.Common/Net/HttpResponseInfo.cs | 9 ++- 3 files changed, 47 insertions(+), 34 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index ae62f34e06..45cf4aceca 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -336,7 +336,7 @@ namespace Emby.Server.Implementations.HttpClientManager options.CancellationToken.ThrowIfCancellationRequested(); var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - return new HttpResponseInfo(response.Headers) + return new HttpResponseInfo(response.Headers, response.Content.Headers) { Content = stream, StatusCode = response.StatusCode, @@ -358,7 +358,7 @@ namespace Emby.Server.Implementations.HttpClientManager await stream.CopyToAsync(memoryStream, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); memoryStream.Position = 0; - return new HttpResponseInfo(response.Headers) + return new HttpResponseInfo(response.Headers, response.Content.Headers) { Content = memoryStream, StatusCode = response.StatusCode, @@ -428,7 +428,7 @@ namespace Emby.Server.Implementations.HttpClientManager options.Progress.Report(100); - var responseInfo = new HttpResponseInfo(response.Headers) + var responseInfo = new HttpResponseInfo(response.Headers, response.Content.Headers) { TempFilePath = tempFile, StatusCode = response.StatusCode, diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index d39c917835..c1468121a8 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -2,7 +2,9 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Linq; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Emby.XmlTv.Classes; @@ -52,7 +54,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings private async Task GetXml(string path, CancellationToken cancellationToken) { - _logger.LogInformation("xmltv path: {path}", path); + _logger.LogInformation("xmltv path: {Path}", path); if (!path.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { @@ -66,24 +68,38 @@ namespace Emby.Server.Implementations.LiveTv.Listings return UnzipIfNeeded(path, cacheFile); } - _logger.LogInformation("Downloading xmltv listings from {path}", path); - - string tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = path, - Progress = new SimpleProgress(), - // It's going to come back gzipped regardless of this value - // So we need to make sure the decompression method is set to gzip - DecompressionMethod = CompressionMethod.Gzip, - - UserAgent = "Emby/3.0" - - }).ConfigureAwait(false); + _logger.LogInformation("Downloading xmltv listings from {Path}", path); Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)); - File.Copy(tempFile, cacheFile, true); + using (var res = await _httpClient.SendAsync( + new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = path, + Progress = new SimpleProgress(), + // It's going to come back gzipped regardless of this value + // So we need to make sure the decompression method is set to gzip + DecompressionMethod = CompressionMethod.Gzip, + + UserAgent = "Emby/3.0" + }, + HttpMethod.Get).ConfigureAwait(false)) + using (var stream = res.Content) + using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew)) + { + if (res.ContentHeaders.ContentEncoding.Contains("gzip")) + { + using (var gzStream = new GZipStream(stream, CompressionMode.Decompress)) + { + await gzStream.CopyToAsync(fileStream).ConfigureAwait(false); + } + } + else + { + await stream.CopyToAsync(fileStream).ConfigureAwait(false); + } + } return UnzipIfNeeded(path, cacheFile); } @@ -159,20 +175,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings throw new ArgumentNullException(nameof(channelId)); } - /* - if (!await EmbyTV.EmbyTVRegistration.Instance.EnableXmlTv().ConfigureAwait(false)) - { - var length = endDateUtc - startDateUtc; - if (length.TotalDays > 1) - { - endDateUtc = startDateUtc.AddDays(1); - } - }*/ - - _logger.LogDebug("Getting xmltv programs for channel {id}", channelId); + _logger.LogDebug("Getting xmltv programs for channel {Id}", channelId); string path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false); - _logger.LogDebug("Opening XmlTvReader for {path}", path); + _logger.LogDebug("Opening XmlTvReader for {Path}", path); var reader = new XmlTvReader(path, GetLanguage(info)); return reader.GetProgrammes(channelId, startDateUtc, endDateUtc, cancellationToken) @@ -265,7 +271,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { // In theory this should never be called because there is always only one lineup string path = await GetXml(info.Path, CancellationToken.None).ConfigureAwait(false); - _logger.LogDebug("Opening XmlTvReader for {path}", path); + _logger.LogDebug("Opening XmlTvReader for {Path}", path); var reader = new XmlTvReader(path, GetLanguage(info)); IEnumerable results = reader.GetChannels(); @@ -277,7 +283,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { // In theory this should never be called because there is always only one lineup string path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false); - _logger.LogDebug("Opening XmlTvReader for {path}", path); + _logger.LogDebug("Opening XmlTvReader for {Path}", path); var reader = new XmlTvReader(path, GetLanguage(info)); var results = reader.GetChannels(); diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs index cd9feabfe0..d65ce897ab 100644 --- a/MediaBrowser.Common/Net/HttpResponseInfo.cs +++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs @@ -52,14 +52,21 @@ namespace MediaBrowser.Common.Net /// The headers. public HttpResponseHeaders Headers { get; set; } + /// + /// Gets or sets the content headers. + /// + /// The content headers. + public HttpContentHeaders ContentHeaders { get; set; } + public HttpResponseInfo() { } - public HttpResponseInfo(HttpResponseHeaders headers) + public HttpResponseInfo(HttpResponseHeaders headers, HttpContentHeaders contentHeader) { Headers = headers; + ContentHeaders = contentHeader; } public void Dispose() -- cgit v1.2.3 From 5d9fa06675963fd3ed12baeede541321d5211f1d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 8 Jul 2019 15:14:51 +0200 Subject: Cleanup --- .../LiveTv/Listings/XmlTvListingsProvider.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index c1468121a8..94225a0aa9 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -11,7 +11,6 @@ using Emby.XmlTv.Classes; using Emby.XmlTv.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; -using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Dto; @@ -29,7 +28,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings private readonly IFileSystem _fileSystem; private readonly IZipClient _zipClient; - public XmlTvListingsProvider(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger, IFileSystem fileSystem, IZipClient zipClient) + public XmlTvListingsProvider( + IServerConfigurationManager config, + IHttpClient httpClient, + ILogger logger, + IFileSystem fileSystem, + IZipClient zipClient) { _config = config; _httpClient = httpClient; @@ -77,12 +81,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { CancellationToken = cancellationToken, Url = path, - Progress = new SimpleProgress(), - // It's going to come back gzipped regardless of this value - // So we need to make sure the decompression method is set to gzip DecompressionMethod = CompressionMethod.Gzip, - - UserAgent = "Emby/3.0" }, HttpMethod.Get).ConfigureAwait(false)) using (var stream = res.Content) @@ -117,7 +116,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings } catch (Exception ex) { - _logger.LogError(ex, "Error extracting from gz file {file}", file); + _logger.LogError(ex, "Error extracting from gz file {File}", file); } try @@ -127,7 +126,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings } catch (Exception ex) { - _logger.LogError(ex, "Error extracting from zip file {file}", file); + _logger.LogError(ex, "Error extracting from zip file {File}", file); } } -- cgit v1.2.3 From 886c88576c04eda76c076d056194afa9140815a7 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 19 Jul 2019 23:22:30 +0200 Subject: Use HttpCompletionOption.ResponseHeadersRead and resort to Content-Length header for non-buffered content --- Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 2752e9324b..a4cd98b400 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -284,7 +284,7 @@ namespace Emby.Server.Implementations.HttpClientManager if (!options.BufferContent) { - var response = await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false); + var response = await client.SendAsync(httpWebRequest, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false); await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); @@ -296,7 +296,7 @@ namespace Emby.Server.Implementations.HttpClientManager Content = stream, StatusCode = response.StatusCode, ContentType = response.Content.Headers.ContentType?.MediaType, - ContentLength = stream.Length, + ContentLength = response.Content.Headers.ContentLength, ResponseUrl = response.Content.Headers.ContentLocation?.ToString() }; } -- cgit v1.2.3 From dadfc09c01ee34b67bfc02f655ac06e4de953805 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 20 Jul 2019 13:36:59 +0200 Subject: Add HttpCompletionOption.ResponseHeadersRead to the buffering option to avoid potentially having 2 copies in memory --- Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index a4cd98b400..528053c21b 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -301,7 +301,7 @@ namespace Emby.Server.Implementations.HttpClientManager }; } - using (var response = await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false)) + using (var response = await client.SendAsync(httpWebRequest, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false)) { await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); -- cgit v1.2.3 From 0d9cdb98f2dc570d9a8dd130f10b7df0e035f6ac Mon Sep 17 00:00:00 2001 From: ancarvalho Date: Tue, 2 Jul 2019 03:01:17 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- Emby.Server.Implementations/Localization/Core/pt-BR.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index dbc9c4c4b8..dd3ef57dfd 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -21,8 +21,8 @@ "HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteArtists": "Artistas Favoritos", "HeaderFavoriteEpisodes": "Episódios Favoritos", - "HeaderFavoriteShows": "Séries Favoritas", - "HeaderFavoriteSongs": "Músicas Favoritas", + "HeaderFavoriteShows": "Shows Favoritos", + "HeaderFavoriteSongs": "Musicas Favoritas", "HeaderLiveTV": "TV ao Vivo", "HeaderNextUp": "Próximos", "HeaderRecordingGroups": "Grupos de Gravação", -- cgit v1.2.3 From 6e29b8ad6faf81b8a5c88b4d25f2c8435f161259 Mon Sep 17 00:00:00 2001 From: ZhiGang Zhung Date: Sat, 29 Jun 2019 17:17:32 +0000 Subject: Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 6f7d362d3b..b94efffcd3 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -5,7 +5,7 @@ "Artists": "艺术家", "AuthenticationSucceededWithUserName": "{0} 成功验证", "Books": "书籍", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "已从 {0} 上传了一张新的相机图像", "Channels": "频道", "ChapterNameValue": "章节 {0}", "Collections": "合集", -- cgit v1.2.3 From cd504e6ee533a4c40f14b1b1d0fefe4bbfd36b6d Mon Sep 17 00:00:00 2001 From: Matsuri Date: Sat, 29 Jun 2019 17:21:34 +0000 Subject: Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index b94efffcd3..63aa6a5577 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -44,7 +44,7 @@ "NameInstallFailed": "{0} 安装失败", "NameSeasonNumber": "季 {0}", "NameSeasonUnknown": "未知季", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NewVersionIsAvailable": "Jellyfin Server 有新版本可以下载。", "NotificationOptionApplicationUpdateAvailable": "有可用的应用程序更新", "NotificationOptionApplicationUpdateInstalled": "应用程序更新已安装", "NotificationOptionAudioPlayback": "音频开始播放", @@ -75,7 +75,7 @@ "Songs": "歌曲", "StartupEmbyServerIsLoading": "Jellyfin 服务器加载中。请稍后再试。", "SubtitleDownloadFailureForItem": "为 {0} 下载字幕失败", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "无法从 {0} 下载 {1} 的字幕", "SubtitlesDownloadedForItem": "已为 {0} 下载了字幕", "Sync": "同步", "System": "系统", @@ -91,7 +91,7 @@ "UserPolicyUpdatedWithName": "用户协议已经被更新为 {0}", "UserStartedPlayingItemWithValues": "{0} 已开始播放 {1}", "UserStoppedPlayingItemWithValues": "{0} 已停止播放 {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueHasBeenAddedToLibrary": "{0} 已添加至您的媒体库中", "ValueSpecialEpisodeName": "特典 - {0}", "VersionNumber": "版本 {0}" } -- cgit v1.2.3 From 8c94187c759330a77571f1a8eb65b9c2a7417f04 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 10 Jul 2019 11:22:02 +0000 Subject: Translated using Weblate (German) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/de/ --- Emby.Server.Implementations/Localization/Core/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index 6fd63a5140..0db2017692 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -27,7 +27,7 @@ "HeaderNextUp": "Als Nächstes", "HeaderRecordingGroups": "Aufnahme-Gruppen", "HomeVideos": "Heimvideos", - "Inherit": "Übernehmen", + "Inherit": "Vererben", "ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt", "ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt", "LabelIpAddressValue": "IP-Adresse: {0}", -- cgit v1.2.3 From 00c92e88c575c4c5adda6269bbd06c82e79f3804 Mon Sep 17 00:00:00 2001 From: exveria1015 Date: Tue, 4 Jun 2019 16:22:41 +0000 Subject: Translated using Weblate (Japanese) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/ --- Emby.Server.Implementations/Localization/Core/ja.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index 53a43a1252..4aa0637c59 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -13,17 +13,17 @@ "DeviceOnlineWithName": "{0} が接続されました", "FailedLoginAttemptWithUserName": "ログインを試行しましたが {0}によって失敗しました", "Favorites": "お気に入り", - "Folders": "フォルダ", + "Folders": "フォルダー", "Genres": "ジャンル", "HeaderAlbumArtists": "アルバムアーティスト", "HeaderCameraUploads": "カメラアップロード", - "HeaderContinueWatching": "視聴中", + "HeaderContinueWatching": "視聴を続ける", "HeaderFavoriteAlbums": "お気に入りのアルバム", "HeaderFavoriteArtists": "お気に入りのアーティスト", "HeaderFavoriteEpisodes": "お気に入りのエピソード", "HeaderFavoriteShows": "お気に入りの番組", "HeaderFavoriteSongs": "お気に入りの曲", - "HeaderLiveTV": "ライブ テレビ", + "HeaderLiveTV": "ライブTV", "HeaderNextUp": "次", "HeaderRecordingGroups": "レコーディンググループ", "HomeVideos": "ホームビデオ", -- cgit v1.2.3 From b337df889e760ff8a268091d56254fc85df0f8a1 Mon Sep 17 00:00:00 2001 From: polVRtong Date: Sun, 21 Jul 2019 06:21:35 +0000 Subject: Translated using Weblate (Korean) Currently translated at 97.8% (92 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/ --- Emby.Server.Implementations/Localization/Core/ko.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index 5a7ba8ba7b..f2b7c408ce 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -20,7 +20,7 @@ "HeaderContinueWatching": "계속 시청하기", "HeaderFavoriteAlbums": "좋아하는 앨범", "HeaderFavoriteArtists": "좋아하는 아티스트", - "HeaderFavoriteEpisodes": "Favorite Episodes", + "HeaderFavoriteEpisodes": "좋아하는 에피소드", "HeaderFavoriteShows": "즐겨찾는 쇼", "HeaderFavoriteSongs": "좋아하는 노래", "HeaderLiveTV": "TV 방송", -- cgit v1.2.3 From ea41155c6b8434e685ef5dc99a7dc7502628a3ca Mon Sep 17 00:00:00 2001 From: vaheed Date: Fri, 14 Jun 2019 14:11:17 +0000 Subject: Translated using Weblate (Persian) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fa/ --- Emby.Server.Implementations/Localization/Core/fa.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index 0a0c7553b8..faa658ed58 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -5,7 +5,7 @@ "Artists": "هنرمندان", "AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد", "Books": "کتاب ها", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده {0}", "Channels": "کانال ها", "ChapterNameValue": "فصل {0}", "Collections": "کلکسیون ها", @@ -34,17 +34,17 @@ "LabelRunningTimeValue": "زمان اجرا: {0}", "Latest": "آخرین", "MessageApplicationUpdated": "سرور Jellyfin بروزرسانی شد", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "سرور جلیفین آپدیت شده به نسخه {0}", "MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد", "MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد", "MixedContent": "محتوای درهم", "Movies": "فیلم های سینمایی", "Music": "موسیقی", "MusicVideos": "موزیک ویدیوها", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "{0} نصب با مشکل مواجه شده", "NameSeasonNumber": "فصل {0}", "NameSeasonUnknown": "فصل های ناشناخته", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NewVersionIsAvailable": "یک نسخه جدید جلیفین برای بروزرسانی آماده میباشد.", "NotificationOptionApplicationUpdateAvailable": "بروزرسانی برنامه موجود است", "NotificationOptionApplicationUpdateInstalled": "بروزرسانی برنامه نصب شد", "NotificationOptionAudioPlayback": "پخش صدا آغاز شد", @@ -70,12 +70,12 @@ "ProviderValue": "ارائه دهنده: {0}", "ScheduledTaskFailedWithName": "{0} ناموفق بود", "ScheduledTaskStartedWithName": "{0} شروع شد", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "{0} احتیاج به راه اندازی مجدد", "Shows": "سریال ها", "Songs": "آهنگ ها", "StartupEmbyServerIsLoading": "سرور Jellyfin در حال بارگیری است. لطفا کمی بعد دوباره تلاش کنید.", "SubtitleDownloadFailureForItem": "دانلود زیرنویس برای {0} ناموفق بود", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "زیرنویس برای دانلود با مشکل مواجه شده از {0} برای {1}", "SubtitlesDownloadedForItem": "زیرنویس {0} دانلود شد", "Sync": "همگامسازی", "System": "سیستم", @@ -91,7 +91,7 @@ "UserPolicyUpdatedWithName": "سیاست کاربری برای {0} بروزرسانی شد", "UserStartedPlayingItemWithValues": "{0} شروع به پخش {1} کرد", "UserStoppedPlayingItemWithValues": "{0} پخش {1} را متوقف کرد", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueHasBeenAddedToLibrary": "{0} اضافه شده به کتابخانه رسانه شما", "ValueSpecialEpisodeName": "ویژه- {0}", "VersionNumber": "نسخه {0}" } -- cgit v1.2.3 From b3aaa9216de2a7d8f626fea5632eccc12dd8afe8 Mon Sep 17 00:00:00 2001 From: Tradutor da Silva Date: Tue, 23 Jul 2019 02:25:54 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- Emby.Server.Implementations/Localization/Core/pt-BR.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index dd3ef57dfd..5d68416e9f 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -5,7 +5,7 @@ "Artists": "Artistas", "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", "Books": "Livros", - "CameraImageUploadedFrom": "Uma nova imagem da câmera foi submetida de {0}", + "CameraImageUploadedFrom": "Uma nova imagem da câmera foi enviada de {0}", "Channels": "Canais", "ChapterNameValue": "Capítulo {0}", "Collections": "Coletâneas", @@ -79,7 +79,7 @@ "SubtitlesDownloadedForItem": "Legendas baixadas para {0}", "Sync": "Sincronizar", "System": "Sistema", - "TvShows": "Séries de TV", + "TvShows": "Séries", "User": "Usuário", "UserCreatedWithName": "O usuário {0} foi criado", "UserDeletedWithName": "O usuário {0} foi excluído", -- cgit v1.2.3 From d56725a43d66354bd0c03b6b9848b063e80d1121 Mon Sep 17 00:00:00 2001 From: tluciomiranda Date: Mon, 15 Jul 2019 11:38:35 +0000 Subject: Translated using Weblate (Portuguese (Portugal)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_PT/ --- .../Localization/Core/pt-PT.json | 174 ++++++++++----------- 1 file changed, 87 insertions(+), 87 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index dc69d8af24..387604f03c 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -1,97 +1,97 @@ { - "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", + "Albums": "Álbuns", + "AppDeviceValues": "Aplicação {0}, Dispositivo:{1}", "Application": "Aplicação", - "Artists": "Artists", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", - "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", - "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", - "Genres": "Genres", - "HeaderAlbumArtists": "Album Artists", + "Artists": "Artistas", + "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", + "Books": "Livros", + "CameraImageUploadedFrom": "Uma nova imagem proveniente de uma câmara foi enviada a partir de {0}", + "Channels": "Canais", + "ChapterNameValue": "Capítulo {0}", + "Collections": "Coleções", + "DeviceOfflineWithName": "{0} desligou-se", + "DeviceOnlineWithName": "{0} ligou-se", + "FailedLoginAttemptWithUserName": "Tentativa de login falhada a partir de {0}", + "Favorites": "Favoritos", + "Folders": "Pastas", + "Genres": "Géneros", + "HeaderAlbumArtists": "Artistas do Álbum", "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "Continuar a ver", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteArtists": "Favorite Artists", - "HeaderFavoriteEpisodes": "Favorite Episodes", + "HeaderContinueWatching": "Continuar a Ver", + "HeaderFavoriteAlbums": "Álbuns Favoritos", + "HeaderFavoriteArtists": "Artistas Favoritos", + "HeaderFavoriteEpisodes": "Episódios Favoritos", "HeaderFavoriteShows": "Séries Favoritas", - "HeaderFavoriteSongs": "Favorite Songs", - "HeaderLiveTV": "Live TV", - "HeaderNextUp": "Next Up", - "HeaderRecordingGroups": "Recording Groups", + "HeaderFavoriteSongs": "Músicas Favoritas", + "HeaderLiveTV": "TV em Direto", + "HeaderNextUp": "A Seguir", + "HeaderRecordingGroups": "Grupos de Gravação", "HomeVideos": "Home videos", "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", - "Latest": "Latest", - "MessageApplicationUpdated": "Jellyfin Server has been updated", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MixedContent": "Mixed content", - "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", + "ItemAddedWithName": "{0} foi adicionado à biblioteca", + "ItemRemovedWithName": "{0} foi removido da biblioteca", + "LabelIpAddressValue": "Endereço IP: {0}", + "LabelRunningTimeValue": "Duração: {0}", + "Latest": "Mais Recente", + "MessageApplicationUpdated": "O servidor Jellyfin foi atualizado", + "MessageApplicationUpdatedTo": "O servidor Jellyfin foi atualizado para a versão {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Configurações de servidor na secção {0} foram atualizadas", + "MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada", + "MixedContent": "Conteúdo Misto", + "Movies": "Filmes", + "Music": "Música", + "MusicVideos": "Videoclips", + "NameInstallFailed": "{0} falha na instalação", + "NameSeasonNumber": "Temporada {0}", + "NameSeasonUnknown": "Temporada Desconhecida", + "NewVersionIsAvailable": "Está disponível para transferência uma nova versão do servidor Jellyfin.", + "NotificationOptionApplicationUpdateAvailable": "Atualização de aplicação disponível", + "NotificationOptionApplicationUpdateInstalled": "Atualização de aplicação instalada", + "NotificationOptionAudioPlayback": "Reprodução Iniciada", + "NotificationOptionAudioPlaybackStopped": "Reprodução Parada", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "Photos": "Photos", - "Playlists": "Playlists", - "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", + "NotificationOptionInstallationFailed": "Falha na instalação", + "NotificationOptionNewLibraryContent": "Novo conteúdo adicionado", + "NotificationOptionPluginError": "Falha na extensão", + "NotificationOptionPluginInstalled": "Extensão instalada", + "NotificationOptionPluginUninstalled": "Extensão desinstalada", + "NotificationOptionPluginUpdateInstalled": "Extensão atualizada", + "NotificationOptionServerRestartRequired": "Necessário reiniciar o servidor", + "NotificationOptionTaskFailed": "Falha em tarefa agendada", + "NotificationOptionUserLockedOut": "Utilizador bloqueado", + "NotificationOptionVideoPlayback": "Reprodução do vídeo iniciada", + "NotificationOptionVideoPlaybackStopped": "Reprodução do vídeo parada", + "Photos": "Fotografias", + "Playlists": "Listas de Reprodução", + "Plugin": "Extensão", + "PluginInstalledWithName": "{0} foi instalado", + "PluginUninstalledWithName": "{0} foi desinstalado", + "PluginUpdatedWithName": "{0} foi atualizado", "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", - "Shows": "Shows", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "ScheduledTaskFailedWithName": "{0} falhou", + "ScheduledTaskStartedWithName": "{0} iniciou", + "ServerNameNeedsToBeRestarted": "{0} necessita de ser reiniciado", + "Shows": "Séries", + "Songs": "Músicas", + "StartupEmbyServerIsLoading": "O servidor Jellyfin está a iniciar. Tente novamente mais tarde.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", - "Sync": "Sync", - "System": "System", - "TvShows": "TV Shows", - "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", - "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", - "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", - "ValueSpecialEpisodeName": "Especial - {0}", - "VersionNumber": "Version {0}" + "SubtitleDownloadFailureFromForItem": "Falha na transferência de legendas a partir de {0} para {1}", + "SubtitlesDownloadedForItem": "Transferidas legendas para {0}", + "Sync": "Sincronização", + "System": "Sistema", + "TvShows": "Programas TV", + "User": "Utilizador", + "UserCreatedWithName": "Utilizador {0} criado", + "UserDeletedWithName": "Utilizador {0} apagado", + "UserDownloadingItemWithValues": "{0} está a transferir {1}", + "UserLockedOutWithName": "Utilizador {0} bloqueado", + "UserOfflineFromDevice": "{0} desligou-se a partir de {1}", + "UserOnlineFromDevice": "{0} ligou-se a partir de {1}", + "UserPasswordChangedWithName": "Palavra-passe alterada para o utilizador {0}", + "UserPolicyUpdatedWithName": "Política de utilizador alterada para {0}", + "UserStartedPlayingItemWithValues": "{0} está a reproduzir {1} em {2}", + "UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}", + "ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia", + "ValueSpecialEpisodeName": "Especial - {0}", + "VersionNumber": "Versão {0}" } -- cgit v1.2.3 From 18bc6c69d561a1c25d44cef98bf6576f57cf9f12 Mon Sep 17 00:00:00 2001 From: Vladimir Jendrol Date: Mon, 8 Jul 2019 19:46:40 +0000 Subject: Translated using Weblate (Slovak) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sk/ --- .../Localization/Core/sk.json | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index bc7e7184e3..6eade79429 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -1,6 +1,6 @@ { "Albums": "Albumy", - "AppDeviceValues": "Aplikácia: {0}, Zariadenie: {1}", + "AppDeviceValues": "Apka: {0}, Zariadenie: {1}", "Application": "Aplikácia", "Artists": "Umelci", "AuthenticationSucceededWithUserName": "{0} úspešne overený", @@ -9,14 +9,14 @@ "Channels": "Kanály", "ChapterNameValue": "Kapitola {0}", "Collections": "Zbierky", - "DeviceOfflineWithName": "{0} je odpojený", + "DeviceOfflineWithName": "{0} sa odpojil", "DeviceOnlineWithName": "{0} je pripojený", "FailedLoginAttemptWithUserName": "Neúspešný pokus o prihlásenie z {0}", "Favorites": "Obľúbené", "Folders": "Priečinky", "Genres": "Žánre", "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", + "HeaderCameraUploads": "Nahrané fotografie", "HeaderContinueWatching": "Pokračujte v pozeraní", "HeaderFavoriteAlbums": "Obľúbené albumy", "HeaderFavoriteArtists": "Obľúbení umelci", @@ -25,7 +25,7 @@ "HeaderFavoriteSongs": "Obľúbené pesničky", "HeaderLiveTV": "Živá TV", "HeaderNextUp": "Nasleduje", - "HeaderRecordingGroups": "Recording Groups", + "HeaderRecordingGroups": "Skupiny nahrávok", "HomeVideos": "Domáce videá", "Inherit": "Zdediť", "ItemAddedWithName": "{0} bol pridaný do knižnice", @@ -34,17 +34,17 @@ "LabelRunningTimeValue": "Dĺžka: {0}", "Latest": "Najnovšie", "MessageApplicationUpdated": "Jellyfin Server bol aktualizovaný", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "Jellyfin Server bol aktualizový na {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Sekcia {0} konfigurácie servera bola aktualizovaná", - "MessageServerConfigurationUpdated": "Konfigurácia servera aktualizovaná", + "MessageServerConfigurationUpdated": "Konfigurácia servera bola aktualizovaná", "MixedContent": "Zmiešaný obsah", "Movies": "Filmy", "Music": "Hudba", "MusicVideos": "Hudobné videá", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "Inštalácia {0} zlyhala", "NameSeasonNumber": "Sezóna {0}", "NameSeasonUnknown": "Neznáma sezóna", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NewVersionIsAvailable": "Nová verzia Jellyfin Server je dostupná na stiahnutie.", "NotificationOptionApplicationUpdateAvailable": "Je dostupná aktualizácia aplikácie", "NotificationOptionApplicationUpdateInstalled": "Aktualizácia aplikácie nainštalovaná", "NotificationOptionAudioPlayback": "Spustené prehrávanie audia", @@ -70,16 +70,16 @@ "ProviderValue": "Poskytovateľ: {0}", "ScheduledTaskFailedWithName": "{0} zlyhalo", "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "{0} vyžaduje reštart", "Shows": "Series", "Songs": "Skladby", "StartupEmbyServerIsLoading": "Jellyfin Server sa spúšťa. Skúste to prosím o chvíľu znova.", "SubtitleDownloadFailureForItem": "Sťahovanie titulkov pre {0} zlyhalo", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Sťahovanie titulkov z {0} pre {1} zlyhalo", "SubtitlesDownloadedForItem": "Titulky pre {0} stiahnuté", - "Sync": "Sync", + "Sync": "Synchronizácia", "System": "Systém", - "TvShows": "TV Shows", + "TvShows": "TV seriály", "User": "Používateľ", "UserCreatedWithName": "Používateľ {0} bol vytvorený", "UserDeletedWithName": "Používateľ {0} bol vymazaný", @@ -91,7 +91,7 @@ "UserPolicyUpdatedWithName": "User policy has been updated for {0}", "UserStartedPlayingItemWithValues": "{0} spustil prehrávanie {1}", "UserStoppedPlayingItemWithValues": "{0} zastavil prehrávanie {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueHasBeenAddedToLibrary": "{0} bolo pridané do vašej knižnice médií", "ValueSpecialEpisodeName": "Špeciál - {0}", "VersionNumber": "Verzia {0}" } -- cgit v1.2.3 From 773af2eef907186b6552b845eb61820909154413 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 24 Jul 2019 23:46:58 +0200 Subject: Don't copy the complete response stream --- .../HttpClientManager/HttpClientManager.cs | 51 ++++++---------------- 1 file changed, 14 insertions(+), 37 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 3e23150d88..331b5e29d7 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -284,47 +284,24 @@ namespace Emby.Server.Implementations.HttpClientManager options.CancellationToken.ThrowIfCancellationRequested(); - if (!options.BufferContent) - { - var response = await client.SendAsync(httpWebRequest, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false); - - await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); + var response = await client.SendAsync( + httpWebRequest, + options.BufferContent ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead, + options.CancellationToken).ConfigureAwait(false); - options.CancellationToken.ThrowIfCancellationRequested(); + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); - var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - return new HttpResponseInfo(response.Headers, response.Content.Headers) - { - Content = stream, - StatusCode = response.StatusCode, - ContentType = response.Content.Headers.ContentType?.MediaType, - ContentLength = response.Content.Headers.ContentLength, - ResponseUrl = response.Content.Headers.ContentLocation?.ToString() - }; - } + options.CancellationToken.ThrowIfCancellationRequested(); - using (var response = await client.SendAsync(httpWebRequest, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false)) + var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return new HttpResponseInfo(response.Headers, response.Content.Headers) { - await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); - - options.CancellationToken.ThrowIfCancellationRequested(); - - using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) - { - var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); - memoryStream.Position = 0; - - return new HttpResponseInfo(response.Headers, response.Content.Headers) - { - Content = memoryStream, - StatusCode = response.StatusCode, - ContentType = response.Content.Headers.ContentType?.MediaType, - ContentLength = memoryStream.Length, - ResponseUrl = response.Content.Headers.ContentLocation?.ToString() - }; - } - } + Content = stream, + StatusCode = response.StatusCode, + ContentType = response.Content.Headers.ContentType?.MediaType, + ContentLength = response.Content.Headers.ContentLength, + ResponseUrl = response.Content.Headers.ContentLocation?.ToString() + }; } public Task Post(HttpRequestOptions options) -- cgit v1.2.3 From ddd1a282ea6398ee26d74321338d0445d0a0c796 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 7 Jul 2019 21:03:26 +0200 Subject: Remove IpAddressInfo and IpEndPointInfo classes --- Emby.Dlna/Main/DlnaEntryPoint.cs | 3 +- Emby.Dlna/PlayTo/PlayToManager.cs | 4 +- Emby.Server.Implementations/ApplicationHost.cs | 53 +++-- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 9 +- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 16 +- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 17 +- Emby.Server.Implementations/Net/SocketFactory.cs | 14 +- Emby.Server.Implementations/Net/UdpSocket.cs | 30 +-- .../Networking/NetworkManager.cs | 232 +++++---------------- Emby.Server.Implementations/Udp/UdpServer.cs | 15 +- Jellyfin.Server/Program.cs | 2 +- MediaBrowser.Common/Net/INetworkManager.cs | 14 +- MediaBrowser.Controller/IServerApplicationHost.cs | 6 +- MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs | 4 +- MediaBrowser.Model/Net/ISocket.cs | 5 +- MediaBrowser.Model/Net/ISocketFactory.cs | 11 +- MediaBrowser.Model/Net/IpAddressInfo.cs | 38 ---- MediaBrowser.Model/Net/IpEndPointInfo.cs | 29 --- MediaBrowser.Model/Net/SocketReceiveResult.cs | 6 +- RSSDP/DeviceAvailableEventArgs.cs | 53 +++-- RSSDP/ISsdpCommunicationsServer.cs | 8 +- RSSDP/RequestReceivedEventArgs.cs | 12 +- RSSDP/ResponseReceivedEventArgs.cs | 12 +- RSSDP/SsdpCommunicationsServer.cs | 52 ++--- RSSDP/SsdpDevice.cs | 3 - RSSDP/SsdpDeviceLocator.cs | 17 +- RSSDP/SsdpDevicePublisher.cs | 32 ++- RSSDP/SsdpRootDevice.cs | 10 +- 28 files changed, 262 insertions(+), 445 deletions(-) delete mode 100644 MediaBrowser.Model/Net/IpAddressInfo.cs delete mode 100644 MediaBrowser.Model/Net/IpEndPointInfo.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 5fbe70dedb..206a873e1d 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -1,4 +1,5 @@ using System; +using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; @@ -247,7 +248,7 @@ namespace Emby.Dlna.Main foreach (var address in addresses) { - if (address.AddressFamily == IpAddressFamily.InterNetworkV6) + if (address.AddressFamily == AddressFamily.InterNetworkV6) { // Not support IPv6 right now continue; diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 28e70d0469..c0a4418711 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; @@ -14,7 +15,6 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; @@ -172,7 +172,7 @@ namespace Emby.Dlna.PlayTo _sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName); string serverAddress; - if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IpAddressInfo.Any) || info.LocalIpAddress.Equals(IpAddressInfo.IPv6Any)) + if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IPAddress.Any) || info.LocalIpAddress.Equals(IPAddress.IPv6Any)) { serverAddress = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 120aade392..c6ba2326aa 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Sockets; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; @@ -1546,14 +1547,22 @@ namespace Emby.Server.Implementations return null; } - public string GetLocalApiUrl(IpAddressInfo ipAddress) + public string GetLocalApiUrl(IPAddress ipAddress) { - if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6) + if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { - return GetLocalApiUrl("[" + ipAddress.Address + "]"); + // Remove the scope id from IPv6 addresses + var str = ipAddress.ToString(); + var index = str.IndexOf('%'); + if (index != -1) + { + str = str.Substring(0, index); + } + + return GetLocalApiUrl("[" + str + "]"); } - return GetLocalApiUrl(ipAddress.Address); + return GetLocalApiUrl(ipAddress.ToString()); } public string GetLocalApiUrl(string host) @@ -1564,19 +1573,28 @@ namespace Emby.Server.Implementations host, HttpsPort.ToString(CultureInfo.InvariantCulture)); } + return string.Format("http://{0}:{1}", host, HttpPort.ToString(CultureInfo.InvariantCulture)); } - public string GetWanApiUrl(IpAddressInfo ipAddress) + public string GetWanApiUrl(IPAddress ipAddress) { - if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6) + if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { - return GetWanApiUrl("[" + ipAddress.Address + "]"); + // Remove the scope id from IPv6 addresses + var str = ipAddress.ToString(); + var index = str.IndexOf('%'); + if (index != -1) + { + str = str.Substring(0, index); + } + + return GetWanApiUrl("[" + str + "]"); } - return GetWanApiUrl(ipAddress.Address); + return GetWanApiUrl(ipAddress.ToString()); } public string GetWanApiUrl(string host) @@ -1587,17 +1605,18 @@ namespace Emby.Server.Implementations host, ServerConfigurationManager.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture)); } + return string.Format("http://{0}:{1}", host, ServerConfigurationManager.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture)); } - public Task> GetLocalIpAddresses(CancellationToken cancellationToken) + public Task> GetLocalIpAddresses(CancellationToken cancellationToken) { return GetLocalIpAddressesInternal(true, 0, cancellationToken); } - private async Task> GetLocalIpAddressesInternal(bool allowLoopback, int limit, CancellationToken cancellationToken) + private async Task> GetLocalIpAddressesInternal(bool allowLoopback, int limit, CancellationToken cancellationToken) { var addresses = ServerConfigurationManager .Configuration @@ -1611,13 +1630,13 @@ namespace Emby.Server.Implementations addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces)); } - var resultList = new List(); + var resultList = new List(); foreach (var address in addresses) { if (!allowLoopback) { - if (address.Equals(IpAddressInfo.Loopback) || address.Equals(IpAddressInfo.IPv6Loopback)) + if (address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback)) { continue; } @@ -1638,7 +1657,7 @@ namespace Emby.Server.Implementations return resultList; } - private IpAddressInfo NormalizeConfiguredLocalAddress(string address) + private IPAddress NormalizeConfiguredLocalAddress(string address) { var index = address.Trim('/').IndexOf('/'); @@ -1647,7 +1666,7 @@ namespace Emby.Server.Implementations address = address.Substring(index + 1); } - if (NetworkManager.TryParseIpAddress(address.Trim('/'), out IpAddressInfo result)) + if (IPAddress.TryParse(address.Trim('/'), out IPAddress result)) { return result; } @@ -1657,10 +1676,10 @@ namespace Emby.Server.Implementations private readonly ConcurrentDictionary _validAddressResults = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - private async Task IsIpAddressValidAsync(IpAddressInfo address, CancellationToken cancellationToken) + private async Task IsIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken) { - if (address.Equals(IpAddressInfo.Loopback) || - address.Equals(IpAddressInfo.IPv6Loopback)) + if (address.Equals(IPAddress.Loopback) || + address.Equals(IPAddress.IPv6Loopback)) { return true; } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 761275f8f3..ed524cae30 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; @@ -11,7 +12,6 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -20,7 +20,6 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun @@ -259,7 +258,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun using (var manager = new HdHomerunManager(_socketFactory, Logger)) { // Legacy HdHomeruns are IPv4 only - var ipInfo = _networkManager.ParseIpAddress(uri.Host); + var ipInfo = IPAddress.Parse(uri.Host); for (int i = 0; i < model.TunerCount; ++i) { @@ -675,13 +674,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun // Need a way to set the Receive timeout on the socket otherwise this might never timeout? try { - await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken); + await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IPEndPoint(IPAddress.Parse("255.255.255.255"), 65001), cancellationToken); var receiveBuffer = new byte[8192]; while (!cancellationToken.IsCancellationRequested) { var response = await udpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); - var deviceIp = response.RemoteEndPoint.IpAddress.Address; + var deviceIp = response.RemoteEndPoint.Address.ToString(); // 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) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 2205c0ecca..6e79441dab 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private uint? _lockkey = null; private int _activeTuner = -1; private readonly ISocketFactory _socketFactory; - private IpAddressInfo _remoteIp; + private IPAddress _remoteIp; private ILogger _logger; private ISocket _currentTcpSocket; @@ -114,7 +114,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - public async Task CheckTunerAvailability(IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken) + public async Task CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken) { using (var socket = _socketFactory.CreateTcpSocket(remoteIp, HdHomeRunPort)) { @@ -122,9 +122,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - private static async Task CheckTunerAvailability(ISocket socket, IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken) + private static async Task CheckTunerAvailability(ISocket socket, IPAddress remoteIp, int tuner, CancellationToken cancellationToken) { - var ipEndPoint = new IpEndPointInfo(remoteIp, HdHomeRunPort); + var ipEndPoint = new IPEndPoint(remoteIp, HdHomeRunPort); var lockkeyMsg = CreateGetMessage(tuner, "lockkey"); await socket.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken); @@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase); } - public async Task StartStreaming(IpAddressInfo remoteIp, IPAddress localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken) + public async Task StartStreaming(IPAddress remoteIp, IPAddress localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken) { _remoteIp = remoteIp; @@ -154,7 +154,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var lockKeyValue = _lockkey.Value; - var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort); + var ipEndPoint = new IPEndPoint(_remoteIp, HdHomeRunPort); for (int i = 0; i < numTuners; ++i) { @@ -217,7 +217,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun foreach (Tuple command in commandList) { var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey); - await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false); + await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, new IPEndPoint(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false); var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out string returnVal)) @@ -242,7 +242,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { _logger.LogInformation("HdHomerunManager.ReleaseLockkey {0}", lockKeyValue); - var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort); + var ipEndPoint = new IPEndPoint(_remoteIp, HdHomeRunPort); var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue); await tcpClient.SendToAsync(releaseTarget, 0, releaseTarget.Length, ipEndPoint, CancellationToken.None).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 7f426ea31f..ec708cf20f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -25,7 +25,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly int _numTuners; private readonly INetworkManager _networkManager; - public HdHomerunUdpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, MediaBrowser.Model.Net.ISocketFactory socketFactory, INetworkManager networkManager) + public HdHomerunUdpStream( + MediaSourceInfo mediaSource, + TunerHostInfo tunerHostInfo, + string originalStreamId, + 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) { _appHost = appHost; @@ -58,7 +70,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Logger.LogInformation("Opening HDHR UDP Live stream from {host}", uri.Host); var remoteAddress = IPAddress.Parse(uri.Host); - var embyRemoteAddress = _networkManager.ParseIpAddress(uri.Host); IPAddress localAddress = null; using (var tcpSocket = CreateSocket(remoteAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp)) { @@ -81,7 +92,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun try { // send url to start streaming - await hdHomerunManager.StartStreaming(embyRemoteAddress, localAddress, localPort, _channelCommands, _numTuners, openCancellationToken).ConfigureAwait(false); + await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, openCancellationToken).ConfigureAwait(false); } catch (Exception ex) { diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 492f48abe8..42ffa4e221 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -16,14 +16,14 @@ namespace Emby.Server.Implementations.Net // but that wasn't really the point so kept to YAGNI principal for now, even if the // interfaces are a bit ugly, specific and make assumptions. - public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort) + public ISocket CreateTcpSocket(IPAddress remoteAddress, int remotePort) { if (remotePort < 0) { throw new ArgumentException("remotePort cannot be less than zero.", nameof(remotePort)); } - var addressFamily = remoteAddress.AddressFamily == IpAddressFamily.InterNetwork + var addressFamily = remoteAddress.AddressFamily == AddressFamily.InterNetwork ? AddressFamily.InterNetwork : AddressFamily.InterNetworkV6; @@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Net try { - return new UdpSocket(retVal, new IpEndPointInfo(remoteAddress, remotePort)); + return new UdpSocket(retVal, new IPEndPoint(remoteAddress, remotePort)); } catch { @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Net /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port. /// /// An implementation of the interface used by RSSDP components to perform acceptSocket operations. - public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort) + public ISocket CreateSsdpUdpSocket(IPAddress localIpAddress, int localPort) { if (localPort < 0) { @@ -115,10 +115,8 @@ namespace Emby.Server.Implementations.Net retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); - var localIp = NetworkManager.ToIPAddress(localIpAddress); - - retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIp)); - return new UdpSocket(retVal, localPort, localIp); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIpAddress)); + return new UdpSocket(retVal, localPort, localIpAddress); } catch { diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 6c55085c83..2908ee9afd 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.Net public Socket Socket => _socket; - public IpAddressInfo LocalIPAddress { get; } + public IPAddress LocalIPAddress { get; } private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() { @@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Net _socket = socket; _localPort = localPort; - LocalIPAddress = NetworkManager.ToIpAddressInfo(ip); + LocalIPAddress = ip; _socket.Bind(new IPEndPoint(ip, _localPort)); @@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.Net { Buffer = e.Buffer, ReceivedBytes = e.BytesTransferred, - RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint), + RemoteEndPoint = e.RemoteEndPoint as IPEndPoint, LocalIPAddress = LocalIPAddress }); } @@ -100,12 +100,12 @@ namespace Emby.Server.Implementations.Net } } - public UdpSocket(Socket socket, IpEndPointInfo endPoint) + public UdpSocket(Socket socket, IPEndPoint endPoint) { if (socket == null) throw new ArgumentNullException(nameof(socket)); _socket = socket; - _socket.Connect(NetworkManager.ToIPEndPoint(endPoint)); + _socket.Connect(endPoint); InitReceiveSocketAsyncEventArgs(); } @@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Net return new SocketReceiveResult { ReceivedBytes = receivedBytes, - RemoteEndPoint = ToIpEndPointInfo((IPEndPoint)remoteEndPoint), + RemoteEndPoint = (IPEndPoint)remoteEndPoint, Buffer = buffer, LocalIPAddress = LocalIPAddress }; @@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.Net return ReceiveAsync(buffer, 0, buffer.Length, cancellationToken); } - public Task SendToAsync(byte[] buffer, int offset, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken) + public Task SendToAsync(byte[] buffer, int offset, int size, IPEndPoint endPoint, CancellationToken cancellationToken) { ThrowIfDisposed(); @@ -227,13 +227,11 @@ namespace Emby.Server.Implementations.Net return taskCompletion.Task; } - public IAsyncResult BeginSendTo(byte[] buffer, int offset, int size, IpEndPointInfo endPoint, AsyncCallback callback, object state) + public IAsyncResult BeginSendTo(byte[] buffer, int offset, int size, IPEndPoint endPoint, AsyncCallback callback, object state) { ThrowIfDisposed(); - var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint); - - return _socket.BeginSendTo(buffer, offset, size, SocketFlags.None, ipEndPoint, callback, state); + return _socket.BeginSendTo(buffer, offset, size, SocketFlags.None, endPoint, callback, state); } public int EndSendTo(IAsyncResult result) @@ -268,15 +266,5 @@ namespace Emby.Server.Implementations.Net _disposed = true; } - - private static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint) - { - if (endpoint == null) - { - return null; - } - - return NetworkManager.ToIpEndPointInfo(endpoint); - } } } diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index c102f9eb55..3cacacf075 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -9,55 +9,38 @@ using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; -using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; -using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations.Networking { public class NetworkManager : INetworkManager { - protected ILogger Logger { get; private set; } + private readonly ILogger _logger; - public event EventHandler NetworkChanged; - public Func LocalSubnetsFn { get; set; } + private IPAddress[] _localIpAddresses; + private readonly object _localIpAddressSyncLock = new object(); - public NetworkManager(ILoggerFactory loggerFactory) + public NetworkManager(ILogger logger) { - Logger = loggerFactory.CreateLogger(nameof(NetworkManager)); - - // In FreeBSD these events cause a crash - if (OperatingSystem.Id != OperatingSystemId.BSD) - { - try - { - NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged; - } - catch (Exception ex) - { - Logger.LogError(ex, "Error binding to NetworkAddressChanged event"); - } + _logger = logger; - try - { - NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged; - } - catch (Exception ex) - { - Logger.LogError(ex, "Error binding to NetworkChange_NetworkAvailabilityChanged event"); - } - } + NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged; + NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged; } - private void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e) + public Func LocalSubnetsFn { get; set; } + + public event EventHandler NetworkChanged; + + private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e) { - Logger.LogDebug("NetworkAvailabilityChanged"); + _logger.LogDebug("NetworkAvailabilityChanged"); OnNetworkChanged(); } - private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e) + private void OnNetworkAddressChanged(object sender, EventArgs e) { - Logger.LogDebug("NetworkAddressChanged"); + _logger.LogDebug("NetworkAddressChanged"); OnNetworkChanged(); } @@ -68,39 +51,35 @@ namespace Emby.Server.Implementations.Networking _localIpAddresses = null; _macAddresses = null; } + if (NetworkChanged != null) { NetworkChanged(this, EventArgs.Empty); } } - private IpAddressInfo[] _localIpAddresses; - private readonly object _localIpAddressSyncLock = new object(); - - public IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface = true) + public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true) { lock (_localIpAddressSyncLock) { if (_localIpAddresses == null) { - var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).Result.Select(ToIpAddressInfo).ToArray(); + var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).ToArray(); _localIpAddresses = addresses; - - return addresses; } + return _localIpAddresses; } } - private async Task> GetLocalIpAddressesInternal(bool ignoreVirtualInterface) + private List GetLocalIpAddressesInternal(bool ignoreVirtualInterface) { - var list = GetIPsDefault(ignoreVirtualInterface) - .ToList(); + var list = GetIPsDefault(ignoreVirtualInterface).ToList(); if (list.Count == 0) { - list.AddRange(await GetLocalIpAddressesFallback().ConfigureAwait(false)); + list = GetLocalIpAddressesFallback().GetAwaiter().GetResult().ToList(); } var listClone = list.ToList(); @@ -351,13 +330,13 @@ namespace Emby.Server.Implementations.Networking try { var host = uri.DnsSafeHost; - Logger.LogDebug("Resolving host {0}", host); + _logger.LogDebug("Resolving host {0}", host); address = GetIpAddresses(host).Result.FirstOrDefault(); if (address != null) { - Logger.LogDebug("{0} resolved to {1}", host, address); + _logger.LogDebug("{0} resolved to {1}", host, address); return IsInLocalNetworkInternal(address.ToString(), false); } @@ -368,7 +347,7 @@ namespace Emby.Server.Implementations.Networking } catch (Exception ex) { - Logger.LogError(ex, "Error resolving hostname"); + _logger.LogError(ex, "Error resolving hostname"); } } } @@ -381,56 +360,41 @@ namespace Emby.Server.Implementations.Networking return Dns.GetHostAddressesAsync(hostName); } - private List GetIPsDefault(bool ignoreVirtualInterface) + private IEnumerable GetIPsDefault(bool ignoreVirtualInterface) { - NetworkInterface[] interfaces; + IEnumerable interfaces; try { - var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown }; - interfaces = NetworkInterface.GetAllNetworkInterfaces() - .Where(i => validStatuses.Contains(i.OperationalStatus)) - .ToArray(); + .Where(x => x.OperationalStatus == OperationalStatus.Up + || x.OperationalStatus == OperationalStatus.Unknown); } - catch (Exception ex) + catch (NetworkInformationException ex) { - Logger.LogError(ex, "Error in GetAllNetworkInterfaces"); - return new List(); + _logger.LogError(ex, "Error in GetAllNetworkInterfaces"); + return Enumerable.Empty(); } return interfaces.SelectMany(network => { + var ipProperties = network.GetIPProperties(); - try - { - // suppress logging because it might be causing nas device wake up - //logger.LogDebug("Querying interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus); - - var ipProperties = network.GetIPProperties(); - - // Try to exclude virtual adapters - // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms - var addr = ipProperties.GatewayAddresses.FirstOrDefault(); - if (addr == null || ignoreVirtualInterface && string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase)) - { - return new List(); - } - - return ipProperties.UnicastAddresses - .Select(i => i.Address) - .Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6) - .ToList(); - } - catch (Exception ex) + // Try to exclude virtual adapters + // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms + var addr = ipProperties.GatewayAddresses.FirstOrDefault(); + if (addr == null + || (ignoreVirtualInterface + && (addr.Address.Equals(IPAddress.Any) || addr.Address.Equals(IPAddress.IPv6Any)))) { - Logger.LogError(ex, "Error querying network interface"); - return new List(); + return Enumerable.Empty(); } + return ipProperties.UnicastAddresses + .Select(i => i.Address) + .Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6); }).GroupBy(i => i.ToString()) - .Select(x => x.First()) - .ToList(); + .Select(x => x.First()); } private static async Task> GetLocalIpAddressesFallback() @@ -612,32 +576,10 @@ namespace Emby.Server.Implementations.Networking return hosts[0]; } - public IpAddressInfo ParseIpAddress(string ipAddress) - { - if (TryParseIpAddress(ipAddress, out var info)) - { - return info; - } - - throw new ArgumentException("Invalid ip address: " + ipAddress); - } - - public bool TryParseIpAddress(string ipAddress, out IpAddressInfo ipAddressInfo) + public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask) { - if (IPAddress.TryParse(ipAddress, out var address)) - { - ipAddressInfo = ToIpAddressInfo(address); - return true; - } - - ipAddressInfo = null; - return false; - } - - public bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask) - { - IPAddress network1 = GetNetworkAddress(ToIPAddress(address1), ToIPAddress(subnetMask)); - IPAddress network2 = GetNetworkAddress(ToIPAddress(address2), ToIPAddress(subnetMask)); + IPAddress network1 = GetNetworkAddress(address1, subnetMask); + IPAddress network2 = GetNetworkAddress(address2, subnetMask); return network1.Equals(network2); } @@ -656,13 +598,13 @@ namespace Emby.Server.Implementations.Networking { broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i])); } + return new IPAddress(broadcastAddress); } - public IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address) + public IPAddress GetLocalIpSubnetMask(IPAddress address) { NetworkInterface[] interfaces; - IPAddress ipaddress = ToIPAddress(address); try { @@ -674,7 +616,7 @@ namespace Emby.Server.Implementations.Networking } catch (Exception ex) { - Logger.LogError(ex, "Error in GetAllNetworkInterfaces"); + _logger.LogError(ex, "Error in GetAllNetworkInterfaces"); return null; } @@ -684,83 +626,15 @@ namespace Emby.Server.Implementations.Networking { foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) { - if (ip.Address.Equals(ipaddress) && ip.IPv4Mask != null) + if (ip.Address.Equals(address) && ip.IPv4Mask != null) { - return ToIpAddressInfo(ip.IPv4Mask); + return ip.IPv4Mask; } } } } - return null; - } - - public static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint) - { - if (endpoint == null) - { - return null; - } - - return new IpEndPointInfo(ToIpAddressInfo(endpoint.Address), endpoint.Port); - } - - public static IPEndPoint ToIPEndPoint(IpEndPointInfo endpoint) - { - if (endpoint == null) - { - return null; - } - - return new IPEndPoint(ToIPAddress(endpoint.IpAddress), endpoint.Port); - } - public static IPAddress ToIPAddress(IpAddressInfo address) - { - if (address.Equals(IpAddressInfo.Any)) - { - return IPAddress.Any; - } - if (address.Equals(IpAddressInfo.IPv6Any)) - { - return IPAddress.IPv6Any; - } - if (address.Equals(IpAddressInfo.Loopback)) - { - return IPAddress.Loopback; - } - if (address.Equals(IpAddressInfo.IPv6Loopback)) - { - return IPAddress.IPv6Loopback; - } - - return IPAddress.Parse(address.Address); - } - - public static IpAddressInfo ToIpAddressInfo(IPAddress address) - { - if (address.Equals(IPAddress.Any)) - { - return IpAddressInfo.Any; - } - if (address.Equals(IPAddress.IPv6Any)) - { - return IpAddressInfo.IPv6Any; - } - if (address.Equals(IPAddress.Loopback)) - { - return IpAddressInfo.Loopback; - } - if (address.Equals(IPAddress.IPv6Loopback)) - { - return IpAddressInfo.IPv6Loopback; - } - return new IpAddressInfo(address.ToString(), address.AddressFamily == AddressFamily.InterNetworkV6 ? IpAddressFamily.InterNetworkV6 : IpAddressFamily.InterNetwork); - } - - public async Task GetHostAddressesAsync(string host) - { - var addresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false); - return addresses.Select(ToIpAddressInfo).ToArray(); + return null; } /// diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index e7cda2993f..185a282ac4 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.Udp private bool _isDisposed; - private readonly List>> _responders = new List>>(); + private readonly List>> _responders = new List>>(); private readonly IServerApplicationHost _appHost; private readonly IJsonSerializer _json; @@ -43,9 +44,9 @@ namespace Emby.Server.Implementations.Udp AddMessageResponder("who is JellyfinServer?", true, RespondToV2Message); } - private void AddMessageResponder(string message, bool isSubstring, Func responder) + private void AddMessageResponder(string message, bool isSubstring, Func responder) { - _responders.Add(new Tuple>(message, isSubstring, responder)); + _responders.Add(new Tuple>(message, isSubstring, responder)); } /// @@ -83,7 +84,7 @@ namespace Emby.Server.Implementations.Udp } } - private Tuple>> GetResponder(byte[] buffer, int bytesReceived, Encoding encoding) + private Tuple>> GetResponder(byte[] buffer, int bytesReceived, Encoding encoding) { var text = encoding.GetString(buffer, 0, bytesReceived); var responder = _responders.FirstOrDefault(i => @@ -99,10 +100,10 @@ namespace Emby.Server.Implementations.Udp { return null; } - return new Tuple>>(text, responder); + return new Tuple>>(text, responder); } - private async Task RespondToV2Message(string messageText, IpEndPointInfo endpoint, Encoding encoding, CancellationToken cancellationToken) + private async Task RespondToV2Message(string messageText, IPEndPoint endpoint, Encoding encoding, CancellationToken cancellationToken) { var parts = messageText.Split('|'); @@ -254,7 +255,7 @@ namespace Emby.Server.Implementations.Udp } } - public async Task SendAsync(byte[] bytes, IpEndPointInfo remoteEndPoint, CancellationToken cancellationToken) + public async Task SendAsync(byte[] bytes, IPEndPoint remoteEndPoint, CancellationToken cancellationToken) { if (_isDisposed) { diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 94308a98e8..08c0983bef 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -143,7 +143,7 @@ namespace Jellyfin.Server options, new ManagedFileSystem(_loggerFactory.CreateLogger(), appPaths), new NullImageEncoder(), - new NetworkManager(_loggerFactory), + new NetworkManager(_loggerFactory.CreateLogger()), appConfig)) { await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false); diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 34c6f58665..61f2bc2f9f 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; @@ -53,17 +54,12 @@ namespace MediaBrowser.Common.Net /// true if [is in local network] [the specified endpoint]; otherwise, false. bool IsInLocalNetwork(string endpoint); - IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface); - - IpAddressInfo ParseIpAddress(string ipAddress); - - bool TryParseIpAddress(string ipAddress, out IpAddressInfo ipAddressInfo); - - Task GetHostAddressesAsync(string host); + IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface); bool IsAddressInSubnets(string addressString, string[] subnets); - bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask); - IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address); + bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask); + + IPAddress GetLocalIpSubnetMask(IPAddress address); } } diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 81b9ff0a57..3f8cc0b83f 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; +using System.Net; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; -using MediaBrowser.Model.Net; using MediaBrowser.Model.System; namespace MediaBrowser.Controller @@ -59,7 +59,7 @@ namespace MediaBrowser.Controller /// Gets the local ip address. /// /// The local ip address. - Task> GetLocalIpAddresses(CancellationToken cancellationToken); + Task> GetLocalIpAddresses(CancellationToken cancellationToken); /// /// Gets the local API URL. @@ -77,7 +77,7 @@ namespace MediaBrowser.Controller /// /// Gets the local API URL. /// - string GetLocalApiUrl(IpAddressInfo address); + string GetLocalApiUrl(IPAddress address); void LaunchUrl(string url); diff --git a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs index 4edbb503bb..c443a8ad18 100644 --- a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs +++ b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using MediaBrowser.Model.Net; +using System.Net; namespace MediaBrowser.Model.Dlna { @@ -8,7 +8,7 @@ namespace MediaBrowser.Model.Dlna { public Uri Location { get; set; } public Dictionary Headers { get; set; } - public IpAddressInfo LocalIpAddress { get; set; } + public IPAddress LocalIpAddress { get; set; } public int LocalPort { get; set; } } } diff --git a/MediaBrowser.Model/Net/ISocket.cs b/MediaBrowser.Model/Net/ISocket.cs index 992ccb49b0..f80de5524c 100644 --- a/MediaBrowser.Model/Net/ISocket.cs +++ b/MediaBrowser.Model/Net/ISocket.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Threading; using System.Threading.Tasks; @@ -9,7 +10,7 @@ namespace MediaBrowser.Model.Net /// public interface ISocket : IDisposable { - IpAddressInfo LocalIPAddress { get; } + IPAddress LocalIPAddress { get; } Task ReceiveAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); @@ -21,6 +22,6 @@ namespace MediaBrowser.Model.Net /// /// Sends a UDP message to a particular end point (uni or multicast). /// - Task SendToAsync(byte[] buffer, int offset, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken); + Task SendToAsync(byte[] buffer, int offset, int bytes, IPEndPoint endPoint, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Model/Net/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs index 69fe134bc7..e58f4cc14d 100644 --- a/MediaBrowser.Model/Net/ISocketFactory.cs +++ b/MediaBrowser.Model/Net/ISocketFactory.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Net; namespace MediaBrowser.Model.Net { @@ -8,7 +9,7 @@ namespace MediaBrowser.Model.Net public interface ISocketFactory { /// - /// Createa a new unicast socket using the specified local port number. + /// Creates a new unicast socket using the specified local port number. /// /// The local port to bind to. /// A implementation. @@ -16,15 +17,15 @@ namespace MediaBrowser.Model.Net ISocket CreateUdpBroadcastSocket(int localPort); - ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort); + ISocket CreateTcpSocket(IPAddress remoteAddress, int remotePort); /// - /// Createa a new unicast socket using the specified local port number. + /// Creates a new unicast socket using the specified local port number. /// - ISocket CreateSsdpUdpSocket(IpAddressInfo localIp, int localPort); + ISocket CreateSsdpUdpSocket(IPAddress localIp, int localPort); /// - /// Createa a new multicast socket using the specified multicast IP address, multicast time to live and local port. + /// Creates a new multicast socket using the specified multicast IP address, multicast time to live and local port. /// /// The multicast IP address to bind to. /// The multicast time to live value. Actually a maximum number of network hops for UDP packets. diff --git a/MediaBrowser.Model/Net/IpAddressInfo.cs b/MediaBrowser.Model/Net/IpAddressInfo.cs deleted file mode 100644 index 87fa55bcae..0000000000 --- a/MediaBrowser.Model/Net/IpAddressInfo.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Net -{ - public class IpAddressInfo - { - public static IpAddressInfo Any = new IpAddressInfo("0.0.0.0", IpAddressFamily.InterNetwork); - public static IpAddressInfo IPv6Any = new IpAddressInfo("00000000000000000000", IpAddressFamily.InterNetworkV6); - public static IpAddressInfo Loopback = new IpAddressInfo("127.0.0.1", IpAddressFamily.InterNetwork); - public static IpAddressInfo IPv6Loopback = new IpAddressInfo("::1", IpAddressFamily.InterNetworkV6); - - public string Address { get; set; } - public IpAddressInfo SubnetMask { get; set; } - public IpAddressFamily AddressFamily { get; set; } - - public IpAddressInfo(string address, IpAddressFamily addressFamily) - { - Address = address; - AddressFamily = addressFamily; - } - - public bool Equals(IpAddressInfo address) - { - return string.Equals(address.Address, Address, StringComparison.OrdinalIgnoreCase); - } - - public override string ToString() - { - return Address; - } - } - - public enum IpAddressFamily - { - InterNetwork, - InterNetworkV6 - } -} diff --git a/MediaBrowser.Model/Net/IpEndPointInfo.cs b/MediaBrowser.Model/Net/IpEndPointInfo.cs deleted file mode 100644 index f8c125144c..0000000000 --- a/MediaBrowser.Model/Net/IpEndPointInfo.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Globalization; - -namespace MediaBrowser.Model.Net -{ - public class IpEndPointInfo - { - public IpAddressInfo IpAddress { get; set; } - - public int Port { get; set; } - - public IpEndPointInfo() - { - - } - - public IpEndPointInfo(IpAddressInfo address, int port) - { - IpAddress = address; - Port = port; - } - - public override string ToString() - { - var ipAddresString = IpAddress == null ? string.Empty : IpAddress.ToString(); - - return ipAddresString + ":" + Port.ToString(CultureInfo.InvariantCulture); - } - } -} diff --git a/MediaBrowser.Model/Net/SocketReceiveResult.cs b/MediaBrowser.Model/Net/SocketReceiveResult.cs index 8c394f7c7d..cd7a2e55f8 100644 --- a/MediaBrowser.Model/Net/SocketReceiveResult.cs +++ b/MediaBrowser.Model/Net/SocketReceiveResult.cs @@ -1,3 +1,5 @@ +using System.Net; + namespace MediaBrowser.Model.Net { /// @@ -18,7 +20,7 @@ namespace MediaBrowser.Model.Net /// /// The the data was received from. /// - public IpEndPointInfo RemoteEndPoint { get; set; } - public IpAddressInfo LocalIPAddress { get; set; } + public IPEndPoint RemoteEndPoint { get; set; } + public IPAddress LocalIPAddress { get; set; } } } diff --git a/RSSDP/DeviceAvailableEventArgs.cs b/RSSDP/DeviceAvailableEventArgs.cs index 9106e27e53..21ac7c6311 100644 --- a/RSSDP/DeviceAvailableEventArgs.cs +++ b/RSSDP/DeviceAvailableEventArgs.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Net; +using System.Net; namespace Rssdp { @@ -11,12 +8,12 @@ namespace Rssdp /// public sealed class DeviceAvailableEventArgs : EventArgs { - public IpAddressInfo LocalIpAddress { get; set; } + public IPAddress LocalIpAddress { get; set; } #region Fields private readonly DiscoveredSsdpDevice _DiscoveredDevice; - private readonly bool _IsNewlyDiscovered; + private readonly bool _IsNewlyDiscovered; #endregion @@ -29,34 +26,34 @@ namespace Rssdp /// A boolean value indicating whether or not this device came from the cache. See for more detail. /// Thrown if the parameter is null. public DeviceAvailableEventArgs(DiscoveredSsdpDevice discoveredDevice, bool isNewlyDiscovered) - { - if (discoveredDevice == null) throw new ArgumentNullException(nameof(discoveredDevice)); + { + if (discoveredDevice == null) throw new ArgumentNullException(nameof(discoveredDevice)); - _DiscoveredDevice = discoveredDevice; - _IsNewlyDiscovered = isNewlyDiscovered; - } + _DiscoveredDevice = discoveredDevice; + _IsNewlyDiscovered = isNewlyDiscovered; + } - #endregion + #endregion - #region Public Properties + #region Public Properties - /// - /// Returns true if the device was discovered due to an alive notification, or a search and was not already in the cache. Returns false if the item came from the cache but matched the current search request. - /// - public bool IsNewlyDiscovered - { - get { return _IsNewlyDiscovered; } - } + /// + /// Returns true if the device was discovered due to an alive notification, or a search and was not already in the cache. Returns false if the item came from the cache but matched the current search request. + /// + public bool IsNewlyDiscovered + { + get { return _IsNewlyDiscovered; } + } /// /// A reference to a instance containing the discovered details and allowing access to the full device description. /// public DiscoveredSsdpDevice DiscoveredDevice - { - get { return _DiscoveredDevice; } - } - - #endregion - - } -} \ No newline at end of file + { + get { return _DiscoveredDevice; } + } + + #endregion + + } +} diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs index c99d684a13..8cf65df116 100644 --- a/RSSDP/ISsdpCommunicationsServer.cs +++ b/RSSDP/ISsdpCommunicationsServer.cs @@ -1,7 +1,7 @@ using System; +using System.Net; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Net; namespace Rssdp.Infrastructure { @@ -40,13 +40,13 @@ namespace Rssdp.Infrastructure /// /// Sends a message to a particular address (uni or multicast) and port. /// - Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken); + Task SendMessage(byte[] messageData, IPEndPoint destination, IPAddress fromLocalIpAddress, CancellationToken cancellationToken); /// /// Sends a message to the SSDP multicast address and port. /// - Task SendMulticastMessage(string message, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken); - Task SendMulticastMessage(string message, int sendCount, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken); + Task SendMulticastMessage(string message, IPAddress fromLocalIpAddress, CancellationToken cancellationToken); + Task SendMulticastMessage(string message, int sendCount, IPAddress fromLocalIpAddress, CancellationToken cancellationToken); #endregion diff --git a/RSSDP/RequestReceivedEventArgs.cs b/RSSDP/RequestReceivedEventArgs.cs index fd3cd9e3a2..b753950f0a 100644 --- a/RSSDP/RequestReceivedEventArgs.cs +++ b/RSSDP/RequestReceivedEventArgs.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; using System.Net; using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Net; namespace Rssdp.Infrastructure { @@ -16,18 +12,18 @@ namespace Rssdp.Infrastructure #region Fields private readonly HttpRequestMessage _Message; - private readonly IpEndPointInfo _ReceivedFrom; + private readonly IPEndPoint _ReceivedFrom; #endregion - public IpAddressInfo LocalIpAddress { get; private set; } + public IPAddress LocalIpAddress { get; private set; } #region Constructors /// /// Full constructor. /// - public RequestReceivedEventArgs(HttpRequestMessage message, IpEndPointInfo receivedFrom, IpAddressInfo localIpAddress) + public RequestReceivedEventArgs(HttpRequestMessage message, IPEndPoint receivedFrom, IPAddress localIpAddress) { _Message = message; _ReceivedFrom = receivedFrom; @@ -49,7 +45,7 @@ namespace Rssdp.Infrastructure /// /// The the request came from. /// - public IpEndPointInfo ReceivedFrom + public IPEndPoint ReceivedFrom { get { return _ReceivedFrom; } } diff --git a/RSSDP/ResponseReceivedEventArgs.cs b/RSSDP/ResponseReceivedEventArgs.cs index 5ed9664eda..f9f9c3040c 100644 --- a/RSSDP/ResponseReceivedEventArgs.cs +++ b/RSSDP/ResponseReceivedEventArgs.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; using System.Net; using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Net; namespace Rssdp.Infrastructure { @@ -14,12 +10,12 @@ namespace Rssdp.Infrastructure public sealed class ResponseReceivedEventArgs : EventArgs { - public IpAddressInfo LocalIpAddress { get; set; } + public IPAddress LocalIpAddress { get; set; } #region Fields private readonly HttpResponseMessage _Message; - private readonly IpEndPointInfo _ReceivedFrom; + private readonly IPEndPoint _ReceivedFrom; #endregion @@ -28,7 +24,7 @@ namespace Rssdp.Infrastructure /// /// Full constructor. /// - public ResponseReceivedEventArgs(HttpResponseMessage message, IpEndPointInfo receivedFrom) + public ResponseReceivedEventArgs(HttpResponseMessage message, IPEndPoint receivedFrom) { _Message = message; _ReceivedFrom = receivedFrom; @@ -49,7 +45,7 @@ namespace Rssdp.Infrastructure /// /// The the response came from. /// - public IpEndPointInfo ReceivedFrom + public IPEndPoint ReceivedFrom { get { return _ReceivedFrom; } } diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 5d2afc37a0..0aa985a261 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Text; @@ -163,7 +164,7 @@ namespace Rssdp.Infrastructure /// /// Sends a message to a particular address (uni or multicast) and port. /// - public async Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken) + public async Task SendMessage(byte[] messageData, IPEndPoint destination, IPAddress fromLocalIpAddress, CancellationToken cancellationToken) { if (messageData == null) throw new ArgumentNullException(nameof(messageData)); @@ -186,7 +187,7 @@ namespace Rssdp.Infrastructure } } - private async Task SendFromSocket(ISocket socket, byte[] messageData, IpEndPointInfo destination, CancellationToken cancellationToken) + private async Task SendFromSocket(ISocket socket, byte[] messageData, IPEndPoint destination, CancellationToken cancellationToken) { try { @@ -206,7 +207,7 @@ namespace Rssdp.Infrastructure } } - private List GetSendSockets(IpAddressInfo fromLocalIpAddress, IpEndPointInfo destination) + private List GetSendSockets(IPAddress fromLocalIpAddress, IPEndPoint destination) { EnsureSendSocketCreated(); @@ -215,24 +216,24 @@ namespace Rssdp.Infrastructure var sockets = _sendSockets.Where(i => i.LocalIPAddress.AddressFamily == fromLocalIpAddress.AddressFamily); // Send from the Any socket and the socket with the matching address - if (fromLocalIpAddress.AddressFamily == IpAddressFamily.InterNetwork) + if (fromLocalIpAddress.AddressFamily == AddressFamily.InterNetwork) { - sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.Any) || fromLocalIpAddress.Equals(i.LocalIPAddress)); + sockets = sockets.Where(i => i.LocalIPAddress.Equals(IPAddress.Any) || fromLocalIpAddress.Equals(i.LocalIPAddress)); // If sending to the loopback address, filter the socket list as well - if (destination.IpAddress.Equals(IpAddressInfo.Loopback)) + if (destination.Address.Equals(IPAddress.Loopback)) { - sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.Any) || i.LocalIPAddress.Equals(IpAddressInfo.Loopback)); + sockets = sockets.Where(i => i.LocalIPAddress.Equals(IPAddress.Any) || i.LocalIPAddress.Equals(IPAddress.Loopback)); } } - else if (fromLocalIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6) + else if (fromLocalIpAddress.AddressFamily == AddressFamily.InterNetworkV6) { - sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.IPv6Any) || fromLocalIpAddress.Equals(i.LocalIPAddress)); + sockets = sockets.Where(i => i.LocalIPAddress.Equals(IPAddress.IPv6Any) || fromLocalIpAddress.Equals(i.LocalIPAddress)); // If sending to the loopback address, filter the socket list as well - if (destination.IpAddress.Equals(IpAddressInfo.IPv6Loopback)) + if (destination.Address.Equals(IPAddress.IPv6Loopback)) { - sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.IPv6Any) || i.LocalIPAddress.Equals(IpAddressInfo.IPv6Loopback)); + sockets = sockets.Where(i => i.LocalIPAddress.Equals(IPAddress.IPv6Any) || i.LocalIPAddress.Equals(IPAddress.IPv6Loopback)); } } @@ -240,7 +241,7 @@ namespace Rssdp.Infrastructure } } - public Task SendMulticastMessage(string message, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken) + public Task SendMulticastMessage(string message, IPAddress fromLocalIpAddress, CancellationToken cancellationToken) { return SendMulticastMessage(message, SsdpConstants.UdpResendCount, fromLocalIpAddress, cancellationToken); } @@ -248,7 +249,7 @@ namespace Rssdp.Infrastructure /// /// Sends a message to the SSDP multicast address and port. /// - public async Task SendMulticastMessage(string message, int sendCount, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken) + public async Task SendMulticastMessage(string message, int sendCount, IPAddress fromLocalIpAddress, CancellationToken cancellationToken) { if (message == null) throw new ArgumentNullException(nameof(message)); @@ -263,12 +264,13 @@ namespace Rssdp.Infrastructure // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP. for (var i = 0; i < sendCount; i++) { - await SendMessageIfSocketNotDisposed(messageData, new IpEndPointInfo - { - IpAddress = new IpAddressInfo(SsdpConstants.MulticastLocalAdminAddress, IpAddressFamily.InterNetwork), - Port = SsdpConstants.MulticastPort - - }, fromLocalIpAddress, cancellationToken).ConfigureAwait(false); + await SendMessageIfSocketNotDisposed( + messageData, + new IPEndPoint( + IPAddress.Parse(SsdpConstants.MulticastLocalAdminAddress), + SsdpConstants.MulticastPort), + fromLocalIpAddress, + cancellationToken).ConfigureAwait(false); await Task.Delay(100, cancellationToken).ConfigureAwait(false); } @@ -336,7 +338,7 @@ namespace Rssdp.Infrastructure #region Private Methods - private Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken) + private Task SendMessageIfSocketNotDisposed(byte[] messageData, IPEndPoint destination, IPAddress fromLocalIpAddress, CancellationToken cancellationToken) { var sockets = _sendSockets; if (sockets != null) @@ -364,13 +366,13 @@ namespace Rssdp.Infrastructure { var sockets = new List(); - sockets.Add(_SocketFactory.CreateSsdpUdpSocket(IpAddressInfo.Any, _LocalPort)); + sockets.Add(_SocketFactory.CreateSsdpUdpSocket(IPAddress.Any, _LocalPort)); if (_enableMultiSocketBinding) { foreach (var address in _networkManager.GetLocalIpAddresses(_config.Configuration.IgnoreVirtualInterfaces)) { - if (address.AddressFamily == IpAddressFamily.InterNetworkV6) + if (address.AddressFamily == AddressFamily.InterNetworkV6) { // Not support IPv6 right now continue; @@ -439,7 +441,7 @@ namespace Rssdp.Infrastructure } } - private void ProcessMessage(string data, IpEndPointInfo endPoint, IpAddressInfo receivedOnLocalIpAddress) + private void ProcessMessage(string data, IPEndPoint endPoint, IPAddress receivedOnLocalIpAddress) { // Responses start with the HTTP version, prefixed with HTTP/ while // requests start with a method which can vary and might be one we haven't @@ -481,7 +483,7 @@ namespace Rssdp.Infrastructure } } - private void OnRequestReceived(HttpRequestMessage data, IpEndPointInfo remoteEndPoint, IpAddressInfo receivedOnLocalIpAddress) + private void OnRequestReceived(HttpRequestMessage data, IPEndPoint remoteEndPoint, IPAddress receivedOnLocalIpAddress) { //SSDP specification says only * is currently used but other uri's might //be implemented in the future and should be ignored unless understood. @@ -496,7 +498,7 @@ namespace Rssdp.Infrastructure handlers(this, new RequestReceivedEventArgs(data, remoteEndPoint, receivedOnLocalIpAddress)); } - private void OnResponseReceived(HttpResponseMessage data, IpEndPointInfo endPoint, IpAddressInfo localIpAddress) + private void OnResponseReceived(HttpResponseMessage data, IPEndPoint endPoint, IPAddress localIpAddress) { var handlers = this.ResponseReceived; if (handlers != null) diff --git a/RSSDP/SsdpDevice.cs b/RSSDP/SsdpDevice.cs index b4c4a88fdd..09f729e83a 100644 --- a/RSSDP/SsdpDevice.cs +++ b/RSSDP/SsdpDevice.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Text; -using System.Threading.Tasks; -using System.Xml; using Rssdp.Infrastructure; namespace Rssdp diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index e17e14c1a6..59a2710d58 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; using System.Linq; +using System.Net; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Net; namespace Rssdp.Infrastructure { @@ -213,7 +210,7 @@ namespace Rssdp.Infrastructure /// Raises the event. /// /// - protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IpAddressInfo localIpAddress) + protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress) { if (this.IsDisposed) return; @@ -295,7 +292,7 @@ namespace Rssdp.Infrastructure #region Discovery/Device Add - private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IpAddressInfo localIpAddress) + private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress localIpAddress) { bool isNewDevice = false; lock (_Devices) @@ -316,7 +313,7 @@ namespace Rssdp.Infrastructure DeviceFound(device, isNewDevice, localIpAddress); } - private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IpAddressInfo localIpAddress) + private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress) { if (!NotificationTypeMatchesFilter(device)) return; @@ -357,7 +354,7 @@ namespace Rssdp.Infrastructure return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken); } - private void ProcessSearchResponseMessage(HttpResponseMessage message, IpAddressInfo localIpAddress) + private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress) { if (!message.IsSuccessStatusCode) return; @@ -378,7 +375,7 @@ namespace Rssdp.Infrastructure } } - private void ProcessNotificationMessage(HttpRequestMessage message, IpAddressInfo localIpAddress) + private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress localIpAddress) { if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0) return; @@ -389,7 +386,7 @@ namespace Rssdp.Infrastructure ProcessByeByeNotification(message); } - private void ProcessAliveNotification(HttpRequestMessage message, IpAddressInfo localIpAddress) + private void ProcessAliveNotification(HttpRequestMessage message, IPAddress localIpAddress) { var location = GetFirstHeaderUriValue("Location", message); if (location != null) diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index 921f33c212..7f3e56394b 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -2,13 +2,10 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Net.Http; -using System.Text; +using System.Net; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Net; using MediaBrowser.Common.Net; -using Rssdp; namespace Rssdp.Infrastructure { @@ -199,7 +196,12 @@ namespace Rssdp.Infrastructure } } - private void ProcessSearchRequest(string mx, string searchTarget, IpEndPointInfo remoteEndPoint, IpAddressInfo receivedOnlocalIpAddress, CancellationToken cancellationToken) + private void ProcessSearchRequest( + string mx, + string searchTarget, + IPEndPoint remoteEndPoint, + IPAddress receivedOnlocalIpAddress, + CancellationToken cancellationToken) { if (String.IsNullOrEmpty(searchTarget)) { @@ -258,7 +260,7 @@ namespace Rssdp.Infrastructure foreach (var device in deviceList) { if (!_sendOnlyMatchedHost || - _networkManager.IsInSameSubnet(device.ToRootDevice().Address, remoteEndPoint.IpAddress, device.ToRootDevice().SubnetMask)) + _networkManager.IsInSameSubnet(device.ToRootDevice().Address, remoteEndPoint.Address, device.ToRootDevice().SubnetMask)) { SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken); } @@ -276,7 +278,11 @@ namespace Rssdp.Infrastructure return _Devices.Union(_Devices.SelectManyRecursive((d) => d.Devices)); } - private void SendDeviceSearchResponses(SsdpDevice device, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress, CancellationToken cancellationToken) + private void SendDeviceSearchResponses( + SsdpDevice device, + IPEndPoint endPoint, + IPAddress receivedOnlocalIpAddress, + CancellationToken cancellationToken) { bool isRootDevice = (device as SsdpRootDevice) != null; if (isRootDevice) @@ -296,7 +302,13 @@ namespace Rssdp.Infrastructure return String.Format("{0}::{1}", udn, fullDeviceType); } - private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress, CancellationToken cancellationToken) + private async void SendSearchResponse( + string searchTarget, + SsdpDevice device, + string uniqueServiceName, + IPEndPoint endPoint, + IPAddress receivedOnlocalIpAddress, + CancellationToken cancellationToken) { var rootDevice = device.ToRootDevice(); @@ -333,7 +345,7 @@ namespace Rssdp.Infrastructure //WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device); } - private bool IsDuplicateSearchRequest(string searchTarget, IpEndPointInfo endPoint) + private bool IsDuplicateSearchRequest(string searchTarget, IPEndPoint endPoint) { var isDuplicateRequest = false; @@ -556,7 +568,7 @@ namespace Rssdp.Infrastructure private class SearchRequest { - public IpEndPointInfo EndPoint { get; set; } + public IPEndPoint EndPoint { get; set; } public DateTime Received { get; set; } public string SearchTarget { get; set; } diff --git a/RSSDP/SsdpRootDevice.cs b/RSSDP/SsdpRootDevice.cs index d918b9040d..0f2de7b156 100644 --- a/RSSDP/SsdpRootDevice.cs +++ b/RSSDP/SsdpRootDevice.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; -using System.Text; -using System.Xml; -using Rssdp.Infrastructure; -using MediaBrowser.Model.Net; +using System.Net; namespace Rssdp { @@ -56,12 +52,12 @@ namespace Rssdp /// /// Gets or sets the Address used to check if the received message from same interface with this device/tree. Required. /// - public IpAddressInfo Address { get; set; } + public IPAddress Address { get; set; } /// /// Gets or sets the SubnetMask used to check if the received message from same interface with this device/tree. Required. /// - public IpAddressInfo SubnetMask { get; set; } + public IPAddress SubnetMask { get; set; } /// /// The base URL to use for all relative url's provided in other propertise (and those of child devices). Optional. -- cgit v1.2.3 From 8270d0cc91783c0c8c052b43af0d633edb8b6b04 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 25 Jul 2019 00:23:56 +0200 Subject: Move IPv6 scope id removal logic to it's own function --- Emby.Server.Implementations/ApplicationHost.cs | 32 +++++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c6ba2326aa..ef2f59d303 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1547,17 +1547,27 @@ namespace Emby.Server.Implementations return null; } + /// + /// Removes the scope id from IPv6 addresses. + /// + /// The IPv6 address. + /// The IPv6 address without the scope id. + private string RemoveScopeId(string address) + { + var index = address.IndexOf('%'); + if (index == -1) + { + return address; + } + + return address.Substring(0, index); + } + public string GetLocalApiUrl(IPAddress ipAddress) { if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { - // Remove the scope id from IPv6 addresses - var str = ipAddress.ToString(); - var index = str.IndexOf('%'); - if (index != -1) - { - str = str.Substring(0, index); - } + var str = RemoveScopeId(ipAddress.ToString()); return GetLocalApiUrl("[" + str + "]"); } @@ -1583,13 +1593,7 @@ namespace Emby.Server.Implementations { if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { - // Remove the scope id from IPv6 addresses - var str = ipAddress.ToString(); - var index = str.IndexOf('%'); - if (index != -1) - { - str = str.Substring(0, index); - } + var str = RemoveScopeId(ipAddress.ToString()); return GetWanApiUrl("[" + str + "]"); } -- cgit v1.2.3 From 5c9d0414234d49a94f323228ac65cf191e80949b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 29 Jul 2019 13:57:36 +0200 Subject: Ignore Ipv6 link-local addresses --- Emby.Server.Implementations/Networking/NetworkManager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 3cacacf075..da06dc5c57 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -95,9 +95,8 @@ namespace Emby.Server.Implementations.Networking private static bool FilterIpAddress(IPAddress address) { - var addressString = address.ToString(); - - if (addressString.StartsWith("169.", StringComparison.OrdinalIgnoreCase)) + if (address.IsIPv6LinkLocal + || address.ToString().StartsWith("169.", StringComparison.OrdinalIgnoreCase)) { return false; } -- cgit v1.2.3 From 998017a76d7e0fcc340bf9dbc3436caef583f6b4 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 29 Jul 2019 16:01:14 +0200 Subject: Include library via NuGet instead of via source --- .../Emby.Server.Implementations.csproj | 1 + Emby.Server.Implementations/Net/UdpSocket.cs | 1 - .../Networking/IPNetwork/BigIntegerExt.cs | 167 -- .../Networking/IPNetwork/IPAddressCollection.cs | 94 - .../Networking/IPNetwork/IPNetwork.cs | 2008 -------------------- .../Networking/IPNetwork/IPNetworkCollection.cs | 129 -- .../Networking/IPNetwork/LICENSE.txt | 24 - .../Networking/NetworkManager.cs | 2 +- 8 files changed, 2 insertions(+), 2424 deletions(-) delete mode 100644 Emby.Server.Implementations/Networking/IPNetwork/BigIntegerExt.cs delete mode 100644 Emby.Server.Implementations/Networking/IPNetwork/IPAddressCollection.cs delete mode 100644 Emby.Server.Implementations/Networking/IPNetwork/IPNetwork.cs delete mode 100644 Emby.Server.Implementations/Networking/IPNetwork/IPNetworkCollection.cs delete mode 100644 Emby.Server.Implementations/Networking/IPNetwork/LICENSE.txt (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 49015a07ec..abbaef26b8 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -20,6 +20,7 @@ + diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 2908ee9afd..dde4a2a34c 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -3,7 +3,6 @@ using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.Networking; using MediaBrowser.Model.Net; namespace Emby.Server.Implementations.Net diff --git a/Emby.Server.Implementations/Networking/IPNetwork/BigIntegerExt.cs b/Emby.Server.Implementations/Networking/IPNetwork/BigIntegerExt.cs deleted file mode 100644 index e4e944839a..0000000000 --- a/Emby.Server.Implementations/Networking/IPNetwork/BigIntegerExt.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Numerics; -using System.Text; - -namespace Emby.Server.Implementations.Networking.IPNetwork -{ - /// - /// Extension methods to convert - /// instances to hexadecimal, octal, and binary strings. - /// - public static class BigIntegerExtensions - { - /// - /// Converts a to a binary string. - /// - /// A . - /// - /// A containing a binary - /// representation of the supplied . - /// - public static string ToBinaryString(this BigInteger bigint) - { - var bytes = bigint.ToByteArray(); - var idx = bytes.Length - 1; - - // Create a StringBuilder having appropriate capacity. - var base2 = new StringBuilder(bytes.Length * 8); - - // Convert first byte to binary. - var binary = Convert.ToString(bytes[idx], 2); - - // Ensure leading zero exists if value is positive. - if (binary[0] != '0' && bigint.Sign == 1) - { - base2.Append('0'); - } - - // Append binary string to StringBuilder. - base2.Append(binary); - - // Convert remaining bytes adding leading zeros. - for (idx--; idx >= 0; idx--) - { - base2.Append(Convert.ToString(bytes[idx], 2).PadLeft(8, '0')); - } - - return base2.ToString(); - } - - /// - /// Converts a to a hexadecimal string. - /// - /// A . - /// - /// A containing a hexadecimal - /// representation of the supplied . - /// - public static string ToHexadecimalString(this BigInteger bigint) - { - return bigint.ToString("X"); - } - - /// - /// Converts a to a octal string. - /// - /// A . - /// - /// A containing an octal - /// representation of the supplied . - /// - public static string ToOctalString(this BigInteger bigint) - { - var bytes = bigint.ToByteArray(); - var idx = bytes.Length - 1; - - // Create a StringBuilder having appropriate capacity. - var base8 = new StringBuilder(((bytes.Length / 3) + 1) * 8); - - // Calculate how many bytes are extra when byte array is split - // into three-byte (24-bit) chunks. - var extra = bytes.Length % 3; - - // If no bytes are extra, use three bytes for first chunk. - if (extra == 0) - { - extra = 3; - } - - // Convert first chunk (24-bits) to integer value. - int int24 = 0; - for (; extra != 0; extra--) - { - int24 <<= 8; - int24 += bytes[idx--]; - } - - // Convert 24-bit integer to octal without adding leading zeros. - var octal = Convert.ToString(int24, 8); - - // Ensure leading zero exists if value is positive. - if (octal[0] != '0') - { - if (bigint.Sign == 1) - { - base8.Append('0'); - } - } - - // Append first converted chunk to StringBuilder. - base8.Append(octal); - - // Convert remaining 24-bit chunks, adding leading zeros. - for (; idx >= 0; idx -= 3) - { - int24 = (bytes[idx] << 16) + (bytes[idx - 1] << 8) + bytes[idx - 2]; - base8.Append(Convert.ToString(int24, 8).PadLeft(8, '0')); - } - - return base8.ToString(); - } - - /// - /// - /// Reverse a Positive BigInteger ONLY - /// Bitwise ~ operator - /// - /// Input : FF FF FF FF - /// Width : 4 - /// Result : 00 00 00 00 - /// - /// - /// Input : 00 00 00 00 - /// Width : 4 - /// Result : FF FF FF FF - /// - /// Input : FF FF FF FF - /// Width : 8 - /// Result : FF FF FF FF 00 00 00 00 - /// - /// - /// Input : 00 00 00 00 - /// Width : 8 - /// Result : FF FF FF FF FF FF FF FF - /// - /// - /// - /// - /// - public static BigInteger PositiveReverse(this BigInteger input, int width) - { - - var result = new List(); - var bytes = input.ToByteArray(); - var work = new byte[width]; - Array.Copy(bytes, 0, work, 0, bytes.Length - 1); // Length -1 : positive BigInteger - - for (int i = 0; i < work.Length; i++) - { - result.Add((byte)(~work[i])); - } - result.Add(0); // positive BigInteger - return new BigInteger(result.ToArray()); - - } - } -} diff --git a/Emby.Server.Implementations/Networking/IPNetwork/IPAddressCollection.cs b/Emby.Server.Implementations/Networking/IPNetwork/IPAddressCollection.cs deleted file mode 100644 index a0c5f73af2..0000000000 --- a/Emby.Server.Implementations/Networking/IPNetwork/IPAddressCollection.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Net; -using System.Numerics; - -namespace Emby.Server.Implementations.Networking.IPNetwork -{ - public class IPAddressCollection : IEnumerable, IEnumerator - { - - private IPNetwork _ipnetwork; - private BigInteger _enumerator; - - internal IPAddressCollection(IPNetwork ipnetwork) - { - this._ipnetwork = ipnetwork; - this._enumerator = -1; - } - - - #region Count, Array, Enumerator - - public BigInteger Count => this._ipnetwork.Total; - - public IPAddress this[BigInteger i] - { - get - { - if (i >= this.Count) - { - throw new ArgumentOutOfRangeException(nameof(i)); - } - byte width = this._ipnetwork.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? (byte)32 : (byte)128; - var ipn = this._ipnetwork.Subnet(width); - return ipn[i].Network; - } - } - - #endregion - - #region IEnumerable Members - - IEnumerator IEnumerable.GetEnumerator() - { - return this; - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this; - } - - #region IEnumerator Members - - public IPAddress Current => this[this._enumerator]; - - #endregion - - #region IDisposable Members - - public void Dispose() - { - // nothing to dispose - return; - } - - #endregion - - #region IEnumerator Members - - object IEnumerator.Current => this.Current; - - public bool MoveNext() - { - this._enumerator++; - if (this._enumerator >= this.Count) - { - return false; - } - return true; - - } - - public void Reset() - { - this._enumerator = -1; - } - - #endregion - - #endregion - } -} diff --git a/Emby.Server.Implementations/Networking/IPNetwork/IPNetwork.cs b/Emby.Server.Implementations/Networking/IPNetwork/IPNetwork.cs deleted file mode 100644 index d6de61c0c9..0000000000 --- a/Emby.Server.Implementations/Networking/IPNetwork/IPNetwork.cs +++ /dev/null @@ -1,2008 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Numerics; -using System.Text.RegularExpressions; - -namespace Emby.Server.Implementations.Networking.IPNetwork -{ - /// - /// IP Network utility class. - /// Use IPNetwork.Parse to create instances. - /// - public class IPNetwork : IComparable - { - - #region properties - - //private uint _network; - private BigInteger _ipaddress; - private AddressFamily _family; - //private uint _netmask; - //private uint _broadcast; - //private uint _firstUsable; - //private uint _lastUsable; - //private uint _usable; - private byte _cidr; - - #endregion - - #region accessors - - private BigInteger _network - { - get - { - var uintNetwork = this._ipaddress & this._netmask; - return uintNetwork; - } - } - - /// - /// Network address - /// - public IPAddress Network => IPNetwork.ToIPAddress(this._network, this._family); - - /// - /// Address Family - /// - public AddressFamily AddressFamily => this._family; - - private BigInteger _netmask => IPNetwork.ToUint(this._cidr, this._family); - - /// - /// Netmask - /// - public IPAddress Netmask => IPNetwork.ToIPAddress(this._netmask, this._family); - - private BigInteger _broadcast - { - get - { - - int width = this._family == System.Net.Sockets.AddressFamily.InterNetwork ? 4 : 16; - var uintBroadcast = this._network + this._netmask.PositiveReverse(width); - return uintBroadcast; - } - } - - /// - /// Broadcast address - /// - public IPAddress Broadcast - { - get - { - if (this._family == System.Net.Sockets.AddressFamily.InterNetworkV6) - { - return null; - } - return IPNetwork.ToIPAddress(this._broadcast, this._family); - } - } - - /// - /// First usable IP adress in Network - /// - public IPAddress FirstUsable - { - get - { - var fisrt = this._family == System.Net.Sockets.AddressFamily.InterNetworkV6 - ? this._network - : (this.Usable <= 0) ? this._network : this._network + 1; - return IPNetwork.ToIPAddress(fisrt, this._family); - } - } - - /// - /// Last usable IP adress in Network - /// - public IPAddress LastUsable - { - get - { - var last = this._family == System.Net.Sockets.AddressFamily.InterNetworkV6 - ? this._broadcast - : (this.Usable <= 0) ? this._network : this._broadcast - 1; - return IPNetwork.ToIPAddress(last, this._family); - } - } - - /// - /// Number of usable IP adress in Network - /// - public BigInteger Usable - { - get - { - - if (this._family == System.Net.Sockets.AddressFamily.InterNetworkV6) - { - return this.Total; - } - byte[] mask = new byte[] { 0xff, 0xff, 0xff, 0xff, 0x00 }; - var bmask = new BigInteger(mask); - var usableIps = (_cidr > 30) ? 0 : ((bmask >> _cidr) - 1); - return usableIps; - } - } - - /// - /// Number of IP adress in Network - /// - public BigInteger Total - { - get - { - - int max = this._family == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128; - var count = BigInteger.Pow(2, (max - _cidr)); - return count; - } - } - - - /// - /// The CIDR netmask notation - /// - public byte Cidr => this._cidr; - - #endregion - - #region constructor - -#if TRAVISCI - public -#else - internal -#endif - - IPNetwork(BigInteger ipaddress, AddressFamily family, byte cidr) - { - - int maxCidr = family == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128; - if (cidr > maxCidr) - { - throw new ArgumentOutOfRangeException(nameof(cidr)); - } - - this._ipaddress = ipaddress; - this._family = family; - this._cidr = cidr; - - } - - #endregion - - #region parsers - - /// - /// 192.168.168.100 - 255.255.255.0 - /// - /// Network : 192.168.168.0 - /// Netmask : 255.255.255.0 - /// Cidr : 24 - /// Start : 192.168.168.1 - /// End : 192.168.168.254 - /// Broadcast : 192.168.168.255 - /// - /// - /// - /// - public static IPNetwork Parse(string ipaddress, string netmask) - { - - IPNetwork ipnetwork = null; - IPNetwork.InternalParse(false, ipaddress, netmask, out ipnetwork); - return ipnetwork; - } - - /// - /// 192.168.168.100/24 - /// - /// Network : 192.168.168.0 - /// Netmask : 255.255.255.0 - /// Cidr : 24 - /// Start : 192.168.168.1 - /// End : 192.168.168.254 - /// Broadcast : 192.168.168.255 - /// - /// - /// - /// - public static IPNetwork Parse(string ipaddress, byte cidr) - { - - IPNetwork ipnetwork = null; - IPNetwork.InternalParse(false, ipaddress, cidr, out ipnetwork); - return ipnetwork; - - } - - /// - /// 192.168.168.100 255.255.255.0 - /// - /// Network : 192.168.168.0 - /// Netmask : 255.255.255.0 - /// Cidr : 24 - /// Start : 192.168.168.1 - /// End : 192.168.168.254 - /// Broadcast : 192.168.168.255 - /// - /// - /// - /// - public static IPNetwork Parse(IPAddress ipaddress, IPAddress netmask) - { - - IPNetwork ipnetwork = null; - IPNetwork.InternalParse(false, ipaddress, netmask, out ipnetwork); - return ipnetwork; - - } - - /// - /// 192.168.0.1/24 - /// 192.168.0.1 255.255.255.0 - /// - /// Network : 192.168.0.0 - /// Netmask : 255.255.255.0 - /// Cidr : 24 - /// Start : 192.168.0.1 - /// End : 192.168.0.254 - /// Broadcast : 192.168.0.255 - /// - /// - /// - public static IPNetwork Parse(string network) - { - - IPNetwork ipnetwork = null; - IPNetwork.InternalParse(false, network, out ipnetwork); - return ipnetwork; - - } - - #endregion - - #region TryParse - - - - /// - /// 192.168.168.100 - 255.255.255.0 - /// - /// Network : 192.168.168.0 - /// Netmask : 255.255.255.0 - /// Cidr : 24 - /// Start : 192.168.168.1 - /// End : 192.168.168.254 - /// Broadcast : 192.168.168.255 - /// - /// - /// - /// - public static bool TryParse(string ipaddress, string netmask, out IPNetwork ipnetwork) - { - - IPNetwork ipnetwork2 = null; - IPNetwork.InternalParse(true, ipaddress, netmask, out ipnetwork2); - bool parsed = (ipnetwork2 != null); - ipnetwork = ipnetwork2; - return parsed; - - } - - - - /// - /// 192.168.168.100/24 - /// - /// Network : 192.168.168.0 - /// Netmask : 255.255.255.0 - /// Cidr : 24 - /// Start : 192.168.168.1 - /// End : 192.168.168.254 - /// Broadcast : 192.168.168.255 - /// - /// - /// - /// - public static bool TryParse(string ipaddress, byte cidr, out IPNetwork ipnetwork) - { - - IPNetwork ipnetwork2 = null; - IPNetwork.InternalParse(true, ipaddress, cidr, out ipnetwork2); - bool parsed = (ipnetwork2 != null); - ipnetwork = ipnetwork2; - return parsed; - - } - - /// - /// 192.168.0.1/24 - /// 192.168.0.1 255.255.255.0 - /// - /// Network : 192.168.0.0 - /// Netmask : 255.255.255.0 - /// Cidr : 24 - /// Start : 192.168.0.1 - /// End : 192.168.0.254 - /// Broadcast : 192.168.0.255 - /// - /// - /// - /// - public static bool TryParse(string network, out IPNetwork ipnetwork) - { - - IPNetwork ipnetwork2 = null; - IPNetwork.InternalParse(true, network, out ipnetwork2); - bool parsed = (ipnetwork2 != null); - ipnetwork = ipnetwork2; - return parsed; - - } - - /// - /// 192.168.0.1/24 - /// 192.168.0.1 255.255.255.0 - /// - /// Network : 192.168.0.0 - /// Netmask : 255.255.255.0 - /// Cidr : 24 - /// Start : 192.168.0.1 - /// End : 192.168.0.254 - /// Broadcast : 192.168.0.255 - /// - /// - /// - /// - /// - public static bool TryParse(IPAddress ipaddress, IPAddress netmask, out IPNetwork ipnetwork) - { - - IPNetwork ipnetwork2 = null; - IPNetwork.InternalParse(true, ipaddress, netmask, out ipnetwork2); - bool parsed = (ipnetwork2 != null); - ipnetwork = ipnetwork2; - return parsed; - - } - - - #endregion - - #region InternalParse - - /// - /// 192.168.168.100 - 255.255.255.0 - /// - /// Network : 192.168.168.0 - /// Netmask : 255.255.255.0 - /// Cidr : 24 - /// Start : 192.168.168.1 - /// End : 192.168.168.254 - /// Broadcast : 192.168.168.255 - /// - /// - /// - /// - private static void InternalParse(bool tryParse, string ipaddress, string netmask, out IPNetwork ipnetwork) - { - - if (string.IsNullOrEmpty(ipaddress)) - { - if (tryParse == false) - { - throw new ArgumentNullException(nameof(ipaddress)); - } - ipnetwork = null; - return; - } - - if (string.IsNullOrEmpty(netmask)) - { - if (tryParse == false) - { - throw new ArgumentNullException(nameof(netmask)); - } - ipnetwork = null; - return; - } - - IPAddress ip = null; - bool ipaddressParsed = IPAddress.TryParse(ipaddress, out ip); - if (ipaddressParsed == false) - { - if (tryParse == false) - { - throw new ArgumentException("ipaddress"); - } - ipnetwork = null; - return; - } - - IPAddress mask = null; - bool netmaskParsed = IPAddress.TryParse(netmask, out mask); - if (netmaskParsed == false) - { - if (tryParse == false) - { - throw new ArgumentException("netmask"); - } - ipnetwork = null; - return; - } - - IPNetwork.InternalParse(tryParse, ip, mask, out ipnetwork); - } - - private static void InternalParse(bool tryParse, string network, out IPNetwork ipnetwork) - { - - if (string.IsNullOrEmpty(network)) - { - if (tryParse == false) - { - throw new ArgumentNullException(nameof(network)); - } - ipnetwork = null; - return; - } - - network = Regex.Replace(network, @"[^0-9a-fA-F\.\/\s\:]+", ""); - network = Regex.Replace(network, @"\s{2,}", " "); - network = network.Trim(); - string[] args = network.Split(new char[] { ' ', '/' }); - byte cidr = 0; - if (args.Length == 1) - { - - if (IPNetwork.TryGuessCidr(args[0], out cidr)) - { - IPNetwork.InternalParse(tryParse, args[0], cidr, out ipnetwork); - return; - } - - if (tryParse == false) - { - throw new ArgumentException("network"); - } - ipnetwork = null; - return; - } - - if (byte.TryParse(args[1], out cidr)) - { - IPNetwork.InternalParse(tryParse, args[0], cidr, out ipnetwork); - return; - } - - IPNetwork.InternalParse(tryParse, args[0], args[1], out ipnetwork); - return; - - } - - - - /// - /// 192.168.168.100 255.255.255.0 - /// - /// Network : 192.168.168.0 - /// Netmask : 255.255.255.0 - /// Cidr : 24 - /// Start : 192.168.168.1 - /// End : 192.168.168.254 - /// Broadcast : 192.168.168.255 - /// - /// - /// - /// - private static void InternalParse(bool tryParse, IPAddress ipaddress, IPAddress netmask, out IPNetwork ipnetwork) - { - - if (ipaddress == null) - { - if (tryParse == false) - { - throw new ArgumentNullException(nameof(ipaddress)); - } - ipnetwork = null; - return; - } - - if (netmask == null) - { - if (tryParse == false) - { - throw new ArgumentNullException(nameof(netmask)); - } - ipnetwork = null; - return; - } - - var uintIpAddress = IPNetwork.ToBigInteger(ipaddress); - bool parsed = IPNetwork.TryToCidr(netmask, out var cidr2); - if (parsed == false) - { - if (tryParse == false) - { - throw new ArgumentException("netmask"); - } - ipnetwork = null; - return; - } - byte cidr = (byte)cidr2; - - var ipnet = new IPNetwork(uintIpAddress, ipaddress.AddressFamily, cidr); - ipnetwork = ipnet; - - return; - } - - - - /// - /// 192.168.168.100/24 - /// - /// Network : 192.168.168.0 - /// Netmask : 255.255.255.0 - /// Cidr : 24 - /// Start : 192.168.168.1 - /// End : 192.168.168.254 - /// Broadcast : 192.168.168.255 - /// - /// - /// - /// - private static void InternalParse(bool tryParse, string ipaddress, byte cidr, out IPNetwork ipnetwork) - { - - if (string.IsNullOrEmpty(ipaddress)) - { - if (tryParse == false) - { - throw new ArgumentNullException(nameof(ipaddress)); - } - ipnetwork = null; - return; - } - - - IPAddress ip = null; - bool ipaddressParsed = IPAddress.TryParse(ipaddress, out ip); - if (ipaddressParsed == false) - { - if (tryParse == false) - { - throw new ArgumentException("ipaddress"); - } - ipnetwork = null; - return; - } - - IPAddress mask = null; - bool parsedNetmask = IPNetwork.TryToNetmask(cidr, ip.AddressFamily, out mask); - if (parsedNetmask == false) - { - if (tryParse == false) - { - throw new ArgumentException("cidr"); - } - ipnetwork = null; - return; - } - - - IPNetwork.InternalParse(tryParse, ip, mask, out ipnetwork); - } - - #endregion - - #region converters - - #region ToUint - - /// - /// Convert an ipadress to decimal - /// 0.0.0.0 -> 0 - /// 0.0.1.0 -> 256 - /// - /// - /// - public static BigInteger ToBigInteger(IPAddress ipaddress) - { - IPNetwork.InternalToBigInteger(false, ipaddress, out var uintIpAddress); - return (BigInteger)uintIpAddress; - - } - - /// - /// Convert an ipadress to decimal - /// 0.0.0.0 -> 0 - /// 0.0.1.0 -> 256 - /// - /// - /// - public static bool TryToBigInteger(IPAddress ipaddress, out BigInteger? uintIpAddress) - { - IPNetwork.InternalToBigInteger(true, ipaddress, out var uintIpAddress2); - bool parsed = (uintIpAddress2 != null); - uintIpAddress = uintIpAddress2; - return parsed; - } - -#if TRAVISCI - public -#else - internal -#endif - static void InternalToBigInteger(bool tryParse, IPAddress ipaddress, out BigInteger? uintIpAddress) - { - - if (ipaddress == null) - { - if (tryParse == false) - { - throw new ArgumentNullException(nameof(ipaddress)); - } - uintIpAddress = null; - return; - } - - byte[] bytes = ipaddress.GetAddressBytes(); - /// 20180217 lduchosal - /// code impossible to reach, GetAddressBytes returns either 4 or 16 bytes length addresses - /// if (bytes.Length != 4 && bytes.Length != 16) { - /// if (tryParse == false) { - /// throw new ArgumentException("bytes"); - /// } - /// uintIpAddress = null; - /// return; - /// } - - Array.Reverse(bytes); - var unsigned = new List(bytes); - unsigned.Add(0); - uintIpAddress = new BigInteger(unsigned.ToArray()); - return; - } - - - /// - /// Convert a cidr to BigInteger netmask - /// - /// - /// - public static BigInteger ToUint(byte cidr, AddressFamily family) - { - IPNetwork.InternalToBigInteger(false, cidr, family, out var uintNetmask); - return (BigInteger)uintNetmask; - } - - - /// - /// Convert a cidr to uint netmask - /// - /// - /// - public static bool TryToUint(byte cidr, AddressFamily family, out BigInteger? uintNetmask) - { - IPNetwork.InternalToBigInteger(true, cidr, family, out var uintNetmask2); - bool parsed = (uintNetmask2 != null); - uintNetmask = uintNetmask2; - return parsed; - } - - /// - /// Convert a cidr to uint netmask - /// - /// - /// -#if TRAVISCI - public -#else - internal -#endif - static void InternalToBigInteger(bool tryParse, byte cidr, AddressFamily family, out BigInteger? uintNetmask) - { - - if (family == AddressFamily.InterNetwork && cidr > 32) - { - if (tryParse == false) - { - throw new ArgumentOutOfRangeException(nameof(cidr)); - } - uintNetmask = null; - return; - } - - if (family == AddressFamily.InterNetworkV6 && cidr > 128) - { - if (tryParse == false) - { - throw new ArgumentOutOfRangeException(nameof(cidr)); - } - uintNetmask = null; - return; - } - - if (family != AddressFamily.InterNetwork - && family != AddressFamily.InterNetworkV6) - { - if (tryParse == false) - { - throw new NotSupportedException(family.ToString()); - } - uintNetmask = null; - return; - } - - if (family == AddressFamily.InterNetwork) - { - - uintNetmask = cidr == 0 ? 0 : 0xffffffff << (32 - cidr); - return; - } - - var mask = new BigInteger(new byte[] { - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0x00 - }); - - var masked = cidr == 0 ? 0 : mask << (128 - cidr); - byte[] m = masked.ToByteArray(); - byte[] bmask = new byte[17]; - int copy = m.Length > 16 ? 16 : m.Length; - Array.Copy(m, 0, bmask, 0, copy); - uintNetmask = new BigInteger(bmask); - - - } - - #endregion - - #region ToCidr - - /// - /// Convert netmask to CIDR - /// 255.255.255.0 -> 24 - /// 255.255.0.0 -> 16 - /// 255.0.0.0 -> 8 - /// - /// - /// - private static void InternalToCidr(bool tryParse, BigInteger netmask, AddressFamily family, out byte? cidr) - { - - if (!IPNetwork.InternalValidNetmask(netmask, family)) - { - if (tryParse == false) - { - throw new ArgumentException("netmask"); - } - cidr = null; - return; - } - - byte cidr2 = IPNetwork.BitsSet(netmask, family); - cidr = cidr2; - return; - - } - /// - /// Convert netmask to CIDR - /// 255.255.255.0 -> 24 - /// 255.255.0.0 -> 16 - /// 255.0.0.0 -> 8 - /// - /// - /// - public static byte ToCidr(IPAddress netmask) - { - IPNetwork.InternalToCidr(false, netmask, out var cidr); - return (byte)cidr; - } - - /// - /// Convert netmask to CIDR - /// 255.255.255.0 -> 24 - /// 255.255.0.0 -> 16 - /// 255.0.0.0 -> 8 - /// - /// - /// - public static bool TryToCidr(IPAddress netmask, out byte? cidr) - { - IPNetwork.InternalToCidr(true, netmask, out var cidr2); - bool parsed = (cidr2 != null); - cidr = cidr2; - return parsed; - } - - private static void InternalToCidr(bool tryParse, IPAddress netmask, out byte? cidr) - { - - if (netmask == null) - { - if (tryParse == false) - { - throw new ArgumentNullException(nameof(netmask)); - } - cidr = null; - return; - } - - bool parsed = IPNetwork.TryToBigInteger(netmask, out var uintNetmask2); - - /// 20180217 lduchosal - /// impossible to reach code. - /// if (parsed == false) { - /// if (tryParse == false) { - /// throw new ArgumentException("netmask"); - /// } - /// cidr = null; - /// return; - /// } - var uintNetmask = (BigInteger)uintNetmask2; - - IPNetwork.InternalToCidr(tryParse, uintNetmask, netmask.AddressFamily, out var cidr2); - cidr = cidr2; - - return; - - } - - - #endregion - - #region ToNetmask - - /// - /// Convert CIDR to netmask - /// 24 -> 255.255.255.0 - /// 16 -> 255.255.0.0 - /// 8 -> 255.0.0.0 - /// - /// - /// - /// - public static IPAddress ToNetmask(byte cidr, AddressFamily family) - { - - IPAddress netmask = null; - IPNetwork.InternalToNetmask(false, cidr, family, out netmask); - return netmask; - } - - /// - /// Convert CIDR to netmask - /// 24 -> 255.255.255.0 - /// 16 -> 255.255.0.0 - /// 8 -> 255.0.0.0 - /// - /// - /// - /// - public static bool TryToNetmask(byte cidr, AddressFamily family, out IPAddress netmask) - { - - IPAddress netmask2 = null; - IPNetwork.InternalToNetmask(true, cidr, family, out netmask2); - bool parsed = (netmask2 != null); - netmask = netmask2; - return parsed; - } - - -#if TRAVISCI - public -#else - internal -#endif - static void InternalToNetmask(bool tryParse, byte cidr, AddressFamily family, out IPAddress netmask) - { - - if (family != AddressFamily.InterNetwork - && family != AddressFamily.InterNetworkV6) - { - if (tryParse == false) - { - throw new ArgumentException("family"); - } - netmask = null; - return; - } - - /// 20180217 lduchosal - /// impossible to reach code, byte cannot be negative : - /// - /// if (cidr < 0) { - /// if (tryParse == false) { - /// throw new ArgumentOutOfRangeException("cidr"); - /// } - /// netmask = null; - /// return; - /// } - - int maxCidr = family == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128; - if (cidr > maxCidr) - { - if (tryParse == false) - { - throw new ArgumentOutOfRangeException(nameof(cidr)); - } - netmask = null; - return; - } - - var mask = IPNetwork.ToUint(cidr, family); - var netmask2 = IPNetwork.ToIPAddress(mask, family); - netmask = netmask2; - - return; - } - - #endregion - - #endregion - - #region utils - - #region BitsSet - - /// - /// Count bits set to 1 in netmask - /// - /// - /// - /// - private static byte BitsSet(BigInteger netmask, AddressFamily family) - { - - string s = netmask.ToBinaryString(); - return (byte)s.Replace("0", "") - .ToCharArray() - .Length; - - } - - - /// - /// Count bits set to 1 in netmask - /// - /// - /// - public static uint BitsSet(IPAddress netmask) - { - var uintNetmask = IPNetwork.ToBigInteger(netmask); - uint bits = IPNetwork.BitsSet(uintNetmask, netmask.AddressFamily); - return bits; - } - - #endregion - - #region ValidNetmask - - /// - /// return true if netmask is a valid netmask - /// 255.255.255.0, 255.0.0.0, 255.255.240.0, ... - /// - /// - /// - /// - public static bool ValidNetmask(IPAddress netmask) - { - - if (netmask == null) - { - throw new ArgumentNullException(nameof(netmask)); - } - var uintNetmask = IPNetwork.ToBigInteger(netmask); - bool valid = IPNetwork.InternalValidNetmask(uintNetmask, netmask.AddressFamily); - return valid; - } - -#if TRAVISCI - public -#else - internal -#endif - static bool InternalValidNetmask(BigInteger netmask, AddressFamily family) - { - - if (family != AddressFamily.InterNetwork - && family != AddressFamily.InterNetworkV6) - { - throw new ArgumentException("family"); - } - - var mask = family == AddressFamily.InterNetwork - ? new BigInteger(0x0ffffffff) - : new BigInteger(new byte[]{ - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0x00 - }); - - var neg = ((~netmask) & (mask)); - bool isNetmask = ((neg + 1) & neg) == 0; - return isNetmask; - - } - - #endregion - - #region ToIPAddress - - /// - /// Transform a uint ipaddress into IPAddress object - /// - /// - /// - public static IPAddress ToIPAddress(BigInteger ipaddress, AddressFamily family) - { - - int width = family == AddressFamily.InterNetwork ? 4 : 16; - byte[] bytes = ipaddress.ToByteArray(); - byte[] bytes2 = new byte[width]; - int copy = bytes.Length > width ? width : bytes.Length; - Array.Copy(bytes, 0, bytes2, 0, copy); - Array.Reverse(bytes2); - - byte[] sized = Resize(bytes2, family); - var ip = new IPAddress(sized); - return ip; - } - -#if TRAVISCI - public -#else - internal -#endif - static byte[] Resize(byte[] bytes, AddressFamily family) - { - - if (family != AddressFamily.InterNetwork - && family != AddressFamily.InterNetworkV6) - { - throw new ArgumentException("family"); - } - - int width = family == AddressFamily.InterNetwork ? 4 : 16; - - if (bytes.Length > width) - { - throw new ArgumentException("bytes"); - } - - byte[] result = new byte[width]; - Array.Copy(bytes, 0, result, 0, bytes.Length); - return result; - } - - #endregion - - #endregion - - #region contains - - /// - /// return true if ipaddress is contained in network - /// - /// - /// - public bool Contains(IPAddress ipaddress) - { - - if (ipaddress == null) - { - throw new ArgumentNullException(nameof(ipaddress)); - } - - if (AddressFamily != ipaddress.AddressFamily) - { - return false; - } - - var uintNetwork = _network; - var uintBroadcast = _broadcast; - var uintAddress = IPNetwork.ToBigInteger(ipaddress); - - bool contains = (uintAddress >= uintNetwork - && uintAddress <= uintBroadcast); - - return contains; - - } - - /// - /// return true is network2 is fully contained in network - /// - /// - /// - public bool Contains(IPNetwork network2) - { - - if (network2 == null) - { - throw new ArgumentNullException(nameof(network2)); - } - - var uintNetwork = _network; - var uintBroadcast = _broadcast; - - var uintFirst = network2._network; - var uintLast = network2._broadcast; - - bool contains = (uintFirst >= uintNetwork - && uintLast <= uintBroadcast); - - return contains; - } - - #endregion - - #region overlap - - /// - /// return true is network2 overlap network - /// - /// - /// - public bool Overlap(IPNetwork network2) - { - - if (network2 == null) - { - throw new ArgumentNullException(nameof(network2)); - } - - var uintNetwork = _network; - var uintBroadcast = _broadcast; - - var uintFirst = network2._network; - var uintLast = network2._broadcast; - - bool overlap = - (uintFirst >= uintNetwork && uintFirst <= uintBroadcast) - || (uintLast >= uintNetwork && uintLast <= uintBroadcast) - || (uintFirst <= uintNetwork && uintLast >= uintBroadcast) - || (uintFirst >= uintNetwork && uintLast <= uintBroadcast); - - return overlap; - } - - #endregion - - #region ToString - - public override string ToString() - { - return string.Format("{0}/{1}", this.Network, this.Cidr); - } - - #endregion - - #region IANA block - - private static readonly Lazy _iana_ablock_reserved = new Lazy(() => IPNetwork.Parse("10.0.0.0/8")); - private static readonly Lazy _iana_bblock_reserved = new Lazy(() => IPNetwork.Parse("172.16.0.0/12")); - private static readonly Lazy _iana_cblock_reserved = new Lazy(() => IPNetwork.Parse("192.168.0.0/16")); - - /// - /// 10.0.0.0/8 - /// - /// - public static IPNetwork IANA_ABLK_RESERVED1 => _iana_ablock_reserved.Value; - - /// - /// 172.12.0.0/12 - /// - /// - public static IPNetwork IANA_BBLK_RESERVED1 => _iana_bblock_reserved.Value; - - /// - /// 192.168.0.0/16 - /// - /// - public static IPNetwork IANA_CBLK_RESERVED1 => _iana_cblock_reserved.Value; - - /// - /// return true if ipaddress is contained in - /// IANA_ABLK_RESERVED1, IANA_BBLK_RESERVED1, IANA_CBLK_RESERVED1 - /// - /// - /// - public static bool IsIANAReserved(IPAddress ipaddress) - { - - if (ipaddress == null) - { - throw new ArgumentNullException(nameof(ipaddress)); - } - - return IPNetwork.IANA_ABLK_RESERVED1.Contains(ipaddress) - || IPNetwork.IANA_BBLK_RESERVED1.Contains(ipaddress) - || IPNetwork.IANA_CBLK_RESERVED1.Contains(ipaddress); - } - - /// - /// return true if ipnetwork is contained in - /// IANA_ABLK_RESERVED1, IANA_BBLK_RESERVED1, IANA_CBLK_RESERVED1 - /// - /// - public bool IsIANAReserved() - { - return IPNetwork.IANA_ABLK_RESERVED1.Contains(this) - || IPNetwork.IANA_BBLK_RESERVED1.Contains(this) - || IPNetwork.IANA_CBLK_RESERVED1.Contains(this); - } - - #endregion - - #region Subnet - - /// - /// Subnet a network into multiple nets of cidr mask - /// Subnet 192.168.0.0/24 into cidr 25 gives 192.168.0.0/25, 192.168.0.128/25 - /// Subnet 10.0.0.0/8 into cidr 9 gives 10.0.0.0/9, 10.128.0.0/9 - /// - /// - /// - public IPNetworkCollection Subnet(byte cidr) - { - IPNetworkCollection ipnetworkCollection = null; - IPNetwork.InternalSubnet(false, this, cidr, out ipnetworkCollection); - return ipnetworkCollection; - } - - /// - /// Subnet a network into multiple nets of cidr mask - /// Subnet 192.168.0.0/24 into cidr 25 gives 192.168.0.0/25, 192.168.0.128/25 - /// Subnet 10.0.0.0/8 into cidr 9 gives 10.0.0.0/9, 10.128.0.0/9 - /// - /// - /// - public bool TrySubnet(byte cidr, out IPNetworkCollection ipnetworkCollection) - { - IPNetworkCollection inc = null; - IPNetwork.InternalSubnet(true, this, cidr, out inc); - if (inc == null) - { - ipnetworkCollection = null; - return false; - } - - ipnetworkCollection = inc; - return true; - } - -#if TRAVISCI - public -#else - internal -#endif - static void InternalSubnet(bool trySubnet, IPNetwork network, byte cidr, out IPNetworkCollection ipnetworkCollection) - { - - if (network == null) - { - if (trySubnet == false) - { - throw new ArgumentNullException(nameof(network)); - } - ipnetworkCollection = null; - return; - } - - int maxCidr = network._family == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128; - if (cidr > maxCidr) - { - if (trySubnet == false) - { - throw new ArgumentOutOfRangeException(nameof(cidr)); - } - ipnetworkCollection = null; - return; - } - - if (cidr < network.Cidr) - { - if (trySubnet == false) - { - throw new ArgumentException("cidr"); - } - ipnetworkCollection = null; - return; - } - - ipnetworkCollection = new IPNetworkCollection(network, cidr); - return; - } - - - - #endregion - - #region Supernet - - /// - /// Supernet two consecutive cidr equal subnet into a single one - /// 192.168.0.0/24 + 192.168.1.0/24 = 192.168.0.0/23 - /// 10.1.0.0/16 + 10.0.0.0/16 = 10.0.0.0/15 - /// 192.168.0.0/24 + 192.168.0.0/25 = 192.168.0.0/24 - /// - /// - /// - public IPNetwork Supernet(IPNetwork network2) - { - IPNetwork supernet = null; - IPNetwork.InternalSupernet(false, this, network2, out supernet); - return supernet; - } - - /// - /// Try to supernet two consecutive cidr equal subnet into a single one - /// 192.168.0.0/24 + 192.168.1.0/24 = 192.168.0.0/23 - /// 10.1.0.0/16 + 10.0.0.0/16 = 10.0.0.0/15 - /// 192.168.0.0/24 + 192.168.0.0/25 = 192.168.0.0/24 - /// - /// - /// - public bool TrySupernet(IPNetwork network2, out IPNetwork supernet) - { - - IPNetwork outSupernet = null; - IPNetwork.InternalSupernet(true, this, network2, out outSupernet); - bool parsed = (outSupernet != null); - supernet = outSupernet; - return parsed; - } - -#if TRAVISCI - public -#else - internal -#endif - static void InternalSupernet(bool trySupernet, IPNetwork network1, IPNetwork network2, out IPNetwork supernet) - { - - if (network1 == null) - { - if (trySupernet == false) - { - throw new ArgumentNullException(nameof(network1)); - } - supernet = null; - return; - } - - if (network2 == null) - { - if (trySupernet == false) - { - throw new ArgumentNullException(nameof(network2)); - } - supernet = null; - return; - } - - - if (network1.Contains(network2)) - { - supernet = new IPNetwork(network1._network, network1._family, network1.Cidr); - return; - } - - if (network2.Contains(network1)) - { - supernet = new IPNetwork(network2._network, network2._family, network2.Cidr); - return; - } - - if (network1._cidr != network2._cidr) - { - if (trySupernet == false) - { - throw new ArgumentException("cidr"); - } - supernet = null; - return; - } - - var first = (network1._network < network2._network) ? network1 : network2; - var last = (network1._network > network2._network) ? network1 : network2; - - /// Starting from here : - /// network1 and network2 have the same cidr, - /// network1 does not contain network2, - /// network2 does not contain network1, - /// first is the lower subnet - /// last is the higher subnet - - - if ((first._broadcast + 1) != last._network) - { - if (trySupernet == false) - { - throw new ArgumentOutOfRangeException(nameof(trySupernet), "TrySupernet was false while the first and last networks are not adjacent."); - } - supernet = null; - return; - } - - var uintSupernet = first._network; - byte cidrSupernet = (byte)(first._cidr - 1); - - var networkSupernet = new IPNetwork(uintSupernet, first._family, cidrSupernet); - if (networkSupernet._network != first._network) - { - if (trySupernet == false) - { - throw new ArgumentException("network"); - } - supernet = null; - return; - } - supernet = networkSupernet; - return; - } - - #endregion - - #region GetHashCode - - public override int GetHashCode() - { - return string.Format("{0}|{1}|{2}", - this._ipaddress.GetHashCode(), - this._network.GetHashCode(), - this._cidr.GetHashCode()).GetHashCode(); - } - - #endregion - - #region SupernetArray - - /// - /// Supernet a list of subnet - /// 192.168.0.0/24 + 192.168.1.0/24 = 192.168.0.0/23 - /// 192.168.0.0/24 + 192.168.1.0/24 + 192.168.2.0/24 + 192.168.3.0/24 = 192.168.0.0/22 - /// - /// The IP networks - /// - public static IPNetwork[] Supernet(IPNetwork[] ipnetworks) - { - InternalSupernet(false, ipnetworks, out var supernet); - return supernet; - } - - /// - /// Supernet a list of subnet - /// 192.168.0.0/24 + 192.168.1.0/24 = 192.168.0.0/23 - /// 192.168.0.0/24 + 192.168.1.0/24 + 192.168.2.0/24 + 192.168.3.0/24 = 192.168.0.0/22 - /// - /// - /// - /// - public static bool TrySupernet(IPNetwork[] ipnetworks, out IPNetwork[] supernet) - { - bool supernetted = InternalSupernet(true, ipnetworks, out supernet); - return supernetted; - - } - -#if TRAVISCI - public -#else - internal -#endif - static bool InternalSupernet(bool trySupernet, IPNetwork[] ipnetworks, out IPNetwork[] supernet) - { - - if (ipnetworks == null) - { - if (trySupernet == false) - { - throw new ArgumentNullException(nameof(ipnetworks)); - } - supernet = null; - return false; - } - - if (ipnetworks.Length <= 0) - { - supernet = new IPNetwork[0]; - return true; - } - - var supernetted = new List(); - var ipns = IPNetwork.Array2List(ipnetworks); - var current = IPNetwork.List2Stack(ipns); - int previousCount = 0; - int currentCount = current.Count; - - while (previousCount != currentCount) - { - - supernetted.Clear(); - while (current.Count > 1) - { - var ipn1 = current.Pop(); - var ipn2 = current.Peek(); - - IPNetwork outNetwork = null; - bool success = ipn1.TrySupernet(ipn2, out outNetwork); - if (success) - { - current.Pop(); - current.Push(outNetwork); - } - else - { - supernetted.Add(ipn1); - } - } - if (current.Count == 1) - { - supernetted.Add(current.Pop()); - } - - previousCount = currentCount; - currentCount = supernetted.Count; - current = IPNetwork.List2Stack(supernetted); - - } - supernet = supernetted.ToArray(); - return true; - } - - private static Stack List2Stack(List list) - { - var stack = new Stack(); - list.ForEach(new Action( - delegate (IPNetwork ipn) - { - stack.Push(ipn); - } - )); - return stack; - } - - private static List Array2List(IPNetwork[] array) - { - var ipns = new List(); - ipns.AddRange(array); - IPNetwork.RemoveNull(ipns); - ipns.Sort(new Comparison( - delegate (IPNetwork ipn1, IPNetwork ipn2) - { - int networkCompare = ipn1._network.CompareTo(ipn2._network); - if (networkCompare == 0) - { - int cidrCompare = ipn1._cidr.CompareTo(ipn2._cidr); - return cidrCompare; - } - return networkCompare; - } - )); - ipns.Reverse(); - - return ipns; - } - - private static void RemoveNull(List ipns) - { - ipns.RemoveAll(new Predicate( - delegate (IPNetwork ipn) - { - if (ipn == null) - { - return true; - } - return false; - } - )); - - } - - #endregion - - #region WideSubnet - - public static IPNetwork WideSubnet(string start, string end) - { - - if (string.IsNullOrEmpty(start)) - { - throw new ArgumentNullException(nameof(start)); - } - - if (string.IsNullOrEmpty(end)) - { - throw new ArgumentNullException(nameof(end)); - } - - if (!IPAddress.TryParse(start, out var startIP)) - { - throw new ArgumentException("start"); - } - - if (!IPAddress.TryParse(end, out var endIP)) - { - throw new ArgumentException("end"); - } - - if (startIP.AddressFamily != endIP.AddressFamily) - { - throw new NotSupportedException("MixedAddressFamily"); - } - - var ipnetwork = new IPNetwork(0, startIP.AddressFamily, 0); - for (byte cidr = 32; cidr >= 0; cidr--) - { - var wideSubnet = IPNetwork.Parse(start, cidr); - if (wideSubnet.Contains(endIP)) - { - ipnetwork = wideSubnet; - break; - } - } - return ipnetwork; - - } - - public static bool TryWideSubnet(IPNetwork[] ipnetworks, out IPNetwork ipnetwork) - { - IPNetwork ipn = null; - IPNetwork.InternalWideSubnet(true, ipnetworks, out ipn); - if (ipn == null) - { - ipnetwork = null; - return false; - } - ipnetwork = ipn; - return true; - } - - public static IPNetwork WideSubnet(IPNetwork[] ipnetworks) - { - IPNetwork ipn = null; - IPNetwork.InternalWideSubnet(false, ipnetworks, out ipn); - return ipn; - } - - internal static void InternalWideSubnet(bool tryWide, IPNetwork[] ipnetworks, out IPNetwork ipnetwork) - { - - if (ipnetworks == null) - { - if (tryWide == false) - { - throw new ArgumentNullException(nameof(ipnetworks)); - } - ipnetwork = null; - return; - } - - - IPNetwork[] nnin = Array.FindAll(ipnetworks, new Predicate( - delegate (IPNetwork ipnet) - { - return ipnet != null; - } - )); - - if (nnin.Length <= 0) - { - if (tryWide == false) - { - throw new ArgumentException("ipnetworks"); - } - ipnetwork = null; - return; - } - - if (nnin.Length == 1) - { - var ipn0 = nnin[0]; - ipnetwork = ipn0; - return; - } - - Array.Sort(nnin); - var nnin0 = nnin[0]; - var uintNnin0 = nnin0._ipaddress; - - var nninX = nnin[nnin.Length - 1]; - var ipaddressX = nninX.Broadcast; - - var family = ipnetworks[0]._family; - foreach (var ipnx in ipnetworks) - { - if (ipnx._family != family) - { - throw new ArgumentException("MixedAddressFamily"); - } - } - - var ipn = new IPNetwork(0, family, 0); - for (byte cidr = nnin0._cidr; cidr >= 0; cidr--) - { - var wideSubnet = new IPNetwork(uintNnin0, family, cidr); - if (wideSubnet.Contains(ipaddressX)) - { - ipn = wideSubnet; - break; - } - } - - ipnetwork = ipn; - return; - } - - #endregion - - #region Print - - /// - /// Print an ipnetwork in a clear representation string - /// - /// - public string Print() - { - - var sw = new StringWriter(); - - sw.WriteLine("IPNetwork : {0}", ToString()); - sw.WriteLine("Network : {0}", Network); - sw.WriteLine("Netmask : {0}", Netmask); - sw.WriteLine("Cidr : {0}", Cidr); - sw.WriteLine("Broadcast : {0}", Broadcast); - sw.WriteLine("FirstUsable : {0}", FirstUsable); - sw.WriteLine("LastUsable : {0}", LastUsable); - sw.WriteLine("Usable : {0}", Usable); - - return sw.ToString(); - } - - #endregion - - #region TryGuessCidr - - /// - /// - /// Class Leading bits Default netmask - /// A (CIDR /8) 00 255.0.0.0 - /// A (CIDR /8) 01 255.0.0.0 - /// B (CIDR /16) 10 255.255.0.0 - /// C (CIDR /24) 11 255.255.255.0 - /// - /// - /// - /// - /// - public static bool TryGuessCidr(string ip, out byte cidr) - { - - IPAddress ipaddress = null; - bool parsed = IPAddress.TryParse(string.Format("{0}", ip), out ipaddress); - if (parsed == false) - { - cidr = 0; - return false; - } - - if (ipaddress.AddressFamily == AddressFamily.InterNetworkV6) - { - cidr = 64; - return true; - } - var uintIPAddress = IPNetwork.ToBigInteger(ipaddress); - uintIPAddress = uintIPAddress >> 29; - if (uintIPAddress <= 3) - { - cidr = 8; - return true; - } - else if (uintIPAddress <= 5) - { - cidr = 16; - return true; - } - else if (uintIPAddress <= 6) - { - cidr = 24; - return true; - } - - cidr = 0; - return false; - - } - - /// - /// Try to parse cidr. Have to be >= 0 and <= 32 or 128 - /// - /// - /// - /// - public static bool TryParseCidr(string sidr, AddressFamily family, out byte? cidr) - { - - byte b = 0; - if (!byte.TryParse(sidr, out b)) - { - cidr = null; - return false; - } - - IPAddress netmask = null; - if (!IPNetwork.TryToNetmask(b, family, out netmask)) - { - cidr = null; - return false; - } - - cidr = b; - return true; - } - - #endregion - - #region ListIPAddress - - public IPAddressCollection ListIPAddress() - { - return new IPAddressCollection(this); - } - - #endregion - - /** - * Need a better way to do it - * -#region TrySubstractNetwork - - public static bool TrySubstractNetwork(IPNetwork[] ipnetworks, IPNetwork substract, out IEnumerable result) { - - if (ipnetworks == null) { - result = null; - return false; - } - if (ipnetworks.Length <= 0) { - result = null; - return false; - } - if (substract == null) { - result = null; - return false; - } - var results = new List(); - foreach (var ipn in ipnetworks) { - if (!Overlap(ipn, substract)) { - results.Add(ipn); - continue; - } - - var collection = ipn.Subnet(substract.Cidr); - var rtemp = new List(); - foreach(var subnet in collection) { - if (subnet != substract) { - rtemp.Add(subnet); - } - } - var supernets = Supernet(rtemp.ToArray()); - results.AddRange(supernets); - } - result = results; - return true; - } -#endregion - * **/ - - #region IComparable Members - - public static int Compare(IPNetwork left, IPNetwork right) - { - // two null IPNetworks are equal - if (ReferenceEquals(left, null) && ReferenceEquals(right, null)) return 0; - - // two same IPNetworks are equal - if (ReferenceEquals(left, right)) return 0; - - // null is always sorted first - if (ReferenceEquals(left, null)) return -1; - if (ReferenceEquals(right, null)) return 1; - - // first test the network - var result = left._network.CompareTo(right._network); - if (result != 0) return result; - - // then test the cidr - result = left._cidr.CompareTo(right._cidr); - return result; - } - - public int CompareTo(IPNetwork other) - { - return Compare(this, other); - } - - public int CompareTo(object obj) - { - // null is at less - if (obj == null) return 1; - - // convert to a proper Cidr object - var other = obj as IPNetwork; - - // type problem if null - if (other == null) - { - throw new ArgumentException( - "The supplied parameter is an invalid type. Please supply an IPNetwork type.", - nameof(obj)); - } - - // perform the comparision - return CompareTo(other); - } - - #endregion - - #region IEquatable Members - - public static bool Equals(IPNetwork left, IPNetwork right) - { - return Compare(left, right) == 0; - } - - public bool Equals(IPNetwork other) - { - return Equals(this, other); - } - - public override bool Equals(object obj) - { - return Equals(this, obj as IPNetwork); - } - - #endregion - - #region Operators - - public static bool operator ==(IPNetwork left, IPNetwork right) - { - return Equals(left, right); - } - - public static bool operator !=(IPNetwork left, IPNetwork right) - { - return !Equals(left, right); - } - - public static bool operator <(IPNetwork left, IPNetwork right) - { - return Compare(left, right) < 0; - } - - public static bool operator >(IPNetwork left, IPNetwork right) - { - return Compare(left, right) > 0; - } - - #endregion - - } -} diff --git a/Emby.Server.Implementations/Networking/IPNetwork/IPNetworkCollection.cs b/Emby.Server.Implementations/Networking/IPNetwork/IPNetworkCollection.cs deleted file mode 100644 index 4cda421e57..0000000000 --- a/Emby.Server.Implementations/Networking/IPNetwork/IPNetworkCollection.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Numerics; - -namespace Emby.Server.Implementations.Networking.IPNetwork -{ - public class IPNetworkCollection : IEnumerable, IEnumerator - { - - private BigInteger _enumerator; - private byte _cidrSubnet; - private IPNetwork _ipnetwork; - - private byte _cidr => this._ipnetwork.Cidr; - - private BigInteger _broadcast => IPNetwork.ToBigInteger(this._ipnetwork.Broadcast); - - private BigInteger _lastUsable => IPNetwork.ToBigInteger(this._ipnetwork.LastUsable); - private BigInteger _network => IPNetwork.ToBigInteger(this._ipnetwork.Network); -#if TRAVISCI - public -#else - internal -#endif - IPNetworkCollection(IPNetwork ipnetwork, byte cidrSubnet) - { - - int maxCidr = ipnetwork.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128; - if (cidrSubnet > maxCidr) - { - throw new ArgumentOutOfRangeException(nameof(cidrSubnet)); - } - - if (cidrSubnet < ipnetwork.Cidr) - { - throw new ArgumentException("cidr"); - } - - this._cidrSubnet = cidrSubnet; - this._ipnetwork = ipnetwork; - this._enumerator = -1; - } - - #region Count, Array, Enumerator - - public BigInteger Count - { - get - { - var count = BigInteger.Pow(2, this._cidrSubnet - this._cidr); - return count; - } - } - - public IPNetwork this[BigInteger i] - { - get - { - if (i >= this.Count) - { - throw new ArgumentOutOfRangeException(nameof(i)); - } - - var last = this._ipnetwork.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6 - ? this._lastUsable : this._broadcast; - var increment = (last - this._network) / this.Count; - var uintNetwork = this._network + ((increment + 1) * i); - var ipn = new IPNetwork(uintNetwork, this._ipnetwork.AddressFamily, this._cidrSubnet); - return ipn; - } - } - - #endregion - - #region IEnumerable Members - - IEnumerator IEnumerable.GetEnumerator() - { - return this; - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this; - } - - #region IEnumerator Members - - public IPNetwork Current => this[this._enumerator]; - - #endregion - - #region IDisposable Members - - public void Dispose() - { - // nothing to dispose - return; - } - - #endregion - - #region IEnumerator Members - - object IEnumerator.Current => this.Current; - - public bool MoveNext() - { - this._enumerator++; - if (this._enumerator >= this.Count) - { - return false; - } - return true; - - } - - public void Reset() - { - this._enumerator = -1; - } - - #endregion - - #endregion - - } -} diff --git a/Emby.Server.Implementations/Networking/IPNetwork/LICENSE.txt b/Emby.Server.Implementations/Networking/IPNetwork/LICENSE.txt deleted file mode 100644 index 45d7392ac4..0000000000 --- a/Emby.Server.Implementations/Networking/IPNetwork/LICENSE.txt +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2015, lduchosal -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 3cacacf075..82add242ac 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Networking if (normalizedSubnet.IndexOf('/') != -1) { - var ipnetwork = IPNetwork.IPNetwork.Parse(normalizedSubnet); + var ipnetwork = IPNetwork.Parse(normalizedSubnet); if (ipnetwork.Contains(address)) { return true; -- cgit v1.2.3 From 0e6417c9fa04ea58638a1594245365da6be6f8c1 Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Sat, 3 Aug 2019 12:37:02 +0200 Subject: Set log level to debug for HTTP range requests --- Emby.Server.Implementations/HttpServer/FileWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index c6b7d31a83..ec41cc0a91 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -106,7 +106,7 @@ namespace Emby.Server.Implementations.HttpServer var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; Headers[HeaderNames.ContentRange] = rangeString; - Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); + Logger.LogDebug("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); } /// -- cgit v1.2.3 From b637cdabaef9703063fed23719bcac5a1709373e Mon Sep 17 00:00:00 2001 From: Michał Date: Fri, 2 Aug 2019 15:32:42 +0000 Subject: Translated using Weblate (Polish) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pl/ --- .../Localization/Core/pl.json | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index 92b12409d1..e72f1a2621 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -18,11 +18,11 @@ "HeaderAlbumArtists": "Wykonawcy albumów", "HeaderCameraUploads": "Przekazane obrazy", "HeaderContinueWatching": "Kontynuuj odtwarzanie", - "HeaderFavoriteAlbums": "Albumy ulubione", - "HeaderFavoriteArtists": "Wykonawcy ulubieni", - "HeaderFavoriteEpisodes": "Odcinki ulubione", - "HeaderFavoriteShows": "Seriale ulubione", - "HeaderFavoriteSongs": "Utwory ulubione", + "HeaderFavoriteAlbums": "Ulubione albumy", + "HeaderFavoriteArtists": "Ulubieni wykonawcy", + "HeaderFavoriteEpisodes": "Ulubione odcinki", + "HeaderFavoriteShows": "Ulubione seriale", + "HeaderFavoriteSongs": "Ulubione utwory", "HeaderLiveTV": "Telewizja", "HeaderNextUp": "Do obejrzenia", "HeaderRecordingGroups": "Grupy nagrań", @@ -41,26 +41,26 @@ "Movies": "Filmy", "Music": "Muzyka", "MusicVideos": "Teledyski", - "NameInstallFailed": "Instalacja {0} nieudana.", + "NameInstallFailed": "Instalacja {0} nieudana", "NameSeasonNumber": "Sezon {0}", - "NameSeasonUnknown": "Sezon nieznany", + "NameSeasonUnknown": "Nieznany sezon", "NewVersionIsAvailable": "Nowa wersja serwera Jellyfin jest dostępna do pobrania.", "NotificationOptionApplicationUpdateAvailable": "Dostępna aktualizacja aplikacji", - "NotificationOptionApplicationUpdateInstalled": "Zainstalowano aktualizację aplikacji", + "NotificationOptionApplicationUpdateInstalled": "Zaktualizowano aplikację", "NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki", "NotificationOptionAudioPlaybackStopped": "Odtwarzane dźwięku zatrzymane", - "NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia mobilnego", - "NotificationOptionInstallationFailed": "Niepowodzenie instalacji", + "NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia przenośnego", + "NotificationOptionInstallationFailed": "Nieudana instalacja", "NotificationOptionNewLibraryContent": "Dodano nową zawartość", "NotificationOptionPluginError": "Awaria wtyczki", "NotificationOptionPluginInstalled": "Zainstalowano wtyczkę", "NotificationOptionPluginUninstalled": "Odinstalowano wtyczkę", - "NotificationOptionPluginUpdateInstalled": "Zainstalowano aktualizację wtyczki", + "NotificationOptionPluginUpdateInstalled": "Zaktualizowano wtyczkę", "NotificationOptionServerRestartRequired": "Wymagane ponowne uruchomienie serwera", "NotificationOptionTaskFailed": "Awaria zaplanowanego zadania", "NotificationOptionUserLockedOut": "Użytkownik zablokowany", "NotificationOptionVideoPlayback": "Rozpoczęto odtwarzanie wideo", - "NotificationOptionVideoPlaybackStopped": "Odtwarzanie wideo zatrzymane", + "NotificationOptionVideoPlaybackStopped": "Zatrzymano odtwarzanie wideo", "Photos": "Zdjęcia", "Playlists": "Listy odtwarzania", "Plugin": "Wtyczka", @@ -73,7 +73,7 @@ "ServerNameNeedsToBeRestarted": "{0} wymaga ponownego uruchomienia", "Shows": "Seriale", "Songs": "Utwory", - "StartupEmbyServerIsLoading": "Twa wczytywanie serwera Jellyfin. Spróbuj ponownie za chwilę.", + "StartupEmbyServerIsLoading": "Trwa wczytywanie serwera Jellyfin. Spróbuj ponownie za chwilę.", "SubtitleDownloadFailureForItem": "Pobieranie napisów dla {0} zakończone niepowodzeniem", "SubtitleDownloadFailureFromForItem": "Nieudane pobieranie napisów z {0} dla {1}", "SubtitlesDownloadedForItem": "Pobrano napisy dla {0}", @@ -91,7 +91,7 @@ "UserPolicyUpdatedWithName": "Zmieniono zasady użytkowania dla {0}", "UserStartedPlayingItemWithValues": "{0} odtwarza {1} na {2}", "UserStoppedPlayingItemWithValues": "{0} zakończył odtwarzanie {1} na {2}", - "ValueHasBeenAddedToLibrary": "{0} został dodany to biblioteki mediów", + "ValueHasBeenAddedToLibrary": "{0} został dodany do biblioteki mediów", "ValueSpecialEpisodeName": "Specjalne - {0}", "VersionNumber": "Wersja {0}" } -- cgit v1.2.3 From 20cbbd4f4c99d9a603756f83e0132caa71c16c13 Mon Sep 17 00:00:00 2001 From: AndersMachmueller Date: Sun, 4 Aug 2019 19:18:50 +0000 Subject: Translated using Weblate (Danish) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/da/ --- Emby.Server.Implementations/Localization/Core/da.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index cb8f4576ad..b01abafa13 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -18,11 +18,11 @@ "HeaderAlbumArtists": "Albumkunstnere", "HeaderCameraUploads": "Kamera Uploads", "HeaderContinueWatching": "Fortsæt Afspilning", - "HeaderFavoriteAlbums": "Favoritalbum", + "HeaderFavoriteAlbums": "Favoritalbummer", "HeaderFavoriteArtists": "Favoritkunstnere", - "HeaderFavoriteEpisodes": "Favorit-afsnit", - "HeaderFavoriteShows": "Favorit-serier", - "HeaderFavoriteSongs": "Favorit-sange", + "HeaderFavoriteEpisodes": "Favoritepisoder", + "HeaderFavoriteShows": "Favoritserier", + "HeaderFavoriteSongs": "Favoritsange", "HeaderLiveTV": "Live TV", "HeaderNextUp": "Næste", "HeaderRecordingGroups": "Optagelsesgrupper", -- cgit v1.2.3 From 5e6e52d3977c679871f5633c59d82cbb3f6c2cce Mon Sep 17 00:00:00 2001 From: Tamás Mogyorósi Date: Thu, 25 Jul 2019 13:56:40 +0000 Subject: Translated using Weblate (Hungarian) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/ --- Emby.Server.Implementations/Localization/Core/hu.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index c0f988abec..a2fc126a25 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -19,9 +19,9 @@ "HeaderCameraUploads": "Kamera feltöltések", "HeaderContinueWatching": "Folyamatban lévő filmek", "HeaderFavoriteAlbums": "Kedvenc Albumok", - "HeaderFavoriteArtists": "Kedvenc Művészek", + "HeaderFavoriteArtists": "Kedvenc Előadók", "HeaderFavoriteEpisodes": "Kedvenc Epizódok", - "HeaderFavoriteShows": "Kedvenc Műsorok", + "HeaderFavoriteShows": "Kedvenc Sorozatok", "HeaderFavoriteSongs": "Kedvenc Dalok", "HeaderLiveTV": "Élő TV", "HeaderNextUp": "Következik", -- cgit v1.2.3 From 89f592687ee7ae7f0e0fffd884dbf2890476410a Mon Sep 17 00:00:00 2001 From: Tradutor da Silva Date: Tue, 23 Jul 2019 02:28:08 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Currently translated at 95.7% (90 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- Emby.Server.Implementations/Localization/Core/pt-BR.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index 5d68416e9f..c4ce16dc85 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -21,8 +21,8 @@ "HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteArtists": "Artistas Favoritos", "HeaderFavoriteEpisodes": "Episódios Favoritos", - "HeaderFavoriteShows": "Shows Favoritos", - "HeaderFavoriteSongs": "Musicas Favoritas", + "HeaderFavoriteShows": "Séries Favoritas", + "HeaderFavoriteSongs": "Músicas Favoritas", "HeaderLiveTV": "TV ao Vivo", "HeaderNextUp": "Próximos", "HeaderRecordingGroups": "Grupos de Gravação", @@ -32,19 +32,19 @@ "ItemRemovedWithName": "{0} foi removido da biblioteca", "LabelIpAddressValue": "Endereço IP: {0}", "LabelRunningTimeValue": "Tempo de execução: {0}", - "Latest": "Recente", - "MessageApplicationUpdated": "O servidor Jellyfin foi atualizado", - "MessageApplicationUpdatedTo": "O Servidor Jellyfin foi atualizado para {0}", + "Latest": "Recentes", + "MessageApplicationUpdated": "Servidor Jellyfin atualizado", + "MessageApplicationUpdatedTo": "Servidor Jellyfin atualizado para {0}", "MessageNamedServerConfigurationUpdatedWithValue": "A seção {0} da configuração do servidor foi atualizada", "MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada", "MixedContent": "Conteúdo misto", "Movies": "Filmes", "Music": "Música", - "MusicVideos": "Vídeos musicais", + "MusicVideos": "Clipes", "NameInstallFailed": "A instalação de {0} falhou", "NameSeasonNumber": "Temporada {0}", "NameSeasonUnknown": "Temporada Desconhecida", - "NewVersionIsAvailable": "Uma nova versão do servidor Jellyfin está disponível para download.", + "NewVersionIsAvailable": "Uma nova versão do Servidor Jellyfin está disponível para download.", "NotificationOptionApplicationUpdateAvailable": "Atualização de aplicativo disponível", "NotificationOptionApplicationUpdateInstalled": "Atualização de aplicativo instalada", "NotificationOptionAudioPlayback": "Reprodução de áudio iniciada", -- cgit v1.2.3 From 9fff4b060e06569ca77636643901aa42767e318d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 28 Jul 2019 23:53:19 +0200 Subject: Replace custom code with Asp.Net Core code --- Emby.Server.Implementations/ApplicationHost.cs | 6 +- .../HttpServer/FileWriter.cs | 174 +++--- .../HttpServer/HttpListenerHost.cs | 154 ++--- .../HttpServer/ResponseFilter.cs | 26 +- .../HttpServer/Security/AuthService.cs | 48 +- Emby.Server.Implementations/Services/HttpResult.cs | 9 +- .../Services/ResponseHelper.cs | 31 +- .../Services/ServiceController.cs | 4 - .../Services/ServiceExec.cs | 2 +- .../Services/ServiceHandler.cs | 103 ++-- .../SocketSharp/RequestMono.cs | 647 --------------------- .../SocketSharp/WebSocketSharpRequest.cs | 230 +++----- .../SocketSharp/WebSocketSharpResponse.cs | 98 ---- MediaBrowser.Api/Playback/BaseStreamingService.cs | 2 +- .../Net/AuthenticatedAttribute.cs | 3 +- MediaBrowser.Model/Services/IHasRequestFilter.cs | 4 +- MediaBrowser.Model/Services/IRequest.cs | 30 +- 17 files changed, 370 insertions(+), 1201 deletions(-) delete mode 100644 Emby.Server.Implementations/SocketSharp/RequestMono.cs delete mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ef2f59d303..a89cf95d3b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -676,7 +676,7 @@ namespace Emby.Server.Implementations var localPath = context.Request.Path.ToString(); var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); + await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false); } public static IStreamHelper StreamHelper { get; set; } @@ -785,7 +785,7 @@ namespace Emby.Server.Implementations HttpServer = new HttpListenerHost( this, - LoggerFactory, + LoggerFactory.CreateLogger(), ServerConfigurationManager, _configuration, NetworkManager, @@ -873,7 +873,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(authContext); serviceCollection.AddSingleton(new SessionContext(UserManager, authContext, SessionManager)); - AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager); + AuthService = new AuthService(authContext, ServerConfigurationManager, SessionManager, NetworkManager); serviceCollection.AddSingleton(AuthService); SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory); diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index ec41cc0a91..2890cca7ce 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -1,50 +1,43 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Net; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer { public class FileWriter : IHttpResult { - private readonly IStreamHelper _streamHelper; - private ILogger Logger { get; set; } - private readonly IFileSystem _fileSystem; - - private string RangeHeader { get; set; } - private bool IsHeadRequest { get; set; } - - private long RangeStart { get; set; } - private long RangeEnd { get; set; } - private long RangeLength { get; set; } - public long TotalContentLength { get; set; } + private static readonly CultureInfo UsCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); - public Action OnComplete { get; set; } - public Action OnError { get; set; } - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - public List Cookies { get; private set; } + private static readonly string[] _skipLogExtensions = { + ".js", + ".html", + ".css" + }; - public FileShareMode FileShare { get; set; } + private readonly IStreamHelper _streamHelper; + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; /// /// The _options /// private readonly IDictionary _options = new Dictionary(); + /// - /// Gets the options. + /// The _requested ranges /// - /// The options. - public IDictionary Headers => _options; - - public string Path { get; set; } + private List> _requestedRanges; public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem, IStreamHelper streamHelper) { @@ -57,7 +50,7 @@ namespace Emby.Server.Implementations.HttpServer _fileSystem = fileSystem; Path = path; - Logger = logger; + _logger = logger; RangeHeader = rangeHeader; Headers[HeaderNames.ContentType] = contentType; @@ -80,39 +73,34 @@ namespace Emby.Server.Implementations.HttpServer Cookies = new List(); } - /// - /// Sets the range values. - /// - private void SetRangeValues() - { - var requestedRange = RequestedRanges[0]; + private string RangeHeader { get; set; } - // If the requested range is "0-", we can optimize by just doing a stream copy - if (!requestedRange.Value.HasValue) - { - RangeEnd = TotalContentLength - 1; - } - else - { - RangeEnd = requestedRange.Value.Value; - } + private bool IsHeadRequest { get; set; } - RangeStart = requestedRange.Key; - RangeLength = 1 + RangeEnd - RangeStart; + private long RangeStart { get; set; } - // Content-Length is the length of what we're serving, not the original content - var lengthString = RangeLength.ToString(CultureInfo.InvariantCulture); - Headers[HeaderNames.ContentLength] = lengthString; - var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; - Headers[HeaderNames.ContentRange] = rangeString; + private long RangeEnd { get; set; } - Logger.LogDebug("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); - } + private long RangeLength { get; set; } + + public long TotalContentLength { get; set; } + + public Action OnComplete { get; set; } + + public Action OnError { get; set; } + + public List Cookies { get; private set; } + + public FileShareMode FileShare { get; set; } /// - /// The _requested ranges + /// Gets the options. /// - private List> _requestedRanges; + /// The options. + public IDictionary Headers => _options; + + public string Path { get; set; } + /// /// Gets the requested ranges. /// @@ -139,6 +127,7 @@ namespace Emby.Server.Implementations.HttpServer { start = long.Parse(vals[0], UsCulture); } + if (!string.IsNullOrEmpty(vals[1])) { end = long.Parse(vals[1], UsCulture); @@ -152,13 +141,50 @@ namespace Emby.Server.Implementations.HttpServer } } - private static readonly string[] SkipLogExtensions = { - ".js", - ".html", - ".css" - }; + public string ContentType { get; set; } + + public IRequest RequestContext { get; set; } + + public object Response { get; set; } + + public int Status { get; set; } + + public HttpStatusCode StatusCode + { + get => (HttpStatusCode)Status; + set => Status = (int)value; + } + + /// + /// Sets the range values. + /// + private void SetRangeValues() + { + var requestedRange = RequestedRanges[0]; + + // If the requested range is "0-", we can optimize by just doing a stream copy + if (!requestedRange.Value.HasValue) + { + RangeEnd = TotalContentLength - 1; + } + else + { + RangeEnd = requestedRange.Value.Value; + } + + RangeStart = requestedRange.Key; + RangeLength = 1 + RangeEnd - RangeStart; + + // Content-Length is the length of what we're serving, not the original content + var lengthString = RangeLength.ToString(CultureInfo.InvariantCulture); + Headers[HeaderNames.ContentLength] = lengthString; + var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; + Headers[HeaderNames.ContentRange] = rangeString; - public async Task WriteToAsync(IResponse response, CancellationToken cancellationToken) + _logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); + } + + public async Task WriteToAsync(HttpResponse response, CancellationToken cancellationToken) { try { @@ -176,16 +202,16 @@ namespace Emby.Server.Implementations.HttpServer { var extension = System.IO.Path.GetExtension(path); - if (extension == null || !SkipLogExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (extension == null || !_skipLogExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { - Logger.LogDebug("Transmit file {0}", path); + _logger.LogDebug("Transmit file {0}", path); } offset = 0; count = 0; } - await response.TransmitFile(path, offset, count, FileShare, _fileSystem, _streamHelper, cancellationToken).ConfigureAwait(false); + await TransmitFile(response.Body, path, offset, count, FileShare, cancellationToken).ConfigureAwait(false); } finally { @@ -193,18 +219,32 @@ namespace Emby.Server.Implementations.HttpServer } } - public string ContentType { get; set; } - - public IRequest RequestContext { get; set; } + public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) + { + var fileOpenOptions = FileOpenOptions.SequentialScan; - public object Response { get; set; } + // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + fileOpenOptions |= FileOpenOptions.Asynchronous; + } - public int Status { get; set; } + using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions)) + { + if (offset > 0) + { + fs.Position = offset; + } - public HttpStatusCode StatusCode - { - get => (HttpStatusCode)Status; - set => Status = (int)value; + if (count > 0) + { + await _streamHelper.CopyToAsync(fs, stream, count, cancellationToken).ConfigureAwait(false); + } + else + { + await fs.CopyToAsync(stream, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); + } + } } } } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index d8938964fa..4c233456c4 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Net.Sockets; using System.Reflection; -using System.Text; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; @@ -30,11 +29,7 @@ namespace Emby.Server.Implementations.HttpServer { public class HttpListenerHost : IHttpServer, IDisposable { - private string DefaultRedirectPath { get; set; } - public string[] UrlPrefixes { get; private set; } - - public event EventHandler> WebSocketConnected; - + private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; private readonly IServerApplicationHost _appHost; @@ -42,18 +37,15 @@ namespace Emby.Server.Implementations.HttpServer private readonly IXmlSerializer _xmlSerializer; private readonly IHttpListener _socketListener; private readonly Func> _funcParseFn; - - public Action[] ResponseFilters { get; set; } - + private readonly string _defaultRedirectPath; private readonly Dictionary ServiceOperationsMap = new Dictionary(); - public static HttpListenerHost Instance { get; protected set; } - private IWebSocketListener[] _webSocketListeners = Array.Empty(); private readonly List _webSocketConnections = new List(); + private bool _disposed = false; public HttpListenerHost( IServerApplicationHost applicationHost, - ILoggerFactory loggerFactory, + ILogger logger, IServerConfigurationManager config, IConfiguration configuration, INetworkManager networkManager, @@ -62,9 +54,9 @@ namespace Emby.Server.Implementations.HttpServer IHttpListener socketListener) { _appHost = applicationHost; - Logger = loggerFactory.CreateLogger("HttpServer"); + _logger = logger; _config = config; - DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; + _defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; _networkManager = networkManager; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; @@ -74,12 +66,20 @@ namespace Emby.Server.Implementations.HttpServer _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); Instance = this; - ResponseFilters = Array.Empty>(); + ResponseFilters = Array.Empty>(); } + public Action[] ResponseFilters { get; set; } + + public static HttpListenerHost Instance { get; protected set; } + + public string[] UrlPrefixes { get; private set; } + public string GlobalResponse { get; set; } - protected ILogger Logger { get; } + public ServiceController ServiceController { get; private set; } + + public event EventHandler> WebSocketConnected; public object CreateInstance(Type type) { @@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.HttpServer /// and no more processing should be done. /// /// - public void ApplyRequestFilters(IRequest req, IResponse res, object requestDto) + public void ApplyRequestFilters(IRequest req, HttpResponse res, object requestDto) { //Exec all RequestFilter attributes with Priority < 0 var attributes = GetRequestFilterAttributes(requestDto.GetType()); @@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.HttpServer return; } - var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, Logger) + var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger) { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, @@ -215,16 +215,16 @@ namespace Emby.Server.Implementations.HttpServer if (logExceptionStackTrace) { - Logger.LogError(ex, "Error processing request"); + _logger.LogError(ex, "Error processing request"); } else if (logExceptionMessage) { - Logger.LogError(ex.Message); + _logger.LogError(ex.Message); } var httpRes = httpReq.Response; - if (httpRes.OriginalResponse.HasStarted) + if (httpRes.HasStarted) { return; } @@ -233,11 +233,11 @@ namespace Emby.Server.Implementations.HttpServer httpRes.StatusCode = statusCode; httpRes.ContentType = "text/html"; - await Write(httpRes, NormalizeExceptionMessage(ex.Message)).ConfigureAwait(false); + await httpRes.WriteAsync(NormalizeExceptionMessage(ex.Message)).ConfigureAwait(false); } catch (Exception errorEx) { - Logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)"); + _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)"); } } @@ -431,7 +431,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 503; httpRes.ContentType = "text/plain"; - await Write(httpRes, "Server shutting down").ConfigureAwait(false); + await httpRes.WriteAsync("Server shutting down", cancellationToken).ConfigureAwait(false); return; } @@ -439,7 +439,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 400; httpRes.ContentType = "text/plain"; - await Write(httpRes, "Invalid host").ConfigureAwait(false); + await httpRes.WriteAsync("Invalid host", cancellationToken).ConfigureAwait(false); return; } @@ -447,7 +447,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 403; httpRes.ContentType = "text/plain"; - await Write(httpRes, "Forbidden").ConfigureAwait(false); + await httpRes.WriteAsync("Forbidden", cancellationToken).ConfigureAwait(false); return; } @@ -460,28 +460,27 @@ namespace Emby.Server.Implementations.HttpServer if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) { httpRes.StatusCode = 200; - httpRes.AddHeader("Access-Control-Allow-Origin", "*"); - httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); - httpRes.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); + httpRes.Headers.Add("Access-Control-Allow-Origin", "*"); + httpRes.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); + httpRes.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); httpRes.ContentType = "text/plain"; - await Write(httpRes, string.Empty).ConfigureAwait(false); + await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false); return; } urlToLog = GetUrlToLog(urlString); - Logger.LogDebug("HTTP {HttpMethod} {Url} UserAgent: {UserAgent} \nHeaders: {@Headers}", urlToLog, httpReq.UserAgent ?? string.Empty, httpReq.HttpMethod, httpReq.Headers); if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) { - RedirectToUrl(httpRes, DefaultRedirectPath); + httpRes.Redirect(_defaultRedirectPath); return; } if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) { - RedirectToUrl(httpRes, "emby/" + DefaultRedirectPath); + httpRes.Redirect("emby/" + _defaultRedirectPath); return; } @@ -494,9 +493,10 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - await Write(httpRes, + await httpRes.WriteAsync( "EmbyPlease update your Emby bookmark to " + newUrl + "").ConfigureAwait(false); + newUrl + "\">" + newUrl + "", + cancellationToken).ConfigureAwait(false); return; } } @@ -511,34 +511,35 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - await Write(httpRes, + await httpRes.WriteAsync( "EmbyPlease update your Emby bookmark to " + newUrl + "").ConfigureAwait(false); + newUrl + "\">" + newUrl + "", + cancellationToken).ConfigureAwait(false); return; } } if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase)) { - RedirectToUrl(httpRes, DefaultRedirectPath); + httpRes.Redirect(_defaultRedirectPath); return; } if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase)) { - RedirectToUrl(httpRes, "../" + DefaultRedirectPath); + httpRes.Redirect("../" + _defaultRedirectPath); return; } if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { - RedirectToUrl(httpRes, DefaultRedirectPath); + httpRes.Redirect(_defaultRedirectPath); return; } if (string.IsNullOrEmpty(localPath)) { - RedirectToUrl(httpRes, "/" + DefaultRedirectPath); + httpRes.Redirect("/" + _defaultRedirectPath); return; } @@ -546,12 +547,12 @@ namespace Emby.Server.Implementations.HttpServer { if (localPath.EndsWith("web/dashboard.html", StringComparison.OrdinalIgnoreCase)) { - RedirectToUrl(httpRes, "index.html#!/dashboard.html"); + httpRes.Redirect("index.html#!/dashboard.html"); } if (localPath.EndsWith("web/home.html", StringComparison.OrdinalIgnoreCase)) { - RedirectToUrl(httpRes, "index.html"); + httpRes.Redirect("index.html"); } } @@ -562,7 +563,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 503; httpRes.ContentType = "text/html"; - await Write(httpRes, GlobalResponse).ConfigureAwait(false); + await httpRes.WriteAsync(GlobalResponse, cancellationToken).ConfigureAwait(false); return; } } @@ -571,7 +572,7 @@ namespace Emby.Server.Implementations.HttpServer if (handler != null) { - await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, cancellationToken).ConfigureAwait(false); + await handler.ProcessRequestAsync(this, httpReq, httpRes, _logger, cancellationToken).ConfigureAwait(false); } else { @@ -598,11 +599,7 @@ namespace Emby.Server.Implementations.HttpServer var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) { - Logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); - } - else - { - Logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); + _logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); } } } @@ -619,18 +616,11 @@ namespace Emby.Server.Implementations.HttpServer return new ServiceHandler(restPath, contentType); } - Logger.LogError("Could not find handler for {PathInfo}", pathInfo); + _logger.LogError("Could not find handler for {PathInfo}", pathInfo); return null; } - private static Task Write(IResponse response, string text) - { - var bOutput = Encoding.UTF8.GetBytes(text); - response.OriginalResponse.ContentLength = bOutput.Length; - return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length); - } - - private void RedirectToSecureUrl(IHttpRequest httpReq, IResponse httpRes, string url) + private void RedirectToSecureUrl(IHttpRequest httpReq, HttpResponse httpRes, string url) { if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) { @@ -640,23 +630,11 @@ namespace Emby.Server.Implementations.HttpServer Scheme = "https" }; url = builder.Uri.ToString(); - - RedirectToUrl(httpRes, url); - } - else - { - RedirectToUrl(httpRes, url); } - } - public static void RedirectToUrl(IResponse httpRes, string url) - { - httpRes.StatusCode = 302; - httpRes.AddHeader("Location", url); + httpRes.Redirect(url); } - public ServiceController ServiceController { get; private set; } - /// /// Adds the rest handlers. /// @@ -672,9 +650,9 @@ namespace Emby.Server.Implementations.HttpServer var types = services.Select(r => r.GetType()); ServiceController.Init(this, types); - ResponseFilters = new Action[] + ResponseFilters = new Action[] { - new ResponseFilter(Logger).FilterResponse + new ResponseFilter(_logger).FilterResponse }; } @@ -772,24 +750,23 @@ namespace Emby.Server.Implementations.HttpServer return "emby/emby/" + path; } - private bool _disposed; - private readonly object _disposeLock = new object(); + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } protected virtual void Dispose(bool disposing) { if (_disposed) return; - lock (_disposeLock) + if (disposing) { - if (_disposed) return; - - _disposed = true; - - if (disposing) - { - Stop(); - } + Stop(); } + + _disposed = true; } /// @@ -803,7 +780,7 @@ namespace Emby.Server.Implementations.HttpServer return Task.CompletedTask; } - Logger.LogDebug("Websocket message received: {0}", result.MessageType); + _logger.LogDebug("Websocket message received: {0}", result.MessageType); IEnumerable GetTasks() { @@ -815,10 +792,5 @@ namespace Emby.Server.Implementations.HttpServer return Task.WhenAll(GetTasks()); } - - public void Dispose() - { - Dispose(true); - } } } diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index 08f4247577..3e731366e2 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; using System.Text; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -9,7 +10,7 @@ namespace Emby.Server.Implementations.HttpServer { public class ResponseFilter { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); private readonly ILogger _logger; public ResponseFilter(ILogger logger) @@ -23,12 +24,12 @@ namespace Emby.Server.Implementations.HttpServer /// The req. /// The res. /// The dto. - public void FilterResponse(IRequest req, IResponse res, object dto) + public void FilterResponse(IRequest req, HttpResponse res, object dto) { // Try to prevent compatibility view - res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); - res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); - res.AddHeader("Access-Control-Allow-Origin", "*"); + res.Headers.Add("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); + res.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); + res.Headers.Add("Access-Control-Allow-Origin", "*"); if (dto is Exception exception) { @@ -39,7 +40,7 @@ namespace Emby.Server.Implementations.HttpServer var error = exception.Message.Replace(Environment.NewLine, " "); error = RemoveControlCharacters(error); - res.AddHeader("X-Application-Error-Code", error); + res.Headers.Add("X-Application-Error-Code", error); } } @@ -54,12 +55,11 @@ namespace Emby.Server.Implementations.HttpServer if (hasHeaders.Headers.TryGetValue(HeaderNames.ContentLength, out string contentLength) && !string.IsNullOrEmpty(contentLength)) { - var length = long.Parse(contentLength, UsCulture); + var length = long.Parse(contentLength, _usCulture); if (length > 0) { - res.OriginalResponse.ContentLength = length; - res.SendChunked = false; + res.ContentLength = length; } } } @@ -72,9 +72,12 @@ namespace Emby.Server.Implementations.HttpServer /// System.String. public static string RemoveControlCharacters(string inString) { - if (inString == null) return null; + if (inString == null) + { + return null; + } - var newString = new StringBuilder(); + var newString = new StringBuilder(inString.Length); foreach (var ch in inString) { @@ -83,6 +86,7 @@ namespace Emby.Server.Implementations.HttpServer newString.Append(ch); } } + return newString.ToString(); } } diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 1027883ed9..3d3f67ca22 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -3,7 +3,6 @@ using System.Linq; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; @@ -13,28 +12,23 @@ namespace Emby.Server.Implementations.HttpServer.Security { public class AuthService : IAuthService { + private readonly IAuthorizationContext _authorizationContext; + private readonly ISessionManager _sessionManager; private readonly IServerConfigurationManager _config; + private readonly INetworkManager _networkManager; - public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, ISessionManager sessionManager, INetworkManager networkManager) + public AuthService( + IAuthorizationContext authorizationContext, + IServerConfigurationManager config, + ISessionManager sessionManager, + INetworkManager networkManager) { - AuthorizationContext = authorizationContext; + _authorizationContext = authorizationContext; _config = config; - SessionManager = sessionManager; - UserManager = userManager; - NetworkManager = networkManager; + _sessionManager = sessionManager; + _networkManager = networkManager; } - public IUserManager UserManager { get; private set; } - public IAuthorizationContext AuthorizationContext { get; private set; } - public ISessionManager SessionManager { get; private set; } - public INetworkManager NetworkManager { get; private set; } - - /// - /// Redirect the client to a specific URL if authentication failed. - /// If this property is null, simply `401 Unauthorized` is returned. - /// - public string HtmlRedirect { get; set; } - public void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues) { ValidateUser(request, authAttribtues); @@ -43,7 +37,7 @@ namespace Emby.Server.Implementations.HttpServer.Security private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) { // This code is executed before the service - var auth = AuthorizationContext.GetAuthorizationInfo(request); + var auth = _authorizationContext.GetAuthorizationInfo(request); if (!IsExemptFromAuthenticationToken(authAttribtues, request)) { @@ -80,7 +74,7 @@ namespace Emby.Server.Implementations.HttpServer.Security !string.IsNullOrEmpty(auth.Client) && !string.IsNullOrEmpty(auth.Device)) { - SessionManager.LogSessionActivity(auth.Client, + _sessionManager.LogSessionActivity(auth.Client, auth.Version, auth.DeviceId, auth.Device, @@ -89,7 +83,9 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - private void ValidateUserAccess(User user, IRequest request, + private void ValidateUserAccess( + User user, + IRequest request, IAuthenticationAttributes authAttribtues, AuthorizationInfo auth) { @@ -101,7 +97,7 @@ namespace Emby.Server.Implementations.HttpServer.Security }; } - if (!user.Policy.EnableRemoteAccess && !NetworkManager.IsInLocalNetwork(request.RemoteIp)) + if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp)) { throw new SecurityException("User account has been disabled.") { @@ -109,11 +105,11 @@ namespace Emby.Server.Implementations.HttpServer.Security }; } - if (!user.Policy.IsAdministrator && - !authAttribtues.EscapeParentalControl && - !user.IsParentalScheduleAllowed()) + if (!user.Policy.IsAdministrator + && !authAttribtues.EscapeParentalControl + && !user.IsParentalScheduleAllowed()) { - request.Response.AddHeader("X-Application-Error-Code", "ParentalControl"); + request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl"); throw new SecurityException("This user account is not allowed access at this time.") { @@ -183,6 +179,7 @@ namespace Emby.Server.Implementations.HttpServer.Security }; } } + if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase)) { if (user == null || !user.Policy.EnableContentDeletion) @@ -193,6 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security }; } } + if (roles.Contains("download", StringComparer.OrdinalIgnoreCase)) { if (user == null || !user.Policy.EnableContentDownloading) diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs index 2b5963a770..095193828b 100644 --- a/Emby.Server.Implementations/Services/HttpResult.cs +++ b/Emby.Server.Implementations/Services/HttpResult.cs @@ -10,8 +10,6 @@ namespace Emby.Server.Implementations.Services public class HttpResult : IHttpResult, IAsyncStreamWriter { - public object Response { get; set; } - public HttpResult(object response, string contentType, HttpStatusCode statusCode) { this.Headers = new Dictionary(); @@ -21,6 +19,8 @@ namespace Emby.Server.Implementations.Services this.StatusCode = statusCode; } + public object Response { get; set; } + public string ContentType { get; set; } public IDictionary Headers { get; private set; } @@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Services public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) { - var response = RequestContext == null ? null : RequestContext.Response; + var response = RequestContext?.Response; if (this.Response is byte[] bytesResponse) { @@ -45,13 +45,14 @@ namespace Emby.Server.Implementations.Services if (response != null) { - response.OriginalResponse.ContentLength = contentLength; + response.ContentLength = contentLength; } if (contentLength > 0) { await responseStream.WriteAsync(bytesResponse, 0, contentLength, cancellationToken).ConfigureAwait(false); } + return; } diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index 251ba3529a..ca2b22fe06 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; @@ -7,13 +6,14 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; +using Microsoft.AspNetCore.Http; using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.Services { public static class ResponseHelper { - public static Task WriteToResponse(IResponse response, IRequest request, object result, CancellationToken cancellationToken) + public static Task WriteToResponse(HttpResponse response, IRequest request, object result, CancellationToken cancellationToken) { if (result == null) { @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Services response.StatusCode = (int)HttpStatusCode.NoContent; } - response.OriginalResponse.ContentLength = 0; + response.ContentLength = 0; return Task.CompletedTask; } @@ -41,7 +41,6 @@ namespace Emby.Server.Implementations.Services httpResult.RequestContext = request; response.StatusCode = httpResult.Status; - response.StatusDescription = httpResult.StatusCode.ToString(); } var responseOptions = result as IHasHeaders; @@ -51,11 +50,11 @@ namespace Emby.Server.Implementations.Services { if (string.Equals(responseHeaders.Key, "Content-Length", StringComparison.OrdinalIgnoreCase)) { - response.OriginalResponse.ContentLength = long.Parse(responseHeaders.Value, CultureInfo.InvariantCulture); + response.ContentLength = long.Parse(responseHeaders.Value, CultureInfo.InvariantCulture); continue; } - response.AddHeader(responseHeaders.Key, responseHeaders.Value); + response.Headers.Add(responseHeaders.Key, responseHeaders.Value); } } @@ -74,31 +73,31 @@ namespace Emby.Server.Implementations.Services switch (result) { case IAsyncStreamWriter asyncStreamWriter: - return asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken); + return asyncStreamWriter.WriteToAsync(response.Body, cancellationToken); case IStreamWriter streamWriter: - streamWriter.WriteTo(response.OutputStream); + streamWriter.WriteTo(response.Body); return Task.CompletedTask; case FileWriter fileWriter: return fileWriter.WriteToAsync(response, cancellationToken); case Stream stream: - return CopyStream(stream, response.OutputStream); + return CopyStream(stream, response.Body); case byte[] bytes: response.ContentType = "application/octet-stream"; - response.OriginalResponse.ContentLength = bytes.Length; + response.ContentLength = bytes.Length; if (bytes.Length > 0) { - return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); + return response.Body.WriteAsync(bytes, 0, bytes.Length, cancellationToken); } return Task.CompletedTask; case string responseText: var responseTextAsBytes = Encoding.UTF8.GetBytes(responseText); - response.OriginalResponse.ContentLength = responseTextAsBytes.Length; + response.ContentLength = responseTextAsBytes.Length; if (responseTextAsBytes.Length > 0) { - return response.OutputStream.WriteAsync(responseTextAsBytes, 0, responseTextAsBytes.Length, cancellationToken); + return response.Body.WriteAsync(responseTextAsBytes, 0, responseTextAsBytes.Length, cancellationToken); } return Task.CompletedTask; @@ -115,7 +114,7 @@ namespace Emby.Server.Implementations.Services } } - public static async Task WriteObject(IRequest request, object result, IResponse response) + public static async Task WriteObject(IRequest request, object result, HttpResponse response) { var contentType = request.ResponseContentType; var serializer = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType); @@ -127,11 +126,11 @@ namespace Emby.Server.Implementations.Services ms.Position = 0; var contentLength = ms.Length; - response.OriginalResponse.ContentLength = contentLength; + response.ContentLength = contentLength; if (contentLength > 0) { - await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false); + await ms.CopyToAsync(response.Body).ConfigureAwait(false); } } } diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index 5e3d529c68..d963f9043c 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -147,7 +147,6 @@ namespace Emby.Server.Implementations.Services public Task Execute(HttpListenerHost httpHost, object requestDto, IRequest req) { - req.Dto = requestDto; var requestType = requestDto.GetType(); req.OperationName = requestType.Name; @@ -161,9 +160,6 @@ namespace Emby.Server.Implementations.Services serviceRequiresContext.Request = req; } - if (req.Dto == null) // Don't override existing batched DTO[] - req.Dto = requestDto; - //Executes the service and returns the result return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName()); } diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index 38952628d8..9124b9c141 100644 --- a/Emby.Server.Implementations/Services/ServiceExec.cs +++ b/Emby.Server.Implementations/Services/ServiceExec.cs @@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Services foreach (var requestFilter in actionContext.RequestFilters) { requestFilter.RequestFilter(request, request.Response, requestDto); - if (request.Response.OriginalResponse.HasStarted) + if (request.Response.HasStarted) { Task.FromResult(null); } diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index d32fce1c77..cf15247bb3 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -5,20 +5,21 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Services { public class ServiceHandler { - public RestPath RestPath { get; } + private RestPath _restPath; - public string ResponseContentType { get; } + private string _responseContentType; internal ServiceHandler(RestPath restPath, string responseContentType) { - RestPath = restPath; - ResponseContentType = responseContentType; + _restPath = restPath; + _responseContentType = responseContentType; } protected static Task CreateContentTypeRequest(HttpListenerHost host, IRequest httpReq, Type requestType, string contentType) @@ -54,7 +55,7 @@ namespace Emby.Server.Implementations.Services private static string GetFormatContentType(string format) { - //built-in formats + // built-in formats switch (format) { case "json": return "application/json"; @@ -63,16 +64,16 @@ namespace Emby.Server.Implementations.Services } } - public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, IResponse httpRes, ILogger logger, CancellationToken cancellationToken) + public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, HttpResponse httpRes, ILogger logger, CancellationToken cancellationToken) { - httpReq.Items["__route"] = RestPath; + httpReq.Items["__route"] = _restPath; - if (ResponseContentType != null) + if (_responseContentType != null) { - httpReq.ResponseContentType = ResponseContentType; + httpReq.ResponseContentType = _responseContentType; } - var request = httpReq.Dto = await CreateRequest(httpHost, httpReq, RestPath, logger).ConfigureAwait(false); + var request = await CreateRequest(httpHost, httpReq, _restPath, logger).ConfigureAwait(false); httpHost.ApplyRequestFilters(httpReq, httpRes, request); @@ -94,7 +95,7 @@ namespace Emby.Server.Implementations.Services if (RequireqRequestStream(requestType)) { // Used by IRequiresRequestStream - var requestParams = await GetRequestParams(httpReq).ConfigureAwait(false); + var requestParams = GetRequestParams(httpReq.Response.HttpContext.Request); var request = ServiceHandler.CreateRequest(httpReq, restPath, requestParams, host.CreateInstance(requestType)); var rawReq = (IRequiresRequestStream)request; @@ -103,7 +104,7 @@ namespace Emby.Server.Implementations.Services } else { - var requestParams = await GetFlattenedRequestParams(httpReq).ConfigureAwait(false); + var requestParams = GetFlattenedRequestParams(httpReq.Response.HttpContext.Request); var requestDto = await CreateContentTypeRequest(host, httpReq, restPath.RequestType, httpReq.ContentType).ConfigureAwait(false); @@ -130,56 +131,42 @@ namespace Emby.Server.Implementations.Services /// /// Duplicate Params are given a unique key by appending a #1 suffix /// - private static async Task> GetRequestParams(IRequest request) + private static Dictionary GetRequestParams(HttpRequest request) { var map = new Dictionary(); - foreach (var name in request.QueryString.Keys) + foreach (var pair in request.Query) { - if (name == null) - { - // thank you ASP.NET - continue; - } - - var values = request.QueryString[name]; + var values = pair.Value; if (values.Count == 1) { - map[name] = values[0]; + map[pair.Key] = values[0]; } else { for (var i = 0; i < values.Count; i++) { - map[name + (i == 0 ? "" : "#" + i)] = values[i]; + map[pair.Key + (i == 0 ? "" : "#" + i)] = values[i]; } } } - if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT"))) + if ( + (IsMethod(request.Method, "POST") || IsMethod(request.Method, "PUT")) + && request.HasFormContentType) { - var formData = await request.GetFormData().ConfigureAwait(false); - if (formData != null) + foreach (var pair in request.Form) { - foreach (var name in formData.Keys) + var values = pair.Value; + if (values.Count == 1) { - if (name == null) - { - // thank you ASP.NET - continue; - } - - var values = formData.GetValues(name); - if (values.Count == 1) - { - map[name] = values[0]; - } - else + map[pair.Key] = values[0]; + } + else + { + for (var i = 0; i < values.Count; i++) { - for (var i = 0; i < values.Count; i++) - { - map[name + (i == 0 ? "" : "#" + i)] = values[i]; - } + map[pair.Key + (i == 0 ? "" : "#" + i)] = values[i]; } } } @@ -196,36 +183,22 @@ namespace Emby.Server.Implementations.Services /// /// Duplicate params have their values joined together in a comma-delimited string /// - private static async Task> GetFlattenedRequestParams(IRequest request) + private static Dictionary GetFlattenedRequestParams(HttpRequest request) { var map = new Dictionary(); - foreach (var name in request.QueryString.Keys) + foreach (var pair in request.Query) { - if (name == null) - { - // thank you ASP.NET - continue; - } - - map[name] = request.QueryString[name]; + map[pair.Key] = pair.Value; } - if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT"))) + if ( + (IsMethod(request.Method, "POST") || IsMethod(request.Method, "PUT")) + && request.HasFormContentType) { - var formData = await request.GetFormData().ConfigureAwait(false); - if (formData != null) + foreach (var pair in request.Form) { - foreach (var name in formData.Keys) - { - if (name == null) - { - // thank you ASP.NET - continue; - } - - map[name] = formData[name]; - } + map[pair.Key] = pair.Value; } } diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs deleted file mode 100644 index ec637186f0..0000000000 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ /dev/null @@ -1,647 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Primitives; -using Microsoft.Net.Http.Headers; - -namespace Emby.Server.Implementations.SocketSharp -{ - public partial class WebSocketSharpRequest : IHttpRequest - { - internal static string GetParameter(ReadOnlySpan header, string attr) - { - int ap = header.IndexOf(attr.AsSpan(), StringComparison.Ordinal); - if (ap == -1) - { - return null; - } - - ap += attr.Length; - if (ap >= header.Length) - { - return null; - } - - char ending = header[ap]; - if (ending != '"') - { - ending = ' '; - } - - var slice = header.Slice(ap + 1); - int end = slice.IndexOf(ending); - if (end == -1) - { - return ending == '"' ? null : header.Slice(ap).ToString(); - } - - return slice.Slice(0, end - ap - 1).ToString(); - } - - private async Task LoadMultiPart(WebROCollection form) - { - string boundary = GetParameter(ContentType.AsSpan(), "; boundary="); - if (boundary == null) - { - return; - } - - using (var requestStream = InputStream) - { - // DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request - // Not ending with \r\n? - var ms = new MemoryStream(32 * 1024); - await requestStream.CopyToAsync(ms).ConfigureAwait(false); - - var input = ms; - ms.WriteByte((byte)'\r'); - ms.WriteByte((byte)'\n'); - - input.Position = 0; - - // Uncomment to debug - // var content = new StreamReader(ms).ReadToEnd(); - // Console.WriteLine(boundary + "::" + content); - // input.Position = 0; - - var multi_part = new HttpMultipart(input, boundary, ContentEncoding); - - HttpMultipart.Element e; - while ((e = multi_part.ReadNextElement()) != null) - { - if (e.Filename == null) - { - byte[] copy = new byte[e.Length]; - - input.Position = e.Start; - await input.ReadAsync(copy, 0, (int)e.Length).ConfigureAwait(false); - - form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length)); - } - else - { - // We use a substream, as in 2.x we will support large uploads streamed to disk, - files[e.Name] = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length); - } - } - } - } - - public async Task GetFormData() - { - var form = new WebROCollection(); - files = new Dictionary(); - - if (IsContentType("multipart/form-data")) - { - await LoadMultiPart(form).ConfigureAwait(false); - } - else if (IsContentType("application/x-www-form-urlencoded")) - { - await LoadWwwForm(form).ConfigureAwait(false); - } - - if (validate_form && !checked_form) - { - checked_form = true; - ValidateNameValueCollection("Form", form); - } - - return form; - } - - public string Accept => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Accept]) ? null : request.Headers[HeaderNames.Accept].ToString(); - - public string Authorization => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Authorization]) ? null : request.Headers[HeaderNames.Authorization].ToString(); - - protected bool validate_form { get; set; } - protected bool checked_form { get; set; } - - private static void ThrowValidationException(string name, string key, string value) - { - string v = "\"" + value + "\""; - if (v.Length > 20) - { - v = v.Substring(0, 16) + "...\""; - } - - string msg = string.Format( - CultureInfo.InvariantCulture, - "A potentially dangerous Request.{0} value was detected from the client ({1}={2}).", - name, - key, - v); - - throw new Exception(msg); - } - - private static void ValidateNameValueCollection(string name, QueryParamCollection coll) - { - if (coll == null) - { - return; - } - - foreach (var pair in coll) - { - var key = pair.Name; - var val = pair.Value; - if (val != null && val.Length > 0 && IsInvalidString(val)) - { - ThrowValidationException(name, key, val); - } - } - } - - internal static bool IsInvalidString(string val) - => IsInvalidString(val, out var validationFailureIndex); - - internal static bool IsInvalidString(string val, out int validationFailureIndex) - { - validationFailureIndex = 0; - - int len = val.Length; - if (len < 2) - { - return false; - } - - char current = val[0]; - for (int idx = 1; idx < len; idx++) - { - char next = val[idx]; - - // See http://secunia.com/advisories/14325 - if (current == '<' || current == '\xff1c') - { - if (next == '!' || next < ' ' - || (next >= 'a' && next <= 'z') - || (next >= 'A' && next <= 'Z')) - { - validationFailureIndex = idx - 1; - return true; - } - } - else if (current == '&' && next == '#') - { - validationFailureIndex = idx - 1; - return true; - } - - current = next; - } - - return false; - } - - private bool IsContentType(string ct) - { - if (ContentType == null) - { - return false; - } - - return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase); - } - - private async Task LoadWwwForm(WebROCollection form) - { - using (var input = InputStream) - { - using (var ms = new MemoryStream()) - { - await input.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - - using (var s = new StreamReader(ms, ContentEncoding)) - { - var key = new StringBuilder(); - var value = new StringBuilder(); - int c; - - while ((c = s.Read()) != -1) - { - if (c == '=') - { - value.Length = 0; - while ((c = s.Read()) != -1) - { - if (c == '&') - { - AddRawKeyValue(form, key, value); - break; - } - else - { - value.Append((char)c); - } - } - - if (c == -1) - { - AddRawKeyValue(form, key, value); - return; - } - } - else if (c == '&') - { - AddRawKeyValue(form, key, value); - } - else - { - key.Append((char)c); - } - } - - if (c == -1) - { - AddRawKeyValue(form, key, value); - } - } - } - } - } - - private static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value) - { - form.Add(WebUtility.UrlDecode(key.ToString()), WebUtility.UrlDecode(value.ToString())); - - key.Length = 0; - value.Length = 0; - } - - private Dictionary files; - - private class WebROCollection : QueryParamCollection - { - public override string ToString() - { - var result = new StringBuilder(); - foreach (var pair in this) - { - if (result.Length > 0) - { - result.Append('&'); - } - - var key = pair.Name; - if (key != null && key.Length > 0) - { - result.Append(key); - result.Append('='); - } - - result.Append(pair.Value); - } - - return result.ToString(); - } - } - private class HttpMultipart - { - - public class Element - { - public string ContentType { get; set; } - - public string Name { get; set; } - - public string Filename { get; set; } - - public Encoding Encoding { get; set; } - - public long Start { get; set; } - - public long Length { get; set; } - - public override string ToString() - { - return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " + - Start.ToString(CultureInfo.CurrentCulture) + ", Length " + Length.ToString(CultureInfo.CurrentCulture); - } - } - - private const byte LF = (byte)'\n'; - - private const byte CR = (byte)'\r'; - - private Stream data; - - private string boundary; - - private byte[] boundaryBytes; - - private byte[] buffer; - - private bool atEof; - - private Encoding encoding; - - private StringBuilder sb; - - // See RFC 2046 - // In the case of multipart entities, in which one or more different - // sets of data are combined in a single body, a "multipart" media type - // field must appear in the entity's header. The body must then contain - // one or more body parts, each preceded by a boundary delimiter line, - // and the last one followed by a closing boundary delimiter line. - // After its boundary delimiter line, each body part then consists of a - // header area, a blank line, and a body area. Thus a body part is - // similar to an RFC 822 message in syntax, but different in meaning. - - public HttpMultipart(Stream data, string b, Encoding encoding) - { - this.data = data; - boundary = b; - boundaryBytes = encoding.GetBytes(b); - buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--' - this.encoding = encoding; - sb = new StringBuilder(); - } - - public Element ReadNextElement() - { - if (atEof || ReadBoundary()) - { - return null; - } - - var elem = new Element(); - ReadOnlySpan header; - while ((header = ReadLine().AsSpan()).Length != 0) - { - if (header.StartsWith("Content-Disposition:".AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - elem.Name = GetContentDispositionAttribute(header, "name"); - elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); - } - else if (header.StartsWith("Content-Type:".AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - elem.ContentType = header.Slice("Content-Type:".Length).Trim().ToString(); - elem.Encoding = GetEncoding(elem.ContentType); - } - } - - long start = data.Position; - elem.Start = start; - long pos = MoveToNextBoundary(); - if (pos == -1) - { - return null; - } - - elem.Length = pos - start; - return elem; - } - - private string ReadLine() - { - // CRLF or LF are ok as line endings. - bool got_cr = false; - int b = 0; - sb.Length = 0; - while (true) - { - b = data.ReadByte(); - if (b == -1) - { - return null; - } - - if (b == LF) - { - break; - } - - got_cr = b == CR; - sb.Append((char)b); - } - - if (got_cr) - { - sb.Length--; - } - - return sb.ToString(); - } - - private static string GetContentDispositionAttribute(ReadOnlySpan l, string name) - { - int idx = l.IndexOf((name + "=\"").AsSpan(), StringComparison.Ordinal); - if (idx < 0) - { - return null; - } - - int begin = idx + name.Length + "=\"".Length; - int end = l.Slice(begin).IndexOf('"'); - if (end < 0) - { - return null; - } - - if (begin == end) - { - return string.Empty; - } - - return l.Slice(begin, end - begin).ToString(); - } - - private string GetContentDispositionAttributeWithEncoding(ReadOnlySpan l, string name) - { - int idx = l.IndexOf((name + "=\"").AsSpan(), StringComparison.Ordinal); - if (idx < 0) - { - return null; - } - - int begin = idx + name.Length + "=\"".Length; - int end = l.Slice(begin).IndexOf('"'); - if (end < 0) - { - return null; - } - - if (begin == end) - { - return string.Empty; - } - - ReadOnlySpan temp = l.Slice(begin, end - begin); - byte[] source = new byte[temp.Length]; - for (int i = temp.Length - 1; i >= 0; i--) - { - source[i] = (byte)temp[i]; - } - - return encoding.GetString(source, 0, source.Length); - } - - private bool ReadBoundary() - { - try - { - string line; - do - { - line = ReadLine(); - } - while (line.Length == 0); - - if (line[0] != '-' || line[1] != '-') - { - return false; - } - - if (!line.EndsWith(boundary, StringComparison.Ordinal)) - { - return true; - } - } - catch - { - - } - - return false; - } - - private static bool CompareBytes(byte[] orig, byte[] other) - { - for (int i = orig.Length - 1; i >= 0; i--) - { - if (orig[i] != other[i]) - { - return false; - } - } - - return true; - } - - private long MoveToNextBoundary() - { - long retval = 0; - bool got_cr = false; - - int state = 0; - int c = data.ReadByte(); - while (true) - { - if (c == -1) - { - return -1; - } - - if (state == 0 && c == LF) - { - retval = data.Position - 1; - if (got_cr) - { - retval--; - } - - state = 1; - c = data.ReadByte(); - } - else if (state == 0) - { - got_cr = c == CR; - c = data.ReadByte(); - } - else if (state == 1 && c == '-') - { - c = data.ReadByte(); - if (c == -1) - { - return -1; - } - - if (c != '-') - { - state = 0; - got_cr = false; - continue; // no ReadByte() here - } - - int nread = data.Read(buffer, 0, buffer.Length); - int bl = buffer.Length; - if (nread != bl) - { - return -1; - } - - if (!CompareBytes(boundaryBytes, buffer)) - { - state = 0; - data.Position = retval + 2; - if (got_cr) - { - data.Position++; - got_cr = false; - } - - c = data.ReadByte(); - continue; - } - - if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-') - { - atEof = true; - } - else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF) - { - state = 0; - data.Position = retval + 2; - if (got_cr) - { - data.Position++; - got_cr = false; - } - - c = data.ReadByte(); - continue; - } - - data.Position = retval + 2; - if (got_cr) - { - data.Position++; - } - - break; - } - else - { - // state == 1 - state = 0; // no ReadByte() here - } - } - - return retval; - } - - private static string StripPath(string path) - { - if (path == null || path.Length == 0) - { - return path; - } - - if (path.IndexOf(":\\", StringComparison.Ordinal) != 1 - && !path.StartsWith("\\\\", StringComparison.Ordinal)) - { - return path; - } - - return path.Substring(path.LastIndexOf('\\') + 1); - } - } - } -} diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 7a630bf100..43f71a69c6 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -6,7 +6,6 @@ using System.Net; using System.Linq; using System.Text; using MediaBrowser.Common.Net; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; @@ -14,44 +13,49 @@ using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; -using IResponse = MediaBrowser.Model.Services.IResponse; namespace Emby.Server.Implementations.SocketSharp { public partial class WebSocketSharpRequest : IHttpRequest { - private readonly HttpRequest request; + public const string FormUrlEncoded = "application/x-www-form-urlencoded"; + public const string MultiPartFormData = "multipart/form-data"; + public const string Soap11 = "text/xml; charset=utf-8"; + + private string _remoteIp; + private Dictionary _items; + private string _responseContentType; + private IHttpFile[] _httpFiles; + private Dictionary _files; - public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger) + public WebSocketSharpRequest(HttpRequest httpRequest, HttpResponse httpResponse, string operationName, ILogger logger) { this.OperationName = operationName; - this.request = httpContext; - this.Response = new WebSocketSharpResponse(logger, response); + this.Request = httpRequest; + this.Response = httpResponse; } - public HttpRequest HttpRequest => request; + public string Accept => StringValues.IsNullOrEmpty(Request.Headers[HeaderNames.Accept]) ? null : Request.Headers[HeaderNames.Accept].ToString(); - public IResponse Response { get; } + public string Authorization => StringValues.IsNullOrEmpty(Request.Headers[HeaderNames.Authorization]) ? null : Request.Headers[HeaderNames.Authorization].ToString(); - public string OperationName { get; set; } + public HttpRequest Request { get; } - public object Dto { get; set; } + public HttpResponse Response { get; } - public string RawUrl => request.GetEncodedPathAndQuery(); + public string OperationName { get; set; } - public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); - // Header[name] returns "" when undefined + public string RawUrl => Request.GetEncodedPathAndQuery(); - private string GetHeader(string name) => request.Headers[name].ToString(); + public string AbsoluteUri => Request.GetDisplayUrl().TrimEnd('/'); - private string remoteIp; public string RemoteIp { get { - if (remoteIp != null) + if (_remoteIp != null) { - return remoteIp; + return _remoteIp; } IPAddress ip; @@ -62,14 +66,84 @@ namespace Emby.Server.Implementations.SocketSharp { if (!IPAddress.TryParse(GetHeader(CustomHeaderNames.XRealIP), out ip)) { - ip = request.HttpContext.Connection.RemoteIpAddress; + ip = Request.HttpContext.Connection.RemoteIpAddress; } } - return remoteIp = NormalizeIp(ip).ToString(); + return _remoteIp = NormalizeIp(ip).ToString(); } } + public string[] AcceptTypes => Request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); + + public Dictionary Items => _items ?? (_items = new Dictionary()); + + public string ResponseContentType + { + get => + _responseContentType + ?? (_responseContentType = GetResponseContentType(Request)); + set => this._responseContentType = value; + } + + public string PathInfo => Request.Path.Value; + + public string UserAgent => Request.Headers[HeaderNames.UserAgent]; + + public IHeaderDictionary Headers => Request.Headers; + + public IQueryCollection QueryString => Request.Query; + + public bool IsLocal => Request.HttpContext.Connection.LocalIpAddress.Equals(Request.HttpContext.Connection.RemoteIpAddress); + + + public string HttpMethod => Request.Method; + + public string Verb => HttpMethod; + + public string ContentType => Request.ContentType; + + public Uri UrlReferrer => Request.GetTypedHeaders().Referer; + + public Stream InputStream => Request.Body; + + public long ContentLength => Request.ContentLength ?? 0; + + + public IHttpFile[] Files + { + get + { + if (_httpFiles != null) + { + return _httpFiles; + } + + if (_files == null) + { + return _httpFiles = Array.Empty(); + } + + var values = _files.Values; + _httpFiles = new IHttpFile[values.Count]; + for (int i = 0; i < values.Count; i++) + { + var reqFile = values.ElementAt(i); + _httpFiles[i] = new HttpFile + { + ContentType = reqFile.ContentType, + ContentLength = reqFile.ContentLength, + FileName = reqFile.FileName, + InputStream = reqFile.InputStream, + }; + } + + return _httpFiles; + } + } + + private string GetHeader(string name) => Request.Headers[name].ToString(); + private static IPAddress NormalizeIp(IPAddress ip) { if (ip.IsIPv4MappedToIPv6) @@ -80,22 +154,6 @@ namespace Emby.Server.Implementations.SocketSharp return ip; } - public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); - - private Dictionary items; - public Dictionary Items => items ?? (items = new Dictionary()); - - private string responseContentType; - public string ResponseContentType - { - get => - responseContentType - ?? (responseContentType = GetResponseContentType(HttpRequest)); - set => this.responseContentType = value; - } - - public const string FormUrlEncoded = "application/x-www-form-urlencoded"; - public const string MultiPartFormData = "multipart/form-data"; public static string GetResponseContentType(HttpRequest httpReq) { var specifiedContentType = GetQueryStringContentType(httpReq); @@ -152,8 +210,6 @@ namespace Emby.Server.Implementations.SocketSharp return serverDefaultContentType; } - public const string Soap11 = "text/xml; charset=utf-8"; - public static bool HasAnyOfContentTypes(HttpRequest request, params string[] contentTypes) { if (contentTypes == null || request.ContentType == null) @@ -224,105 +280,5 @@ namespace Emby.Server.Implementations.SocketSharp var pos = strVal.IndexOf(needle); return pos == -1 ? strVal : strVal.Slice(0, pos); } - - public string PathInfo => this.request.Path.Value; - - public string UserAgent => request.Headers[HeaderNames.UserAgent]; - - public IHeaderDictionary Headers => request.Headers; - - public IQueryCollection QueryString => request.Query; - - public bool IsLocal => string.Equals(request.HttpContext.Connection.LocalIpAddress.ToString(), request.HttpContext.Connection.RemoteIpAddress.ToString()); - - private string httpMethod; - public string HttpMethod => - httpMethod - ?? (httpMethod = request.Method); - - public string Verb => HttpMethod; - - public string ContentType => request.ContentType; - - private Encoding ContentEncoding - { - get - { - // TODO is this necessary? - if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP")) - { - string postDataCharset = Headers["x-up-devcap-post-charset"]; - if (!string.IsNullOrEmpty(postDataCharset)) - { - try - { - return Encoding.GetEncoding(postDataCharset); - } - catch (ArgumentException) - { - } - } - } - - return request.GetTypedHeaders().ContentType.Encoding ?? Encoding.UTF8; - } - } - - public Uri UrlReferrer => request.GetTypedHeaders().Referer; - - public static Encoding GetEncoding(string contentTypeHeader) - { - var param = GetParameter(contentTypeHeader.AsSpan(), "charset="); - if (param == null) - { - return null; - } - - try - { - return Encoding.GetEncoding(param); - } - catch (ArgumentException) - { - return null; - } - } - - public Stream InputStream => request.Body; - - public long ContentLength => request.ContentLength ?? 0; - - private IHttpFile[] httpFiles; - public IHttpFile[] Files - { - get - { - if (httpFiles != null) - { - return httpFiles; - } - - if (files == null) - { - return httpFiles = Array.Empty(); - } - - var values = files.Values; - httpFiles = new IHttpFile[values.Count]; - for (int i = 0; i < values.Count; i++) - { - var reqFile = values.ElementAt(i); - httpFiles[i] = new HttpFile - { - ContentType = reqFile.ContentType, - ContentLength = reqFile.ContentLength, - FileName = reqFile.FileName, - InputStream = reqFile.InputStream, - }; - } - - return httpFiles; - } - } } } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs deleted file mode 100644 index 0f67eaa622..0000000000 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using IRequest = MediaBrowser.Model.Services.IRequest; - -namespace Emby.Server.Implementations.SocketSharp -{ - public class WebSocketSharpResponse : IResponse - { - private readonly ILogger _logger; - - public WebSocketSharpResponse(ILogger logger, HttpResponse response) - { - _logger = logger; - OriginalResponse = response; - } - - public HttpResponse OriginalResponse { get; } - - public int StatusCode - { - get => OriginalResponse.StatusCode; - set => OriginalResponse.StatusCode = value; - } - - public string StatusDescription { get; set; } - - public string ContentType - { - get => OriginalResponse.ContentType; - set => OriginalResponse.ContentType = value; - } - - public void AddHeader(string name, string value) - { - if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) - { - ContentType = value; - return; - } - - OriginalResponse.Headers.Add(name, value); - } - - public void Redirect(string url) - { - OriginalResponse.Redirect(url); - } - - public Stream OutputStream => OriginalResponse.Body; - - public bool SendChunked { get; set; } - - const int StreamCopyToBufferSize = 81920; - public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) - { - var allowAsync = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - - //if (count <= 0) - //{ - // allowAsync = true; - //} - - var fileOpenOptions = FileOpenOptions.SequentialScan; - - if (allowAsync) - { - fileOpenOptions |= FileOpenOptions.Asynchronous; - } - - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - - using (var fs = fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions)) - { - if (offset > 0) - { - fs.Position = offset; - } - - if (count > 0) - { - await streamHelper.CopyToAsync(fs, OutputStream, count, cancellationToken).ConfigureAwait(false); - } - else - { - await fs.CopyToAsync(OutputStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } - } - } - } -} diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 3994016240..f0d5147a88 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1019,7 +1019,7 @@ namespace MediaBrowser.Api.Playback foreach (var item in responseHeaders) { - Request.Response.AddHeader(item.Key, item.Value); + Request.Response.Headers.Add(item.Key, item.Value); } } diff --git a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs index 64c2294e3d..29fb81e32a 100644 --- a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs +++ b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs @@ -1,5 +1,6 @@ using System; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { @@ -33,7 +34,7 @@ namespace MediaBrowser.Controller.Net /// The http request wrapper /// The http response wrapper /// The request DTO - public void RequestFilter(IRequest request, IResponse response, object requestDto) + public void RequestFilter(IRequest request, HttpResponse response, object requestDto) { AuthService.Authenticate(request, this); } diff --git a/MediaBrowser.Model/Services/IHasRequestFilter.cs b/MediaBrowser.Model/Services/IHasRequestFilter.cs index d4e6aa8e00..81a2dba692 100644 --- a/MediaBrowser.Model/Services/IHasRequestFilter.cs +++ b/MediaBrowser.Model/Services/IHasRequestFilter.cs @@ -1,3 +1,5 @@ +using Microsoft.AspNetCore.Http; + namespace MediaBrowser.Model.Services { public interface IHasRequestFilter @@ -15,6 +17,6 @@ namespace MediaBrowser.Model.Services /// The http request wrapper /// The http response wrapper /// The request DTO - void RequestFilter(IRequest req, IResponse res, object requestDto); + void RequestFilter(IRequest req, HttpResponse res, object requestDto); } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 4f6ddb476e..3852b1dd74 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Services { public interface IRequest { - IResponse Response { get; } + HttpResponse Response { get; } /// /// The name of the service being called (e.g. Request DTO Name) @@ -22,11 +22,6 @@ namespace MediaBrowser.Model.Services /// string Verb { get; } - /// - /// The Request DTO, after it has been deserialized. - /// - object Dto { get; set; } - /// /// The request ContentType /// @@ -50,8 +45,6 @@ namespace MediaBrowser.Model.Services IQueryCollection QueryString { get; } - Task GetFormData(); - string RawUrl { get; } string AbsoluteUri { get; } @@ -98,25 +91,4 @@ namespace MediaBrowser.Model.Services { IRequest Request { get; set; } } - - public interface IResponse - { - HttpResponse OriginalResponse { get; } - - int StatusCode { get; set; } - - string StatusDescription { get; set; } - - string ContentType { get; set; } - - void AddHeader(string name, string value); - - void Redirect(string url); - - Stream OutputStream { get; } - - Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken); - - bool SendChunked { get; set; } - } } -- cgit v1.2.3 From cf7290343ff4a440537c61332cdc76c5fbcbb7e9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 29 Jul 2019 00:33:28 +0200 Subject: Fix build --- .../SocketSharp/WebSocketSharpRequest.cs | 38 ---------------------- MediaBrowser.Api/Devices/DeviceService.cs | 15 +++++---- MediaBrowser.Model/Services/IRequest.cs | 8 ----- 3 files changed, 8 insertions(+), 53 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 43f71a69c6..332ce39034 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -1,17 +1,14 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Net; using System.Linq; -using System.Text; using MediaBrowser.Common.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; namespace Emby.Server.Implementations.SocketSharp @@ -25,8 +22,6 @@ namespace Emby.Server.Implementations.SocketSharp private string _remoteIp; private Dictionary _items; private string _responseContentType; - private IHttpFile[] _httpFiles; - private Dictionary _files; public WebSocketSharpRequest(HttpRequest httpRequest, HttpResponse httpResponse, string operationName, ILogger logger) { @@ -109,39 +104,6 @@ namespace Emby.Server.Implementations.SocketSharp public long ContentLength => Request.ContentLength ?? 0; - - public IHttpFile[] Files - { - get - { - if (_httpFiles != null) - { - return _httpFiles; - } - - if (_files == null) - { - return _httpFiles = Array.Empty(); - } - - var values = _files.Values; - _httpFiles = new IHttpFile[values.Count]; - for (int i = 0; i < values.Count; i++) - { - var reqFile = values.ElementAt(i); - _httpFiles[i] = new HttpFile - { - ContentType = reqFile.ContentType, - ContentLength = reqFile.ContentLength, - FileName = reqFile.FileName, - InputStream = reqFile.InputStream, - }; - } - - return _httpFiles; - } - } - private string GetHeader(string name) => Request.Headers[name].ToString(); private static IPAddress NormalizeIp(IPAddress ip) diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs index dc211af6b5..697a84f5c2 100644 --- a/MediaBrowser.Api/Devices/DeviceService.cs +++ b/MediaBrowser.Api/Devices/DeviceService.cs @@ -133,12 +133,15 @@ namespace MediaBrowser.Api.Devices var album = Request.QueryString["Album"]; var id = Request.QueryString["Id"]; var name = Request.QueryString["Name"]; + var req = Request.Response.HttpContext.Request; - if (Request.ContentType.IndexOf("multi", StringComparison.OrdinalIgnoreCase) == -1) + if (req.HasFormContentType) { - return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo + var file = req.Form.Files.Count == 0 ? null : req.Form.Files[0]; + + return _deviceManager.AcceptCameraUpload(deviceId, file.OpenReadStream(), new LocalFileInfo { - MimeType = Request.ContentType, + MimeType = file.ContentType, Album = album, Name = name, Id = id @@ -146,11 +149,9 @@ namespace MediaBrowser.Api.Devices } else { - var file = Request.Files.Length == 0 ? null : Request.Files[0]; - - return _deviceManager.AcceptCameraUpload(deviceId, file.InputStream, new LocalFileInfo + return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo { - MimeType = file.ContentType, + MimeType = Request.ContentType, Album = album, Name = name, Id = id diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 3852b1dd74..7a41526986 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services @@ -67,11 +64,6 @@ namespace MediaBrowser.Model.Services long ContentLength { get; } - /// - /// Access to the multi-part/formdata files posted on this request - /// - IHttpFile[] Files { get; } - /// /// The value of the Referrer, null if not available /// -- cgit v1.2.3 From 0116190050be69b2ac61d46681f2c7478a1318b9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 9 Aug 2019 22:37:44 +0200 Subject: Minor changes --- Emby.Server.Implementations/Services/ServiceHandler.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index cf15247bb3..934560de3c 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Services public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary requestParams, object requestDto) { var pathInfo = !restPath.IsWildCardPath - ? GetSanitizedPathInfo(httpReq.PathInfo, out string contentType) + ? GetSanitizedPathInfo(httpReq.PathInfo, out _) : httpReq.PathInfo; return restPath.CreateRequest(pathInfo, requestParams, requestDto); @@ -146,13 +146,12 @@ namespace Emby.Server.Implementations.Services { for (var i = 0; i < values.Count; i++) { - map[pair.Key + (i == 0 ? "" : "#" + i)] = values[i]; + map[pair.Key + (i == 0 ? string.Empty : "#" + i)] = values[i]; } } } - if ( - (IsMethod(request.Method, "POST") || IsMethod(request.Method, "PUT")) + if ((IsMethod(request.Method, "POST") || IsMethod(request.Method, "PUT")) && request.HasFormContentType) { foreach (var pair in request.Form) @@ -166,7 +165,7 @@ namespace Emby.Server.Implementations.Services { for (var i = 0; i < values.Count; i++) { - map[pair.Key + (i == 0 ? "" : "#" + i)] = values[i]; + map[pair.Key + (i == 0 ? string.Empty : "#" + i)] = values[i]; } } } @@ -176,9 +175,7 @@ namespace Emby.Server.Implementations.Services } private static bool IsMethod(string method, string expected) - { - return string.Equals(method, expected, StringComparison.OrdinalIgnoreCase); - } + => string.Equals(method, expected, StringComparison.OrdinalIgnoreCase); /// /// Duplicate params have their values joined together in a comma-delimited string @@ -192,8 +189,7 @@ namespace Emby.Server.Implementations.Services map[pair.Key] = pair.Value; } - if ( - (IsMethod(request.Method, "POST") || IsMethod(request.Method, "PUT")) + if ((IsMethod(request.Method, "POST") || IsMethod(request.Method, "PUT")) && request.HasFormContentType) { foreach (var pair in request.Form) -- cgit v1.2.3 From 1cad93c276fef512d6d4731d32704e05433aed01 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 29 Jul 2019 15:26:17 +0200 Subject: Use System.Net abstractions instead of raw socket --- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 2 +- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 207 ++++++++++++--------- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 139 +------------- Emby.Server.Implementations/Net/SocketFactory.cs | 42 ----- MediaBrowser.Model/Net/ISocketFactory.cs | 12 -- 5 files changed, 127 insertions(+), 275 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index ed524cae30..42acfbc5cb 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -255,7 +255,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var uri = new Uri(GetApiUrl(info)); - using (var manager = new HdHomerunManager(_socketFactory, Logger)) + using (var manager = new HdHomerunManager(Logger)) { // Legacy HdHomeruns are IPv4 only var ipInfo = IPAddress.Parse(uri.Host); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 6e79441dab..a6538be782 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -1,12 +1,13 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Net; +using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun @@ -78,37 +79,36 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public class HdHomerunManager : IDisposable { - public static int HdHomeRunPort = 65001; + public const int HdHomeRunPort = 65001; // Message constants - private static byte GetSetName = 3; - private static byte GetSetValue = 4; - private static byte GetSetLockkey = 21; - private static ushort GetSetRequest = 4; - private static ushort GetSetReply = 5; + private const byte GetSetName = 3; + private const byte GetSetValue = 4; + private const byte GetSetLockkey = 21; + private const ushort GetSetRequest = 4; + private const ushort GetSetReply = 5; + + private readonly ILogger _logger; private uint? _lockkey = null; private int _activeTuner = -1; - private readonly ISocketFactory _socketFactory; - private IPAddress _remoteIp; + private IPEndPoint _remoteEndPoint; - private ILogger _logger; - private ISocket _currentTcpSocket; + private TcpClient _tcpClient; - public HdHomerunManager(ISocketFactory socketFactory, ILogger logger) + public HdHomerunManager(ILogger logger) { - _socketFactory = socketFactory; _logger = logger; } public void Dispose() { - using (var socket = _currentTcpSocket) + using (var socket = _tcpClient) { if (socket != null) { - _currentTcpSocket = null; + _tcpClient = null; - var task = StopStreaming(socket); + var task = StopStreaming(_tcpClient); Task.WaitAll(task); } } @@ -116,35 +116,38 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public async Task CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken) { - using (var socket = _socketFactory.CreateTcpSocket(remoteIp, HdHomeRunPort)) + using (var client = new TcpClient(new IPEndPoint(remoteIp, HdHomeRunPort))) + using (var stream = client.GetStream()) { - return await CheckTunerAvailability(socket, remoteIp, tuner, cancellationToken).ConfigureAwait(false); + return await CheckTunerAvailability(stream, tuner, cancellationToken).ConfigureAwait(false); } } - private static async Task CheckTunerAvailability(ISocket socket, IPAddress remoteIp, int tuner, CancellationToken cancellationToken) + private static async Task CheckTunerAvailability(NetworkStream stream, int tuner, CancellationToken cancellationToken) { - var ipEndPoint = new IPEndPoint(remoteIp, HdHomeRunPort); - var lockkeyMsg = CreateGetMessage(tuner, "lockkey"); - await socket.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken); + await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false); - var receiveBuffer = new byte[8192]; - var response = await socket.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); + byte[] buffer = ArrayPool.Shared.Rent(8192); + try + { + int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); - ParseReturnMessage(response.Buffer, response.ReceivedBytes, out string returnVal); + ParseReturnMessage(buffer, receivedBytes, out string returnVal); - return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase); + return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase); + } + finally + { + ArrayPool.Shared.Return(buffer); + } } public async Task StartStreaming(IPAddress remoteIp, IPAddress localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken) { - _remoteIp = remoteIp; + _remoteEndPoint = new IPEndPoint(remoteIp, HdHomeRunPort); - var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort); - _currentTcpSocket = tcpClient; - - var receiveBuffer = new byte[8192]; + _tcpClient = new TcpClient(_remoteEndPoint); if (!_lockkey.HasValue) { @@ -153,51 +156,61 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } var lockKeyValue = _lockkey.Value; + var stream = _tcpClient.GetStream(); - var ipEndPoint = new IPEndPoint(_remoteIp, HdHomeRunPort); - - for (int i = 0; i < numTuners; ++i) + byte[] buffer = ArrayPool.Shared.Rent(8192); + try { - if (!await CheckTunerAvailability(tcpClient, _remoteIp, i, cancellationToken).ConfigureAwait(false)) - continue; - - _activeTuner = i; - var lockKeyString = string.Format("{0:d}", lockKeyValue); - var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null); - await tcpClient.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); - var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); - // parse response to make sure it worked - if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out var returnVal)) - continue; - - var commandList = commands.GetCommands(); - foreach (Tuple command in commandList) + for (int i = 0; i < numTuners; ++i) { - var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue); - await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); - response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); + if (!await CheckTunerAvailability(stream, i, cancellationToken).ConfigureAwait(false)) + { + continue; + } + + _activeTuner = i; + var lockKeyString = string.Format("{0:d}", lockKeyValue); + 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(response.Buffer, response.ReceivedBytes, out returnVal)) + if (!ParseReturnMessage(buffer, receivedBytes, out var returnVal)) { - await ReleaseLockkey(tcpClient, lockKeyValue).ConfigureAwait(false); continue; } - } + var commandList = commands.GetCommands(); + foreach (Tuple 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)) + { + await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false); + continue; + } + } - var targetValue = string.Format("rtp://{0}:{1}", localIp, localPort); - var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue); + var targetValue = string.Format("rtp://{0}:{1}", localIp, localPort); + var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue); - await tcpClient.SendToAsync(targetMsg, 0, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); - response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); - // parse response to make sure it worked - if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal)) - { - await ReleaseLockkey(tcpClient, lockKeyValue).ConfigureAwait(false); - continue; - } + 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)) + { + await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false); + continue; + } - return; + return; + } + } + finally + { + ArrayPool.Shared.Return(buffer); } _activeTuner = -1; @@ -207,53 +220,70 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public async Task ChangeChannel(IHdHomerunChannelCommands commands, CancellationToken cancellationToken) { if (!_lockkey.HasValue) + { return; + } - using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort)) + using (var tcpClient = new TcpClient(_remoteEndPoint)) + using (var stream = tcpClient.GetStream()) { var commandList = commands.GetCommands(); - var receiveBuffer = new byte[8192]; - - foreach (Tuple command in commandList) + byte[] buffer = ArrayPool.Shared.Rent(8192); + try { - var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey); - await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, new IPEndPoint(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false); - var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); - // parse response to make sure it worked - if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out string returnVal)) + foreach (Tuple command in commandList) { - return; + 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)) + { + return; + } } } + finally + { + ArrayPool.Shared.Return(buffer); + } } } - public Task StopStreaming(ISocket socket) + public Task StopStreaming(TcpClient client) { var lockKey = _lockkey; if (!lockKey.HasValue) + { return Task.CompletedTask; + } - return ReleaseLockkey(socket, lockKey.Value); + return ReleaseLockkey(client, lockKey.Value); } - private async Task ReleaseLockkey(ISocket tcpClient, uint lockKeyValue) + private async Task ReleaseLockkey(TcpClient client, uint lockKeyValue) { _logger.LogInformation("HdHomerunManager.ReleaseLockkey {0}", lockKeyValue); - var ipEndPoint = new IPEndPoint(_remoteIp, HdHomeRunPort); + var stream = client.GetStream(); var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue); - await tcpClient.SendToAsync(releaseTarget, 0, releaseTarget.Length, ipEndPoint, CancellationToken.None).ConfigureAwait(false); - - var receiveBuffer = new byte[8192]; + await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length, CancellationToken.None).ConfigureAwait(false); - await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false); - var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue); - _lockkey = null; - await tcpClient.SendToAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, ipEndPoint, CancellationToken.None).ConfigureAwait(false); - await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false); + var buffer = ArrayPool.Shared.Rent(8192); + try + { + await stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).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); + } + finally + { + ArrayPool.Shared.Return(buffer); + } } private static byte[] CreateGetMessage(int tuner, string name) @@ -270,7 +300,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; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index ec708cf20f..1d79a5f96e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -49,13 +49,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun EnableStreamSharing = true; } - private static Socket CreateSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) - { - var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp); - - return socket; - } - public override async Task Open(CancellationToken openCancellationToken) { LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); @@ -71,13 +64,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var remoteAddress = IPAddress.Parse(uri.Host); IPAddress localAddress = null; - using (var tcpSocket = CreateSocket(remoteAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp)) + using (var tcpClient = new TcpClient()) { try { - tcpSocket.Connect(new IPEndPoint(remoteAddress, HdHomerunManager.HdHomeRunPort)); - localAddress = ((IPEndPoint)tcpSocket.LocalEndPoint).Address; - tcpSocket.Close(); + await tcpClient.ConnectAsync(remoteAddress, HdHomerunManager.HdHomeRunPort).ConfigureAwait(false); + localAddress = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address; + tcpClient.Close(); } catch (Exception ex) { @@ -87,7 +80,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } var udpClient = _socketFactory.CreateUdpSocket(localPort); - var hdHomerunManager = new HdHomerunManager(_socketFactory, Logger); + var hdHomerunManager = new HdHomerunManager(Logger); try { @@ -103,6 +96,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { Logger.LogError(ex, "Error opening live stream:"); } + throw; } } @@ -199,126 +193,5 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } } - - public class UdpClientStream : Stream - { - private static int RtpHeaderBytes = 12; - private static int PacketSize = 1316; - private readonly MediaBrowser.Model.Net.ISocket _udpClient; - bool disposed; - - public UdpClientStream(MediaBrowser.Model.Net.ISocket udpClient) : base() - { - _udpClient = udpClient; - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer)); - - if (offset + count < 0) - throw new ArgumentOutOfRangeException(nameof(offset), "offset + count must not be negative"); - - if (offset + count > buffer.Length) - throw new ArgumentException("offset + count must not be greater than the length of buffer"); - - if (disposed) - throw new ObjectDisposedException(nameof(UdpClientStream)); - - // This will always receive a 1328 packet size (PacketSize + RtpHeaderSize) - // The RTP header will be stripped so see how many reads we need to make to fill the buffer. - int numReads = count / PacketSize; - int totalBytesRead = 0; - byte[] receiveBuffer = new byte[81920]; - - for (int i = 0; i < numReads; ++i) - { - var data = await _udpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); - - var bytesRead = data.ReceivedBytes - RtpHeaderBytes; - - // remove rtp header - Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, buffer, offset, bytesRead); - offset += bytesRead; - totalBytesRead += bytesRead; - } - return totalBytesRead; - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer)); - - if (offset + count < 0) - throw new ArgumentOutOfRangeException("offset + count must not be negative", "offset+count"); - - if (offset + count > buffer.Length) - throw new ArgumentException("offset + count must not be greater than the length of buffer"); - - if (disposed) - throw new ObjectDisposedException(nameof(UdpClientStream)); - - // This will always receive a 1328 packet size (PacketSize + RtpHeaderSize) - // The RTP header will be stripped so see how many reads we need to make to fill the buffer. - int numReads = count / PacketSize; - int totalBytesRead = 0; - byte[] receiveBuffer = new byte[81920]; - - for (int i = 0; i < numReads; ++i) - { - var receivedBytes = _udpClient.Receive(receiveBuffer, 0, receiveBuffer.Length); - - var bytesRead = receivedBytes - RtpHeaderBytes; - - // remove rtp header - Buffer.BlockCopy(receiveBuffer, RtpHeaderBytes, buffer, offset, bytesRead); - offset += bytesRead; - totalBytesRead += bytesRead; - } - return totalBytesRead; - } - - protected override void Dispose(bool disposing) - { - disposed = true; - } - - public override bool CanRead => throw new NotImplementedException(); - - public override bool CanSeek => throw new NotImplementedException(); - - public override bool CanWrite => throw new NotImplementedException(); - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - - set => throw new NotImplementedException(); - } - - public override void Flush() - { - throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - } } } diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 42ffa4e221..cb53ce50c7 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -2,54 +2,12 @@ using System; using System.IO; using System.Net; using System.Net.Sockets; -using Emby.Server.Implementations.Networking; using MediaBrowser.Model.Net; namespace Emby.Server.Implementations.Net { public class SocketFactory : ISocketFactory { - // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS - // Be careful to check any changes compile and work for all platform projects it is shared in. - - // Not entirely happy with this. Would have liked to have done something more generic/reusable, - // but that wasn't really the point so kept to YAGNI principal for now, even if the - // interfaces are a bit ugly, specific and make assumptions. - - public ISocket CreateTcpSocket(IPAddress remoteAddress, int remotePort) - { - if (remotePort < 0) - { - throw new ArgumentException("remotePort cannot be less than zero.", nameof(remotePort)); - } - - var addressFamily = remoteAddress.AddressFamily == AddressFamily.InterNetwork - ? AddressFamily.InterNetwork - : AddressFamily.InterNetworkV6; - - var retVal = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); - - try - { - retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - } - catch (SocketException) - { - // This is not supported on all operating systems (qnap) - } - - try - { - return new UdpSocket(retVal, new IPEndPoint(remoteAddress, remotePort)); - } - catch - { - retVal?.Dispose(); - - throw; - } - } - /// /// Creates a new UDP acceptSocket and binds it to the specified local port. /// diff --git a/MediaBrowser.Model/Net/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs index e58f4cc14d..2f857f1af0 100644 --- a/MediaBrowser.Model/Net/ISocketFactory.cs +++ b/MediaBrowser.Model/Net/ISocketFactory.cs @@ -17,8 +17,6 @@ namespace MediaBrowser.Model.Net ISocket CreateUdpBroadcastSocket(int localPort); - ISocket CreateTcpSocket(IPAddress remoteAddress, int remotePort); - /// /// Creates a new unicast socket using the specified local port number. /// @@ -35,14 +33,4 @@ namespace MediaBrowser.Model.Net Stream CreateNetworkStream(ISocket socket, bool ownsSocket); } - - public enum SocketType - { - Stream - } - - public enum ProtocolType - { - Tcp - } } -- cgit v1.2.3 From a5cb069f26ed6f4f1842ceb984342a4ef9c98d08 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 29 Jul 2019 16:30:16 +0200 Subject: Update HdHomerunManager.cs --- .../LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index a6538be782..c195524285 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -108,7 +108,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { _tcpClient = null; - var task = StopStreaming(_tcpClient); + var task = StopStreaming(socket); Task.WaitAll(task); } } -- cgit v1.2.3 From 2a58c643d24354ad8b6c45451bcedf82caa344c0 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 9 Aug 2019 23:16:24 +0200 Subject: Fix more warnings --- Emby.Server.Implementations/ApplicationHost.cs | 45 +++++++++++----------- Emby.Server.Implementations/Devices/DeviceId.cs | 1 - .../LiveTv/TunerHosts/M3uParser.cs | 1 + .../Networking/NetworkManager.cs | 38 +++++------------- Jellyfin.Server/Program.cs | 11 +++--- MediaBrowser.Common/Net/INetworkManager.cs | 4 +- MediaBrowser.Controller/IServerApplicationHost.cs | 2 +- MediaBrowser.Model/System/WakeOnLanInfo.cs | 37 ++++++++++++++++++ 8 files changed, 77 insertions(+), 62 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ef2f59d303..f0aa60428b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -108,9 +108,9 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; using ServiceStack; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; @@ -386,7 +386,7 @@ namespace Emby.Server.Implementations fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); - NetworkManager.NetworkChanged += NetworkManager_NetworkChanged; + NetworkManager.NetworkChanged += OnNetworkChanged; } public string ExpandVirtualPath(string path) @@ -410,7 +410,7 @@ namespace Emby.Server.Implementations return ServerConfigurationManager.Configuration.LocalNetworkSubnets; } - private void NetworkManager_NetworkChanged(object sender, EventArgs e) + private void OnNetworkChanged(object sender, EventArgs e) { _validAddressResults.Clear(); } @@ -421,7 +421,7 @@ namespace Emby.Server.Implementations /// Gets the current application user agent /// /// The application user agent. - public string ApplicationUserAgent => Name.Replace(' ','-') + '/' + ApplicationVersion; + public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion; /// /// Gets the email address for use within a comment section of a user agent field. @@ -429,14 +429,11 @@ namespace Emby.Server.Implementations /// public string ApplicationUserAgentAddress { get; } = "team@jellyfin.org"; - private string _productName; - /// - /// Gets the current application name + /// Gets the current application name. /// /// The application name. - public string ApplicationProductName - => _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName); + public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName; private DeviceId _deviceId; @@ -604,10 +601,15 @@ namespace Emby.Server.Implementations foreach (var plugin in Plugins) { - pluginBuilder.AppendLine(string.Format("{0} {1}", plugin.Name, plugin.Version)); + pluginBuilder.AppendLine( + string.Format( + CultureInfo.InvariantCulture, + "{0} {1}", + plugin.Name, + plugin.Version)); } - Logger.LogInformation("Plugins: {plugins}", pluginBuilder.ToString()); + Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString()); } DiscoverTypes(); @@ -629,7 +631,7 @@ namespace Emby.Server.Implementations if (EnableHttps && Certificate != null) { - options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); + options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate)); } }) .UseContentRoot(contentRoot) @@ -643,6 +645,7 @@ namespace Emby.Server.Implementations app.UseWebSockets(); app.UseResponseCompression(); + // TODO app.UseMiddleware(); app.Use(ExecuteWebsocketHandlerAsync); app.Use(ExecuteHttpHandlerAsync); @@ -1044,8 +1047,8 @@ namespace Emby.Server.Implementations .Cast() .ToList(); - await Task.WhenAll(StartEntryPoints(entries, true)); - await Task.WhenAll(StartEntryPoints(entries, false)); + await Task.WhenAll(StartEntryPoints(entries, true)).ConfigureAwait(false); + await Task.WhenAll(StartEntryPoints(entries, false)).ConfigureAwait(false); } /// @@ -1458,15 +1461,10 @@ namespace Emby.Server.Implementations }; } - public WakeOnLanInfo[] GetWakeOnLanInfo() - { - return NetworkManager.GetMacAddresses() - .Select(i => new WakeOnLanInfo - { - MacAddress = i - }) - .ToArray(); - } + public IEnumerable GetWakeOnLanInfo() + => NetworkManager.GetMacAddresses() + .Select(i => new WakeOnLanInfo(i)) + .ToList(); public async Task GetPublicSystemInfo(CancellationToken cancellationToken) { @@ -1482,6 +1480,7 @@ namespace Emby.Server.Implementations { wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns); } + return new PublicSystemInfo { Version = ApplicationVersion, diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index 495c3436ab..a470fc482c 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -2,7 +2,6 @@ using System; using System.IO; using System.Text; using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Devices diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 814031b126..a632db3eb4 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -58,6 +58,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts UserAgent = _appHost.ApplicationUserAgent }); } + return Task.FromResult((Stream)File.OpenRead(url)); } diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index f613dc2958..7d85a0666a 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -425,47 +425,27 @@ namespace Emby.Server.Implementations.Networking var localEndPoint = new IPEndPoint(IPAddress.Any, 0); using (var udpClient = new UdpClient(localEndPoint)) { - var port = ((IPEndPoint)(udpClient.Client.LocalEndPoint)).Port; + var port = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; return port; } } - private List _macAddresses; - public List GetMacAddresses() + private List _macAddresses; + public List GetMacAddresses() { if (_macAddresses == null) { - _macAddresses = GetMacAddressesInternal(); + _macAddresses = GetMacAddressesInternal().ToList(); } + return _macAddresses; } - private static List GetMacAddressesInternal() - { - return NetworkInterface.GetAllNetworkInterfaces() + private static IEnumerable GetMacAddressesInternal() + => NetworkInterface.GetAllNetworkInterfaces() .Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback) - .Select(i => - { - try - { - var physicalAddress = i.GetPhysicalAddress(); - - if (physicalAddress == null) - { - return null; - } - - return physicalAddress.ToString(); - } - catch (Exception) - { - //TODO Log exception. - return null; - } - }) - .Where(i => i != null) - .ToList(); - } + .Select(x => x.GetPhysicalAddress()) + .Where(x => x != null && x != PhysicalAddress.None); /// /// Parses the specified endpointstring. diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 08c0983bef..9529904930 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -18,7 +18,6 @@ using Jellyfin.Drawing.Skia; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -41,12 +40,12 @@ namespace Jellyfin.Server // For backwards compatibility. // Modify any input arguments now which start with single-hyphen to POSIX standard // double-hyphen to allow parsing by CommandLineParser package. - const string pattern = @"^(-[^-\s]{2})"; // Match -xx, not -x, not --xx, not xx - const string substitution = @"-$1"; // Prepend with additional single-hyphen - var regex = new Regex(pattern); + const string Pattern = @"^(-[^-\s]{2})"; // Match -xx, not -x, not --xx, not xx + const string Substitution = @"-$1"; // Prepend with additional single-hyphen + var regex = new Regex(Pattern); for (var i = 0; i < args.Length; i++) { - args[i] = regex.Replace(args[i], substitution); + args[i] = regex.Replace(args[i], Substitution); } // Parse the command line arguments and either start the app or exit indicating error @@ -134,7 +133,7 @@ namespace Jellyfin.Server Batteries_V2.Init(); if (raw.sqlite3_enable_shared_cache(1) != raw.SQLITE_OK) { - Console.WriteLine("WARN: Failed to enable shared cache for SQLite"); + _logger.LogWarning("Failed to enable shared cache for SQLite"); } using (var appHost = new CoreAppHost( diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 61f2bc2f9f..1df74d9955 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Net; -using System.Threading.Tasks; +using System.Net.NetworkInformation; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; @@ -25,7 +25,7 @@ namespace MediaBrowser.Common.Net /// Returns MAC Address from first Network Card in Computer /// /// [string] MAC Address - List GetMacAddresses(); + List GetMacAddresses(); /// /// Determines whether [is in private address space] [the specified endpoint]. diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 3f8cc0b83f..61b2c15ae2 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -83,7 +83,7 @@ namespace MediaBrowser.Controller void EnableLoopback(string appName); - WakeOnLanInfo[] GetWakeOnLanInfo(); + IEnumerable GetWakeOnLanInfo(); string ExpandVirtualPath(string path); string ReverseVirtualPath(string path); diff --git a/MediaBrowser.Model/System/WakeOnLanInfo.cs b/MediaBrowser.Model/System/WakeOnLanInfo.cs index 031458735e..534ad19ecc 100644 --- a/MediaBrowser.Model/System/WakeOnLanInfo.cs +++ b/MediaBrowser.Model/System/WakeOnLanInfo.cs @@ -1,10 +1,47 @@ +using System.Net.NetworkInformation; + namespace MediaBrowser.Model.System { + /// + /// Provides the MAC address and port for wake-on-LAN functionality. + /// public class WakeOnLanInfo { + /// + /// Returns the MAC address of the device. + /// + /// The MAC address. public string MacAddress { get; set; } + + /// + /// Returns the wake-on-LAN port. + /// + /// The wake-on-LAN port. public int Port { get; set; } + /// + /// Initializes a new instance of the class. + /// + /// The MAC address. + public WakeOnLanInfo(PhysicalAddress macAddress) + { + MacAddress = macAddress.ToString(); + Port = 9; + } + + /// + /// Initializes a new instance of the class. + /// + /// The MAC address. + public WakeOnLanInfo(string macAddress) + { + MacAddress = macAddress; + Port = 9; + } + + /// + /// Initializes a new instance of the class. + /// public WakeOnLanInfo() { Port = 9; -- cgit v1.2.3 From 6032f31aa660e3b0fe1936217109f9fb47853ba3 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 28 Feb 2019 23:22:57 +0100 Subject: Use CultureInvariant string conversion for Guids --- Emby.Dlna/Didl/DidlBuilder.cs | 4 ++-- Emby.Dlna/DlnaManager.cs | 5 ++-- Emby.Dlna/Eventing/EventManager.cs | 2 +- Emby.Dlna/Main/DlnaEntryPoint.cs | 3 ++- Emby.Dlna/PlayTo/PlayToManager.cs | 5 ++-- Emby.Drawing/ImageProcessor.cs | 6 ++--- Emby.Notifications/NotificationManager.cs | 5 ++-- .../Activity/ActivityLogEntryPoint.cs | 4 ++-- .../Activity/ActivityRepository.cs | 4 ++-- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Channels/ChannelManager.cs | 17 +++++++------- .../Collections/CollectionManager.cs | 3 ++- .../Data/SqliteDisplayPreferencesRepository.cs | 3 ++- .../Data/SqliteItemRepository.cs | 19 +++++++-------- Emby.Server.Implementations/Devices/DeviceId.cs | 4 ++-- .../Devices/DeviceManager.cs | 4 ++-- Emby.Server.Implementations/Dto/DtoService.cs | 17 +++++++------- .../EntryPoints/LibraryChangedNotifier.cs | 16 ++++++------- .../EntryPoints/ServerEventNotifier.cs | 3 ++- .../EntryPoints/UserDataChangeNotifier.cs | 6 ++--- .../HttpClientManager/HttpClientManager.cs | 2 +- .../IO/ManagedFileSystem.cs | 3 ++- .../Images/BaseDynamicImageProvider.cs | 3 ++- .../Library/ExclusiveLiveStream.cs | 3 ++- .../Library/LibraryManager.cs | 14 +++++------ .../Library/LiveStreamHelper.cs | 2 +- .../Library/MediaSourceManager.cs | 6 ++--- .../Library/UserDataManager.cs | 2 +- .../Library/UserViewManager.cs | 9 ++++---- .../Library/Validators/ArtistsValidator.cs | 3 ++- .../Library/Validators/PeopleValidator.cs | 4 ++-- .../Library/Validators/StudiosValidator.cs | 3 ++- .../LiveTv/EmbyTV/EmbyTV.cs | 8 +++---- .../LiveTv/Listings/XmlTvListingsProvider.cs | 4 ++-- .../LiveTv/LiveTvDtoService.cs | 27 +++++++++++----------- .../LiveTv/LiveTvManager.cs | 23 +++++++++--------- .../LiveTv/LiveTvMediaSourceProvider.cs | 3 ++- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 3 ++- .../LiveTv/TunerHosts/LiveStream.cs | 3 ++- .../LiveTv/TunerHosts/M3UTunerHost.cs | 7 +++--- .../LiveTv/TunerHosts/M3uParser.cs | 4 ++-- .../Playlists/PlaylistManager.cs | 7 +++--- .../ScheduledTasks/ScheduledTaskWorker.cs | 4 ++-- .../Security/AuthenticationRepository.cs | 2 +- .../Serialization/JsonSerializer.cs | 3 ++- .../Session/HttpSessionController.cs | 2 +- .../Session/SessionManager.cs | 14 ++++++----- Emby.Server.Implementations/TV/TVSeriesManager.cs | 3 ++- MediaBrowser.Api/Images/ImageService.cs | 3 ++- .../Library/LibraryStructureService.cs | 3 ++- MediaBrowser.Api/Movies/MoviesService.cs | 6 ++--- MediaBrowser.Api/Playback/BaseStreamingService.cs | 8 ++++++- MediaBrowser.Api/Playback/MediaInfoService.cs | 3 ++- MediaBrowser.Api/SearchService.cs | 5 ++-- MediaBrowser.Api/Session/SessionsService.cs | 3 ++- MediaBrowser.Api/Subtitles/SubtitleService.cs | 4 ++-- MediaBrowser.Api/TvShowsService.cs | 3 ++- MediaBrowser.Api/UserLibrary/ItemsService.cs | 2 +- MediaBrowser.Api/UserLibrary/UserViewsService.cs | 3 ++- MediaBrowser.Api/VideosService.cs | 3 ++- MediaBrowser.Controller/Channels/Channel.cs | 7 +++--- MediaBrowser.Controller/Entities/BaseItem.cs | 14 +++++------ MediaBrowser.Controller/Entities/Folder.cs | 5 ++-- MediaBrowser.Controller/Entities/LinkedChild.cs | 5 ++-- MediaBrowser.Controller/Entities/TV/Series.cs | 3 ++- MediaBrowser.Controller/Entities/User.cs | 3 ++- .../Entities/UserViewBuilder.cs | 3 ++- MediaBrowser.Controller/LiveTv/LiveTvChannel.cs | 4 ++-- MediaBrowser.Controller/LiveTv/LiveTvProgram.cs | 3 ++- MediaBrowser.Controller/Playlists/Playlist.cs | 3 ++- .../Providers/MetadataResult.cs | 3 ++- MediaBrowser.Providers/Manager/ProviderManager.cs | 8 +++---- .../MediaInfo/AudioImageProvider.cs | 7 +++--- .../Subtitles/SubtitleManager.cs | 5 ++-- jellyfin.ruleset | 4 ++++ 75 files changed, 240 insertions(+), 186 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index a21aff9f91..26adfde839 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -1082,7 +1082,7 @@ namespace Emby.Dlna.Didl public static string GetClientId(Guid idValue, StubType? stubType) { - var id = idValue.ToString("N"); + var id = idValue.ToString("N", CultureInfo.InvariantCulture); if (stubType.HasValue) { @@ -1096,7 +1096,7 @@ namespace Emby.Dlna.Didl { var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0", _serverAddress, - info.ItemId.ToString("N"), + info.ItemId.ToString("N", CultureInfo.InvariantCulture), info.Type, info.ImageTag, format, diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 2b76d27025..d5d788021d 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; @@ -300,7 +301,7 @@ namespace Emby.Dlna profile = ReserializeProfile(tempProfile); - profile.Id = path.ToLowerInvariant().GetMD5().ToString("N"); + profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); _profiles[path] = new Tuple(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile); @@ -352,7 +353,7 @@ namespace Emby.Dlna Info = new DeviceProfileInfo { - Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N"), + Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture), Name = _fileSystem.GetFileNameWithoutExtension(file), Type = type } diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/EventManager.cs index b4ff3ec1d5..4b542a820c 100644 --- a/Emby.Dlna/Eventing/EventManager.cs +++ b/Emby.Dlna/Eventing/EventManager.cs @@ -55,7 +55,7 @@ namespace Emby.Dlna.Eventing public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl) { var timeout = ParseTimeout(requestedTimeoutString) ?? 300; - var id = "uuid:" + Guid.NewGuid().ToString("N"); + var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); // Remove logging for now because some devices are sending this very frequently // TODO re-enable with dlna debug logging setting diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 206a873e1d..77bde0ca24 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -1,5 +1,6 @@ using System; using System.Net.Sockets; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; @@ -307,7 +308,7 @@ namespace Emby.Dlna.Main { guid = text.GetMD5(); } - return guid.ToString("N"); + return guid.ToString("N", CultureInfo.InvariantCulture); } private void SetProperies(SsdpDevice device, string fullDeviceType) diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index c0a4418711..a3a013096c 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Net; using System.Threading; @@ -141,7 +142,7 @@ namespace Emby.Dlna.PlayTo return usn; } - return usn.GetMD5().ToString("N"); + return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture); } private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken) @@ -156,7 +157,7 @@ namespace Emby.Dlna.PlayTo } else { - uuid = location.GetMD5().ToString("N"); + uuid = location.GetMD5().ToString("N", CultureInfo.InvariantCulture); } var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null); diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 6d209d8d01..a7d95eb209 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -454,14 +454,14 @@ namespace Emby.Drawing // Optimization if (imageEnhancers.Length == 0) { - return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N"); + return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); } // Cache name is created with supported enhancers combined with the last config change so we pick up new config changes var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList(); cacheKeys.Add(originalImagePath + dateModified.Ticks); - return string.Join("|", cacheKeys).GetMD5().ToString("N"); + return string.Join("|", cacheKeys).GetMD5().ToString("N", CultureInfo.InvariantCulture); } private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) @@ -480,7 +480,7 @@ namespace Emby.Drawing { try { - string filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N"); + string filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture); string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png"; var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension); diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs index 3d1d4722dd..a767e541e1 100644 --- a/Emby.Notifications/NotificationManager.cs +++ b/Emby.Notifications/NotificationManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -101,7 +102,7 @@ namespace Emby.Notifications var config = GetConfiguration(); return _userManager.Users - .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Policy)) + .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i.Policy)) .Select(i => i.Id); } @@ -197,7 +198,7 @@ namespace Emby.Notifications return _services.Select(i => new NameIdPair { Name = i.Name, - Id = i.Name.GetMD5().ToString("N") + Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture) }).OrderBy(i => i.Name); } diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 0530a251cc..3e44c9c0a2 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -75,7 +76,6 @@ namespace Emby.Server.Implementations.Activity _sessionManager.AuthenticationFailed += OnAuthenticationFailed; _sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded; _sessionManager.SessionEnded += OnSessionEnded; - _sessionManager.PlaybackStart += OnPlaybackStart; _sessionManager.PlaybackStopped += OnPlaybackStopped; @@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.Activity { Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)), Type = "SubtitleDownloadFailure", - ItemId = e.Item.Id.ToString("N"), + ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture), ShortOverview = e.Exception.Message }); } diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index de46ab9655..91371b8336 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Activity } else { - statement.TryBind("@UserId", entry.UserId.ToString("N")); + statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture)); } statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); @@ -141,7 +141,7 @@ namespace Emby.Server.Implementations.Activity } else { - statement.TryBind("@UserId", entry.UserId.ToString("N")); + statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture)); } statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ef2f59d303..4ff90a04b0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1220,7 +1220,7 @@ namespace Emby.Server.Implementations // Generate self-signed cert var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns); - var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N") + ".pfx"); + var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".pfx"); const string Password = "embycert"; return new CertificateInfo diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index e9961e8bd7..8e5f5b5617 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -206,7 +207,7 @@ namespace Emby.Server.Implementations.Channels try { - return GetChannelProvider(i).IsEnabledFor(user.Id.ToString("N")); + return GetChannelProvider(i).IsEnabledFor(user.Id.ToString("N", CultureInfo.InvariantCulture)); } catch { @@ -511,7 +512,7 @@ namespace Emby.Server.Implementations.Channels IncludeItemTypes = new[] { typeof(Channel).Name }, OrderBy = new ValueTuple[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) } - }).Select(i => GetChannelFeatures(i.ToString("N"))).ToArray(); + }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray(); } public ChannelFeatures GetChannelFeatures(string id) @@ -552,7 +553,7 @@ namespace Emby.Server.Implementations.Channels SupportsSortOrderToggle = features.SupportsSortOrderToggle, SupportsLatestMedia = supportsLatest, Name = channel.Name, - Id = channel.Id.ToString("N"), + Id = channel.Id.ToString("N", CultureInfo.InvariantCulture), SupportsContentDownloading = features.SupportsContentDownloading, AutoRefreshLevels = features.AutoRefreshLevels }; @@ -740,7 +741,7 @@ namespace Emby.Server.Implementations.Channels bool sortDescending, CancellationToken cancellationToken) { - var userId = user == null ? null : user.Id.ToString("N"); + var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture); var cacheLength = CacheLength; var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending); @@ -836,7 +837,7 @@ namespace Emby.Server.Implementations.Channels ChannelItemSortField? sortField, bool sortDescending) { - var channelId = GetInternalChannelId(channel.Name).ToString("N"); + var channelId = GetInternalChannelId(channel.Name).ToString("N", CultureInfo.InvariantCulture); var userCacheKey = string.Empty; @@ -846,10 +847,10 @@ namespace Emby.Server.Implementations.Channels userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty; } - var filename = string.IsNullOrEmpty(externalFolderId) ? "root" : externalFolderId.GetMD5().ToString("N"); + var filename = string.IsNullOrEmpty(externalFolderId) ? "root" : externalFolderId.GetMD5().ToString("N", CultureInfo.InvariantCulture); filename += userCacheKey; - var version = ((channel.DataVersion ?? string.Empty) + "2").GetMD5().ToString("N"); + var version = ((channel.DataVersion ?? string.Empty) + "2").GetMD5().ToString("N", CultureInfo.InvariantCulture); if (sortField.HasValue) { @@ -860,7 +861,7 @@ namespace Emby.Server.Implementations.Channels filename += "-sortDescending"; } - filename = filename.GetMD5().ToString("N"); + filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture); return Path.Combine(_config.ApplicationPaths.CachePath, "channels", diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 2b99e0ddfc..bb5057b1c2 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -182,7 +183,7 @@ namespace Emby.Server.Implementations.Collections public void AddToCollection(Guid collectionId, IEnumerable ids) { - AddToCollection(collectionId, ids.Select(i => i.ToString("N")), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))); + AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))); } private void AddToCollection(Guid collectionId, IEnumerable ids, bool fireEvent, MetadataRefreshOptions refreshOptions) diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 01ef9851dc..87096e72f5 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Threading; using MediaBrowser.Common.Configuration; @@ -182,7 +183,7 @@ namespace Emby.Server.Implementations.Data return new DisplayPreferences { - Id = guidId.ToString("N") + Id = guidId.ToString("N", CultureInfo.InvariantCulture) }; } } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 1cefcec7ca..bb4c34f02f 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -696,7 +696,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@EndDate"); } - saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(Guid.Empty) ? null : item.ChannelId.ToString("N")); + saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(Guid.Empty) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture)); if (item is IHasProgramAttributes hasProgramAttributes) { @@ -851,7 +851,7 @@ namespace Emby.Server.Implementations.Data } else { - saveItemStatement.TryBind("@TopParentId", topParent.Id.ToString("N")); + saveItemStatement.TryBind("@TopParentId", topParent.Id.ToString("N", CultureInfo.InvariantCulture)); } if (item is Trailer trailer && trailer.TrailerTypes.Length > 0) @@ -3548,12 +3548,12 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("ChannelId=@ChannelId"); if (statement != null) { - statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N")); + statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture)); } } else if (query.ChannelIds.Length > 1) { - var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i.ToString("N") + "'")); + var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); whereClauses.Add($"ChannelId in ({inClause})"); } @@ -4537,12 +4537,12 @@ namespace Emby.Server.Implementations.Data } if (statement != null) { - statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N")); + statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture)); } } else if (queryTopParentIds.Length > 1) { - var val = string.Join(",", queryTopParentIds.Select(i => "'" + i.ToString("N") + "'")); + var val = string.Join(",", queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); if (enableItemsByName && includedItemByNameTypes.Count == 1) { @@ -4574,7 +4574,7 @@ namespace Emby.Server.Implementations.Data } if (query.AncestorIds.Length > 1) { - var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N") + "'")); + var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause)); } if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey)) @@ -4637,7 +4637,7 @@ namespace Emby.Server.Implementations.Data foreach (var folderId in query.BoxSetLibraryFolders) { - folderIdQueries.Add("data like '%" + folderId.ToString("N") + "%'"); + folderIdQueries.Add("data like '%" + folderId.ToString("N", CultureInfo.InvariantCulture) + "%'"); } whereClauses.Add("(" + string.Join(" OR ", folderIdQueries) + ")"); @@ -5161,7 +5161,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type var ancestorId = ancestorIds[i]; statement.TryBind("@AncestorId" + index, ancestorId.ToGuidBlob()); - statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N")); + statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture)); } statement.Reset(); @@ -5579,6 +5579,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { counts.TrailerCount = value; } + counts.ItemCount += value; } diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index 495c3436ab..7344dc72f3 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -1,8 +1,8 @@ using System; +using System.Globalization; using System.IO; using System.Text; using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Devices @@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.Devices private static string GetNewId() { - return Guid.NewGuid().ToString("N"); + return Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); } private string GetDeviceId() diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 7d6529a675..d1704b3736 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Entities; @@ -195,7 +195,7 @@ namespace Emby.Server.Implementations.Devices private string GetDevicePath(string id) { - return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N")); + return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture)); } public ContentUploadHistory GetCameraUploadHistory(string deviceId) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 2f1b60be9c..6e7aa13139 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -213,7 +214,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.DisplayPreferencesId)) { - dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N"); + dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N", CultureInfo.InvariantCulture); } if (user != null) @@ -444,7 +445,7 @@ namespace Emby.Server.Implementations.Dto /// item public string GetDtoId(BaseItem item) { - return item.Id.ToString("N"); + return item.Id.ToString("N", CultureInfo.InvariantCulture); } private static void SetBookProperties(BaseItemDto dto, Book item) @@ -608,7 +609,7 @@ namespace Emby.Server.Implementations.Dto if (dictionary.TryGetValue(person.Name, out Person entity)) { baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); - baseItemPerson.Id = entity.Id.ToString("N"); + baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture); list.Add(baseItemPerson); } } @@ -893,7 +894,7 @@ namespace Emby.Server.Implementations.Dto //var artistItems = _libraryManager.GetArtists(new InternalItemsQuery //{ // EnableTotalRecordCount = false, - // ItemIds = new[] { item.Id.ToString("N") } + // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } //}); //dto.ArtistItems = artistItems.Items @@ -903,7 +904,7 @@ namespace Emby.Server.Implementations.Dto // return new NameIdPair // { // Name = artist.Name, - // Id = artist.Id.ToString("N") + // Id = artist.Id.ToString("N", CultureInfo.InvariantCulture) // }; // }) // .ToList(); @@ -946,7 +947,7 @@ namespace Emby.Server.Implementations.Dto //var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery //{ // EnableTotalRecordCount = false, - // ItemIds = new[] { item.Id.ToString("N") } + // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } //}); //dto.AlbumArtists = artistItems.Items @@ -956,7 +957,7 @@ namespace Emby.Server.Implementations.Dto // return new NameIdPair // { // Name = artist.Name, - // Id = artist.Id.ToString("N") + // Id = artist.Id.ToString("N", CultureInfo.InvariantCulture) // }; // }) // .ToList(); @@ -1044,7 +1045,7 @@ namespace Emby.Server.Implementations.Dto } else { - string id = item.Id.ToString("N"); + string id = item.Id.ToString("N", CultureInfo.InvariantCulture); mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.MediaStreams) .ToArray(); diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 8369f4f593..9c0db2cf5e 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.EntryPoints _lastProgressMessageTimes[item.Id] = DateTime.UtcNow; var dict = new Dictionary(); - dict["ItemId"] = item.Id.ToString("N"); + dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture); dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture); try @@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.EntryPoints foreach (var collectionFolder in collectionFolders) { var collectionFolderDict = new Dictionary(); - collectionFolderDict["ItemId"] = collectionFolder.Id.ToString("N"); + collectionFolderDict["ItemId"] = collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture); collectionFolderDict["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture); try @@ -378,15 +378,15 @@ namespace Emby.Server.Implementations.EntryPoints return new LibraryUpdateInfo { - ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), + ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), - ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), + ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), - ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), + ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), - FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), + FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), - FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), + FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray() }; @@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.EntryPoints var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren); foreach (var folder in allUserRootChildren) { - list.Add(folder.Id.ToString("N")); + list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture)); } } diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 091dd6a45b..141e729584 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Plugins; @@ -134,7 +135,7 @@ namespace Emby.Server.Implementations.EntryPoints /// The e. void userManager_UserDeleted(object sender, GenericEventArgs e) { - SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N")); + SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)); } void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index a5badaceec..bae3422ff6 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -8,7 +9,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; @@ -125,12 +125,12 @@ namespace Emby.Server.Implementations.EntryPoints .Select(i => { var dto = _userDataManager.GetUserDataDto(i, user); - dto.ItemId = i.Id.ToString("N"); + dto.ItemId = i.Id.ToString("N", CultureInfo.InvariantCulture); return dto; }) .ToArray(); - var userIdString = userId.ToString("N"); + var userIdString = userId.ToString("N", CultureInfo.InvariantCulture); return new UserDataChangeInfo { diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 331b5e29d7..9ca33d7db7 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -195,7 +195,7 @@ namespace Emby.Server.Implementations.HttpClientManager } var url = options.Url; - var urlHash = url.ToLowerInvariant().GetMD5().ToString("N"); + var urlHash = url.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash); diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 8517abed6c..ae8371a32f 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Diagnostics; using System.IO; using System.Linq; @@ -555,7 +556,7 @@ namespace Emby.Server.Implementations.IO throw new ArgumentNullException(nameof(file2)); } - var temp1 = Path.Combine(_tempPath, Guid.NewGuid().ToString("N")); + var temp1 = Path.Combine(_tempPath, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); // Copying over will fail against hidden files SetHidden(file1, false); diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 46f209b4b4..d8faceadb2 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -89,7 +90,7 @@ namespace Emby.Server.Implementations.Images ImageType imageType, CancellationToken cancellationToken) { - var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N")); + var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); Directory.CreateDirectory(Path.GetDirectoryName(outputPathWithoutExtension)); string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0); diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 45a33a2969..a3c879f121 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; @@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.Library EnableStreamSharing = false; _closeFn = closeFn; ConsumerCount = 1; - UniqueId = Guid.NewGuid().ToString("N"); + UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); } public Task Close() diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 4b5063ada6..30ff855cc2 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1187,12 +1187,12 @@ namespace Emby.Server.Implementations.Library if (libraryFolder != null && libraryFolder.HasImage(ImageType.Primary)) { - info.PrimaryImageItemId = libraryFolder.Id.ToString("N"); + info.PrimaryImageItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture); } if (libraryFolder != null) { - info.ItemId = libraryFolder.Id.ToString("N"); + info.ItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture); info.LibraryOptions = GetLibraryOptions(libraryFolder); if (refreshQueue != null) @@ -2135,12 +2135,12 @@ namespace Emby.Server.Implementations.Library string viewType, string sortName) { - var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N"); - var idValues = "38_namedview_" + name + user.Id.ToString("N") + (parentIdString ?? string.Empty) + (viewType ?? string.Empty); + var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture); + var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType ?? string.Empty); var id = GetNewItemId(idValues, typeof(UserView)); - var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N")); + var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture)); var item = GetItemById(id) as UserView; @@ -2271,7 +2271,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(name)); } - var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N"); + var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture); var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType ?? string.Empty); if (!string.IsNullOrEmpty(uniqueId)) { @@ -2280,7 +2280,7 @@ namespace Emby.Server.Implementations.Library var id = GetNewItemId(idValues, typeof(UserView)); - var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N")); + var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture)); var item = GetItemById(id) as UserView; diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index c3082a78a7..33e6f24341 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Library var now = DateTime.UtcNow; MediaInfo mediaInfo = null; - var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json"); + var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json"); if (!string.IsNullOrEmpty(cacheKey)) { diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 24ab8e7619..d83e1fc021 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -269,7 +269,7 @@ namespace Emby.Server.Implementations.Library private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource) { - var prefix = provider.GetType().FullName.GetMD5().ToString("N") + LiveStreamIdDelimeter; + var prefix = provider.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + LiveStreamIdDelimeter; if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { @@ -626,7 +626,7 @@ namespace Emby.Server.Implementations.Library var now = DateTime.UtcNow; MediaInfo mediaInfo = null; - var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json"); + var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json"); if (!string.IsNullOrEmpty(cacheKey)) { @@ -854,7 +854,7 @@ namespace Emby.Server.Implementations.Library var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2); - var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), keys[0], StringComparison.OrdinalIgnoreCase)); + var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase)); var splitIndex = key.IndexOf(LiveStreamIdDelimeter); var keyId = key.Substring(splitIndex + 1); diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index dfa1edaffd..36adc0b9c4 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -152,7 +152,7 @@ namespace Emby.Server.Implementations.Library /// System.String. private static string GetCacheKey(long internalUserId, Guid itemId) { - return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N"); + return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N", CultureInfo.InvariantCulture); } public UserItemData GetUserData(User user, BaseItem item) diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index e9ce682ee1..71f16ac3e9 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using MediaBrowser.Controller.Channels; @@ -117,7 +118,7 @@ namespace Emby.Server.Implementations.Library if (!query.IncludeHidden) { - list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N"))).ToList(); + list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList(); } var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); @@ -127,7 +128,7 @@ namespace Emby.Server.Implementations.Library return list .OrderBy(i => { - var index = orders.IndexOf(i.Id.ToString("N")); + var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture)); if (index == -1) { @@ -136,7 +137,7 @@ namespace Emby.Server.Implementations.Library { if (!view.DisplayParentId.Equals(Guid.Empty)) { - index = orders.IndexOf(view.DisplayParentId.ToString("N")); + index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture)); } } } @@ -269,7 +270,7 @@ namespace Emby.Server.Implementations.Library { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N"))) + .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) .ToList(); } diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index 294348660f..b584cc649d 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Validators continue; } - _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name); + _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name); _libraryManager.DeleteItem(item, new DeleteOptions { diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index 7899cf01b3..d00c6cde11 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -1,7 +1,7 @@ using System; +using System.Globalization; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.Library.Validators foreach (var item in deadEntities) { - _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name); + _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name); _libraryManager.DeleteItem(item, new DeleteOptions { diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index da4645a11d..93ded9e7bd 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; @@ -76,7 +77,7 @@ namespace Emby.Server.Implementations.Library.Validators foreach (var item in deadEntities) { - _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name); + _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name); _libraryManager.DeleteItem(item, new DeleteOptions { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 7b210d2313..d7411af502 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -681,7 +681,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - timer.Id = Guid.NewGuid().ToString("N"); + timer.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); LiveTvProgram programInfo = null; @@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public async Task CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken) { - info.Id = Guid.NewGuid().ToString("N"); + info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); // populate info.seriesID var program = GetProgramInfoFromCache(info.ProgramId); @@ -1059,7 +1059,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var json = _jsonSerializer.SerializeToString(mediaSource); mediaSource = _jsonSerializer.DeserializeFromString(json); - mediaSource.Id = Guid.NewGuid().ToString("N") + "_" + mediaSource.Id; + mediaSource.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_" + mediaSource.Id; //if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing) //{ @@ -2529,7 +2529,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var timer = new TimerInfo { ChannelId = channelId, - Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N"), + Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N", CultureInfo.InvariantCulture), StartDate = parent.StartDate, EndDate = parent.EndDate.Value, ProgramId = parent.ExternalId, diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 94225a0aa9..88693f22a4 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -211,7 +211,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source), OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null, CommunityRating = program.StarRating, - SeriesId = program.Episode == null ? null : program.Title.GetMD5().ToString("N") + SeriesId = program.Episode == null ? null : program.Title.GetMD5().ToString("N", CultureInfo.InvariantCulture) }; if (string.IsNullOrWhiteSpace(program.ProgramId)) @@ -227,7 +227,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture); } - programInfo.ShowId = uniqueString.GetMD5().ToString("N"); + programInfo.ShowId = uniqueString.GetMD5().ToString("N", CultureInfo.InvariantCulture); // If we don't have valid episode info, assume it's a unique program, otherwise recordings might be skipped if (programInfo.IsSeries diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 1144c9ab17..e584664c94 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -52,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv ExternalId = info.Id, ChannelId = GetInternalChannelId(service.Name, info.ChannelId), Status = info.Status, - SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N"), + SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture), PrePaddingSeconds = info.PrePaddingSeconds, PostPaddingSeconds = info.PostPaddingSeconds, IsPostPaddingRequired = info.IsPostPaddingRequired, @@ -69,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv if (!string.IsNullOrEmpty(info.ProgramId)) { - dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N"); + dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N", CultureInfo.InvariantCulture); } if (program != null) @@ -107,7 +108,7 @@ namespace Emby.Server.Implementations.LiveTv { var dto = new SeriesTimerInfoDto { - Id = GetInternalSeriesTimerId(info.Id).ToString("N"), + Id = GetInternalSeriesTimerId(info.Id).ToString("N", CultureInfo.InvariantCulture), Overview = info.Overview, EndDate = info.EndDate, Name = info.Name, @@ -139,7 +140,7 @@ namespace Emby.Server.Implementations.LiveTv if (!string.IsNullOrEmpty(info.ProgramId)) { - dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N"); + dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N", CultureInfo.InvariantCulture); } dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days.ToArray()); @@ -169,7 +170,7 @@ namespace Emby.Server.Implementations.LiveTv try { dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image); - dto.ParentThumbItemId = librarySeries.Id.ToString("N"); + dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -185,7 +186,7 @@ namespace Emby.Server.Implementations.LiveTv { _imageProcessor.GetImageCacheTag(librarySeries, image) }; - dto.ParentBackdropItemId = librarySeries.Id.ToString("N"); + dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -213,7 +214,7 @@ namespace Emby.Server.Implementations.LiveTv try { dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image); - dto.ParentPrimaryImageItemId = program.Id.ToString("N"); + dto.ParentPrimaryImageItemId = program.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -232,7 +233,7 @@ namespace Emby.Server.Implementations.LiveTv { _imageProcessor.GetImageCacheTag(program, image) }; - dto.ParentBackdropItemId = program.Id.ToString("N"); + dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -263,7 +264,7 @@ namespace Emby.Server.Implementations.LiveTv try { dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image); - dto.ParentThumbItemId = librarySeries.Id.ToString("N"); + dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -279,7 +280,7 @@ namespace Emby.Server.Implementations.LiveTv { _imageProcessor.GetImageCacheTag(librarySeries, image) }; - dto.ParentBackdropItemId = librarySeries.Id.ToString("N"); + dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -320,7 +321,7 @@ namespace Emby.Server.Implementations.LiveTv try { dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image); - dto.ParentPrimaryImageItemId = program.Id.ToString("N"); + dto.ParentPrimaryImageItemId = program.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -339,7 +340,7 @@ namespace Emby.Server.Implementations.LiveTv { _imageProcessor.GetImageCacheTag(program, image) }; - dto.ParentBackdropItemId = program.Id.ToString("N"); + dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -407,7 +408,7 @@ namespace Emby.Server.Implementations.LiveTv { var name = ServiceName + externalId + InternalVersionNumber; - return name.ToLowerInvariant().GetMD5().ToString("N"); + return name.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); } public Guid GetInternalSeriesTimerId(string externalId) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 9093d9740f..1e5198dd6c 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -258,7 +259,7 @@ namespace Emby.Server.Implementations.LiveTv } info.RequiresClosing = true; - var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_"; + var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_"; info.LiveStreamId = idPrefix + info.Id; @@ -820,7 +821,7 @@ namespace Emby.Server.Implementations.LiveTv if (!string.IsNullOrWhiteSpace(query.SeriesTimerId)) { var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery { }, cancellationToken).ConfigureAwait(false); - var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N"), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase)); + var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N", CultureInfo.InvariantCulture), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase)); if (seriesTimer != null) { internalQuery.ExternalSeriesId = seriesTimer.SeriesId; @@ -997,7 +998,7 @@ namespace Emby.Server.Implementations.LiveTv if (!string.IsNullOrEmpty(timer.SeriesTimerId)) { program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.SeriesTimerId) - .ToString("N"); + .ToString("N", CultureInfo.InvariantCulture); foundSeriesTimer = true; } @@ -1018,7 +1019,7 @@ namespace Emby.Server.Implementations.LiveTv if (seriesTimer != null) { program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(seriesTimer.Id) - .ToString("N"); + .ToString("N", CultureInfo.InvariantCulture); } } } @@ -1472,7 +1473,7 @@ namespace Emby.Server.Implementations.LiveTv dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null - : _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N"); + : _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture); dto.TimerId = string.IsNullOrEmpty(info.Id) ? null @@ -2027,7 +2028,7 @@ namespace Emby.Server.Implementations.LiveTv info.StartDate = program.StartDate; info.Name = program.Name; info.Overview = program.Overview; - info.ProgramId = programDto.Id.ToString("N"); + info.ProgramId = programDto.Id.ToString("N", CultureInfo.InvariantCulture); info.ExternalProgramId = program.ExternalId; if (program.EndDate.HasValue) @@ -2088,7 +2089,7 @@ namespace Emby.Server.Implementations.LiveTv if (service is ISupportsNewTimerIds supportsNewTimerIds) { newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false); - newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N"); + newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N", CultureInfo.InvariantCulture); } else { @@ -2192,7 +2193,7 @@ namespace Emby.Server.Implementations.LiveTv info.EnabledUsers = _userManager.Users .Where(IsLiveTvEnabled) - .Select(i => i.Id.ToString("N")) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) .ToArray(); return info; @@ -2219,7 +2220,7 @@ namespace Emby.Server.Implementations.LiveTv { var parts = id.Split(new[] { '_' }, 2); - var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase)); + var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), parts[0], StringComparison.OrdinalIgnoreCase)); if (service == null) { @@ -2269,7 +2270,7 @@ namespace Emby.Server.Implementations.LiveTv if (index == -1 || string.IsNullOrWhiteSpace(info.Id)) { - info.Id = Guid.NewGuid().ToString("N"); + info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); list.Add(info); config.TunerHosts = list.ToArray(); } @@ -2312,7 +2313,7 @@ namespace Emby.Server.Implementations.LiveTv if (index == -1 || string.IsNullOrWhiteSpace(info.Id)) { - info.Id = Guid.NewGuid().ToString("N"); + info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); list.Add(info); config.ListingProviders = list.ToArray(); } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index cd1731de55..52d60c004a 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -101,7 +102,7 @@ namespace Emby.Server.Implementations.LiveTv { var openKeys = new List(); openKeys.Add(item.GetType().Name); - openKeys.Add(item.Id.ToString("N")); + openKeys.Add(item.Id.ToString("N", CultureInfo.InvariantCulture)); openKeys.Add(source.Id ?? string.Empty); source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray()); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index ed524cae30..db016ec70b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -460,7 +461,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { id = "native"; } - id += "_" + channelId.GetMD5().ToString("N") + "_" + url.GetMD5().ToString("N"); + id += "_" + channelId.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_" + url.GetMD5().ToString("N", CultureInfo.InvariantCulture); var mediaSource = new MediaSourceInfo { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index ece2cbd547..b4395e2e1d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -42,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts MediaSource = mediaSource; Logger = logger; EnableStreamSharing = true; - UniqueId = Guid.NewGuid().ToString("N"); + UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); if (tuner != null) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 2d9bec53f0..6c5c80827d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private string GetFullChannelIdPrefix(TunerHostInfo info) { - return ChannelIdPrefix + info.Url.GetMD5().ToString("N"); + return ChannelIdPrefix + info.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture); } protected override async Task> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken) @@ -61,7 +62,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Name = Name, SourceType = Type, Status = LiveTvTunerStatus.Available, - Id = i.Url.GetMD5().ToString("N"), + Id = i.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture), Url = i.Url }) .ToList(); @@ -173,7 +174,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts ReadAtNativeFramerate = false, - Id = channel.Path.GetMD5().ToString("N"), + Id = channel.Path.GetMD5().ToString("N", CultureInfo.InvariantCulture), IsInfiniteStream = true, IsRemote = isRemote, diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 814031b126..e8cd129f5c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -92,11 +92,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var channel = GetChannelnfo(extInf, tunerHostId, line); if (string.IsNullOrWhiteSpace(channel.Id)) { - channel.Id = channelIdPrefix + line.GetMD5().ToString("N"); + channel.Id = channelIdPrefix + line.GetMD5().ToString("N", CultureInfo.InvariantCulture); } else { - channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N"); + channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N", CultureInfo.InvariantCulture); } channel.Path = line; diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 29836e0bff..40b568b40a 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -129,7 +130,7 @@ namespace Emby.Server.Implementations.Playlists { new Share { - UserId = options.UserId.Equals(Guid.Empty) ? null : options.UserId.ToString("N"), + UserId = options.UserId.Equals(Guid.Empty) ? null : options.UserId.ToString("N", CultureInfo.InvariantCulture), CanEdit = true } } @@ -144,7 +145,7 @@ namespace Emby.Server.Implementations.Playlists if (options.ItemIdList.Length > 0) { - AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user, new DtoOptions(false) + AddToPlaylistInternal(playlist.Id.ToString("N", CultureInfo.InvariantCulture), options.ItemIdList, user, new DtoOptions(false) { EnableImages = true }); @@ -152,7 +153,7 @@ namespace Emby.Server.Implementations.Playlists return new PlaylistCreationResult { - Id = playlist.Id.ToString("N") + Id = playlist.Id.ToString("N", CultureInfo.InvariantCulture) }; } finally diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 08bb39578b..83226b07f1 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { if (_id == null) { - _id = ScheduledTask.GetType().FullName.GetMD5().ToString("N"); + _id = ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); } return _id; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 545e11bf9f..26a08cbe9a 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -174,7 +174,7 @@ namespace Emby.Server.Implementations.Security if (!query.UserId.Equals(Guid.Empty)) { - statement.TryBind("@UserId", query.UserId.ToString("N")); + statement.TryBind("@UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture)); } if (!string.IsNullOrEmpty(query.DeviceId)) diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs index 8ae7fd90cc..36196ee36c 100644 --- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs +++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Threading.Tasks; using MediaBrowser.Model.IO; @@ -245,7 +246,7 @@ namespace Emby.Server.Implementations.Serialization return null; } - return guid.ToString("N"); + return guid.ToString("N", CultureInfo.InvariantCulture); } /// diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index 9281f82b39..1104a7a85b 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Session { var dict = new Dictionary(); - dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N")).ToArray()); + dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray()); if (command.StartPositionTicks.HasValue) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 53ed5fc223..7ee573da5f 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.Session { if (string.IsNullOrEmpty(info.MediaSourceId)) { - info.MediaSourceId = info.ItemId.ToString("N"); + info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); } if (!info.ItemId.Equals(Guid.Empty) && info.Item == null && libraryItem != null) @@ -463,7 +463,7 @@ namespace Emby.Server.Implementations.Session Client = appName, DeviceId = deviceId, ApplicationVersion = appVersion, - Id = key.GetMD5().ToString("N"), + Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture), ServerId = _appHost.SystemId }; @@ -845,7 +845,7 @@ namespace Emby.Server.Implementations.Session // Normalize if (string.IsNullOrEmpty(info.MediaSourceId)) { - info.MediaSourceId = info.ItemId.ToString("N"); + info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); } if (!info.ItemId.Equals(Guid.Empty) && info.Item == null && libraryItem != null) @@ -1029,7 +1029,7 @@ namespace Emby.Server.Implementations.Session private static async Task SendMessageToSession(SessionInfo session, string name, T data, CancellationToken cancellationToken) { var controllers = session.SessionControllers.ToArray(); - var messageId = Guid.NewGuid().ToString("N"); + var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); foreach (var controller in controllers) { @@ -1234,7 +1234,7 @@ namespace Emby.Server.Implementations.Session AssertCanControl(session, controllingSession); if (!controllingSession.UserId.Equals(Guid.Empty)) { - command.ControllingUserId = controllingSession.UserId.ToString("N"); + command.ControllingUserId = controllingSession.UserId.ToString("N", CultureInfo.InvariantCulture); } } @@ -1484,7 +1484,7 @@ namespace Emby.Server.Implementations.Session DeviceId = deviceId, DeviceName = deviceName, UserId = user.Id, - AccessToken = Guid.NewGuid().ToString("N"), + AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), UserName = user.Name }; @@ -1822,6 +1822,7 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); + return SendMessageToSessions(sessions, name, data, cancellationToken); } @@ -1831,6 +1832,7 @@ namespace Emby.Server.Implementations.Session var sessions = Sessions .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)); + return SendMessageToSessions(sessions, name, data, cancellationToken); } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 630ef4893b..4c2f24e6f2 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -73,7 +74,7 @@ namespace Emby.Server.Implementations.TV { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N"))) + .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) .ToArray(); } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 10bbc9e5d8..23c7339d27 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -537,7 +538,7 @@ namespace MediaBrowser.Api.Images if (item == null) { - throw new ResourceNotFoundException(string.Format("Item {0} not found.", itemId.ToString("N"))); + throw new ResourceNotFoundException(string.Format("Item {0} not found.", itemId.ToString("N", CultureInfo.InvariantCulture))); } } diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index d6bcf7878b..7266bf9f92 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -272,7 +273,7 @@ namespace MediaBrowser.Api.Library // Changing capitalization. Handle windows case insensitivity if (string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase)) { - var tempPath = Path.Combine(rootFolderPath, Guid.NewGuid().ToString("N")); + var tempPath = Path.Combine(rootFolderPath, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); Directory.Move(currentPath, tempPath); currentPath = tempPath; } diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 91766255fd..d601fb5007 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; @@ -11,7 +12,6 @@ using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; @@ -268,7 +268,7 @@ namespace MediaBrowser.Api.Movies EnableGroupByMetadataKey = true, DtoOptions = dtoOptions - }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) + }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) .Select(x => x.First()) .Take(itemLimit) .ToList(); @@ -309,7 +309,7 @@ namespace MediaBrowser.Api.Movies EnableGroupByMetadataKey = true, DtoOptions = dtoOptions - }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) + }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) .Select(x => x.First()) .Take(itemLimit) .ToList(); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 3994016240..114d3f7a24 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -142,7 +142,7 @@ namespace MediaBrowser.Api.Playback data += "-" + (state.Request.DeviceId ?? string.Empty) + "-" + (state.Request.PlaySessionId ?? string.Empty); - var filename = data.GetMD5().ToString("N"); + var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture); var ext = outputFileExtension.ToLowerInvariant(); var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath; @@ -215,6 +215,12 @@ namespace MediaBrowser.Api.Playback var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); +<<<<<<< HEAD +======= + var transcodingId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true); + +>>>>>>> Use CultureInvariant string conversion for Guids var process = new Process() { StartInfo = new ProcessStartInfo() diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index ab3994a639..da8f99a3dd 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -306,7 +307,7 @@ namespace MediaBrowser.Api.Playback { result.MediaSources = Clone(result.MediaSources); - result.PlaySessionId = Guid.NewGuid().ToString("N"); + result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); } return result; diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index ecf07c9122..6c67d4fb15 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -305,7 +306,7 @@ namespace MediaBrowser.Api if (tag != null) { hint.ThumbImageTag = tag; - hint.ThumbImageItemId = itemWithImage.Id.ToString("N"); + hint.ThumbImageItemId = itemWithImage.Id.ToString("N", CultureInfo.InvariantCulture); } } } @@ -326,7 +327,7 @@ namespace MediaBrowser.Api if (tag != null) { hint.BackdropImageTag = tag; - hint.BackdropImageItemId = itemWithImage.Id.ToString("N"); + hint.BackdropImageItemId = itemWithImage.Id.ToString("N", CultureInfo.InvariantCulture); } } } diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index 4109b12bfa..76392e27c9 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -316,7 +317,7 @@ namespace MediaBrowser.Api.Session _authRepo.Create(new AuthenticationInfo { AppName = request.App, - AccessToken = Guid.NewGuid().ToString("N"), + AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), DateCreated = DateTime.UtcNow, DeviceId = _appHost.SystemId, DeviceName = _appHost.FriendlyName, diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index 08aa540a54..52043d3df6 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -168,7 +168,7 @@ namespace MediaBrowser.Api.Subtitles builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); - long positionTicks = 0; + long positionTicks = 0; var accessToken = _authContext.GetAuthorizationInfo(Request).Token; @@ -206,7 +206,7 @@ namespace MediaBrowser.Api.Subtitles { var item = (Video)_libraryManager.GetItemById(request.Id); - var idString = request.Id.ToString("N"); + var idString = request.Id.ToString("N", CultureInfo.InvariantCulture); var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false, null) .First(i => string.Equals(i.Id, request.MediaSourceId ?? idString)); diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index b0900a5548..2951fa6b47 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; @@ -470,7 +471,7 @@ namespace MediaBrowser.Api if (!string.IsNullOrWhiteSpace(request.StartItemId)) { - episodes = episodes.SkipWhile(i => !string.Equals(i.Id.ToString("N"), request.StartItemId, StringComparison.OrdinalIgnoreCase)).ToList(); + episodes = episodes.SkipWhile(i => !string.Equals(i.Id.ToString("N", CultureInfo.InvariantCulture), request.StartItemId, StringComparison.OrdinalIgnoreCase)).ToList(); } // This must be the last filter diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index f842230ee3..c605cd3962 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -99,7 +99,7 @@ namespace MediaBrowser.Api.UserLibrary { ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !excludeFolderIds.Contains(i.Id.ToString("N"))) + .Where(i => !excludeFolderIds.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) .Select(i => i.Id) .ToArray(); } diff --git a/MediaBrowser.Api/UserLibrary/UserViewsService.cs b/MediaBrowser.Api/UserLibrary/UserViewsService.cs index 1d61c5c1e8..2fa5d8933c 100644 --- a/MediaBrowser.Api/UserLibrary/UserViewsService.cs +++ b/MediaBrowser.Api/UserLibrary/UserViewsService.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -116,7 +117,7 @@ namespace MediaBrowser.Api.UserLibrary .Select(i => new SpecialViewOption { Name = i.Name, - Id = i.Id.ToString("N") + Id = i.Id.ToString("N", CultureInfo.InvariantCulture) }) .OrderBy(i => i.Name) diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index 061f724380..474036f5cb 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Threading; using MediaBrowser.Controller.Configuration; @@ -168,7 +169,7 @@ namespace MediaBrowser.Api foreach (var item in items.Where(i => i.Id != primaryVersion.Id)) { - item.SetPrimaryVersionId(primaryVersion.Id.ToString("N")); + item.SetPrimaryVersionId(primaryVersion.Id.ToString("N", CultureInfo.InvariantCulture)); item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index adf03fb661..89159973b8 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Threading; using MediaBrowser.Common.Progress; @@ -14,14 +15,14 @@ namespace MediaBrowser.Controller.Channels { if (user.Policy.BlockedChannels != null) { - if (user.Policy.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (user.Policy.BlockedChannels.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } } else { - if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } @@ -60,7 +61,7 @@ namespace MediaBrowser.Controller.Channels public static string GetInternalMetadataPath(string basePath, Guid id) { - return System.IO.Path.Combine(basePath, "channels", id.ToString("N"), "metadata"); + return System.IO.Path.Combine(basePath, "channels", id.ToString("N", CultureInfo.InvariantCulture), "metadata"); } public override bool CanDelete() diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 10a603e42c..2ae856b020 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -503,7 +503,7 @@ namespace MediaBrowser.Controller.Entities foreach (var folder in collectionFolders) { - if (allowed.Contains(folder.Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (allowed.Contains(folder.Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return true; } @@ -664,10 +664,10 @@ namespace MediaBrowser.Controller.Entities { if (SourceType == SourceType.Channel) { - return System.IO.Path.Combine(basePath, "channels", ChannelId.ToString("N"), Id.ToString("N")); + return System.IO.Path.Combine(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture)); } - var idString = Id.ToString("N"); + var idString = Id.ToString("N", CultureInfo.InvariantCulture); basePath = System.IO.Path.Combine(basePath, "library"); @@ -1095,7 +1095,7 @@ namespace MediaBrowser.Controller.Entities var info = new MediaSourceInfo { - Id = item.Id.ToString("N"), + Id = item.Id.ToString("N", CultureInfo.InvariantCulture), Protocol = protocol ?? MediaProtocol.File, MediaStreams = MediaSourceManager.GetMediaStreams(item.Id), Name = GetMediaSourceName(item), @@ -1113,7 +1113,7 @@ namespace MediaBrowser.Controller.Entities if (info.Protocol == MediaProtocol.File) { - info.ETag = item.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N"); + info.ETag = item.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N", CultureInfo.InvariantCulture); } var video = item as Video; @@ -1626,7 +1626,7 @@ namespace MediaBrowser.Controller.Entities public virtual string CreatePresentationUniqueKey() { - return Id.ToString("N"); + return Id.ToString("N", CultureInfo.InvariantCulture); } [IgnoreDataMember] @@ -2736,7 +2736,7 @@ namespace MediaBrowser.Controller.Entities { var list = GetEtagValues(user); - return string.Join("|", list.ToArray()).GetMD5().ToString("N"); + return string.Join("|", list.ToArray()).GetMD5().ToString("N", CultureInfo.InvariantCulture); } protected virtual List GetEtagValues(User user) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index c056bc0b4e..d841b7ef84 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -177,7 +178,7 @@ namespace MediaBrowser.Controller.Entities { if (user.Policy.BlockedMediaFolders != null) { - if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) || + if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase) || // Backwards compatibility user.Policy.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase)) @@ -187,7 +188,7 @@ namespace MediaBrowser.Controller.Entities } else { - if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index bb2d03246a..8230604888 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; @@ -29,7 +30,7 @@ namespace MediaBrowser.Controller.Entities if (string.IsNullOrEmpty(child.Path)) { - child.LibraryItemId = item.Id.ToString("N"); + child.LibraryItemId = item.Id.ToString("N", CultureInfo.InvariantCulture); } return child; @@ -37,7 +38,7 @@ namespace MediaBrowser.Controller.Entities public LinkedChild() { - Id = Guid.NewGuid().ToString("N"); + Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); } } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index eae834f6f0..1aacc13c9a 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -91,7 +92,7 @@ namespace MediaBrowser.Controller.Entities.TV } var folders = LibraryManager.GetCollectionFolders(this) - .Select(i => i.Id.ToString("N")) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) .ToArray(); if (folders.Length == 0) diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 9952ba418b..968d725798 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -230,7 +231,7 @@ namespace MediaBrowser.Controller.Entities // 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")); + var idPath = System.IO.Path.Combine(parentPath, Id.ToString("N", CultureInfo.InvariantCulture)); if (!Directory.Exists(usernamePath) && Directory.Exists(idPath)) { Directory.Move(idPath, usernamePath); diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index e483c8f349..454bdc4ae2 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; @@ -987,7 +988,7 @@ namespace MediaBrowser.Controller.Entities private UserView GetUserViewWithName(string name, string type, string sortName, BaseItem parent) { - return _userViewManager.GetUserSubView(parent.Id, parent.Id.ToString("N"), type, sortName); + return _userViewManager.GetUserSubView(parent.Id, parent.Id.ToString("N", CultureInfo.InvariantCulture), type, sortName); } private UserView GetUserView(string type, string localizationKey, string sortName, BaseItem parent) diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 55f47aae9b..351662b294 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -89,7 +89,7 @@ namespace MediaBrowser.Controller.LiveTv var info = new MediaSourceInfo { - Id = Id.ToString("N"), + Id = Id.ToString("N", CultureInfo.InvariantCulture), Protocol = PathProtocol ?? MediaProtocol.File, MediaStreams = new List(), Name = Name, @@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.LiveTv protected override string GetInternalMetadataPath(string basePath) { - return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N"), "metadata"); + return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N", CultureInfo.InvariantCulture), "metadata"); } public override bool CanDelete() diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 8bde6a5da8..bdaf10d005 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; @@ -188,7 +189,7 @@ namespace MediaBrowser.Controller.LiveTv protected override string GetInternalMetadataPath(string basePath) { - return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N")); + return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N", CultureInfo.InvariantCulture)); } public override bool CanDelete() diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index e832607251..aff687f882 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -239,7 +240,7 @@ namespace MediaBrowser.Controller.Playlists return base.IsVisible(user); } - var userId = user.Id.ToString("N"); + var userId = user.Id.ToString("N", CultureInfo.InvariantCulture); foreach (var share in shares) { if (string.Equals(share.UserId, userId, StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index f4b915c06f..ebff81b7f8 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Providers @@ -55,7 +56,7 @@ namespace MediaBrowser.Controller.Providers foreach (var i in UserDataList) { - if (string.Equals(userId, i.UserId.ToString("N"), StringComparison.OrdinalIgnoreCase)) + if (string.Equals(userId, i.UserId.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase)) { userData = i; } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 860ea13cf6..a22eaaa514 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -934,7 +934,7 @@ namespace MediaBrowser.Providers.Manager public void OnRefreshStart(BaseItem item) { - //_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N")); + //_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture)); var id = item.Id; lock (_activeRefreshes) @@ -947,7 +947,7 @@ namespace MediaBrowser.Providers.Manager public void OnRefreshComplete(BaseItem item) { - //_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N")); + //_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture)); lock (_activeRefreshes) { _activeRefreshes.Remove(item.Id); @@ -971,7 +971,7 @@ namespace MediaBrowser.Providers.Manager public void OnRefreshProgress(BaseItem item, double progress) { - //_logger.LogInformation("OnRefreshProgress {0} {1}", item.Id.ToString("N"), progress); + //_logger.LogInformation("OnRefreshProgress {0} {1}", item.Id.ToString("N", CultureInfo.InvariantCulture), progress); var id = item.Id; lock (_activeRefreshes) @@ -985,7 +985,7 @@ namespace MediaBrowser.Providers.Manager else { // TODO: Need to hunt down the conditions for this happening - //throw new Exception(string.Format("Refresh for item {0} {1} is not in progress", item.GetType().Name, item.Id.ToString("N"))); + //throw new Exception(string.Format("Refresh for item {0} {1} is not in progress", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture))); } } } diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 61a8a122b9..7023ef706b 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -99,11 +100,11 @@ namespace MediaBrowser.Providers.MediaInfo if (!string.IsNullOrWhiteSpace(item.Album) && !string.IsNullOrWhiteSpace(albumArtist)) { - filename = (item.Album + "-" + albumArtist).GetMD5().ToString("N"); + filename = (item.Album + "-" + albumArtist).GetMD5().ToString("N", CultureInfo.InvariantCulture); } else { - filename = item.Id.ToString("N"); + filename = item.Id.ToString("N", CultureInfo.InvariantCulture); } filename += ".jpg"; @@ -111,7 +112,7 @@ namespace MediaBrowser.Providers.MediaInfo else { // If it's an audio book or audio podcast, allow unique image per item - filename = item.Id.ToString("N") + ".jpg"; + filename = item.Id.ToString("N", CultureInfo.InvariantCulture) + ".jpg"; } var prefix = filename.Substring(0, 1); diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 7fc6909f55..b4a4c36e54 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -296,7 +295,7 @@ namespace MediaBrowser.Providers.Subtitles private string GetProviderId(string name) { - return name.ToLowerInvariant().GetMD5().ToString("N"); + return name.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); } private ISubtitleProvider GetProvider(string id) diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 1249a60c05..e7e02a7d5b 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -27,6 +27,10 @@ + + -- cgit v1.2.3 From 52c1b45feb75c55075135005167a308269013400 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 2 Jul 2019 21:47:36 +0200 Subject: Fix build --- .../HttpClientManager/HttpClientManager.cs | 1 + Emby.Server.Implementations/Security/AuthenticationRepository.cs | 4 ++-- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- MediaBrowser.Api/Playback/BaseStreamingService.cs | 8 +------- 4 files changed, 5 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 9ca33d7db7..a933b53f5c 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Globalization; using System.IO; using System.Linq; using System.Net; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 26a08cbe9a..0b5ee5d039 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -97,7 +97,7 @@ namespace Emby.Server.Implementations.Security statement.TryBind("@AppName", info.AppName); statement.TryBind("@AppVersion", info.AppVersion); statement.TryBind("@DeviceName", info.DeviceName); - statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N"))); + statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture))); statement.TryBind("@UserName", info.UserName); statement.TryBind("@IsActive", true); statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); @@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.Security statement.TryBind("@AppName", info.AppName); statement.TryBind("@AppVersion", info.AppVersion); statement.TryBind("@DeviceName", info.DeviceName); - statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N"))); + statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture))); statement.TryBind("@UserName", info.UserName); statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 7ee573da5f..0347100a48 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1041,7 +1041,7 @@ namespace Emby.Server.Implementations.Session { IEnumerable GetTasks() { - var messageId = Guid.NewGuid().ToString("N"); + var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); foreach (var session in sessions) { var controllers = session.SessionControllers; diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 114d3f7a24..d9f4a583c9 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -215,12 +215,6 @@ namespace MediaBrowser.Api.Playback var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); -<<<<<<< HEAD -======= - var transcodingId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true); - ->>>>>>> Use CultureInvariant string conversion for Guids var process = new Process() { StartInfo = new ProcessStartInfo() @@ -246,7 +240,7 @@ namespace MediaBrowser.Api.Playback var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, state.Request.PlaySessionId, state.MediaSource.LiveStreamId, - Guid.NewGuid().ToString("N"), + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), TranscodingJobType, process, state.Request.DeviceId, -- cgit v1.2.3 From 25917db07a835bf0e5c6afdc0fec165851fffab2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 9 Aug 2019 23:50:40 +0200 Subject: Fix doc releated warnings --- .../AppBase/BaseApplicationPaths.cs | 37 +++++---- .../AppBase/BaseConfigurationManager.cs | 92 ++++++++++++---------- Emby.Server.Implementations/ApplicationHost.cs | 6 +- .../ConfigurationOptions.cs | 4 +- .../Emby.Server.Implementations.csproj | 1 + .../ServerApplicationPaths.cs | 27 ++++--- 6 files changed, 91 insertions(+), 76 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 00cfa0c9a9..f67a09daa3 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -10,6 +10,8 @@ namespace Emby.Server.Implementations.AppBase /// public abstract class BaseApplicationPaths : IApplicationPaths { + private string _dataPath; + /// /// Initializes a new instance of the class. /// @@ -30,27 +32,27 @@ namespace Emby.Server.Implementations.AppBase } /// - /// Gets the path to the program data folder + /// Gets the path to the program data folder. /// /// The program data path. - public string ProgramDataPath { get; private set; } + public string ProgramDataPath { get; } /// - /// Gets the path to the web UI resources folder + /// Gets the path to the web UI resources folder. /// /// The web UI resources path. - public string WebPath { get; set; } + public string WebPath { get; } /// - /// Gets the path to the system folder + /// Gets the path to the system folder. /// + /// The path to the system folder. public string ProgramSystemPath { get; } = AppContext.BaseDirectory; /// - /// Gets the folder path to the data directory + /// Gets the folder path to the data directory. /// /// The data directory. - private string _dataPath; public string DataPath { get => _dataPath; @@ -58,8 +60,9 @@ namespace Emby.Server.Implementations.AppBase } /// - /// Gets the magic strings used for virtual path manipulation. + /// Gets the magic string used for virtual path manipulation. /// + /// The magic string used for virtual path manipulation. public string VirtualDataPath { get; } = "%AppDataPath%"; /// @@ -69,43 +72,43 @@ namespace Emby.Server.Implementations.AppBase public string ImageCachePath => Path.Combine(CachePath, "images"); /// - /// Gets the path to the plugin directory + /// Gets the path to the plugin directory. /// /// The plugins path. public string PluginsPath => Path.Combine(ProgramDataPath, "plugins"); /// - /// Gets the path to the plugin configurations directory + /// Gets the path to the plugin configurations directory. /// /// The plugin configurations path. public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations"); /// - /// Gets the path to the log directory + /// Gets the path to the log directory. /// /// The log directory path. - public string LogDirectoryPath { get; private set; } + public string LogDirectoryPath { get; } /// - /// Gets the path to the application configuration root directory + /// Gets the path to the application configuration root directory. /// /// The configuration directory path. - public string ConfigurationDirectoryPath { get; private set; } + public string ConfigurationDirectoryPath { get; } /// - /// Gets the path to the system configuration file + /// Gets the path to the system configuration file. /// /// The system configuration file path. public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml"); /// - /// Gets the folder path to the cache directory + /// Gets or sets the folder path to the cache directory. /// /// The cache directory. public string CachePath { get; set; } /// - /// Gets the folder path to the temp directory within the cache folder + /// Gets the folder path to the temp directory within the cache folder. /// /// The temp directory. public string TempDirectory => Path.Combine(CachePath, "temp"); diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index af60a8dce4..4832c19c4e 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -19,11 +20,44 @@ namespace Emby.Server.Implementations.AppBase /// public abstract class BaseConfigurationManager : IConfigurationManager { + private readonly IFileSystem _fileSystem; + + private readonly ConcurrentDictionary _configurations = new ConcurrentDictionary(); + + private ConfigurationStore[] _configurationStores = Array.Empty(); + private IConfigurationFactory[] _configurationFactories = Array.Empty(); + /// - /// Gets the type of the configuration. + /// The _configuration loaded. /// - /// The type of the configuration. - protected abstract Type ConfigurationType { get; } + private bool _configurationLoaded; + + /// + /// The _configuration sync lock. + /// + private object _configurationSyncLock = new object(); + + /// + /// The _configuration. + /// + private BaseApplicationConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The application paths. + /// The logger factory. + /// The XML serializer. + /// The file system + protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem) + { + CommonApplicationPaths = applicationPaths; + XmlSerializer = xmlSerializer; + _fileSystem = fileSystem; + Logger = loggerFactory.CreateLogger(GetType().Name); + + UpdateCachePath(); + } /// /// Occurs when [configuration updated]. @@ -40,6 +74,12 @@ namespace Emby.Server.Implementations.AppBase /// public event EventHandler NamedConfigurationUpdated; + /// + /// Gets the type of the configuration. + /// + /// The type of the configuration. + protected abstract Type ConfigurationType { get; } + /// /// Gets the logger. /// @@ -56,20 +96,7 @@ namespace Emby.Server.Implementations.AppBase /// /// The application paths. public IApplicationPaths CommonApplicationPaths { get; private set; } - public readonly IFileSystem FileSystem; - /// - /// The _configuration loaded - /// - private bool _configurationLoaded; - /// - /// The _configuration sync lock - /// - private object _configurationSyncLock = new object(); - /// - /// The _configuration - /// - private BaseApplicationConfiguration _configuration; /// /// Gets the system configuration /// @@ -90,26 +117,6 @@ namespace Emby.Server.Implementations.AppBase } } - private ConfigurationStore[] _configurationStores = { }; - private IConfigurationFactory[] _configurationFactories = { }; - - /// - /// Initializes a new instance of the class. - /// - /// The application paths. - /// The logger factory. - /// The XML serializer. - /// The file system - protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem) - { - CommonApplicationPaths = applicationPaths; - XmlSerializer = xmlSerializer; - FileSystem = fileSystem; - Logger = loggerFactory.CreateLogger(GetType().Name); - - UpdateCachePath(); - } - public virtual void AddParts(IEnumerable factories) { _configurationFactories = factories.ToArray(); @@ -171,6 +178,7 @@ namespace Emby.Server.Implementations.AppBase private void UpdateCachePath() { string cachePath; + // If the configuration file has no entry (i.e. not set in UI) if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath)) { @@ -207,12 +215,16 @@ namespace Emby.Server.Implementations.AppBase var newPath = newConfig.CachePath; if (!string.IsNullOrWhiteSpace(newPath) - && !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath)) + && !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath, StringComparison.Ordinal)) { // Validate if (!Directory.Exists(newPath)) { - throw new FileNotFoundException(string.Format("{0} does not exist.", newPath)); + throw new FileNotFoundException( + string.Format( + CultureInfo.InvariantCulture, + "{0} does not exist.", + newPath)); } EnsureWriteAccess(newPath); @@ -223,11 +235,9 @@ namespace Emby.Server.Implementations.AppBase { var file = Path.Combine(path, Guid.NewGuid().ToString()); File.WriteAllText(file, string.Empty); - FileSystem.DeleteFile(file); + _fileSystem.DeleteFile(file); } - private readonly ConcurrentDictionary _configurations = new ConcurrentDictionary(); - private string GetConfigurationFile(string key) { return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml"); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f0aa60428b..0493fd9f5a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -418,7 +418,7 @@ namespace Emby.Server.Implementations public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3); /// - /// Gets the current application user agent + /// Gets the current application user agent. /// /// The application user agent. public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion; @@ -467,8 +467,8 @@ namespace Emby.Server.Implementations /// /// Creates an instance of type and resolves all constructor dependencies /// - /// /// The type - /// T + /// /// The type. + /// T. public T CreateInstance() => ActivatorUtilities.CreateInstance(_serviceProvider); diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 9bc60972a1..62408ee703 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -6,8 +6,8 @@ namespace Emby.Server.Implementations { public static readonly Dictionary Configuration = new Dictionary { - {"HttpListenerHost:DefaultRedirectPath", "web/index.html"}, - {"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"} + { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, + { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" } }; } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index abbaef26b8..f07f8e3bfd 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -44,6 +44,7 @@ netstandard2.0 false + true diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index adaf23234f..2f5a8af802 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -10,8 +10,12 @@ namespace Emby.Server.Implementations /// public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths { + private string _defaultTranscodingTempPath; + private string _transcodingTempPath; + private string _internalMetadataPath; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public ServerApplicationPaths( string programDataPath, @@ -30,7 +34,7 @@ namespace Emby.Server.Implementations public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory; /// - /// Gets the path to the base root media directory + /// Gets the path to the base root media directory. /// /// The root folder path. public string RootFolderPath => Path.Combine(ProgramDataPath, "root"); @@ -48,7 +52,7 @@ namespace Emby.Server.Implementations public string LocalizationPath => Path.Combine(ProgramDataPath, "localization"); /// - /// Gets the path to the People directory + /// Gets the path to the People directory. /// /// The people path. public string PeoplePath => Path.Combine(InternalMetadataPath, "People"); @@ -56,37 +60,37 @@ namespace Emby.Server.Implementations public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists"); /// - /// Gets the path to the Genre directory + /// Gets the path to the Genre directory. /// /// The genre path. public string GenrePath => Path.Combine(InternalMetadataPath, "Genre"); /// - /// Gets the path to the Genre directory + /// Gets the path to the Genre directory. /// /// The genre path. public string MusicGenrePath => Path.Combine(InternalMetadataPath, "MusicGenre"); /// - /// Gets the path to the Studio directory + /// Gets the path to the Studio directory. /// /// The studio path. public string StudioPath => Path.Combine(InternalMetadataPath, "Studio"); /// - /// Gets the path to the Year directory + /// Gets the path to the Year directory. /// /// The year path. public string YearPath => Path.Combine(InternalMetadataPath, "Year"); /// - /// Gets the path to the General IBN directory + /// Gets the path to the General IBN directory. /// /// The general path. public string GeneralPath => Path.Combine(InternalMetadataPath, "general"); /// - /// Gets the path to the Ratings IBN directory + /// Gets the path to the Ratings IBN directory. /// /// The ratings path. public string RatingsPath => Path.Combine(InternalMetadataPath, "ratings"); @@ -98,15 +102,13 @@ namespace Emby.Server.Implementations public string MediaInfoImagesPath => Path.Combine(InternalMetadataPath, "mediainfo"); /// - /// Gets the path to the user configuration directory + /// Gets the path to the user configuration directory. /// /// The user configuration directory path. public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users"); - private string _defaultTranscodingTempPath; public string DefaultTranscodingTempPath => _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp")); - private string _transcodingTempPath; public string TranscodingTempPath { get => _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath); @@ -139,7 +141,6 @@ namespace Emby.Server.Implementations return path; } - private string _internalMetadataPath; public string InternalMetadataPath { get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata")); -- cgit v1.2.3 From c987203f5aaa5b838e32aa0f7446942c76383708 Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 5 Aug 2019 16:22:43 -0700 Subject: remove old routes from http server --- .../HttpServer/HttpListenerHost.cs | 61 ---------------------- 1 file changed, 61 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 4c233456c4..2d2be7135a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -484,53 +484,6 @@ namespace Emby.Server.Implementations.HttpServer return; } - if (localPath.IndexOf("mediabrowser/web", StringComparison.OrdinalIgnoreCase) != -1) - { - httpRes.StatusCode = 200; - httpRes.ContentType = "text/html"; - var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase) - .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase); - - if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) - { - await httpRes.WriteAsync( - "EmbyPlease update your Emby bookmark to " + newUrl + "", - cancellationToken).ConfigureAwait(false); - return; - } - } - - if (localPath.IndexOf("dashboard/", StringComparison.OrdinalIgnoreCase) != -1 && - localPath.IndexOf("web/dashboard", StringComparison.OrdinalIgnoreCase) == -1) - { - httpRes.StatusCode = 200; - httpRes.ContentType = "text/html"; - var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase) - .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase); - - if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) - { - await httpRes.WriteAsync( - "EmbyPlease update your Emby bookmark to " + newUrl + "", - cancellationToken).ConfigureAwait(false); - return; - } - } - - if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase)) - { - httpRes.Redirect(_defaultRedirectPath); - return; - } - - if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase)) - { - httpRes.Redirect("../" + _defaultRedirectPath); - return; - } - if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { httpRes.Redirect(_defaultRedirectPath); @@ -543,19 +496,6 @@ namespace Emby.Server.Implementations.HttpServer return; } - if (!string.Equals(httpReq.QueryString["r"], "0", StringComparison.OrdinalIgnoreCase)) - { - if (localPath.EndsWith("web/dashboard.html", StringComparison.OrdinalIgnoreCase)) - { - httpRes.Redirect("index.html#!/dashboard.html"); - } - - if (localPath.EndsWith("web/home.html", StringComparison.OrdinalIgnoreCase)) - { - httpRes.Redirect("index.html"); - } - } - if (!string.IsNullOrEmpty(GlobalResponse)) { // We don't want the address pings in ApplicationHost to fail @@ -569,7 +509,6 @@ namespace Emby.Server.Implementations.HttpServer } var handler = GetServiceHandler(httpReq); - if (handler != null) { await handler.ProcessRequestAsync(this, httpReq, httpRes, _logger, cancellationToken).ConfigureAwait(false); -- cgit v1.2.3 From d521e5c36a0396a6e0ee562d8bda6fb5ffc6b0a7 Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 5 Aug 2019 16:25:18 -0700 Subject: add base url to server configuration --- .../HttpServer/HttpListenerHost.cs | 58 +++------------------- .../Configuration/ServerConfiguration.cs | 2 + 2 files changed, 10 insertions(+), 50 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 2d2be7135a..48b229e88a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -7,6 +7,7 @@ using System.Net.Sockets; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Emby.Server.Implementations.Configuration; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Services; using MediaBrowser.Common.Extensions; @@ -470,17 +471,10 @@ namespace Emby.Server.Implementations.HttpServer urlToLog = GetUrlToLog(urlString); - if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) || - string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(localPath, "/" + _config.Configuration.BaseUrl + "/", StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, "/" + _config.Configuration.BaseUrl, StringComparison.OrdinalIgnoreCase)) { - httpRes.Redirect(_defaultRedirectPath); - return; - } - - if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) || - string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) - { - httpRes.Redirect("emby/" + _defaultRedirectPath); + httpRes.Redirect("/" + _config.Configuration.BaseUrl + "/" + _defaultRedirectPath); return; } @@ -602,22 +596,7 @@ namespace Emby.Server.Implementations.HttpServer foreach (var route in clone) { - routes.Add(new RouteAttribute(NormalizeEmbyRoutePath(route.Path), route.Verbs) - { - Notes = route.Notes, - Priority = route.Priority, - Summary = route.Summary - }); - - routes.Add(new RouteAttribute(NormalizeMediaBrowserRoutePath(route.Path), route.Verbs) - { - Notes = route.Notes, - Priority = route.Priority, - Summary = route.Summary - }); - - // needed because apps add /emby, and some users also add /emby, thereby double prefixing - routes.Add(new RouteAttribute(DoubleNormalizeEmbyRoutePath(route.Path), route.Verbs) + routes.Add(new RouteAttribute(NormalizeCustomRoutePath(_config.Configuration.BaseUrl, route.Path), route.Verbs) { Notes = route.Notes, Priority = route.Priority, @@ -658,35 +637,14 @@ namespace Emby.Server.Implementations.HttpServer return _socketListener.ProcessWebSocketRequest(context); } - //TODO Add Jellyfin Route Path Normalizer - private static string NormalizeEmbyRoutePath(string path) - { - if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) - { - return "/emby" + path; - } - - return "emby/" + path; - } - - private static string NormalizeMediaBrowserRoutePath(string path) - { - if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) - { - return "/mediabrowser" + path; - } - - return "mediabrowser/" + path; - } - - private static string DoubleNormalizeEmbyRoutePath(string path) + private static string NormalizeCustomRoutePath(string baseUrl, string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) { - return "/emby/emby" + path; + return "/" + baseUrl + path; } - return "emby/emby/" + path; + return baseUrl + "/" + path; } /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 2673597caa..d64ea35eb9 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -163,6 +163,7 @@ namespace MediaBrowser.Model.Configuration public string ServerName { get; set; } public string WanDdns { get; set; } + public string BaseUrl { get; set; } public string UICulture { get; set; } @@ -243,6 +244,7 @@ namespace MediaBrowser.Model.Configuration SortRemoveCharacters = new[] { ",", "&", "-", "{", "}", "'" }; SortRemoveWords = new[] { "the", "a", "an" }; + BaseUrl = "jellyfin"; UICulture = "en-US"; MetadataOptions = new[] -- cgit v1.2.3 From 97d6c2db6b5749c12ef2f92f6622c28555173c48 Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 5 Aug 2019 16:27:10 -0700 Subject: keep old base url for now --- .../HttpServer/HttpListenerHost.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 48b229e88a..bdcf5d0b7c 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -602,6 +602,13 @@ namespace Emby.Server.Implementations.HttpServer Priority = route.Priority, Summary = route.Summary }); + + routes.Add(new RouteAttribute(NormalizeOldRoutePath(route.Path), route.Verbs) + { + Notes = route.Notes, + Priority = route.Priority, + Summary = route.Summary + }); } return routes.ToArray(); @@ -637,6 +644,17 @@ namespace Emby.Server.Implementations.HttpServer return _socketListener.ProcessWebSocketRequest(context); } + // this method was left for compatibility with third party clients + private static string NormalizeOldRoutePath(string path) + { + if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) + { + return "/emby" + path; + } + + return "emby/" + path; + } + private static string NormalizeCustomRoutePath(string baseUrl, string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) -- cgit v1.2.3 From 003238ef5e5151c43738fedcc90f83fd5064580a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 11 Aug 2019 15:11:53 +0200 Subject: Update deps + document startup project * Fixed the release build * Documented all public/internal members of Jellyfin.Server * Enable TreatWarningsAsErrors for debug builds for Jellyfin.Server This will ensure that any new public/internal members of Jellyfin.Server are documented --- Emby.Naming/Emby.Naming.csproj | 2 +- Emby.Photos/Emby.Photos.csproj | 2 +- .../Emby.Server.Implementations.csproj | 10 +++----- Jellyfin.Server/CoreAppHost.cs | 17 ++++++++++++++ Jellyfin.Server/Jellyfin.Server.csproj | 10 ++++---- Jellyfin.Server/Program.cs | 27 +++++++++++++++++----- Jellyfin.Server/StartupOptions.cs | 26 +++++++++++++++++++++ .../MediaBrowser.MediaEncoding.csproj | 2 +- 8 files changed, 74 insertions(+), 22 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 9e2a4950f9..0b1ce2fce6 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -23,7 +23,7 @@ - + diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj index c9830abc5f..8a79bf7e18 100644 --- a/Emby.Photos/Emby.Photos.csproj +++ b/Emby.Photos/Emby.Photos.csproj @@ -10,7 +10,7 @@ - + diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 73a64b0cd4..c78d96d4ab 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -32,7 +32,7 @@ - + @@ -48,17 +48,13 @@ - + latest - - true - - - + diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index b9b0cc3825..8b4b61e290 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -9,8 +9,21 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server { + /// + /// Implementation of the abstract class. + /// public class CoreAppHost : ApplicationHost { + /// + /// Initializes a new instance of the class. + /// + /// The to be used by the . + /// The to be used by the . + /// The to be used by the . + /// The to be used by the . + /// The to be used by the . + /// The to be used by the . + /// The to be used by the . public CoreAppHost( ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, @@ -30,15 +43,19 @@ namespace Jellyfin.Server { } + /// public override bool CanSelfRestart => StartupOptions.RestartPath != null; + /// protected override void RestartInternal() => Program.Restart(); + /// protected override IEnumerable GetAssembliesWithPartsInternal() { yield return typeof(CoreAppHost).Assembly; } + /// protected override void ShutdownInternal() => Program.Shutdown(); } } diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 641b3f1827..e872834779 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -9,10 +9,8 @@ - + latest - - SA1600;SA1601;SA1629;CS1591 true @@ -26,7 +24,7 @@ - + @@ -36,7 +34,7 @@ - + @@ -45,7 +43,7 @@ - + diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 9529904930..82b903198e 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -28,6 +28,9 @@ using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Jellyfin.Server { + /// + /// Class containing the entry point of the application. + /// public static class Program { private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); @@ -35,6 +38,11 @@ namespace Jellyfin.Server private static ILogger _logger; private static bool _restartOnShutdown; + /// + /// The entry point of the application. + /// + /// The command line arguments passed. + /// . public static Task Main(string[] args) { // For backwards compatibility. @@ -53,7 +61,10 @@ namespace Jellyfin.Server .MapResult(StartApp, _ => Task.CompletedTask); } - public static void Shutdown() + /// + /// Shuts down the application. + /// + internal static void Shutdown() { if (!_tokenSource.IsCancellationRequested) { @@ -61,7 +72,10 @@ namespace Jellyfin.Server } } - public static void Restart() + /// + /// Restarts the application. + /// + internal static void Restart() { _restartOnShutdown = true; @@ -171,11 +185,12 @@ namespace Jellyfin.Server /// /// Create the data, config and log paths from the variety of inputs(command line args, /// environment variables) or decide on what default to use. For Windows it's %AppPath% - /// for everything else the XDG approach is followed: - /// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + /// for everything else the + /// XDG approach + /// is followed. /// - /// StartupOptions - /// ServerApplicationPaths + /// The for this instance. + /// . private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options) { // dataDir diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 8296d414ef..bb0adaf630 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -8,36 +8,62 @@ namespace Jellyfin.Server /// public class StartupOptions : IStartupOptions { + /// + /// Gets or sets the path to the data directory. + /// + /// The path to the data directory. [Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")] public string DataDir { get; set; } + /// + /// Gets or sets the path to the web directory. + /// + /// The path to the web directory. [Option('w', "webdir", Required = false, HelpText = "Path to the Jellyfin web UI resources.")] public string WebDir { get; set; } + /// + /// Gets or sets the path to the cache directory. + /// + /// The path to the cache directory. [Option('C', "cachedir", Required = false, HelpText = "Path to use for caching.")] public string CacheDir { get; set; } + /// + /// Gets or sets the path to the config directory. + /// + /// The path to the config directory. [Option('c', "configdir", Required = false, HelpText = "Path to use for configuration data (user settings and pictures).")] public string ConfigDir { get; set; } + /// + /// Gets or sets the path to the log directory. + /// + /// The path to the log directory. [Option('l', "logdir", Required = false, HelpText = "Path to use for writing log files.")] public string LogDir { get; set; } + /// [Option("ffmpeg", Required = false, HelpText = "Path to external FFmpeg executable to use in place of default found in PATH.")] public string FFmpegPath { get; set; } + /// [Option("service", Required = false, HelpText = "Run as headless service.")] public bool IsService { get; set; } + /// [Option("noautorunwebapp", Required = false, HelpText = "Run headless if startup wizard is complete.")] public bool NoAutoRunWebApp { get; set; } + /// [Option("package-name", Required = false, HelpText = "Used when packaging Jellyfin (example, synology).")] public string PackageName { get; set; } + /// [Option("restartpath", Required = false, HelpText = "Path to restart script.")] public string RestartPath { get; set; } + /// [Option("restartargs", Required = false, HelpText = "Arguments for restart script.")] public string RestartArgs { get; set; } } diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index c0f92ac4a4..681a2e3724 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -18,7 +18,7 @@ - + -- cgit v1.2.3 From 5eaf5465a55df0359f85077b7922ca4a45681831 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 29 Jul 2019 23:47:25 +0200 Subject: Check checksum for plugin downloads * Compare the MD5 checksum when downloading plugins * Reduced log spam due to http requests * Removed 'GetTempFileResponse' function from HttpClientManager * Fixed caching for HttpClientManager --- Emby.Dlna/PlayTo/SsdpHttpClient.cs | 10 - Emby.Server.Implementations/ApplicationHost.cs | 14 +- .../HttpClientManager/HttpClientManager.cs | 206 ++------------------- .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 5 +- .../Updates/InstallationManager.cs | 140 +++++--------- MediaBrowser.Api/PackageService.cs | 2 +- MediaBrowser.Common/Extensions/HexHelper.cs | 22 +++ MediaBrowser.Common/Net/HttpRequestOptions.cs | 12 -- MediaBrowser.Common/Net/IHttpClient.cs | 16 -- .../Updates/IInstallationManager.cs | 7 +- MediaBrowser.Model/Cryptography/PasswordHash.cs | 1 + MediaBrowser.Model/Updates/InstallationInfo.cs | 6 - .../Studios/StudiosImageProvider.cs | 28 ++- Mono.Nat/Upnp/Messages/GetServicesMessage.cs | 39 ++-- Mono.Nat/Upnp/Messages/UpnpMessage.cs | 46 ++--- Mono.Nat/Upnp/UpnpNatDevice.cs | 4 +- jellyfin.ruleset | 2 + 17 files changed, 141 insertions(+), 419 deletions(-) create mode 100644 MediaBrowser.Common/Extensions/HexHelper.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 22aaa68858..217ea3a4bc 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -73,9 +73,6 @@ namespace Emby.Dlna.PlayTo UserAgent = USERAGENT, LogErrorResponseBody = true, BufferContent = false, - - // The periodic requests may keep some devices awake - LogRequestAsDebug = true }; options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture); @@ -98,9 +95,6 @@ namespace Emby.Dlna.PlayTo LogErrorResponseBody = true, BufferContent = false, - // The periodic requests may keep some devices awake - LogRequestAsDebug = true, - CancellationToken = cancellationToken }; @@ -135,13 +129,9 @@ namespace Emby.Dlna.PlayTo { Url = url, UserAgent = USERAGENT, - LogRequest = logRequest || _config.GetDlnaConfiguration().EnableDebugLog, LogErrorResponseBody = true, BufferContent = false, - // The periodic requests may keep some devices awake - LogRequestAsDebug = true, - CancellationToken = cancellationToken }; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 09847b2f86..6074e52181 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1528,8 +1528,6 @@ namespace Emby.Server.Implementations { Url = Url, LogErrorResponseBody = false, - LogErrors = false, - LogRequest = false, BufferContent = false, CancellationToken = cancellationToken }).ConfigureAwait(false)) @@ -1681,8 +1679,8 @@ namespace Emby.Server.Implementations private async Task IsIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken) { - if (address.Equals(IPAddress.Loopback) || - address.Equals(IPAddress.IPv6Loopback)) + if (address.Equals(IPAddress.Loopback) + || address.Equals(IPAddress.IPv6Loopback)) { return true; } @@ -1695,12 +1693,6 @@ namespace Emby.Server.Implementations return cachedResult; } -#if DEBUG - const bool LogPing = true; -#else - const bool LogPing = false; -#endif - try { using (var response = await HttpClient.SendAsync( @@ -1708,8 +1700,6 @@ namespace Emby.Server.Implementations { Url = apiUrl, LogErrorResponseBody = false, - LogErrors = LogPing, - LogRequest = LogPing, BufferContent = false, CancellationToken = cancellationToken }, HttpMethod.Post).ConfigureAwait(false)) diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index a933b53f5c..0dd4d4ca5b 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -5,9 +5,6 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -20,7 +17,7 @@ using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpClientManager { /// - /// Class HttpClientManager + /// Class HttpClientManager. /// public class HttpClientManager : IHttpClient { @@ -45,19 +42,9 @@ namespace Emby.Server.Implementations.HttpClientManager IFileSystem fileSystem, Func defaultUserAgentFn) { - if (appPaths == null) - { - throw new ArgumentNullException(nameof(appPaths)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - _logger = logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _fileSystem = fileSystem; - _appPaths = appPaths; + _appPaths = appPaths ?? throw new ArgumentNullException(nameof(appPaths)); _defaultUserAgentFn = defaultUserAgentFn; } @@ -118,7 +105,7 @@ namespace Emby.Server.Implementations.HttpClientManager request.Headers.Add(HeaderNames.Connection, "Keep-Alive"); } - //request.Headers.Add(HeaderNames.CacheControl, "no-cache"); + // request.Headers.Add(HeaderNames.CacheControl, "no-cache"); /* if (!string.IsNullOrWhiteSpace(userInfo)) @@ -196,7 +183,7 @@ namespace Emby.Server.Implementations.HttpClientManager } var url = options.Url; - var urlHash = url.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); + var urlHash = url.ToUpperInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash); @@ -239,7 +226,13 @@ namespace Emby.Server.Implementations.HttpClientManager { Directory.CreateDirectory(Path.GetDirectoryName(responseCachePath)); - using (var fileStream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true)) + using (var fileStream = new FileStream( + responseCachePath, + FileMode.Create, + FileAccess.Write, + FileShare.None, + StreamDefaults.DefaultFileStreamBufferSize, + true)) { await response.Content.CopyToAsync(fileStream).ConfigureAwait(false); @@ -278,16 +271,11 @@ namespace Emby.Server.Implementations.HttpClientManager } } - if (options.LogRequest) - { - _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToString(), options.Url); - } - options.CancellationToken.ThrowIfCancellationRequested(); var response = await client.SendAsync( httpWebRequest, - options.BufferContent ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead, + options.BufferContent || options.CacheMode == CacheMode.Unconditional ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false); await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); @@ -308,138 +296,6 @@ namespace Emby.Server.Implementations.HttpClientManager public Task Post(HttpRequestOptions options) => SendAsync(options, HttpMethod.Post); - /// - /// Downloads the contents of a given url into a temporary location - /// - /// The options. - /// Task{System.String}. - public async Task GetTempFile(HttpRequestOptions options) - { - var response = await GetTempFileResponse(options).ConfigureAwait(false); - return response.TempFilePath; - } - - public async Task GetTempFileResponse(HttpRequestOptions options) - { - ValidateParams(options); - - Directory.CreateDirectory(_appPaths.TempDirectory); - - var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp"); - - if (options.Progress == null) - { - throw new ArgumentException("Options did not have a Progress value.", nameof(options)); - } - - options.CancellationToken.ThrowIfCancellationRequested(); - - var httpWebRequest = GetRequestMessage(options, HttpMethod.Get); - - options.Progress.Report(0); - - if (options.LogRequest) - { - _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url); - } - - var client = GetHttpClient(options.Url); - - try - { - options.CancellationToken.ThrowIfCancellationRequested(); - - using (var response = (await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false))) - { - await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); - - options.CancellationToken.ThrowIfCancellationRequested(); - - using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) - using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) - { - await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); - } - - options.Progress.Report(100); - - var responseInfo = new HttpResponseInfo(response.Headers, response.Content.Headers) - { - TempFilePath = tempFile, - StatusCode = response.StatusCode, - ContentType = response.Content.Headers.ContentType?.MediaType, - ContentLength = response.Content.Headers.ContentLength - }; - - return responseInfo; - } - } - catch (Exception ex) - { - if (File.Exists(tempFile)) - { - File.Delete(tempFile); - } - - throw GetException(ex, options); - } - } - - private Exception GetException(Exception ex, HttpRequestOptions options) - { - if (ex is HttpException) - { - return ex; - } - - var webException = ex as WebException - ?? ex.InnerException as WebException; - - if (webException != null) - { - if (options.LogErrors) - { - _logger.LogError(webException, "Error {Status} getting response from {Url}", webException.Status, options.Url); - } - - var exception = new HttpException(webException.Message, webException); - - using (var response = webException.Response as HttpWebResponse) - { - if (response != null) - { - exception.StatusCode = response.StatusCode; - } - } - - if (!exception.StatusCode.HasValue) - { - if (webException.Status == WebExceptionStatus.NameResolutionFailure || - webException.Status == WebExceptionStatus.ConnectFailure) - { - exception.IsTimedOut = true; - } - } - - return exception; - } - - var operationCanceledException = ex as OperationCanceledException - ?? ex.InnerException as OperationCanceledException; - - if (operationCanceledException != null) - { - return GetCancellationException(options, options.CancellationToken, operationCanceledException); - } - - if (options.LogErrors) - { - _logger.LogError(ex, "Error getting response from {Url}", options.Url); - } - - return ex; - } - private void ValidateParams(HttpRequestOptions options) { if (string.IsNullOrEmpty(options.Url)) @@ -471,35 +327,6 @@ namespace Emby.Server.Implementations.HttpClientManager return url; } - /// - /// Throws the cancellation exception. - /// - /// The options. - /// The cancellation token. - /// The exception. - /// Exception. - private Exception GetCancellationException(HttpRequestOptions options, CancellationToken cancellationToken, OperationCanceledException exception) - { - // If the HttpClient's timeout is reached, it will cancel the Task internally - if (!cancellationToken.IsCancellationRequested) - { - var msg = string.Format("Connection to {0} timed out", options.Url); - - if (options.LogErrors) - { - _logger.LogError(msg); - } - - // Throw an HttpException so that the caller doesn't think it was cancelled by user code - return new HttpException(msg, exception) - { - IsTimedOut = true - }; - } - - return exception; - } - private async Task EnsureSuccessStatusCode(HttpResponseMessage response, HttpRequestOptions options) { if (response.IsSuccessStatusCode) @@ -507,8 +334,11 @@ namespace Emby.Server.Implementations.HttpClientManager return; } - var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - _logger.LogError("HTTP request failed with message: {Message}", msg); + if (options.LogErrorResponseBody) + { + var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + _logger.LogError("HTTP request failed with message: {Message}", msg); + } throw new HttpException(response.ReasonPhrase) { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index bde7d5c81d..7afeba9dd7 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.ScheduledTasks try { - await _installationManager.InstallPackage(package, new SimpleProgress(), cancellationToken).ConfigureAwait(false); + await _installationManager.InstallPackage(package, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -87,8 +87,7 @@ namespace Emby.Server.Implementations.ScheduledTasks // Update progress lock (progress) { - numComplete++; - progress.Report(90.0 * numComplete / packagesToInstall.Count + 10); + progress.Report((90.0 * ++numComplete / packagesToInstall.Count) + 10); } } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 9bc85633d4..c603199643 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -4,13 +4,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; -using MediaBrowser.Common.Progress; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Events; @@ -126,13 +127,16 @@ namespace Emby.Server.Implementations.Updates /// Task{List{PackageInfo}}. public async Task> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken) { - using (var response = await _httpClient.SendAsync(new HttpRequestOptions - { - Url = "https://repo.jellyfin.org/releases/plugin/manifest.json", - CancellationToken = cancellationToken, - CacheLength = GetCacheLength() - }, HttpMethod.Get).ConfigureAwait(false)) - using (var stream = response.Content) + using (var response = await _httpClient.SendAsync( + new HttpRequestOptions + { + Url = "https://repo.jellyfin.org/releases/plugin/manifest.json", + CancellationToken = cancellationToken, + CacheMode = CacheMode.Unconditional, + CacheLength = GetCacheLength() + }, + HttpMethod.Get).ConfigureAwait(false)) + using (Stream stream = response.Content) { return FilterPackages(await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false)); } @@ -309,27 +313,14 @@ namespace Emby.Server.Implementations.Updates .Where(p => !string.IsNullOrEmpty(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase))); } - /// - /// Installs the package. - /// - /// The package. - /// if set to true [is plugin]. - /// The progress. - /// The cancellation token. - /// Task. - /// package - public async Task InstallPackage(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken) + /// + public async Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken) { if (package == null) { throw new ArgumentNullException(nameof(package)); } - if (progress == null) - { - throw new ArgumentNullException(nameof(progress)); - } - var installationInfo = new InstallationInfo { Id = Guid.NewGuid(), @@ -349,16 +340,6 @@ namespace Emby.Server.Implementations.Updates _currentInstallations.Add(tuple); } - var innerProgress = new ActionableProgress(); - - // Whenever the progress updates, update the outer progress object and InstallationInfo - innerProgress.RegisterAction(percent => - { - progress.Report(percent); - - installationInfo.PercentComplete = percent; - }); - var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token; var installationEventArgs = new InstallationEventArgs @@ -371,7 +352,7 @@ namespace Emby.Server.Implementations.Updates try { - await InstallPackageInternal(package, innerProgress, linkedToken).ConfigureAwait(false); + await InstallPackageInternal(package, linkedToken).ConfigureAwait(false); lock (_currentInstallations) { @@ -423,20 +404,16 @@ namespace Emby.Server.Implementations.Updates /// Installs the package internal. /// /// The package. - /// if set to true [is plugin]. - /// The progress. /// The cancellation token. - /// Task. - private async Task InstallPackageInternal(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken) + /// . + private async Task InstallPackageInternal(PackageVersionInfo package, CancellationToken cancellationToken) { // Set last update time if we were installed before IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase)) ?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase)); - string targetPath = plugin == null ? null : plugin.AssemblyFilePath; - // Do the install - await PerformPackageInstallation(progress, targetPath, package, cancellationToken).ConfigureAwait(false); + await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false); // Do plugin-specific processing if (plugin == null) @@ -455,76 +432,57 @@ namespace Emby.Server.Implementations.Updates _applicationHost.NotifyPendingRestart(); } - private async Task PerformPackageInstallation(IProgress progress, string target, PackageVersionInfo package, CancellationToken cancellationToken) + private async Task PerformPackageInstallation(PackageVersionInfo package, CancellationToken cancellationToken) { - // TODO: Remove the `string target` argument as it is not used any longer - var extension = Path.GetExtension(package.targetFilename); - var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase); - - if (!isArchive) + if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase)) { _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.targetFilename); return; } // Always override the passed-in target (which is a file) and figure it out again - target = Path.Combine(_appPaths.PluginsPath, package.name); - _logger.LogDebug("Installing plugin to {Filename}.", target); - - // Download to temporary file so that, if interrupted, it won't destroy the existing installation - _logger.LogDebug("Downloading ZIP."); - var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - Url = package.sourceUrl, - CancellationToken = cancellationToken, - Progress = progress - - }).ConfigureAwait(false); + string targetDir = Path.Combine(_appPaths.PluginsPath, package.name); - cancellationToken.ThrowIfCancellationRequested(); - - // TODO: Validate with a checksum, *properly* - - // Check if the target directory already exists, and remove it if so - if (Directory.Exists(target)) - { - _logger.LogDebug("Deleting existing plugin at {Filename}.", target); - Directory.Delete(target, true); - } +// CA5351: Do Not Use Broken Cryptographic Algorithms +#pragma warning disable CA5351 + using (var res = await _httpClient.SendAsync( + new HttpRequestOptions + { + Url = package.sourceUrl, + CancellationToken = cancellationToken, + // We need it to be buffered for setting the position + BufferContent = true + }, + HttpMethod.Get).ConfigureAwait(false)) + using (var stream = res.Content) + using (var md5 = MD5.Create()) + { + cancellationToken.ThrowIfCancellationRequested(); + + var hash = HexHelper.ToHexString(md5.ComputeHash(stream)); + if (!string.Equals(package.checksum, hash, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("{0}, {1}", package.checksum, hash); + throw new InvalidDataException($"The checksums didn't match while installing {package.name}."); + } - // Success - move it to the real target - try - { - _logger.LogDebug("Extracting ZIP {TempFile} to {Filename}.", tempFile, target); - using (var stream = File.OpenRead(tempFile)) + if (Directory.Exists(targetDir)) { - _zipClient.ExtractAllFromZip(stream, target, true); + Directory.Delete(targetDir); } - } - catch (IOException ex) - { - _logger.LogError(ex, "Error attempting to extract {TempFile} to {TargetFile}", tempFile, target); - throw; - } - try - { - _logger.LogDebug("Deleting temporary file {Filename}.", tempFile); - _fileSystem.DeleteFile(tempFile); - } - catch (IOException ex) - { - // Don't fail because of this - _logger.LogError(ex, "Error deleting temp file {TempFile}", tempFile); + stream.Position = 0; + _zipClient.ExtractAllFromZip(stream, targetDir, true); } + +#pragma warning restore CA5351 } /// /// Uninstalls a plugin /// /// The plugin. - /// public void UninstallPlugin(IPlugin plugin) { plugin.OnUninstalling(); diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index cf1e08d53a..915c9784e4 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -197,7 +197,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException(string.Format("Package not found: {0}", request.Name)); } - await _installationManager.InstallPackage(package, new SimpleProgress(), CancellationToken.None); + await _installationManager.InstallPackage(package, CancellationToken.None); } /// diff --git a/MediaBrowser.Common/Extensions/HexHelper.cs b/MediaBrowser.Common/Extensions/HexHelper.cs new file mode 100644 index 0000000000..3d80d94ac3 --- /dev/null +++ b/MediaBrowser.Common/Extensions/HexHelper.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; + +namespace MediaBrowser.Common.Extensions +{ + public static class HexHelper + { + public static byte[] FromHexString(string str) + { + byte[] bytes = new byte[str.Length / 2]; + for (int i = 0; i < str.Length; i += 2) + { + bytes[i / 2] = byte.Parse(str.Substring(i, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + + return bytes; + } + + public static string ToHexString(byte[] bytes) + => BitConverter.ToString(bytes).Replace("-", ""); + } +} diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index 76bd35e578..94b972a026 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -64,12 +64,6 @@ namespace MediaBrowser.Common.Net set => RequestHeaders[HeaderNames.Host] = value; } - /// - /// Gets or sets the progress. - /// - /// The progress. - public IProgress Progress { get; set; } - public Dictionary RequestHeaders { get; private set; } public string RequestContentType { get; set; } @@ -79,10 +73,6 @@ namespace MediaBrowser.Common.Net public bool BufferContent { get; set; } - public bool LogRequest { get; set; } - public bool LogRequestAsDebug { get; set; } - public bool LogErrors { get; set; } - public bool LogErrorResponseBody { get; set; } public bool EnableKeepAlive { get; set; } @@ -105,8 +95,6 @@ namespace MediaBrowser.Common.Net { RequestHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - LogRequest = true; - LogErrors = true; CacheMode = CacheMode.None; DecompressionMethod = CompressionMethod.Deflate; } diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs index db69c6f2cd..d84a4d664c 100644 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ b/MediaBrowser.Common/Net/IHttpClient.cs @@ -47,21 +47,5 @@ namespace MediaBrowser.Common.Net /// The options. /// Task{HttpResponseInfo}. Task Post(HttpRequestOptions options); - - /// - /// Downloads the contents of a given url into a temporary location - /// - /// The options. - /// Task{System.String}. - /// progress - /// - Task GetTempFile(HttpRequestOptions options); - - /// - /// Gets the temporary file response. - /// - /// The options. - /// Task{HttpResponseInfo}. - Task GetTempFileResponse(HttpRequestOptions options); } } diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 3472a5692f..d43024ac85 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -97,12 +97,9 @@ namespace MediaBrowser.Common.Updates /// Installs the package. /// /// The package. - /// if set to true [is plugin]. - /// The progress. /// The cancellation token. - /// Task. - /// package - Task InstallPackage(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken); + /// . + Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken); /// /// Uninstalls a plugin diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index df32fdb003..4bcf0c117f 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -84,6 +84,7 @@ namespace MediaBrowser.Model.Cryptography _hash = Array.Empty(); } + // TODO: move this class and use the HexHelper class public static byte[] ConvertFromByteString(string byteString) { byte[] bytes = new byte[byteString.Length / 2]; diff --git a/MediaBrowser.Model/Updates/InstallationInfo.cs b/MediaBrowser.Model/Updates/InstallationInfo.cs index a3f19e2360..7554e9fe20 100644 --- a/MediaBrowser.Model/Updates/InstallationInfo.cs +++ b/MediaBrowser.Model/Updates/InstallationInfo.cs @@ -36,11 +36,5 @@ namespace MediaBrowser.Model.Updates /// /// The update class. public PackageVersionClass UpdateClass { get; set; } - - /// - /// Gets or sets the percent complete. - /// - /// The percent complete. - public double? PercentComplete { get; set; } } } diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index 4b41589f1c..ef412db5ac 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -2,10 +2,10 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; -using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; @@ -143,26 +143,20 @@ namespace MediaBrowser.Providers.Studios if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1) { - var temp = await httpClient.GetTempFile(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Progress = new SimpleProgress(), - Url = url - - }).ConfigureAwait(false); - Directory.CreateDirectory(Path.GetDirectoryName(file)); - try - { - File.Copy(temp, file, true); - } - catch + using (var res = await httpClient.SendAsync( + new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url + }, + HttpMethod.Get).ConfigureAwait(false)) + using (var content = res.Content) + using (var fileStream = new FileStream(file, FileMode.Create)) { - + await content.CopyToAsync(fileStream).ConfigureAwait(false); } - - return temp; } return file; diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs index 72b4c2b255..f619f5ca41 100644 --- a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs +++ b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs @@ -25,50 +25,37 @@ // using System; -using System.Diagnostics; using System.Net; using MediaBrowser.Common.Net; -using Microsoft.Extensions.Logging; namespace Mono.Nat.Upnp { internal class GetServicesMessage : MessageBase { - private string servicesDescriptionUrl; - private EndPoint hostAddress; - private readonly ILogger _logger; + private string _servicesDescriptionUrl; + private EndPoint _hostAddress; - public GetServicesMessage(string description, EndPoint hostAddress, ILogger logger) + public GetServicesMessage(string description, EndPoint hostAddress) : base(null) { if (string.IsNullOrEmpty(description)) - _logger.LogWarning("Description is null"); - - if (hostAddress == null) - _logger.LogWarning("hostaddress is null"); - - this.servicesDescriptionUrl = description; - this.hostAddress = hostAddress; - _logger = logger; - } - - public override string Method - { - get { - return "GET"; + throw new ArgumentException("Description is null/empty", nameof(description)); } + + this._servicesDescriptionUrl = description; + this._hostAddress = hostAddress ?? throw new ArgumentNullException(nameof(hostAddress)); } + public override string Method => "GET"; + public override HttpRequestOptions Encode() { - var req = new HttpRequestOptions(); - - // The periodic request logging may keep some devices awake - req.LogRequestAsDebug = true; - req.LogErrors = false; + var req = new HttpRequestOptions() + { + Url = $"http://{this._hostAddress}{this._servicesDescriptionUrl}" + }; - req.Url = "http://" + this.hostAddress.ToString() + this.servicesDescriptionUrl; req.RequestHeaders.Add("ACCEPT-LANGUAGE", "en"); return req; diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs index ade9df50b5..d47241d4a1 100644 --- a/Mono.Nat/Upnp/Messages/UpnpMessage.cs +++ b/Mono.Nat/Upnp/Messages/UpnpMessage.cs @@ -24,13 +24,8 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -using System; -using System.Diagnostics; using System.Xml; -using System.Net; -using System.IO; using System.Text; -using System.Globalization; using MediaBrowser.Common.Net; namespace Mono.Nat.Upnp @@ -46,38 +41,31 @@ namespace Mono.Nat.Upnp protected HttpRequestOptions CreateRequest(string upnpMethod, string methodParameters) { - string ss = "http://" + this.device.HostEndPoint.ToString() + this.device.ControlUrl; + var req = new HttpRequestOptions() + { + Url = $"http://{this.device.HostEndPoint}{this.device.ControlUrl}", + EnableKeepAlive = false, + RequestContentType = "text/xml", + RequestContent = "" + + "" + + "" + + methodParameters + + "" + + "" + + "\r\n\r\n" + }; - var req = new HttpRequestOptions(); - req.LogErrors = false; - - // The periodic request logging may keep some devices awake - req.LogRequestAsDebug = true; - - req.Url = ss; - req.EnableKeepAlive = false; - req.RequestContentType = "text/xml"; req.RequestHeaders.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\""); - req.RequestContent = "" - + "" - + "" - + methodParameters - + "" - + "" - + "\r\n\r\n"; return req; } public abstract HttpRequestOptions Encode(); - public virtual string Method - { - get { return "POST"; } - } + public virtual string Method => "POST"; protected void WriteFullElement(XmlWriter writer, string element, string value) { diff --git a/Mono.Nat/Upnp/UpnpNatDevice.cs b/Mono.Nat/Upnp/UpnpNatDevice.cs index fd408ee638..3ff1eeb90e 100644 --- a/Mono.Nat/Upnp/UpnpNatDevice.cs +++ b/Mono.Nat/Upnp/UpnpNatDevice.cs @@ -27,11 +27,9 @@ // using System; -using System.IO; using System.Net; using System.Xml; using System.Text; -using System.Diagnostics; using System.Threading.Tasks; using MediaBrowser.Common.Net; using Microsoft.Extensions.Logging; @@ -96,7 +94,7 @@ namespace Mono.Nat.Upnp public async Task GetServicesList() { // Create a HTTPWebRequest to download the list of services the device offers - var message = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint, _logger); + var message = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint); using (var response = await _httpClient.SendAsync(message.Encode(), message.Method).ConfigureAwait(false)) { diff --git a/jellyfin.ruleset b/jellyfin.ruleset index e7e02a7d5b..8ea1d6b161 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -38,5 +38,7 @@ + + -- cgit v1.2.3 From 72436892154892c53c75ee5fdcbcb3bf843ac85c Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 11 Aug 2019 15:57:36 +0200 Subject: Minor improvements --- Emby.Server.Implementations/Updates/InstallationManager.cs | 8 +------- MediaBrowser.Api/PackageService.cs | 2 +- MediaBrowser.Common/Updates/IInstallationManager.cs | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index c603199643..2f84b91ecd 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -279,12 +279,7 @@ namespace Emby.Server.Implementations.Updates var package = availablePackages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase)) ?? availablePackages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)); - if (package == null) - { - return null; - } - - return package.versions + return package?.versions .OrderByDescending(x => x.Version) .FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, currentServerVersion)); } @@ -308,7 +303,6 @@ namespace Emby.Server.Implementations.Updates var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, systemUpdateLevel); return latestPluginInfo != null && latestPluginInfo.Version > p.Version ? latestPluginInfo : null; - }).Where(i => i != null) .Where(p => !string.IsNullOrEmpty(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase))); } diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index 915c9784e4..baa6f7bb97 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -197,7 +197,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException(string.Format("Package not found: {0}", request.Name)); } - await _installationManager.InstallPackage(package, CancellationToken.None); + await _installationManager.InstallPackage(package); } /// diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index d43024ac85..88ac7e473c 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -99,7 +99,7 @@ namespace MediaBrowser.Common.Updates /// The package. /// The cancellation token. /// . - Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken); + Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken = default); /// /// Uninstalls a plugin -- cgit v1.2.3 From 838e5d05d51c4c171c07d512f741304a6dc58f24 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 11 Aug 2019 16:52:37 +0200 Subject: Document all public/internal members of Emby.Drawing Forces all new public/internal members to be documented. Enables TreatWarningsAsErrors for Emby.Drawing --- BDInfo/BDInfo.csproj | 1 + DvdLib/DvdLib.csproj | 1 + Emby.Dlna/Emby.Dlna.csproj | 1 + Emby.Drawing/Emby.Drawing.csproj | 2 + Emby.Drawing/ImageProcessor.cs | 159 ++++++++------------- Emby.Drawing/NullImageEncoder.cs | 34 +++-- Emby.IsoMounting/IsoMounter/IsoMounter.csproj | 1 + Emby.Notifications/Emby.Notifications.csproj | 1 + Emby.Photos/Emby.Photos.csproj | 1 + Emby.Server.Implementations/ApplicationHost.cs | 21 +-- Emby.Server.Implementations/Dto/DtoService.cs | 2 +- Emby.XmlTv/Emby.XmlTv/Emby.XmlTv.csproj | 1 + Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj | 1 + MediaBrowser.Api/Images/ImageService.cs | 8 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + MediaBrowser.Common/IApplicationHost.cs | 8 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 1 + MediaBrowser.Controller/Drawing/IImageEncoder.cs | 21 +-- MediaBrowser.Controller/Drawing/IImageProcessor.cs | 30 ++-- .../Drawing/ImageProcessingOptions.cs | 5 +- .../MediaBrowser.Controller.csproj | 1 + .../MediaBrowser.LocalMetadata.csproj | 1 + .../MediaBrowser.MediaEncoding.csproj | 1 + MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + .../MediaBrowser.Providers.csproj | 1 + .../MediaBrowser.WebDashboard.csproj | 1 + .../MediaBrowser.XbmcMetadata.csproj | 1 + 27 files changed, 141 insertions(+), 166 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/BDInfo/BDInfo.csproj b/BDInfo/BDInfo.csproj index b2c752d0c8..9dbaa9e2f0 100644 --- a/BDInfo/BDInfo.csproj +++ b/BDInfo/BDInfo.csproj @@ -11,6 +11,7 @@ netstandard2.0 false + true diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj index b2c752d0c8..9dbaa9e2f0 100644 --- a/DvdLib/DvdLib.csproj +++ b/DvdLib/DvdLib.csproj @@ -11,6 +11,7 @@ netstandard2.0 false + true diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 4c07087c53..34b49120bb 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -14,6 +14,7 @@ netstandard2.0 false + true diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index 9f97baf772..716de059dc 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -3,6 +3,8 @@ netstandard2.0 false + true + true diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index a7d95eb209..ce8089e59c 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -22,42 +22,47 @@ using Microsoft.Extensions.Logging; namespace Emby.Drawing { /// - /// Class ImageProcessor + /// Class ImageProcessor. /// public class ImageProcessor : IImageProcessor, IDisposable { - /// - /// The us culture - /// - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); + // Increment this when there's a change requiring caches to be invalidated + private const string Version = "3"; - /// - /// Gets the list of currently registered image processors - /// Image processors are specialized metadata providers that run after the normal ones - /// - /// The image enhancers. - public IImageEnhancer[] ImageEnhancers { get; private set; } + private static readonly HashSet _transparentImageTypes + = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; /// /// The _logger /// private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; private readonly IServerApplicationPaths _appPaths; private IImageEncoder _imageEncoder; private readonly Func _libraryManager; private readonly Func _mediaEncoder; + private readonly Dictionary _locks = new Dictionary(); + private bool _disposed = false; + + /// + /// + /// + /// + /// + /// + /// + /// + /// public ImageProcessor( - ILoggerFactory loggerFactory, + ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IImageEncoder imageEncoder, Func libraryManager, Func mediaEncoder) { - _logger = loggerFactory.CreateLogger(nameof(ImageProcessor)); + _logger = logger; _fileSystem = fileSystem; _imageEncoder = imageEncoder; _libraryManager = libraryManager; @@ -69,20 +74,11 @@ namespace Emby.Drawing ImageHelper.ImageProcessor = this; } - public IImageEncoder ImageEncoder - { - get => _imageEncoder; - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } + private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images"); - _imageEncoder = value; - } - } + private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images"); + /// public IReadOnlyCollection SupportedInputFormats => new HashSet(StringComparer.OrdinalIgnoreCase) { @@ -115,18 +111,20 @@ namespace Emby.Drawing "wbmp" }; + /// + public IReadOnlyCollection ImageEnhancers { get; set; } + /// public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation; - private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images"); - - private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images"); - - public void AddParts(IEnumerable enhancers) + /// + public IImageEncoder ImageEncoder { - ImageEnhancers = enhancers.ToArray(); + get => _imageEncoder; + set => _imageEncoder = value ?? throw new ArgumentNullException(nameof(value)); } + /// public async Task ProcessImage(ImageProcessingOptions options, Stream toStream) { var file = await ProcessImage(options).ConfigureAwait(false); @@ -137,15 +135,15 @@ namespace Emby.Drawing } } + /// public IReadOnlyCollection GetSupportedImageOutputFormats() => _imageEncoder.SupportedOutputFormats; - private static readonly HashSet TransparentImageTypes - = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; - + /// public bool SupportsTransparency(string path) - => TransparentImageTypes.Contains(Path.GetExtension(path)); + => _transparentImageTypes.Contains(Path.GetExtension(path)); + /// public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options) { if (options == null) @@ -187,9 +185,9 @@ namespace Emby.Drawing } dateModified = supportedImageInfo.dateModified; - bool requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath)); + bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath)); - if (options.Enhancers.Length > 0) + if (options.Enhancers.Count > 0) { if (item == null) { @@ -279,7 +277,7 @@ namespace Emby.Drawing } } - private ImageFormat GetOutputFormat(ImageFormat[] clientSupportedFormats, bool requiresTransparency) + private ImageFormat GetOutputFormat(IReadOnlyCollection clientSupportedFormats, bool requiresTransparency) { var serverFormats = GetSupportedImageOutputFormats(); @@ -320,11 +318,6 @@ namespace Emby.Drawing } } - /// - /// Increment this when there's a change requiring caches to be invalidated - /// - private const string Version = "3"; - /// /// Gets the cache file path based on a set of parameters /// @@ -372,9 +365,11 @@ namespace Emby.Drawing return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant()); } + /// public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info) => GetImageDimensions(item, info, true); + /// public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem) { int width = info.Width; @@ -400,26 +395,19 @@ namespace Emby.Drawing return size; } - /// - /// Gets the size of the image. - /// + /// public ImageDimensions GetImageDimensions(string path) => _imageEncoder.GetImageSize(path); - /// - /// Gets the image cache tag. - /// - /// The item. - /// The image. - /// Guid. - /// item + /// public string GetImageCacheTag(BaseItem item, ItemImageInfo image) { - var supportedEnhancers = GetSupportedEnhancers(item, image.Type); + var supportedEnhancers = GetSupportedEnhancers(item, image.Type).ToArray(); return GetImageCacheTag(item, image, supportedEnhancers); } + /// public string GetImageCacheTag(BaseItem item, ChapterInfo chapter) { try @@ -437,22 +425,15 @@ namespace Emby.Drawing } } - /// - /// Gets the image cache tag. - /// - /// The item. - /// The image. - /// The image enhancers. - /// Guid. - /// item - public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IImageEnhancer[] imageEnhancers) + /// + public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection imageEnhancers) { string originalImagePath = image.Path; DateTime dateModified = image.DateModified; ImageType imageType = image.Type; // Optimization - if (imageEnhancers.Length == 0) + if (imageEnhancers.Count == 0) { return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); } @@ -480,7 +461,7 @@ namespace Emby.Drawing { try { - string filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture); + string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture); string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png"; var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension); @@ -507,16 +488,10 @@ namespace Emby.Drawing return (originalImagePath, dateModified); } - /// - /// Gets the enhanced image. - /// - /// The item. - /// Type of the image. - /// Index of the image. - /// Task{System.String}. + /// public async Task GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex) { - var enhancers = GetSupportedEnhancers(item, imageType); + var enhancers = GetSupportedEnhancers(item, imageType).ToArray(); ItemImageInfo imageInfo = item.GetImageInfo(imageType, imageIndex); @@ -532,7 +507,7 @@ namespace Emby.Drawing bool inputImageSupportsTransparency, BaseItem item, int imageIndex, - IImageEnhancer[] enhancers, + IReadOnlyCollection enhancers, CancellationToken cancellationToken) { var originalImagePath = image.Path; @@ -573,6 +548,7 @@ namespace Emby.Drawing /// Index of the image. /// The supported enhancers. /// The cache unique identifier. + /// The cancellation token. /// Task<System.String>. /// /// originalImagePath @@ -584,9 +560,9 @@ namespace Emby.Drawing BaseItem item, ImageType imageType, int imageIndex, - IImageEnhancer[] supportedEnhancers, + IReadOnlyCollection supportedEnhancers, string cacheGuid, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(originalImagePath)) { @@ -680,6 +656,7 @@ namespace Emby.Drawing { throw new ArgumentNullException(nameof(path)); } + if (string.IsNullOrEmpty(uniqueName)) { throw new ArgumentNullException(nameof(uniqueName)); @@ -722,6 +699,7 @@ namespace Emby.Drawing return Path.Combine(path, prefix, filename); } + /// public void CreateImageCollage(ImageCollageOptions options) { _logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath); @@ -731,38 +709,25 @@ namespace Emby.Drawing _logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath); } - public IImageEnhancer[] GetSupportedEnhancers(BaseItem item, ImageType imageType) + /// + public IEnumerable GetSupportedEnhancers(BaseItem item, ImageType imageType) { - List list = null; - foreach (var i in ImageEnhancers) { - try - { - if (i.Supports(item, imageType)) - { - if (list == null) - { - list = new List(); - } - list.Add(i); - } - } - catch (Exception ex) + if (i.Supports(item, imageType)) { - _logger.LogError(ex, "Error in image enhancer: {0}", i.GetType().Name); + yield return i; } } - - return list == null ? Array.Empty() : list.ToArray(); } - private Dictionary _locks = new Dictionary(); + private class LockInfo { public SemaphoreSlim Lock = new SemaphoreSlim(1, 1); public int Count = 1; } + private LockInfo GetLock(string key) { lock (_locks) @@ -795,7 +760,7 @@ namespace Emby.Drawing } } - private bool _disposed; + /// public void Dispose() { _disposed = true; diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index fc4a5af9fc..5af7f16225 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -5,36 +5,40 @@ using MediaBrowser.Model.Drawing; namespace Emby.Drawing { + /// + /// A fallback implementation of . + /// public class NullImageEncoder : IImageEncoder { + /// public IReadOnlyCollection SupportedInputFormats => new HashSet(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" }; + /// public IReadOnlyCollection SupportedOutputFormats => new HashSet() { ImageFormat.Jpg, ImageFormat.Png }; - public void CropWhiteSpace(string inputPath, string outputPath) - { - throw new NotImplementedException(); - } - - public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) - { - throw new NotImplementedException(); - } - - public void CreateImageCollage(ImageCollageOptions options) - { - throw new NotImplementedException(); - } - + /// public string Name => "Null Image Encoder"; + /// public bool SupportsImageCollageCreation => false; + /// public bool SupportsImageEncoding => false; + /// public ImageDimensions GetImageSize(string path) + => throw new NotImplementedException(); + + /// + public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) + { + throw new NotImplementedException(); + } + + /// + public void CreateImageCollage(ImageCollageOptions options) { throw new NotImplementedException(); } diff --git a/Emby.IsoMounting/IsoMounter/IsoMounter.csproj b/Emby.IsoMounting/IsoMounter/IsoMounter.csproj index dafa51cd51..0778b987bc 100644 --- a/Emby.IsoMounting/IsoMounter/IsoMounter.csproj +++ b/Emby.IsoMounting/IsoMounter/IsoMounter.csproj @@ -12,6 +12,7 @@ netstandard2.0 false + true diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj index 5c68e48c8f..cbd3bde4f9 100644 --- a/Emby.Notifications/Emby.Notifications.csproj +++ b/Emby.Notifications/Emby.Notifications.csproj @@ -3,6 +3,7 @@ netstandard2.0 false + true diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj index c9830abc5f..39b97a3bff 100644 --- a/Emby.Photos/Emby.Photos.csproj +++ b/Emby.Photos/Emby.Photos.csproj @@ -16,6 +16,7 @@ netstandard2.0 false + true diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 09847b2f86..b620f6be2e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -510,13 +510,8 @@ namespace Emby.Server.Implementations return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i)); } - /// - /// Gets the exports. - /// - /// The type - /// if set to true [manage lifetime]. - /// IEnumerable{``0}. - public IEnumerable GetExports(bool manageLifetime = true) + /// + public IReadOnlyCollection GetExports(bool manageLifetime = true) { var parts = GetExportTypes() .Select(CreateInstanceSafe) @@ -538,6 +533,7 @@ namespace Emby.Server.Implementations /// /// Runs the startup tasks. /// + /// . public async Task RunStartupTasksAsync() { Logger.LogInformation("Running startup tasks"); @@ -550,7 +546,7 @@ namespace Emby.Server.Implementations Logger.LogInformation("ServerId: {0}", SystemId); - var entryPoints = GetExports().ToList(); + var entryPoints = GetExports(); var stopWatch = new Stopwatch(); stopWatch.Start(); @@ -801,7 +797,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(HttpServer); - ImageProcessor = GetImageProcessor(); + ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); serviceCollection.AddSingleton(ImageProcessor); TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); @@ -953,11 +949,6 @@ namespace Emby.Server.Implementations } } - private IImageProcessor GetImageProcessor() - { - return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); - } - /// /// Gets the user repository. /// @@ -1087,7 +1078,7 @@ namespace Emby.Server.Implementations GetExports(), GetExports()); - ImageProcessor.AddParts(GetExports()); + ImageProcessor.ImageEnhancers = GetExports(); LiveTvManager.AddParts(GetExports(), GetExports(), GetExports()); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 6e7aa13139..1a7f10634b 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1364,7 +1364,7 @@ namespace Emby.Server.Implementations.Dto return null; } - var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary); + var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary).ToArray(); ImageDimensions size; diff --git a/Emby.XmlTv/Emby.XmlTv/Emby.XmlTv.csproj b/Emby.XmlTv/Emby.XmlTv/Emby.XmlTv.csproj index 0225be2c2f..04f5581736 100644 --- a/Emby.XmlTv/Emby.XmlTv/Emby.XmlTv.csproj +++ b/Emby.XmlTv/Emby.XmlTv/Emby.XmlTv.csproj @@ -3,6 +3,7 @@ netstandard2.0 false + true diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index f023bc55dc..396bdd4b71 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -3,6 +3,7 @@ netstandard2.0 false + true diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 23c7339d27..6d3037b24c 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -550,14 +550,14 @@ namespace MediaBrowser.Api.Images } IImageEnhancer[] supportedImageEnhancers; - if (_imageProcessor.ImageEnhancers.Length > 0) + if (_imageProcessor.ImageEnhancers.Count > 0) { if (item == null) { item = _libraryManager.GetItemById(itemId); } - supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type) : Array.Empty(); + supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type).ToArray() : Array.Empty(); } else { @@ -606,8 +606,8 @@ namespace MediaBrowser.Api.Images ImageRequest request, ItemImageInfo image, bool cropwhitespace, - ImageFormat[] supportedFormats, - IImageEnhancer[] enhancers, + IReadOnlyCollection supportedFormats, + IReadOnlyCollection enhancers, TimeSpan? cacheDuration, IDictionary headers, bool isHeadRequest) diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index ba29c656bf..f653270a6c 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -12,6 +12,7 @@ netstandard2.0 false + true diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index cb7343440a..2248e9c859 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -75,10 +75,10 @@ namespace MediaBrowser.Common /// /// Gets the exports. /// - /// - /// if set to true [manage liftime]. - /// IEnumerable{``0}. - IEnumerable GetExports(bool manageLifetime = true); + /// The type. + /// If set to true [manage lifetime]. + /// . + IReadOnlyCollection GetExports(bool manageLifetime = true); /// /// Resolves this instance. diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 05b48a2a12..1b753aa294 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -23,6 +23,7 @@ netstandard2.0 false + true diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 4eaecd0a0e..a0f9ae46e4 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -17,16 +17,6 @@ namespace MediaBrowser.Controller.Drawing /// The supported output formats. IReadOnlyCollection SupportedOutputFormats { get; } - /// - /// Encodes the image. - /// - string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat); - - /// - /// Creates the image collage. - /// - /// The options. - void CreateImageCollage(ImageCollageOptions options); /// /// Gets the name. /// @@ -46,5 +36,16 @@ namespace MediaBrowser.Controller.Drawing bool SupportsImageEncoding { get; } ImageDimensions GetImageSize(string path); + + /// + /// Encodes the image. + /// + string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat); + + /// + /// Creates the image collage. + /// + /// The options. + void CreateImageCollage(ImageCollageOptions options); } } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index a11e2186fd..a58a11bd1f 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -24,7 +24,15 @@ namespace MediaBrowser.Controller.Drawing /// Gets the image enhancers. /// /// The image enhancers. - IImageEnhancer[] ImageEnhancers { get; } + IReadOnlyCollection ImageEnhancers { get; set; } + + /// + /// Gets a value indicating whether [supports image collage creation]. + /// + /// true if [supports image collage creation]; otherwise, false. + bool SupportsImageCollageCreation { get; } + + IImageEncoder ImageEncoder { get; set; } /// /// Gets the dimensions of the image. @@ -50,19 +58,13 @@ namespace MediaBrowser.Controller.Drawing /// ImageDimensions ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem); - /// - /// Adds the parts. - /// - /// The enhancers. - void AddParts(IEnumerable enhancers); - /// /// Gets the supported enhancers. /// /// The item. /// Type of the image. /// IEnumerable{IImageEnhancer}. - IImageEnhancer[] GetSupportedEnhancers(BaseItem item, ImageType imageType); + IEnumerable GetSupportedEnhancers(BaseItem item, ImageType imageType); /// /// Gets the image cache tag. @@ -80,7 +82,7 @@ namespace MediaBrowser.Controller.Drawing /// The image. /// The image enhancers. /// Guid. - string GetImageCacheTag(BaseItem item, ItemImageInfo image, IImageEnhancer[] imageEnhancers); + string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection imageEnhancers); /// /// Processes the image. @@ -109,7 +111,7 @@ namespace MediaBrowser.Controller.Drawing /// /// Gets the supported image output formats. /// - /// IReadOnlyCollection{ImageOutput}. + /// . IReadOnlyCollection GetSupportedImageOutputFormats(); /// @@ -118,14 +120,6 @@ namespace MediaBrowser.Controller.Drawing /// The options. void CreateImageCollage(ImageCollageOptions options); - /// - /// Gets a value indicating whether [supports image collage creation]. - /// - /// true if [supports image collage creation]; otherwise, false. - bool SupportsImageCollageCreation { get; } - - IImageEncoder ImageEncoder { get; set; } - bool SupportsTransparency(string path); } } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index db432f500e..29addf6e65 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Controller.Entities; @@ -33,9 +34,9 @@ namespace MediaBrowser.Controller.Drawing public int Quality { get; set; } - public IImageEnhancer[] Enhancers { get; set; } + public IReadOnlyCollection Enhancers { get; set; } - public ImageFormat[] SupportedOutputFormats { get; set; } + public IReadOnlyCollection SupportedOutputFormats { get; set; } public bool AddPlayedIndicator { get; set; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 01893f1b55..c6bca25182 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -19,6 +19,7 @@ netstandard2.0 false + true diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index 867b82eded..a8f8da9b83 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -12,6 +12,7 @@ netstandard2.0 false + true diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index c0f92ac4a4..05af9fd03d 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -3,6 +3,7 @@ netstandard2.0 false + true diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 3de2cca2d2..e9f43ea56b 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -10,6 +10,7 @@ netstandard2.0 false + true diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 5941ed4369..ab4759c61e 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -21,6 +21,7 @@ netstandard2.0 false + true diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index c099e77d65..8839868944 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -18,6 +18,7 @@ netstandard2.0 false + true diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index ba29c656bf..f653270a6c 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -12,6 +12,7 @@ netstandard2.0 false + true -- cgit v1.2.3 From 4762e2fc6c87b462d375fa8d3063cd9c9d6a9d4a Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Wed, 14 Aug 2019 01:51:46 -0400 Subject: Add a task to clean up transcode cache --- .../Tasks/DeleteTranscodingTempTask.cs | 157 +++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs new file mode 100644 index 0000000000..6ec0799898 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks +{ + /// + /// Deletes all transcoding temp files + /// + public class DeleteTranscodingTempTask : IScheduledTask, IConfigurableScheduledTask + { + /// + /// Gets or sets the application paths. + /// + /// The application paths. + //private IApplicationPaths ApplicationPaths { get; set; } + protected ServerApplicationPaths ApplicationPaths { get; set; } + + + private readonly ILogger _logger; + + private readonly IFileSystem _fileSystem; + + /// + /// Initializes a new instance of the class. + /// + public DeleteTranscodingTempTask(ServerApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem) + { + ApplicationPaths = appPaths; + _logger = logger; + _fileSystem = fileSystem; + } + + /// + /// Creates the triggers that define when the task will run + /// + /// IEnumerable{BaseTaskTrigger}. + public IEnumerable GetDefaultTriggers() => new List(); + + /// + /// Returns the task to be executed + /// + /// The cancellation token. + /// The progress. + /// Task. + public Task Execute(CancellationToken cancellationToken, IProgress progress) + { + var minDateModified = DateTime.UtcNow.AddDays(-1); + progress.Report(50); + + try + { + DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodingTempPath, minDateModified, progress); + } + catch (DirectoryNotFoundException) + { + // No biggie here. Nothing to delete + } + + return Task.CompletedTask; + } + + + /// + /// Deletes the transcoded temp files from directory with a last write time less than a given date + /// + /// The task cancellation token. + /// The directory. + /// The min date modified. + /// The progress. + private void DeleteTempFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress progress) + { + var filesToDelete = _fileSystem.GetFiles(directory, true) + .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) + .ToList(); + + var index = 0; + + foreach (var file in filesToDelete) + { + double percent = index; + percent /= filesToDelete.Count; + + progress.Report(100 * percent); + + cancellationToken.ThrowIfCancellationRequested(); + + DeleteFile(file.FullName); + + index++; + } + + DeleteEmptyFolders(directory); + + progress.Report(100); + } + + private void DeleteEmptyFolders(string parent) + { + foreach (var directory in _fileSystem.GetDirectoryPaths(parent)) + { + DeleteEmptyFolders(directory); + if (!_fileSystem.GetFileSystemEntryPaths(directory).Any()) + { + try + { + Directory.Delete(directory, false); + } + catch (UnauthorizedAccessException ex) + { + _logger.LogError(ex, "Error deleting directory {path}", directory); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting directory {path}", directory); + } + } + } + } + + private void DeleteFile(string path) + { + try + { + _fileSystem.DeleteFile(path); + } + catch (UnauthorizedAccessException ex) + { + _logger.LogError(ex, "Error deleting file {path}", path); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting file {path}", path); + } + } + + public string Name => "Transcoding temp cleanup"; + + public string Description => "Deletes transcoding temp files older than 24 hours."; + + public string Category => "Maintenance"; + + public string Key => "DeleteTranscodingTempFiles"; + + public bool IsHidden => false; + + public bool IsEnabled => false; + + public bool IsLogged => true; + } +} -- cgit v1.2.3 From 35da4ffa3efec3292bd9422154d7536ea89da89f Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Wed, 14 Aug 2019 11:59:14 -0400 Subject: Remove unneeded comment --- .../ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs | 1 - 1 file changed, 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs index 6ec0799898..ad9b56535d 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs @@ -19,7 +19,6 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// Gets or sets the application paths. /// /// The application paths. - //private IApplicationPaths ApplicationPaths { get; set; } protected ServerApplicationPaths ApplicationPaths { get; set; } -- cgit v1.2.3 From e5b163b86aa9d8d8236a9aad832cc6009af762c4 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 14 Aug 2019 20:24:44 +0200 Subject: Fix possible nullref --- Emby.Server.Implementations/ApplicationHost.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 09847b2f86..38e61605a7 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -121,6 +121,8 @@ namespace Emby.Server.Implementations /// public abstract class ApplicationHost : IServerApplicationHost, IDisposable { + private SqliteUserRepository _userRepository; + /// /// Gets a value indicating whether this instance can self restart. /// @@ -291,8 +293,6 @@ namespace Emby.Server.Implementations /// The user data repository. private IUserDataManager UserDataManager { get; set; } - private IUserRepository UserRepository { get; set; } - internal SqliteItemRepository ItemRepository { get; set; } private INotificationManager NotificationManager { get; set; } @@ -766,9 +766,9 @@ namespace Emby.Server.Implementations AuthenticationRepository = GetAuthenticationRepository(); serviceCollection.AddSingleton(AuthenticationRepository); - UserRepository = GetUserRepository(); + _userRepository = GetUserRepository(); - UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager); + UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager); serviceCollection.AddSingleton(UserManager); LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager); @@ -961,8 +961,8 @@ namespace Emby.Server.Implementations /// /// Gets the user repository. /// - /// Task{IUserRepository}. - private IUserRepository GetUserRepository() + /// . + private SqliteUserRepository GetUserRepository() { var repo = new SqliteUserRepository(LoggerFactory, ApplicationPaths, JsonSerializer); @@ -1910,11 +1910,9 @@ namespace Emby.Server.Implementations } } - UserRepository.Dispose(); + _userRepository?.Dispose(); } - UserRepository = null; - _disposed = true; } } -- cgit v1.2.3 From 2fdf7f1098a1de41d7459b66620f82b79f27c4b8 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 14 Aug 2019 20:35:36 +0200 Subject: Properly dispose DisplayPreferencesRepository --- Emby.Server.Implementations/ApplicationHost.cs | 21 +++++++++++++++++---- .../Data/SqliteDisplayPreferencesRepository.cs | 4 ++-- .../Data/SqliteUserRepository.cs | 4 ++-- 3 files changed, 21 insertions(+), 8 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 38e61605a7..966abfc414 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -123,6 +123,8 @@ namespace Emby.Server.Implementations { private SqliteUserRepository _userRepository; + private SqliteDisplayPreferencesRepository _displayPreferencesRepository; + /// /// Gets a value indicating whether this instance can self restart. /// @@ -757,8 +759,12 @@ namespace Emby.Server.Implementations UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager); serviceCollection.AddSingleton(UserDataManager); - var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager); - serviceCollection.AddSingleton(displayPreferencesRepo); + _displayPreferencesRepository = new SqliteDisplayPreferencesRepository( + LoggerFactory.CreateLogger(), + JsonSerializer, + ApplicationPaths, + FileSystemManager); + serviceCollection.AddSingleton(_displayPreferencesRepository); ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, LocalizationManager); serviceCollection.AddSingleton(ItemRepository); @@ -884,7 +890,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager)); - displayPreferencesRepo.Initialize(); + _displayPreferencesRepository.Initialize(); var userDataRepo = new SqliteUserDataRepository(LoggerFactory, ApplicationPaths); @@ -964,7 +970,10 @@ namespace Emby.Server.Implementations /// . private SqliteUserRepository GetUserRepository() { - var repo = new SqliteUserRepository(LoggerFactory, ApplicationPaths, JsonSerializer); + var repo = new SqliteUserRepository( + LoggerFactory.CreateLogger(), + ApplicationPaths, + JsonSerializer); repo.Initialize(); @@ -1911,8 +1920,12 @@ namespace Emby.Server.Implementations } _userRepository?.Dispose(); + _displayPreferencesRepository.Dispose(); } + _userRepository = null; + _displayPreferencesRepository = null; + _disposed = true; } } diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 87096e72f5..77f5d94792 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -21,8 +21,8 @@ namespace Emby.Server.Implementations.Data { private readonly IFileSystem _fileSystem; - public SqliteDisplayPreferencesRepository(ILoggerFactory loggerFactory, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IFileSystem fileSystem) - : base(loggerFactory.CreateLogger(nameof(SqliteDisplayPreferencesRepository))) + public SqliteDisplayPreferencesRepository(ILogger logger, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IFileSystem fileSystem) + : base(logger) { _jsonSerializer = jsonSerializer; _fileSystem = fileSystem; diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index de2354eefc..d6d250fb31 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -18,10 +18,10 @@ namespace Emby.Server.Implementations.Data private readonly IJsonSerializer _jsonSerializer; public SqliteUserRepository( - ILoggerFactory loggerFactory, + ILogger logger, IServerApplicationPaths appPaths, IJsonSerializer jsonSerializer) - : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) + : base(logger) { _jsonSerializer = jsonSerializer; -- cgit v1.2.3 From d62a3f0e5735347ecca0bf7e00c4388b3783e54b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 15 Aug 2019 00:00:21 +0200 Subject: Fix master --- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 4 ++-- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 010ad63846..a5bb47afbe 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -124,7 +124,7 @@ namespace Emby.Server.Implementations.Data } WriteConnection.Execute("PRAGMA temp_store=" + (int)TempStore); - + // Configuration and pragmas can affect VACUUM so it needs to be last. WriteConnection.Execute("VACUUM"); @@ -218,7 +218,7 @@ namespace Emby.Server.Implementations.Data WriteLock.Wait(); try { - WriteConnection.Dispose(); + WriteConnection?.Dispose(); } finally { diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index e872834779..ec7c026e59 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -43,7 +43,7 @@ - + -- cgit v1.2.3 From c699c546e4124da8c1978b86d1a424d24d3d77d6 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 16 Aug 2019 17:31:47 +0200 Subject: Improve LocalizationManager --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Localization/LocalizationManager.cs | 171 ++++++++++----------- MediaBrowser.Api/ItemUpdateService.cs | 4 +- MediaBrowser.Api/LocalizationService.cs | 5 +- MediaBrowser.Model/Globalization/CultureDto.cs | 1 + .../Globalization/ILocalizationManager.cs | 35 +++-- .../TV/TheTVDB/TvdbSeriesProvider.cs | 17 +- 7 files changed, 116 insertions(+), 119 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 68b02d4a22..0b3b81f943 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -746,7 +746,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ServerConfigurationManager); - LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory); + LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory.CreateLogger()); await LocalizationManager.LoadAll().ConfigureAwait(false); serviceCollection.AddSingleton(LocalizationManager); diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 8c49b64055..13cdc50ca4 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -17,43 +17,49 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Localization { /// - /// Class LocalizationManager + /// Class LocalizationManager. /// public class LocalizationManager : ILocalizationManager { - /// - /// The _configuration manager - /// - private readonly IServerConfigurationManager _configurationManager; + private const string DefaultCulture = "en-US"; + private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly; + private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; /// - /// The us culture + /// The _configuration manager. /// - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + private readonly IServerConfigurationManager _configurationManager; + private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; private readonly Dictionary> _allParentalRatings = new Dictionary>(StringComparer.OrdinalIgnoreCase); - private readonly IJsonSerializer _jsonSerializer; - private readonly ILogger _logger; - private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly; + private readonly ConcurrentDictionary> _dictionaries = + new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); + + private List _cultures; /// /// Initializes a new instance of the class. /// /// The configuration manager. /// The json serializer. - /// The logger factory + /// The logger. public LocalizationManager( IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, - ILoggerFactory loggerFactory) + ILogger logger) { _configurationManager = configurationManager; _jsonSerializer = jsonSerializer; - _logger = loggerFactory.CreateLogger(nameof(LocalizationManager)); + _logger = logger; } + /// + /// Loads all resources into memory. + /// + /// . public async Task LoadAll() { const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings."; @@ -82,9 +88,10 @@ namespace Emby.Server.Implementations.Localization string[] parts = line.Split(','); if (parts.Length == 2 - && int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value)) + && int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) { - dict.Add(parts[0], new ParentalRating { Name = parts[0], Value = value }); + var name = parts[0]; + dict.Add(name, new ParentalRating(name, value)); } #if DEBUG else @@ -101,16 +108,11 @@ namespace Emby.Server.Implementations.Localization await LoadCultures().ConfigureAwait(false); } - public string NormalizeFormKD(string text) - => text.Normalize(NormalizationForm.FormKD); - - private CultureDto[] _cultures; - /// /// Gets the cultures. /// - /// IEnumerable{CultureDto}. - public CultureDto[] GetCultures() + /// . + public IEnumerable GetCultures() => _cultures; private async Task LoadCultures() @@ -168,9 +170,10 @@ namespace Emby.Server.Implementations.Localization } } - _cultures = list.ToArray(); + _cultures = list; } + /// public CultureDto FindLanguageInfo(string language) => GetCultures() .FirstOrDefault(i => @@ -179,25 +182,19 @@ namespace Emby.Server.Implementations.Localization || i.ThreeLetterISOLanguageNames.Contains(language, StringComparer.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); - /// - /// Gets the countries. - /// - /// IEnumerable{CountryInfo}. - public Task GetCountries() - => _jsonSerializer.DeserializeFromStreamAsync( + /// + public IEnumerable GetCountries() + => _jsonSerializer.DeserializeFromStream>( _assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json")); - /// - /// Gets the parental ratings. - /// - /// IEnumerable{ParentalRating}. + /// public IEnumerable GetParentalRatings() => GetParentalRatingsDictionary().Values; /// /// Gets the parental ratings dictionary. /// - /// Dictionary{System.StringParentalRating}. + /// . private Dictionary GetParentalRatingsDictionary() { var countryCode = _configurationManager.Configuration.MetadataCountryCode; @@ -207,14 +204,14 @@ namespace Emby.Server.Implementations.Localization countryCode = "us"; } - return GetRatings(countryCode) ?? GetRatings("us"); + return GetRatings(countryCode) ?? GetRatings("us"); } /// /// Gets the ratings. /// /// The country code. - /// The ratings + /// The ratings. private Dictionary GetRatings(string countryCode) { _allParentalRatings.TryGetValue(countryCode, out var value); @@ -222,14 +219,7 @@ namespace Emby.Server.Implementations.Localization return value; } - private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; - /// - /// - /// Gets the rating level. - /// - /// Rating field - /// The rating level> public int? GetRatingLevel(string rating) { if (string.IsNullOrEmpty(rating)) @@ -277,6 +267,7 @@ namespace Emby.Server.Implementations.Localization return null; } + /// public bool HasUnicodeCategory(string value, UnicodeCategory category) { foreach (var chr in value) @@ -290,11 +281,13 @@ namespace Emby.Server.Implementations.Localization return false; } + /// public string GetLocalizedString(string phrase) { return GetLocalizedString(phrase, _configurationManager.Configuration.UICulture); } + /// public string GetLocalizedString(string phrase, string culture) { if (string.IsNullOrEmpty(culture)) @@ -317,12 +310,7 @@ namespace Emby.Server.Implementations.Localization return phrase; } - private const string DefaultCulture = "en-US"; - - private readonly ConcurrentDictionary> _dictionaries = - new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); - - public Dictionary GetLocalizationDictionary(string culture) + private Dictionary GetLocalizationDictionary(string culture) { if (string.IsNullOrEmpty(culture)) { @@ -332,8 +320,9 @@ namespace Emby.Server.Implementations.Localization const string prefix = "Core"; var key = prefix + culture; - return _dictionaries.GetOrAdd(key, - f => GetDictionary(prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); + return _dictionaries.GetOrAdd( + key, + f => GetDictionary(prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); } private async Task> GetDictionary(string prefix, string culture, string baseFilename) @@ -390,45 +379,45 @@ namespace Emby.Server.Implementations.Localization return culture + ".json"; } - public LocalizationOption[] GetLocalizationOptions() - => new LocalizationOption[] - { - new LocalizationOption("Arabic", "ar"), - new LocalizationOption("Bulgarian (Bulgaria)", "bg-BG"), - new LocalizationOption("Catalan", "ca"), - new LocalizationOption("Chinese Simplified", "zh-CN"), - new LocalizationOption("Chinese Traditional", "zh-TW"), - new LocalizationOption("Croatian", "hr"), - new LocalizationOption("Czech", "cs"), - new LocalizationOption("Danish", "da"), - new LocalizationOption("Dutch", "nl"), - new LocalizationOption("English (United Kingdom)", "en-GB"), - new LocalizationOption("English (United States)", "en-US"), - new LocalizationOption("French", "fr"), - new LocalizationOption("French (Canada)", "fr-CA"), - new LocalizationOption("German", "de"), - new LocalizationOption("Greek", "el"), - new LocalizationOption("Hebrew", "he"), - new LocalizationOption("Hungarian", "hu"), - new LocalizationOption("Italian", "it"), - new LocalizationOption("Kazakh", "kk"), - new LocalizationOption("Korean", "ko"), - new LocalizationOption("Lithuanian", "lt-LT"), - new LocalizationOption("Malay", "ms"), - new LocalizationOption("Norwegian Bokmål", "nb"), - new LocalizationOption("Persian", "fa"), - new LocalizationOption("Polish", "pl"), - new LocalizationOption("Portuguese (Brazil)", "pt-BR"), - new LocalizationOption("Portuguese (Portugal)", "pt-PT"), - new LocalizationOption("Russian", "ru"), - new LocalizationOption("Slovak", "sk"), - new LocalizationOption("Slovenian (Slovenia)", "sl-SI"), - new LocalizationOption("Spanish", "es"), - new LocalizationOption("Spanish (Argentina)", "es-AR"), - new LocalizationOption("Spanish (Mexico)", "es-MX"), - new LocalizationOption("Swedish", "sv"), - new LocalizationOption("Swiss German", "gsw"), - new LocalizationOption("Turkish", "tr") - }; + /// + public IEnumerable GetLocalizationOptions() + { + yield return new LocalizationOption("Arabic", "ar"); + yield return new LocalizationOption("Bulgarian (Bulgaria)", "bg-BG"); + yield return new LocalizationOption("Catalan", "ca"); + yield return new LocalizationOption("Chinese Simplified", "zh-CN"); + yield return new LocalizationOption("Chinese Traditional", "zh-TW"); + yield return new LocalizationOption("Croatian", "hr"); + yield return new LocalizationOption("Czech", "cs"); + yield return new LocalizationOption("Danish", "da"); + yield return new LocalizationOption("Dutch", "nl"); + yield return new LocalizationOption("English (United Kingdom)", "en-GB"); + yield return new LocalizationOption("English (United States)", "en-US"); + yield return new LocalizationOption("French", "fr"); + yield return new LocalizationOption("French (Canada)", "fr-CA"); + yield return new LocalizationOption("German", "de"); + yield return new LocalizationOption("Greek", "el"); + yield return new LocalizationOption("Hebrew", "he"); + yield return new LocalizationOption("Hungarian", "hu"); + yield return new LocalizationOption("Italian", "it"); + yield return new LocalizationOption("Kazakh", "kk"); + yield return new LocalizationOption("Korean", "ko"); + yield return new LocalizationOption("Lithuanian", "lt-LT"); + yield return new LocalizationOption("Malay", "ms"); + yield return new LocalizationOption("Norwegian Bokmål", "nb"); + yield return new LocalizationOption("Persian", "fa"); + yield return new LocalizationOption("Polish", "pl"); + yield return new LocalizationOption("Portuguese (Brazil)", "pt-BR"); + yield return new LocalizationOption("Portuguese (Portugal)", "pt-PT"); + yield return new LocalizationOption("Russian", "ru"); + yield return new LocalizationOption("Slovak", "sk"); + yield return new LocalizationOption("Slovenian (Slovenia)", "sl-SI"); + yield return new LocalizationOption("Spanish", "es"); + yield return new LocalizationOption("Spanish (Argentina)", "es-AR"); + yield return new LocalizationOption("Spanish (Mexico)", "es-MX"); + yield return new LocalizationOption("Swedish", "sv"); + yield return new LocalizationOption("Swiss German", "gsw"); + yield return new LocalizationOption("Turkish", "tr"); + } } } diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 825732888e..d6514d62e9 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -69,8 +69,8 @@ namespace MediaBrowser.Api { ParentalRatingOptions = _localizationManager.GetParentalRatings().ToArray(), ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(), - Countries = await _localizationManager.GetCountries(), - Cultures = _localizationManager.GetCultures() + Countries = _localizationManager.GetCountries().ToArray(), + Cultures = _localizationManager.GetCultures().ToArray() }; if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName) && diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs index eeff67e13c..3b2e18852a 100644 --- a/MediaBrowser.Api/LocalizationService.cs +++ b/MediaBrowser.Api/LocalizationService.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; @@ -82,9 +81,9 @@ namespace MediaBrowser.Api /// /// The request. /// System.Object. - public async Task Get(GetCountries request) + public object Get(GetCountries request) { - var result = await _localization.GetCountries(); + var result = _localization.GetCountries(); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Model/Globalization/CultureDto.cs b/MediaBrowser.Model/Globalization/CultureDto.cs index f229f20553..a213d4147f 100644 --- a/MediaBrowser.Model/Globalization/CultureDto.cs +++ b/MediaBrowser.Model/Globalization/CultureDto.cs @@ -38,6 +38,7 @@ namespace MediaBrowser.Model.Globalization { return vals[0]; } + return null; } } diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index a9ce60a2ab..91d946db83 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Globalization; -using System.Threading.Tasks; using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Globalization @@ -13,23 +12,26 @@ namespace MediaBrowser.Model.Globalization /// /// Gets the cultures. /// - /// IEnumerable{CultureDto}. - CultureDto[] GetCultures(); + /// . + IEnumerable GetCultures(); + /// /// Gets the countries. /// - /// IEnumerable{CountryInfo}. - Task GetCountries(); + /// . + IEnumerable GetCountries(); + /// /// Gets the parental ratings. /// - /// IEnumerable{ParentalRating}. + /// . IEnumerable GetParentalRatings(); + /// /// Gets the rating level. /// /// The rating. - /// System.Int32. + /// or null. int? GetRatingLevel(string rating); /// @@ -37,7 +39,7 @@ namespace MediaBrowser.Model.Globalization /// /// The phrase. /// The culture. - /// System.String. + /// . string GetLocalizedString(string phrase, string culture); /// @@ -50,13 +52,22 @@ namespace MediaBrowser.Model.Globalization /// /// Gets the localization options. /// - /// IEnumerable{LocalizatonOption}. - LocalizationOption[] GetLocalizationOptions(); - - string NormalizeFormKD(string text); + /// . + IEnumerable GetLocalizationOptions(); + /// + /// Checks if the string contains a character with the specified unicode category. + /// + /// The string. + /// The unicode category. + /// Wether or not the string contains a character with the specified unicode category. bool HasUnicodeCategory(string value, UnicodeCategory category); + /// + /// Returns the correct for the given language. + /// + /// The language. + /// The correct for the given language. CultureDto FindLanguageInfo(string language); } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index c739f3f494..1578e43412 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -285,7 +285,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB private string GetComparableName(string name) { name = name.ToLowerInvariant(); - name = _localizationManager.NormalizeFormKD(name); + name = name.Normalize(NormalizationForm.FormKD); var sb = new StringBuilder(); foreach (var c in name) { @@ -310,19 +310,16 @@ namespace MediaBrowser.Providers.TV.TheTVDB sb.Append(c); } } - name = sb.ToString(); - name = name.Replace(", the", ""); - name = name.Replace("the ", " "); - name = name.Replace(" the ", " "); + sb.Replace(", the", string.Empty).Replace("the ", " ").Replace(" the ", " "); - string prevName; + int prevLength; do { - prevName = name; - name = name.Replace(" ", " "); - } while (name.Length != prevName.Length); + prevLength = sb.Length; + sb.Replace(" ", " "); + } while (name.Length != prevLength); - return name.Trim(); + return sb.ToString().Trim(); } private void MapSeriesToResult(MetadataResult result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage) -- cgit v1.2.3 From dc662beefeb918b6ae967acb9849a2856cfc3672 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 16 Aug 2019 21:03:45 +0200 Subject: Add analysers to Emby.IsoMounting and enable TreatWarningsAsErrors --- .../Configuration/PluginConfiguration.cs | 3 + Emby.IsoMounting/IsoMounter/IsoMounter.csproj | 12 + Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs | 477 +++++++-------------- Emby.IsoMounting/IsoMounter/LinuxMount.cs | 73 ++-- Emby.IsoMounting/IsoMounter/Plugin.cs | 25 +- Emby.Server.Implementations/IO/IsoManager.cs | 26 +- .../SocketSharp/WebSocketSharpListener.cs | 2 +- MediaBrowser.Model/IO/IIsoManager.cs | 2 +- MediaBrowser.Model/IO/IIsoMounter.cs | 2 +- .../Plugins/BasePluginConfiguration.cs | 2 +- 10 files changed, 215 insertions(+), 409 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.IsoMounting/IsoMounter/Configuration/PluginConfiguration.cs b/Emby.IsoMounting/IsoMounter/Configuration/PluginConfiguration.cs index 4755e4e824..ca6f40cc4f 100644 --- a/Emby.IsoMounting/IsoMounter/Configuration/PluginConfiguration.cs +++ b/Emby.IsoMounting/IsoMounter/Configuration/PluginConfiguration.cs @@ -2,6 +2,9 @@ using MediaBrowser.Model.Plugins; namespace IsoMounter.Configuration { + /// + /// Class PluginConfiguration. + /// public class PluginConfiguration : BasePluginConfiguration { } diff --git a/Emby.IsoMounting/IsoMounter/IsoMounter.csproj b/Emby.IsoMounting/IsoMounter/IsoMounter.csproj index 0778b987bc..4fa07fbf17 100644 --- a/Emby.IsoMounting/IsoMounter/IsoMounter.csproj +++ b/Emby.IsoMounting/IsoMounter/IsoMounter.csproj @@ -13,6 +13,18 @@ netstandard2.0 false true + true + + + + + + + + + + + ../../jellyfin.ruleset diff --git a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs index 2f0003be88..48cb2e1d53 100644 --- a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs +++ b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs @@ -1,9 +1,10 @@ using System; +using System.Diagnostics; +using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; @@ -11,441 +12,274 @@ using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace IsoMounter { + /// + /// The ISO manager implementation for Linux. + /// public class LinuxIsoManager : IIsoMounter { - [DllImport("libc", SetLastError = true)] - static extern uint getuid(); + private const string MountCommand = "mount"; + private const string UnmountCommand = "umount"; + private const string SudoCommand = "sudo"; - #region Private Fields - - private readonly bool ExecutablesAvailable; private readonly ILogger _logger; - private readonly string MountCommand; - private readonly string MountPointRoot; - private readonly IProcessFactory ProcessFactory; - private readonly string SudoCommand; - private readonly string UmountCommand; - - #endregion + private readonly string _mountPointRoot; - #region Constructor(s) - - public LinuxIsoManager(ILogger logger, IProcessFactory processFactory) + /// + /// Initializes a new instance of the class. + /// + /// The logger. + public LinuxIsoManager(ILogger logger) { _logger = logger; - ProcessFactory = processFactory; - MountPointRoot = Path.DirectorySeparatorChar + "tmp" + Path.DirectorySeparatorChar + "Emby"; + _mountPointRoot = Path.DirectorySeparatorChar + "tmp" + Path.DirectorySeparatorChar + "Emby"; _logger.LogDebug( "[{0}] System PATH is currently set to [{1}].", Name, - Environment.GetEnvironmentVariable("PATH") ?? "" - ); + Environment.GetEnvironmentVariable("PATH") ?? string.Empty); _logger.LogDebug( "[{0}] System path separator is [{1}].", Name, - Path.PathSeparator - ); + Path.PathSeparator); _logger.LogDebug( "[{0}] Mount point root is [{1}].", Name, - MountPointRoot - ); - - // - // Get the location of the executables we need to support mounting/unmounting ISO images. - // - - SudoCommand = GetFullPathForExecutable("sudo"); - - _logger.LogInformation( - "[{0}] Using version of [sudo] located at [{1}].", - Name, - SudoCommand - ); - - MountCommand = GetFullPathForExecutable("mount"); - - _logger.LogInformation( - "[{0}] Using version of [mount] located at [{1}].", - Name, - MountCommand - ); - - UmountCommand = GetFullPathForExecutable("umount"); - - _logger.LogInformation( - "[{0}] Using version of [umount] located at [{1}].", - Name, - UmountCommand - ); - - if (!string.IsNullOrEmpty(SudoCommand) && !string.IsNullOrEmpty(MountCommand) && !string.IsNullOrEmpty(UmountCommand)) - { - ExecutablesAvailable = true; - } - else - { - ExecutablesAvailable = false; - } - + _mountPointRoot); } - #endregion - - #region Interface Implementation for IIsoMounter - - public bool IsInstalled => true; - + /// public string Name => "LinuxMount"; - public bool RequiresInstallation => false; +#pragma warning disable SA1300 +#pragma warning disable SA1400 + [DllImport("libc", SetLastError = true)] + static extern uint getuid(); + +#pragma warning restore SA1300 +#pragma warning restore SA1400 + /// public bool CanMount(string path) { - if (OperatingSystem.Id != OperatingSystemId.Linux) { return false; } + _logger.LogInformation( - "[{0}] Checking we can attempt to mount [{1}], Extension = [{2}], Operating System = [{3}], Executables Available = [{4}].", + "[{0}] Checking we can attempt to mount [{1}], Extension = [{2}], Operating System = [{3}].", Name, path, Path.GetExtension(path), - OperatingSystem.Name, - ExecutablesAvailable - ); + OperatingSystem.Name); - if (ExecutablesAvailable) - { - return string.Equals(Path.GetExtension(path), ".iso", StringComparison.OrdinalIgnoreCase); - } - else - { - return false; - } - } - - public Task Install(CancellationToken cancellationToken) - { - return Task.FromResult(false); + return string.Equals(Path.GetExtension(path), ".iso", StringComparison.OrdinalIgnoreCase); } + /// public Task Mount(string isoPath, CancellationToken cancellationToken) { - if (MountISO(isoPath, out LinuxMount mountedISO)) - { - return Task.FromResult(mountedISO); - } - else + string cmdArguments; + string cmdFilename; + string mountPoint = Path.Combine(_mountPointRoot, Guid.NewGuid().ToString()); + + if (string.IsNullOrEmpty(isoPath)) { - throw new IOException(string.Format( - "An error occurred trying to mount image [$0].", - isoPath - )); + throw new ArgumentNullException(nameof(isoPath)); } - } - #endregion - - #region Interface Implementation for IDisposable - - // Flag: Has Dispose already been called? - private bool disposed = false; - - public void Dispose() - { - - // Dispose of unmanaged resources. - Dispose(true); - - // Suppress finalization. - GC.SuppressFinalize(this); + _logger.LogInformation( + "[{Name}] Attempting to mount [{Path}].", + Name, + isoPath); - } + _logger.LogDebug( + "[{Name}] ISO will be mounted at [{Path}].", + Name, + mountPoint); - protected virtual void Dispose(bool disposing) - { + try + { + Directory.CreateDirectory(mountPoint); + } + catch (UnauthorizedAccessException ex) + { + throw new IOException("Unable to create mount point(Permission denied) for " + isoPath, ex); + } + catch (Exception ex) + { + throw new IOException("Unable to create mount point for " + isoPath, ex); + } - if (disposed) + if (GetUID() == 0) + { + cmdFilename = MountCommand; + cmdArguments = string.Format( + CultureInfo.InvariantCulture, + "\"{0}\" \"{1}\"", + isoPath, + mountPoint); + } + else { - return; + cmdFilename = SudoCommand; + cmdArguments = string.Format( + CultureInfo.InvariantCulture, + "\"{0}\" \"{1}\" \"{2}\"", + MountCommand, + isoPath, + mountPoint); } - _logger.LogInformation( - "[{0}] Disposing [{1}].", + _logger.LogDebug( + "[{0}] Mount command [{1}], mount arguments [{2}].", Name, - disposing - ); + cmdFilename, + cmdArguments); - if (disposing) + int exitcode = ExecuteCommand(cmdFilename, cmdArguments); + if (exitcode == 0) { + _logger.LogInformation( + "[{0}] ISO mount completed successfully.", + Name); - // - // Free managed objects here. - // - + return Task.FromResult(new LinuxMount(this, isoPath, mountPoint)); } - // - // Free any unmanaged objects here. - // - - disposed = true; - - } - - #endregion - - #region Private Methods - - private string GetFullPathForExecutable(string name) - { + _logger.LogInformation( + "[{0}] ISO mount completed with errors.", + Name); - foreach (string test in (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator)) + try { - string path = test.Trim(); - - if (!string.IsNullOrEmpty(path) && File.Exists(path = Path.Combine(path, name))) - { - return Path.GetFullPath(path); - } + Directory.Delete(mountPoint, false); + } + catch (Exception ex) + { + _logger.LogError(ex, "[{Name}] Unhandled exception removing mount point.", Name); + throw; } - return string.Empty; + throw new ExternalException("Mount command failed", exitcode); } private uint GetUID() { - var uid = getuid(); _logger.LogDebug( "[{0}] GetUserId() returned [{2}].", Name, - uid - ); + uid); return uid; - } - private bool ExecuteCommand(string cmdFilename, string cmdArguments) + private int ExecuteCommand(string cmdFilename, string cmdArguments) { - - bool processFailed = false; - - var process = ProcessFactory.Create( - new ProcessOptions - { - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - FileName = cmdFilename, - Arguments = cmdArguments, - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true - } - ); + var startInfo = new ProcessStartInfo + { + FileName = cmdFilename, + Arguments = cmdArguments, + UseShellExecute = false, + CreateNoWindow = true, + ErrorDialog = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + var process = new Process() + { + StartInfo = startInfo + }; try { process.Start(); - //StreamReader outputReader = process.StandardOutput.; - //StreamReader errorReader = process.StandardError; - _logger.LogDebug( "[{Name}] Standard output from process is [{Error}].", Name, - process.StandardOutput.ReadToEnd() - ); + process.StandardOutput.ReadToEnd()); _logger.LogDebug( "[{Name}] Standard error from process is [{Error}].", Name, - process.StandardError.ReadToEnd() - ); + process.StandardError.ReadToEnd()); + + return process.ExitCode; } catch (Exception ex) { - processFailed = true; _logger.LogDebug(ex, "[{Name}] Unhandled exception executing command.", Name); + throw; } - - if (!processFailed && process.ExitCode == 0) + finally { - return true; + process?.Dispose(); } - else - { - return false; - } - } - private bool MountISO(string isoPath, out LinuxMount mountedISO) + /// + /// Unmounts the specified mount. + /// + /// The mount. + internal void OnUnmount(LinuxMount mount) { - - string cmdArguments; - string cmdFilename; - string mountPoint = Path.Combine(MountPointRoot, Guid.NewGuid().ToString()); - - if (!string.IsNullOrEmpty(isoPath)) - { - - _logger.LogInformation( - "[{Name}] Attempting to mount [{Path}].", - Name, - isoPath - ); - - _logger.LogDebug( - "[{Name}] ISO will be mounted at [{Path}].", - Name, - mountPoint - ); - - } - else + if (mount == null) { - - throw new ArgumentNullException(nameof(isoPath)); - - } - - try - { - Directory.CreateDirectory(mountPoint); - } - catch (UnauthorizedAccessException) - { - throw new IOException("Unable to create mount point(Permission denied) for " + isoPath); - } - catch (Exception) - { - throw new IOException("Unable to create mount point for " + isoPath); - } - - if (GetUID() == 0) - { - cmdFilename = MountCommand; - cmdArguments = string.Format("\"{0}\" \"{1}\"", isoPath, mountPoint); - } - else - { - cmdFilename = SudoCommand; - cmdArguments = string.Format("\"{0}\" \"{1}\" \"{2}\"", MountCommand, isoPath, mountPoint); + throw new ArgumentNullException(nameof(mount)); } - _logger.LogDebug( - "[{0}] Mount command [{1}], mount arguments [{2}].", + _logger.LogInformation( + "[{0}] Attempting to unmount ISO [{1}] mounted on [{2}].", Name, - cmdFilename, - cmdArguments - ); - - if (ExecuteCommand(cmdFilename, cmdArguments)) - { - - _logger.LogInformation( - "[{0}] ISO mount completed successfully.", - Name - ); - - mountedISO = new LinuxMount(this, isoPath, mountPoint); - - } - else - { - - _logger.LogInformation( - "[{0}] ISO mount completed with errors.", - Name - ); - - try - { - Directory.Delete(mountPoint, false); - } - catch (Exception ex) - { - _logger.LogInformation(ex, "[{Name}] Unhandled exception removing mount point.", Name); - } - - mountedISO = null; - - } - - return mountedISO != null; - - } - - private void UnmountISO(LinuxMount mount) - { + mount.IsoPath, + mount.MountedPath); string cmdArguments; string cmdFilename; - if (mount != null) - { - - _logger.LogInformation( - "[{0}] Attempting to unmount ISO [{1}] mounted on [{2}].", - Name, - mount.IsoPath, - mount.MountedPath - ); - - } - else - { - - throw new ArgumentNullException(nameof(mount)); - - } - if (GetUID() == 0) { - cmdFilename = UmountCommand; - cmdArguments = string.Format("\"{0}\"", mount.MountedPath); + cmdFilename = UnmountCommand; + cmdArguments = string.Format( + CultureInfo.InvariantCulture, + "\"{0}\"", + mount.MountedPath); } else { cmdFilename = SudoCommand; - cmdArguments = string.Format("\"{0}\" \"{1}\"", UmountCommand, mount.MountedPath); + cmdArguments = string.Format( + CultureInfo.InvariantCulture, + "\"{0}\" \"{1}\"", + UnmountCommand, + mount.MountedPath); } _logger.LogDebug( "[{0}] Umount command [{1}], umount arguments [{2}].", Name, cmdFilename, - cmdArguments - ); + cmdArguments); - if (ExecuteCommand(cmdFilename, cmdArguments)) + int exitcode = ExecuteCommand(cmdFilename, cmdArguments); + if (exitcode == 0) { - _logger.LogInformation( "[{0}] ISO unmount completed successfully.", - Name - ); - + Name); } else { - _logger.LogInformation( "[{0}] ISO unmount completed with errors.", - Name - ); - + Name); } try @@ -454,24 +288,11 @@ namespace IsoMounter } catch (Exception ex) { - _logger.LogInformation(ex, "[{Name}] Unhandled exception removing mount point.", Name); + _logger.LogError(ex, "[{Name}] Unhandled exception removing mount point.", Name); + throw; } - } - - #endregion - - #region Internal Methods - - internal void OnUnmount(LinuxMount mount) - { - - UnmountISO(mount); + throw new ExternalException("Mount command failed", exitcode); } - - #endregion - } - } - diff --git a/Emby.IsoMounting/IsoMounter/LinuxMount.cs b/Emby.IsoMounting/IsoMounter/LinuxMount.cs index b8636822b6..ccad8ce20e 100644 --- a/Emby.IsoMounting/IsoMounter/LinuxMount.cs +++ b/Emby.IsoMounting/IsoMounter/LinuxMount.cs @@ -3,81 +3,56 @@ using MediaBrowser.Model.IO; namespace IsoMounter { + /// + /// Class LinuxMount. + /// internal class LinuxMount : IIsoMount { + private readonly LinuxIsoManager _linuxIsoManager; - #region Private Fields - - private readonly LinuxIsoManager linuxIsoManager; - - #endregion - - #region Constructor(s) + private bool _disposed = false; + /// + /// Initializes a new instance of the class. + /// + /// The ISO manager that mounted this ISO file. + /// The path to the ISO file. + /// The folder the ISO is mounted in. internal LinuxMount(LinuxIsoManager isoManager, string isoPath, string mountFolder) { - - linuxIsoManager = isoManager; + _linuxIsoManager = isoManager; IsoPath = isoPath; MountedPath = mountFolder; - } - #endregion - - #region Interface Implementation for IDisposable + /// + public string IsoPath { get; } - // Flag: Has Dispose already been called? - private bool disposed = false; + /// + public string MountedPath { get; } + /// public void Dispose() { - - // Dispose of unmanaged resources. Dispose(true); - - // Suppress finalization. GC.SuppressFinalize(this); - } + /// + /// Releases the unmanaged resources and disposes of the managed resources used. + /// + /// Whether or not the managed resources should be disposed. protected virtual void Dispose(bool disposing) { - - if (disposed) + if (_disposed) { return; } - if (disposing) - { - - // - // Free managed objects here. - // - - linuxIsoManager.OnUnmount(this); - - } - - // - // Free any unmanaged objects here. - // - - disposed = true; + _linuxIsoManager.OnUnmount(this); + _disposed = true; } - - #endregion - - #region Interface Implementation for IIsoMount - - public string IsoPath { get; private set; } - public string MountedPath { get; private set; } - - #endregion - } - } diff --git a/Emby.IsoMounting/IsoMounter/Plugin.cs b/Emby.IsoMounting/IsoMounter/Plugin.cs index f45b39d3e4..433294d745 100644 --- a/Emby.IsoMounting/IsoMounter/Plugin.cs +++ b/Emby.IsoMounting/IsoMounter/Plugin.cs @@ -6,25 +6,28 @@ using MediaBrowser.Model.Serialization; namespace IsoMounter { + /// + /// The LinuxMount plugin class. + /// public class Plugin : BasePlugin { - public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) + /// + /// Initializes a new instance of the class. + /// + /// The application paths. + /// The XML serializer. + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) { } - private Guid _id = new Guid("4682DD4C-A675-4F1B-8E7C-79ADF137A8F8"); - public override Guid Id => _id; + /// + public override Guid Id { get; } = new Guid("4682DD4C-A675-4F1B-8E7C-79ADF137A8F8"); - /// - /// Gets the name of the plugin - /// - /// The name. + /// public override string Name => "Iso Mounter"; - /// - /// Gets the description. - /// - /// The description. + /// public override string Description => "Mount and stream ISO contents"; } } diff --git a/Emby.Server.Implementations/IO/IsoManager.cs b/Emby.Server.Implementations/IO/IsoManager.cs index f0a15097c9..94e92c2a6a 100644 --- a/Emby.Server.Implementations/IO/IsoManager.cs +++ b/Emby.Server.Implementations/IO/IsoManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -8,12 +9,12 @@ using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.IO { /// - /// Class IsoManager + /// Class IsoManager. /// public class IsoManager : IIsoManager { /// - /// The _mounters + /// The _mounters. /// private readonly List _mounters = new List(); @@ -22,9 +23,7 @@ namespace Emby.Server.Implementations.IO /// /// The iso path. /// The cancellation token. - /// IsoMount. - /// isoPath - /// + /// . public Task Mount(string isoPath, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(isoPath)) @@ -36,7 +35,11 @@ namespace Emby.Server.Implementations.IO if (mounter == null) { - throw new ArgumentException(string.Format("No mounters are able to mount {0}", isoPath)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "No mounters are able to mount {0}", + isoPath)); } return mounter.Mount(isoPath, cancellationToken); @@ -60,16 +63,5 @@ namespace Emby.Server.Implementations.IO { _mounters.AddRange(mounters); } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - foreach (var mounter in _mounters) - { - mounter.Dispose(); - } - } } } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index dd313b3363..e93bff1244 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.SocketSharp /// /// Releases the unmanaged resources and disposes of the managed resources used. /// - /// Whether or not the managed resources should be disposed + /// Whether or not the managed resources should be disposed. protected virtual void Dispose(bool disposing) { if (_disposed) diff --git a/MediaBrowser.Model/IO/IIsoManager.cs b/MediaBrowser.Model/IO/IIsoManager.cs index 24b6e5f054..eb0cb4bfbb 100644 --- a/MediaBrowser.Model/IO/IIsoManager.cs +++ b/MediaBrowser.Model/IO/IIsoManager.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Model.IO { - public interface IIsoManager : IDisposable + public interface IIsoManager { /// /// Mounts the specified iso path. diff --git a/MediaBrowser.Model/IO/IIsoMounter.cs b/MediaBrowser.Model/IO/IIsoMounter.cs index f0153a9280..766a9e4e6c 100644 --- a/MediaBrowser.Model/IO/IIsoMounter.cs +++ b/MediaBrowser.Model/IO/IIsoMounter.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Model.IO { - public interface IIsoMounter : IDisposable + public interface IIsoMounter { /// /// Mounts the specified iso path. diff --git a/MediaBrowser.Model/Plugins/BasePluginConfiguration.cs b/MediaBrowser.Model/Plugins/BasePluginConfiguration.cs index 39db22133e..ac540782cc 100644 --- a/MediaBrowser.Model/Plugins/BasePluginConfiguration.cs +++ b/MediaBrowser.Model/Plugins/BasePluginConfiguration.cs @@ -1,7 +1,7 @@ namespace MediaBrowser.Model.Plugins { /// - /// Class BasePluginConfiguration + /// Class BasePluginConfiguration. /// public class BasePluginConfiguration { -- cgit v1.2.3 From 8d3b5c851ded74b9f34eb2cd963187761a3f6f61 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 9 Jun 2019 22:08:01 +0200 Subject: Improvements to UserManager --- Emby.Notifications/NotificationManager.cs | 2 +- Emby.Server.Implementations/ApplicationHost.cs | 3 +- .../Data/SqliteUserDataRepository.cs | 4 +- .../EntryPoints/RefreshUsersMetadata.cs | 4 +- .../Library/DefaultPasswordResetProvider.cs | 44 +-- Emby.Server.Implementations/Library/UserManager.cs | 358 ++++++++++----------- .../Session/SessionManager.cs | 8 +- MediaBrowser.Api/UserService.cs | 11 +- .../Authentication/AuthenticationException.cs | 1 + MediaBrowser.Controller/Entities/User.cs | 66 +--- MediaBrowser.Controller/Library/IUserManager.cs | 10 +- 11 files changed, 237 insertions(+), 274 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs index a767e541e1..eecbbea071 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 0b3b81f943..c390ba635d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -770,7 +770,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(), _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager); + serviceCollection.AddSingleton(UserManager); LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager); diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 4035bb99d1..9d4855bcf3 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 users) { var userIdsWithUserData = GetAllUserIdsWithUserData(db); diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs index b7565adec4..b2328121e3 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 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 c7044820c7..fa6bbcf91c 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; } + /// + public string Name => "Default Password Reset Provider"; + + /// + public bool IsEnabled => true; + + /// public async Task RedeemPasswordResetPin(string pin) { SerializablePasswordReset spr; - HashSet usersreset = new HashSet(); - foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) + List usersreset = new List(); + 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 } } + /// public async Task 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 c8c8a108d5..0865278831 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; @@ -40,35 +39,20 @@ namespace Emby.Server.Implementations.Library /// public class UserManager : IUserManager { - /// - /// Gets the users. - /// - /// The users. - public IEnumerable Users => _users; - - private User[] _users; - /// /// The _logger /// private readonly ILogger _logger; - /// - /// Gets or sets the configuration manager. - /// - /// The configuration manager. - private IServerConfigurationManager ConfigurationManager { get; set; } + private readonly object _policySyncLock = new object(); /// /// Gets the active user repository /// /// The user repository. - private IUserRepository UserRepository { get; set; } - public event EventHandler> UserPasswordChanged; - + private readonly IUserRepository _userRepository; private readonly IXmlSerializer _xmlSerializer; private readonly IJsonSerializer _jsonSerializer; - private readonly INetworkManager _networkManager; private readonly Func _imageProcessorFactory; @@ -76,6 +60,8 @@ namespace Emby.Server.Implementations.Library private readonly IServerApplicationHost _appHost; private readonly IFileSystem _fileSystem; + private ConcurrentDictionary _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 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(); + _users = null; + } + + public event EventHandler> UserPasswordChanged; + + /// + /// Occurs when [user updated]. + /// + public event EventHandler> UserUpdated; + + public event EventHandler> UserPolicyUpdated; + + public event EventHandler> UserConfigurationUpdated; + + public event EventHandler> UserLockedOut; + + public event EventHandler> UserCreated; + + /// + /// Occurs when [user deleted]. + /// + public event EventHandler> UserDeleted; + + /// + public IEnumerable Users => _users.Values; + + /// + public IEnumerable UsersIds => _users.Keys; + + /// + /// Called when [user updated]. + /// + /// The user. + private void OnUserUpdated(User user) + { + UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + + /// + /// Called when [user deleted]. + /// + /// The user. + private void OnUserDeleted(User user) + { + UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); } public NameIdPair[] GetAuthenticationProviders() @@ -137,7 +165,7 @@ namespace Emby.Server.Implementations.Library .ToArray(); } - public void AddParts(IEnumerable authenticationProviders,IEnumerable passwordResetProviders) + public void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders) { _authenticationProviders = authenticationProviders.ToArray(); @@ -150,54 +178,21 @@ namespace Emby.Server.Implementations.Library _defaultPasswordResetProvider = passwordResetProviders.OfType().First(); } - #region UserUpdated Event /// - /// Occurs when [user updated]. - /// - public event EventHandler> UserUpdated; - public event EventHandler> UserPolicyUpdated; - public event EventHandler> UserConfigurationUpdated; - public event EventHandler> UserLockedOut; - - /// - /// Called when [user updated]. - /// - /// The user. - private void OnUserUpdated(User user) - { - UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - #region UserDeleted Event - /// - /// Occurs when [user deleted]. - /// - public event EventHandler> UserDeleted; - /// - /// Called when [user deleted]. - /// - /// The user. - private void OnUserDeleted(User user) - { - UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - /// - /// Gets a User by Id + /// Gets a User by Id. /// /// The id. /// User. - /// + /// 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; } /// @@ -206,15 +201,13 @@ namespace Emby.Server.Implementations.Library /// The identifier. /// User. 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 } /// - /// Loads the users from the repository + /// Loads the users from the repository. /// - /// IEnumerable{User}. - private List 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( + users.Select(x => new KeyValuePair(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 }; + _users = new ConcurrentDictionary(); + _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> UserCreated; - - private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); - /// /// Creates the user. /// @@ -751,7 +754,7 @@ namespace Emby.Server.Implementations.Library /// User. /// name /// - public async Task 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 { Argument = user }, _logger); + EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); - return user; - } - finally - { - _userListLock.Release(); - } + return user; } /// @@ -799,57 +791,59 @@ namespace Emby.Server.Implementations.Library /// Task. /// user /// - 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); } /// @@ -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/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 0347100a48..61329160ae 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 fa70a52aaf..21a94a4e08 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; } /// @@ -503,9 +503,14 @@ namespace MediaBrowser.Api } } + /// + /// Posts the specified request. + /// + /// The request. + /// System.Object. public async Task 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 045cbcdaed..62eca3ea9f 100644 --- a/MediaBrowser.Controller/Authentication/AuthenticationException.cs +++ b/MediaBrowser.Controller/Authentication/AuthenticationException.cs @@ -1,4 +1,5 @@ using System; + namespace MediaBrowser.Controller.Authentication { /// diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 968d725798..7d245d4aab 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; } - - /// - /// From now on all user paths will be Id-based. - /// This is for backwards compatibility. - /// - public bool UsesIdForConfigurationPath { get; set; } /// /// Gets or sets the password. @@ -31,7 +24,6 @@ namespace MediaBrowser.Controller.Entities /// The password. 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 /// 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 7f73708931..bbedc04425 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -22,6 +22,12 @@ namespace MediaBrowser.Controller.Library /// The users. IEnumerable Users { get; } + /// + /// Gets the user ids. + /// + /// The users ids. + IEnumerable UsersIds { get; } + /// /// Occurs when [user updated]. /// @@ -92,7 +98,7 @@ namespace MediaBrowser.Controller.Library /// User. /// name /// - Task CreateUser(string name); + User CreateUser(string name); /// /// Deletes the user. @@ -101,7 +107,7 @@ namespace MediaBrowser.Controller.Library /// Task. /// user /// - Task DeleteUser(User user); + void DeleteUser(User user); /// /// Resets the password. -- cgit v1.2.3 From 237db8ae92ab33885d6b0f9b024bbe38f3552c3b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 7 Jul 2019 16:39:35 +0200 Subject: Clean up livestreaming code --- Emby.Dlna/PlayTo/SsdpHttpClient.cs | 36 ++-- Emby.Server.Implementations/ApplicationHost.cs | 24 +-- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 184 +++++++++++---------- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 115 +++++++------ .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 77 ++++----- .../LiveTv/TunerHosts/LiveStream.cs | 78 +++++---- .../LiveTv/TunerHosts/M3UTunerHost.cs | 19 ++- .../LiveTv/TunerHosts/SharedHttpStream.cs | 21 ++- MediaBrowser.Model/IO/StreamDefaults.cs | 2 +- 9 files changed, 295 insertions(+), 261 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 217ea3a4bc..66c634150a 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 SendCommandAsync(string baseUrl, + public async Task 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.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0b3b81f943..0002554720 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; /// @@ -325,14 +323,6 @@ namespace Emby.Server.Implementations /// The installation manager. protected IInstallationManager InstallationManager { get; private set; } - /// - /// Gets or sets the zip client. - /// - /// The zip client. - 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; } - /// /// Registers resources that classes will depend on /// @@ -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(this); serviceCollection.AddSingleton(ApplicationPaths); @@ -836,8 +821,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/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index ed254accb0..85754ca8ba 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> 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>(stream).ConfigureAwait(false) ?? new List(); - 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>(stream).ConfigureAwait(false) ?? new List(); - 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(stream).ConfigureAwait(false); + var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync(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(); - 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 c195524285..0238ee4587 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -14,7 +14,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { public interface IHdHomerunChannelCommands { - IEnumerable> GetCommands(); + IEnumerable<(string, string)> GetCommands(); } public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands @@ -33,16 +33,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - public IEnumerable> GetCommands() + public IEnumerable<(string, string)> GetCommands() { - var commands = new List>(); - 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,23 +58,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _profile = profile; } - public IEnumerable> GetCommands() + public IEnumerable<(string, string)> GetCommands() { - var commands = new List>(); - 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; } } @@ -87,19 +85,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 +99,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { _tcpClient = null; - var task = StopStreaming(socket); - Task.WaitAll(task); + StopStreaming(socket).GetAwaiter().GetResult(); } } } @@ -174,19 +164,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun 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 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; @@ -199,7 +189,7 @@ 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 +221,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun byte[] buffer = ArrayPool.Shared.Rent(8192); try { - foreach (Tuple 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 +254,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.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 { @@ -316,7 +304,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun int messageLength = byteName.Length + byteValue.Length + 12; if (lockkey.HasValue) + { messageLength += 6; + } var message = new byte[messageLength]; @@ -324,21 +314,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++] = (byte)4; var lockKeyBytes = BitConverter.GetBytes(lockkey.Value); if (flipEndian) + { Array.Reverse(lockKeyBytes); + } + Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4); offset += 4; } @@ -346,7 +335,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 +367,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 +382,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 +392,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 +506,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 1d79a5f96e..cb085fff88 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,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } var udpClient = _socketFactory.CreateUdpSocket(localPort); - var hdHomerunManager = new HdHomerunManager(Logger); + var hdHomerunManager = new HdHomerunManager(); try { @@ -103,7 +106,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var taskCompletionSource = new TaskCompletionSource(); - 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 +156,43 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun }); } - private static void Resolve(TaskCompletionSource openTaskCompletionSource) - { - Task.Run(() => - { - openTaskCompletionSource.TrySetResult(true); - }); - } - - private const int RtpHeaderBytes = 12; - private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource 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.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.Shared.Return(buffer); + } } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index b4395e2e1d..d12c96392d 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 6c5c80827d..a02a9ade49 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 7de9931c76..bee4fcd59a 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.DefaultFileStreamBufferSize, + () => 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 { TempFilePath }).ConfigureAwait(false); }); diff --git a/MediaBrowser.Model/IO/StreamDefaults.cs b/MediaBrowser.Model/IO/StreamDefaults.cs index bef20e74fe..1dc29e06ef 100644 --- a/MediaBrowser.Model/IO/StreamDefaults.cs +++ b/MediaBrowser.Model/IO/StreamDefaults.cs @@ -13,6 +13,6 @@ namespace MediaBrowser.Model.IO /// /// The default file stream buffer size /// - public const int DefaultFileStreamBufferSize = 81920; + public const int DefaultFileStreamBufferSize = 4096; } } -- cgit v1.2.3 From 4b37caa63adf9d356eba6e6d240aeed5c8d36978 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 8 Jul 2019 13:28:47 +0200 Subject: Update SharedHttpStream.cs --- Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index bee4fcd59a..c6e894560d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts await StreamHelper.CopyToAsync( stream, fileStream, - StreamDefaults.DefaultFileStreamBufferSize, + StreamDefaults.DefaultCopyToBufferSize, () => Resolve(openTaskCompletionSource), cancellationToken).ConfigureAwait(false); } -- cgit v1.2.3 From 1bc9b42c576ae4d029f39d880d4c9a7827e0c96f Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 16 Aug 2019 21:18:37 +0200 Subject: More fixes --- .../LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs | 15 ++++++++++----- .../LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 8 +++++++- 2 files changed, 17 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 0238ee4587..3699b988c6 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,7 +9,6 @@ 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 { @@ -78,6 +78,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public class HdHomerunManager : IDisposable { public const int HdHomeRunPort = 65001; + // Message constants private const byte GetSetName = 3; private const byte GetSetValue = 4; @@ -163,6 +164,7 @@ 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 _)) { @@ -175,6 +177,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun 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 _)) { @@ -188,6 +191,7 @@ 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 _)) { @@ -226,6 +230,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun 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 _)) { @@ -276,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]; @@ -299,8 +304,8 @@ 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) @@ -321,7 +326,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun if (lockkey.HasValue) { message[offset++] = GetSetLockkey; - message[offset++] = (byte)4; + message[offset++] = 4; var lockKeyBytes = BitConverter.GetBytes(lockkey.Value); if (flipEndian) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index cb085fff88..fbbab07f8b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -88,7 +88,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun 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) { -- cgit v1.2.3 From e7c05dcfaf5b06a98e89c6eee6e8ccb47c44fdf1 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 18 Aug 2019 17:22:45 +0200 Subject: Speed up BaseItem deserialization --- .../Data/SqliteItemRepository.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index bb4c34f02f..a29ef769d3 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1298,18 +1298,13 @@ namespace Emby.Server.Implementations.Data if (TypeRequiresDeserialization(type)) { - using (var stream = new MemoryStream(reader[1].ToBlob())) + try { - stream.Position = 0; - - try - { - item = _jsonSerializer.DeserializeFromStream(stream, type) as BaseItem; - } - catch (SerializationException ex) - { - Logger.LogError(ex, "Error deserializing item"); - } + item = _jsonSerializer.DeserializeFromString(reader[1].ToString(), type) as BaseItem; + } + catch (SerializationException ex) + { + Logger.LogError(ex, "Error deserializing item"); } } -- cgit v1.2.3 From f48eaccc5105f0af8f320e0406b95ff868733a50 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 18 Aug 2019 17:32:41 +0200 Subject: Use reader.GetString instead of indexing --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index a29ef769d3..3ca0728fe3 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1300,7 +1300,7 @@ namespace Emby.Server.Implementations.Data { try { - item = _jsonSerializer.DeserializeFromString(reader[1].ToString(), type) as BaseItem; + item = _jsonSerializer.DeserializeFromString(reader.GetString(1), type) as BaseItem; } catch (SerializationException ex) { -- cgit v1.2.3 From 24fac4b19172eae6a46208d712de09ac97e59d07 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 18 Aug 2019 20:12:25 +0200 Subject: Fix UserNotFoundError --- Emby.Server.Implementations/Library/UserManager.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 0865278831..a7ea13ca61 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -558,6 +558,7 @@ namespace Emby.Server.Implementations.Library { _users = new ConcurrentDictionary( users.Select(x => new KeyValuePair(x.Id, x))); + return; } var defaultName = Environment.UserName; -- cgit v1.2.3 From f70a63d5759c5c9a3e0127a89763a3f644970634 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 18 Aug 2019 22:05:06 +0200 Subject: Return DB asap --- .../Activity/ActivityRepository.cs | 134 +++--- .../Data/SqliteDisplayPreferencesRepository.cs | 61 ++- .../Data/SqliteItemRepository.cs | 467 +++++++++++---------- .../Security/AuthenticationRepository.cs | 85 ++-- 4 files changed, 373 insertions(+), 374 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index 91371b8336..541b23afd4 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -155,94 +155,100 @@ namespace Emby.Server.Implementations.Activity public QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit) { - using (var connection = GetConnection(true)) - { - var commandText = BaseActivitySelectText; - var whereClauses = new List(); + var commandText = BaseActivitySelectText; + var whereClauses = new List(); - if (minDate.HasValue) + if (minDate.HasValue) + { + whereClauses.Add("DateCreated>=@DateCreated"); + } + if (hasUserId.HasValue) + { + if (hasUserId.Value) { - whereClauses.Add("DateCreated>=@DateCreated"); + whereClauses.Add("UserId not null"); } - if (hasUserId.HasValue) + else { - if (hasUserId.Value) - { - whereClauses.Add("UserId not null"); - } - else - { - whereClauses.Add("UserId is null"); - } + whereClauses.Add("UserId is null"); } + } - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - if (startIndex.HasValue && startIndex.Value > 0) - { - var pagingWhereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})", - pagingWhereText, - startIndex.Value.ToString(_usCulture))); - } + var whereTextWithoutPaging = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); - var whereText = whereClauses.Count == 0 ? + if (startIndex.HasValue && startIndex.Value > 0) + { + var pagingWhereText = whereClauses.Count == 0 ? string.Empty : " where " + string.Join(" AND ", whereClauses.ToArray()); - commandText += whereText; + whereClauses.Add( + string.Format( + CultureInfo.InvariantCulture, + "Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})", + pagingWhereText, + startIndex.Value)); + } - commandText += " ORDER BY DateCreated DESC"; + var whereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); - if (limit.HasValue) - { - commandText += " LIMIT " + limit.Value.ToString(_usCulture); - } + commandText += whereText; - var statementTexts = new List(); - statementTexts.Add(commandText); - statementTexts.Add("select count (Id) from ActivityLog" + whereTextWithoutPaging); + commandText += " ORDER BY DateCreated DESC"; - return connection.RunInTransaction(db => - { - var list = new List(); - var result = new QueryResult(); + if (limit.HasValue) + { + commandText += " LIMIT " + limit.Value.ToString(_usCulture); + } + + var statementTexts = new[] + { + commandText, + "select count (Id) from ActivityLog" + whereTextWithoutPaging + }; - var statements = PrepareAll(db, statementTexts).ToList(); + var list = new List(); + var result = new QueryResult(); - using (var statement = statements[0]) + using (var connection = GetConnection(true)) + { + connection.RunInTransaction( + db => { - if (minDate.HasValue) - { - statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); - } + var statements = PrepareAll(db, statementTexts).ToList(); - foreach (var row in statement.ExecuteQuery()) + using (var statement = statements[0]) { - list.Add(GetEntry(row)); + if (minDate.HasValue) + { + statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); + } + + foreach (var row in statement.ExecuteQuery()) + { + list.Add(GetEntry(row)); + } } - } - using (var statement = statements[1]) - { - if (minDate.HasValue) + using (var statement = statements[1]) { - statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); - } + if (minDate.HasValue) + { + statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); + } - result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); - } - - result.Items = list.ToArray(); - return result; - - }, ReadTransactionMode); + result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); + } + }, + ReadTransactionMode); } + + result.Items = list.ToArray(); + return result; } private static ActivityLogEntry GetEntry(IReadOnlyList reader) diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 77f5d94792..1a67ab912a 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -62,14 +62,14 @@ namespace Emby.Server.Implementations.Data /// Task. private void InitializeInternal() { - using (var connection = GetConnection()) + string[] queries = { - string[] queries = { - - "create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)", - "create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)" - }; + "create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)", + "create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)" + }; + using (var connection = GetConnection()) + { connection.RunQueries(queries); } } @@ -81,7 +81,6 @@ namespace Emby.Server.Implementations.Data /// The user id. /// The client. /// The cancellation token. - /// Task. /// item public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken) { @@ -99,10 +98,9 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection()) { - connection.RunInTransaction(db => - { - SaveDisplayPreferences(displayPreferences, userId, client, db); - }, TransactionMode); + connection.RunInTransaction( + db => SaveDisplayPreferences(displayPreferences, userId, client, db), + TransactionMode); } } @@ -127,7 +125,6 @@ namespace Emby.Server.Implementations.Data /// The display preferences. /// The user id. /// The cancellation token. - /// Task. /// item public void SaveAllDisplayPreferences(IEnumerable displayPreferences, Guid userId, CancellationToken cancellationToken) { @@ -140,13 +137,15 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection()) { - connection.RunInTransaction(db => - { - foreach (var displayPreference in displayPreferences) + connection.RunInTransaction( + db => { - SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db); - } - }, TransactionMode); + foreach (var displayPreference in displayPreferences) + { + SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db); + } + }, + TransactionMode); } } @@ -180,12 +179,12 @@ namespace Emby.Server.Implementations.Data return Get(row); } } - - return new DisplayPreferences - { - Id = guidId.ToString("N", CultureInfo.InvariantCulture) - }; } + + return new DisplayPreferences + { + Id = guidId.ToString("N", CultureInfo.InvariantCulture) + }; } /// @@ -215,22 +214,12 @@ namespace Emby.Server.Implementations.Data } private DisplayPreferences Get(IReadOnlyList row) - { - using (var stream = new MemoryStream(row[0].ToBlob())) - { - stream.Position = 0; - return _jsonSerializer.DeserializeFromStream(stream); - } - } + => _jsonSerializer.DeserializeFromString(row.GetString(0)); public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken) - { - SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken); - } + => SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken); public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client) - { - return GetDisplayPreferences(displayPreferencesId, new Guid(userId), client); - } + => GetDisplayPreferences(displayPreferencesId, new Guid(userId), client); } } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 3ca0728fe3..9d983307f2 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -99,35 +99,114 @@ namespace Emby.Server.Implementations.Data /// public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager) { - using (var connection = GetConnection()) - { - const string createMediaStreamsTableCommand + const string CreateMediaStreamsTableCommand = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))"; - string[] queries = { - "PRAGMA locking_mode=EXCLUSIVE", + string[] queries = + { + "PRAGMA locking_mode=EXCLUSIVE", - "create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLOB NULL, ParentId GUID NULL, Path TEXT NULL)", + "create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLOB NULL, ParentId GUID NULL, Path TEXT NULL)", - "create table if not exists AncestorIds (ItemId GUID NOT NULL, AncestorId GUID NOT NULL, AncestorIdText TEXT NOT NULL, PRIMARY KEY (ItemId, AncestorId))", - "create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)", - "create index if not exists idx_AncestorIds5 on AncestorIds(AncestorIdText,ItemId)", + "create table if not exists AncestorIds (ItemId GUID NOT NULL, AncestorId GUID NOT NULL, AncestorIdText TEXT NOT NULL, PRIMARY KEY (ItemId, AncestorId))", + "create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)", + "create index if not exists idx_AncestorIds5 on AncestorIds(AncestorIdText,ItemId)", - "create table if not exists ItemValues (ItemId GUID NOT NULL, Type INT NOT NULL, Value TEXT NOT NULL, CleanValue TEXT NOT NULL)", + "create table if not exists ItemValues (ItemId GUID NOT NULL, Type INT NOT NULL, Value TEXT NOT NULL, CleanValue TEXT NOT NULL)", - "create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)", + "create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)", - "drop index if exists idxPeopleItemId", - "create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)", - "create index if not exists idxPeopleName on People(Name)", + "drop index if exists idxPeopleItemId", + "create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)", + "create index if not exists idxPeopleName on People(Name)", - "create table if not exists " + ChaptersTableName + " (ItemId GUID, ChapterIndex INT NOT NULL, StartPositionTicks BIGINT NOT NULL, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))", + "create table if not exists " + ChaptersTableName + " (ItemId GUID, ChapterIndex INT NOT NULL, StartPositionTicks BIGINT NOT NULL, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))", - createMediaStreamsTableCommand, + CreateMediaStreamsTableCommand, - "pragma shrink_memory" - }; + "pragma shrink_memory" + }; + + + string[] postQueries = + { + // obsolete + "drop index if exists idx_TypedBaseItems", + "drop index if exists idx_mediastreams", + "drop index if exists idx_mediastreams1", + "drop index if exists idx_"+ChaptersTableName, + "drop index if exists idx_UserDataKeys1", + "drop index if exists idx_UserDataKeys2", + "drop index if exists idx_TypeTopParentId3", + "drop index if exists idx_TypeTopParentId2", + "drop index if exists idx_TypeTopParentId4", + "drop index if exists idx_Type", + "drop index if exists idx_TypeTopParentId", + "drop index if exists idx_GuidType", + "drop index if exists idx_TopParentId", + "drop index if exists idx_TypeTopParentId6", + "drop index if exists idx_ItemValues2", + "drop index if exists Idx_ProviderIds", + "drop index if exists idx_ItemValues3", + "drop index if exists idx_ItemValues4", + "drop index if exists idx_ItemValues5", + "drop index if exists idx_UserDataKeys3", + "drop table if exists UserDataKeys", + "drop table if exists ProviderIds", + "drop index if exists Idx_ProviderIds1", + "drop table if exists Images", + "drop index if exists idx_Images", + "drop index if exists idx_TypeSeriesPresentationUniqueKey", + "drop index if exists idx_SeriesPresentationUniqueKey", + "drop index if exists idx_TypeSeriesPresentationUniqueKey2", + "drop index if exists idx_AncestorIds3", + "drop index if exists idx_AncestorIds4", + "drop index if exists idx_AncestorIds2", + + "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)", + "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)", + + "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", + "create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)", + "create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)", + + // covering index + "create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)", + + // series + "create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)", + + // series counts + // seriesdateplayed sort order + "create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUniqueKey,Type,IsFolder,IsVirtualItem)", + + // live tv programs + "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)", + + // covering index for getitemvalues + "create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)", + + // used by movie suggestions + "create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniqueKey)", + "create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)", + + // latest items + "create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey,DateCreated)", + "create index if not exists idx_TypeTopParentId8 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem,PresentationUniqueKey,DateCreated)", + + // resume + "create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)", + + // items by name + "create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)", + "create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)", + + // Used to update inherited tags + "create index if not exists idx_ItemValues8 on ItemValues(Type, ItemId, Value)", + }; + using (var connection = GetConnection()) + { connection.RunQueries(queries); connection.RunInTransaction(db => @@ -235,83 +314,6 @@ namespace Emby.Server.Implementations.Data }, TransactionMode); - string[] postQueries = - { - // obsolete - "drop index if exists idx_TypedBaseItems", - "drop index if exists idx_mediastreams", - "drop index if exists idx_mediastreams1", - "drop index if exists idx_"+ChaptersTableName, - "drop index if exists idx_UserDataKeys1", - "drop index if exists idx_UserDataKeys2", - "drop index if exists idx_TypeTopParentId3", - "drop index if exists idx_TypeTopParentId2", - "drop index if exists idx_TypeTopParentId4", - "drop index if exists idx_Type", - "drop index if exists idx_TypeTopParentId", - "drop index if exists idx_GuidType", - "drop index if exists idx_TopParentId", - "drop index if exists idx_TypeTopParentId6", - "drop index if exists idx_ItemValues2", - "drop index if exists Idx_ProviderIds", - "drop index if exists idx_ItemValues3", - "drop index if exists idx_ItemValues4", - "drop index if exists idx_ItemValues5", - "drop index if exists idx_UserDataKeys3", - "drop table if exists UserDataKeys", - "drop table if exists ProviderIds", - "drop index if exists Idx_ProviderIds1", - "drop table if exists Images", - "drop index if exists idx_Images", - "drop index if exists idx_TypeSeriesPresentationUniqueKey", - "drop index if exists idx_SeriesPresentationUniqueKey", - "drop index if exists idx_TypeSeriesPresentationUniqueKey2", - "drop index if exists idx_AncestorIds3", - "drop index if exists idx_AncestorIds4", - "drop index if exists idx_AncestorIds2", - - "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)", - "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)", - - "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", - "create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)", - "create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)", - - // covering index - "create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)", - - // series - "create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)", - - // series counts - // seriesdateplayed sort order - "create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUniqueKey,Type,IsFolder,IsVirtualItem)", - - // live tv programs - "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)", - - // covering index for getitemvalues - "create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)", - - // used by movie suggestions - "create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniqueKey)", - "create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)", - - // latest items - "create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey,DateCreated)", - "create index if not exists idx_TypeTopParentId8 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem,PresentationUniqueKey,DateCreated)", - - // resume - "create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)", - - // items by name - "create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)", - "create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)", - - // Used to update inherited tags - "create index if not exists idx_ItemValues8 on ItemValues(Type, ItemId, Value)", - }; - connection.RunQueries(postQueries); } @@ -1994,14 +1996,14 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(chapters)); } + var idBlob = id.ToGuidBlob(); + using (var connection = GetConnection()) { connection.RunInTransaction(db => { - var idBlob = id.ToGuidBlob(); - - // First delete chapters - db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob); + // First delete chapters + db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob); InsertChapters(idBlob, chapters, db); @@ -2530,6 +2532,7 @@ namespace Emby.Server.Implementations.Data commandText += " where " + string.Join(" AND ", whereClauses); } + int count; using (var connection = GetConnection(true)) { using (var statement = PrepareStatement(connection, commandText)) @@ -2545,11 +2548,12 @@ namespace Emby.Server.Implementations.Data // Running this again will bind the params GetWhereClauses(query, statement); - var count = statement.ExecuteQuery().SelectScalarInt().First(); - LogQueryTime("GetCount", commandText, now); - return count; + count = statement.ExecuteQuery().SelectScalarInt().First(); } } + + LogQueryTime("GetCount", commandText, now); + return count; } public List GetItemList(InternalItemsQuery query) @@ -2599,10 +2603,9 @@ namespace Emby.Server.Implementations.Data } } + var items = new List(); using (var connection = GetConnection(true)) { - var items = new List(); - using (var statement = PrepareStatement(connection, commandText)) { if (EnableJoinUserData(query)) @@ -2653,11 +2656,11 @@ namespace Emby.Server.Implementations.Data items = newList; } + } - LogQueryTime("GetItemList", commandText, now); + LogQueryTime("GetItemList", commandText, now); - return items; - } + return items; } private string FixUnicodeChars(string buffer) @@ -2750,8 +2753,6 @@ namespace Emby.Server.Implementations.Data var now = DateTime.UtcNow; - var list = new List(); - // Hack for right now since we currently don't support filtering out these duplicates within a query if (query.Limit.HasValue && query.EnableGroupByMetadataKey) { @@ -2817,11 +2818,13 @@ namespace Emby.Server.Implementations.Data statementTexts.Add(commandText); } + var list = new List(); + var result = new QueryResult(); using (var connection = GetConnection(true)) { - return connection.RunInTransaction(db => + connection.RunInTransaction(db => { - var result = new QueryResult(); + var statements = PrepareAll(db, statementTexts).ToList(); if (!isReturningZeroItems) @@ -2876,14 +2879,12 @@ namespace Emby.Server.Implementations.Data result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } } - - LogQueryTime("GetItems", commandText, now); - - result.Items = list.ToArray(); - return result; - }, ReadTransactionMode); } + + LogQueryTime("GetItems", commandText, now); + result.Items = list.ToArray(); + return result; } private string GetOrderByText(InternalItemsQuery query) @@ -3049,10 +3050,9 @@ namespace Emby.Server.Implementations.Data } } + var list = new List(); using (var connection = GetConnection(true)) { - var list = new List(); - using (var statement = PrepareStatement(connection, commandText)) { if (EnableJoinUserData(query)) @@ -3071,11 +3071,10 @@ namespace Emby.Server.Implementations.Data list.Add(row[0].ReadGuidFromBlob()); } } - - LogQueryTime("GetItemList", commandText, now); - - return list; } + + LogQueryTime("GetItemList", commandText, now); + return list; } public List> GetItemIdsWithPath(InternalItemsQuery query) @@ -3137,6 +3136,7 @@ namespace Emby.Server.Implementations.Data { path = row.GetString(1); } + list.Add(new Tuple(id, path)); } } @@ -3198,7 +3198,7 @@ namespace Emby.Server.Implementations.Data } } - var list = new List(); + var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0; var statementTexts = new List(); @@ -3228,12 +3228,12 @@ namespace Emby.Server.Implementations.Data statementTexts.Add(commandText); } + var list = new List(); + var result = new QueryResult(); using (var connection = GetConnection(true)) { - return connection.RunInTransaction(db => + connection.RunInTransaction(db => { - var result = new QueryResult(); - var statements = PrepareAll(db, statementTexts).ToList(); if (!isReturningZeroItems) @@ -3276,14 +3276,13 @@ namespace Emby.Server.Implementations.Data result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } } - - LogQueryTime("GetItemIds", commandText, now); - - result.Items = list.ToArray(); - return result; - }, ReadTransactionMode); } + + LogQueryTime("GetItemIds", commandText, now); + + result.Items = list.ToArray(); + return result; } private bool IsAlphaNumeric(string str) @@ -4859,22 +4858,25 @@ namespace Emby.Server.Implementations.Data private void UpdateInheritedTags(CancellationToken cancellationToken) { - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => + string sql = string.Join( + ";", + new string[] { - connection.ExecuteAll(string.Join(";", new string[] - { - "delete from itemvalues where type = 6", + "delete from itemvalues where type = 6", - "insert into itemvalues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4", + "insert into itemvalues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4", - @"insert into itemvalues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue + @"insert into itemvalues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue FROM AncestorIds LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId) where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4 " + }); - })); + using (var connection = GetConnection()) + { + connection.RunInTransaction(db => + { + connection.ExecuteAll(sql); }, TransactionMode); } @@ -4928,23 +4930,23 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { var idBlob = id.ToGuidBlob(); - // Delete people - ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob); + // Delete people + ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob); - // Delete chapters - ExecuteWithSingleParam(db, "delete from " + ChaptersTableName + " where ItemId=@Id", idBlob); + // Delete chapters + ExecuteWithSingleParam(db, "delete from " + ChaptersTableName + " where ItemId=@Id", idBlob); - // Delete media streams - ExecuteWithSingleParam(db, "delete from mediastreams where ItemId=@Id", idBlob); + // Delete media streams + ExecuteWithSingleParam(db, "delete from mediastreams where ItemId=@Id", idBlob); - // Delete ancestors - ExecuteWithSingleParam(db, "delete from AncestorIds where ItemId=@Id", idBlob); + // Delete ancestors + ExecuteWithSingleParam(db, "delete from AncestorIds where ItemId=@Id", idBlob); - // Delete item values - ExecuteWithSingleParam(db, "delete from ItemValues where ItemId=@Id", idBlob); + // Delete item values + ExecuteWithSingleParam(db, "delete from ItemValues where ItemId=@Id", idBlob); - // Delete the item - ExecuteWithSingleParam(db, "delete from TypedBaseItems where guid=@Id", idBlob); + // Delete the item + ExecuteWithSingleParam(db, "delete from TypedBaseItems where guid=@Id", idBlob); }, TransactionMode); } } @@ -4992,6 +4994,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type list.Add(row.GetString(0)); } } + return list; } } @@ -5242,10 +5245,9 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type commandText += " Group By CleanValue"; + var list = new List(); using (var connection = GetConnection(true)) { - var list = new List(); - using (var statement = PrepareStatement(connection, commandText)) { foreach (var row in statement.ExecuteQuery()) @@ -5257,10 +5259,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } - LogQueryTime("GetItemValueNames", commandText, now); - - return list; } + + LogQueryTime("GetItemValueNames", commandText, now); + return list; } private QueryResult<(BaseItem, ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType) @@ -5417,6 +5419,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { statementTexts.Add(commandText); } + if (query.EnableTotalRecordCount) { var countText = "select " @@ -5428,98 +5431,98 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statementTexts.Add(countText); } + var list = new List<(BaseItem, ItemCounts)>(); + var result = new QueryResult<(BaseItem, ItemCounts)>(); using (var connection = GetConnection(true)) { - return connection.RunInTransaction(db => - { - var list = new List<(BaseItem, ItemCounts)>(); - var result = new QueryResult<(BaseItem, ItemCounts)>(); - - var statements = PrepareAll(db, statementTexts).ToList(); - - if (!isReturningZeroItems) + connection.RunInTransaction( + db => { - using (var statement = statements[0]) + var statements = PrepareAll(db, statementTexts).ToList(); + + if (!isReturningZeroItems) { - statement.TryBind("@SelectType", returnType); - if (EnableJoinUserData(query)) + using (var statement = statements[0]) { - statement.TryBind("@UserId", query.User.InternalId); - } + statement.TryBind("@SelectType", returnType); + if (EnableJoinUserData(query)) + { + statement.TryBind("@UserId", query.User.InternalId); + } - if (typeSubQuery != null) - { - GetWhereClauses(typeSubQuery, null); - } - BindSimilarParams(query, statement); - BindSearchParams(query, statement); - GetWhereClauses(innerQuery, statement); - GetWhereClauses(outerQuery, statement); + if (typeSubQuery != null) + { + GetWhereClauses(typeSubQuery, null); + } - var hasEpisodeAttributes = HasEpisodeAttributes(query); - var hasProgramAttributes = HasProgramAttributes(query); - var hasServiceName = HasServiceName(query); - var hasStartDate = HasStartDate(query); - var hasTrailerTypes = HasTrailerTypes(query); - var hasArtistFields = HasArtistFields(query); - var hasSeriesFields = HasSeriesFields(query); + BindSimilarParams(query, statement); + BindSearchParams(query, statement); + GetWhereClauses(innerQuery, statement); + GetWhereClauses(outerQuery, statement); - foreach (var row in statement.ExecuteQuery()) - { - var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); - if (item != null) + var hasEpisodeAttributes = HasEpisodeAttributes(query); + var hasProgramAttributes = HasProgramAttributes(query); + var hasServiceName = HasServiceName(query); + var hasStartDate = HasStartDate(query); + var hasTrailerTypes = HasTrailerTypes(query); + var hasArtistFields = HasArtistFields(query); + var hasSeriesFields = HasSeriesFields(query); + + foreach (var row in statement.ExecuteQuery()) { - var countStartColumn = columns.Count - 1; + var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); + if (item != null) + { + var countStartColumn = columns.Count - 1; - list.Add((item, GetItemCounts(row, countStartColumn, typesToCount))); + list.Add((item, GetItemCounts(row, countStartColumn, typesToCount))); + } } } - - LogQueryTime("GetItemValues", commandText, now); } - } - if (query.EnableTotalRecordCount) - { - commandText = "select " - + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) - + GetFromText() - + GetJoinUserDataText(query) - + whereText; - - using (var statement = statements[statements.Count - 1]) + if (query.EnableTotalRecordCount) { - statement.TryBind("@SelectType", returnType); - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } + commandText = "select " + + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + + GetFromText() + + GetJoinUserDataText(query) + + whereText; - if (typeSubQuery != null) + using (var statement = statements[statements.Count - 1]) { - GetWhereClauses(typeSubQuery, null); - } - BindSimilarParams(query, statement); - BindSearchParams(query, statement); - GetWhereClauses(innerQuery, statement); - GetWhereClauses(outerQuery, statement); + statement.TryBind("@SelectType", returnType); + if (EnableJoinUserData(query)) + { + statement.TryBind("@UserId", query.User.InternalId); + } - result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); + if (typeSubQuery != null) + { + GetWhereClauses(typeSubQuery, null); + } + BindSimilarParams(query, statement); + BindSearchParams(query, statement); + GetWhereClauses(innerQuery, statement); + GetWhereClauses(outerQuery, statement); - LogQueryTime("GetItemValues", commandText, now); + result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); + } } - } - - if (result.TotalRecordCount == 0) - { - result.TotalRecordCount = list.Count; - } - result.Items = list.ToArray(); + }, + ReadTransactionMode); + } - return result; + LogQueryTime("GetItemValues", commandText, now); - }, ReadTransactionMode); + if (result.TotalRecordCount == 0) + { + result.TotalRecordCount = list.Count; } + + result.Items = list.ToArray(); + + return result; } private ItemCounts GetItemCounts(IReadOnlyList reader, int countStartColumn, string[] typesToCount) @@ -5702,8 +5705,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { var itemIdBlob = itemId.ToGuidBlob(); - // First delete chapters - db.Execute("delete from People where ItemId=@ItemId", itemIdBlob); + // First delete chapters + db.Execute("delete from People where ItemId=@ItemId", itemIdBlob); InsertPeople(itemIdBlob, people, db); @@ -5863,8 +5866,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { var itemIdBlob = id.ToGuidBlob(); - // First delete chapters - db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob); + // First delete chapters + db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob); InsertMediaStreams(itemIdBlob, streams, db); diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 0b5ee5d039..1ef5c4b996 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -23,22 +23,22 @@ namespace Emby.Server.Implementations.Security public void Initialize() { - using (var connection = GetConnection()) + string[] queries = { - var tableNewlyCreated = !TableExists(connection, "Tokens"); + "create table if not exists Tokens (Id INTEGER PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT NOT NULL, AppName TEXT NOT NULL, AppVersion TEXT NOT NULL, DeviceName TEXT NOT NULL, UserId TEXT, UserName TEXT, IsActive BIT NOT NULL, DateCreated DATETIME NOT NULL, DateLastActivity DATETIME NOT NULL)", + "create table if not exists Devices (Id TEXT NOT NULL PRIMARY KEY, CustomName TEXT, Capabilities TEXT)", + "drop index if exists idx_AccessTokens", + "drop index if exists Tokens1", + "drop index if exists Tokens2", - string[] queries = { - - "create table if not exists Tokens (Id INTEGER PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT NOT NULL, AppName TEXT NOT NULL, AppVersion TEXT NOT NULL, DeviceName TEXT NOT NULL, UserId TEXT, UserName TEXT, IsActive BIT NOT NULL, DateCreated DATETIME NOT NULL, DateLastActivity DATETIME NOT NULL)", - "create table if not exists Devices (Id TEXT NOT NULL PRIMARY KEY, CustomName TEXT, Capabilities TEXT)", + "create index if not exists Tokens3 on Tokens (AccessToken, DateLastActivity)", + "create index if not exists Tokens4 on Tokens (Id, DateLastActivity)", + "create index if not exists Devices1 on Devices (Id)" + }; - "drop index if exists idx_AccessTokens", - "drop index if exists Tokens1", - "drop index if exists Tokens2", - "create index if not exists Tokens3 on Tokens (AccessToken, DateLastActivity)", - "create index if not exists Tokens4 on Tokens (Id, DateLastActivity)", - "create index if not exists Devices1 on Devices (Id)" - }; + using (var connection = GetConnection()) + { + var tableNewlyCreated = !TableExists(connection, "Tokens"); connection.RunQueries(queries); @@ -244,45 +244,46 @@ namespace Emby.Server.Implementations.Security } } - var list = new List(); + var statementTexts = new[] + { + commandText, + "select count (Id) from Tokens" + whereTextWithoutPaging + }; + var list = new List(); + var result = new QueryResult(); using (var connection = GetConnection(true)) { - return connection.RunInTransaction(db => - { - var result = new QueryResult(); - - var statementTexts = new List(); - statementTexts.Add(commandText); - statementTexts.Add("select count (Id) from Tokens" + whereTextWithoutPaging); - - var statements = PrepareAll(db, statementTexts) - .ToList(); - - using (var statement = statements[0]) + connection.RunInTransaction( + db => { - BindAuthenticationQueryParams(query, statement); + var statements = PrepareAll(db, statementTexts) + .ToList(); - foreach (var row in statement.ExecuteQuery()) + using (var statement = statements[0]) { - list.Add(Get(row)); - } + BindAuthenticationQueryParams(query, statement); - using (var totalCountStatement = statements[1]) - { - BindAuthenticationQueryParams(query, totalCountStatement); - - result.TotalRecordCount = totalCountStatement.ExecuteQuery() - .SelectScalarInt() - .First(); - } - } + foreach (var row in statement.ExecuteQuery()) + { + list.Add(Get(row)); + } - result.Items = list.ToArray(); - return result; + using (var totalCountStatement = statements[1]) + { + BindAuthenticationQueryParams(query, totalCountStatement); - }, ReadTransactionMode); + result.TotalRecordCount = totalCountStatement.ExecuteQuery() + .SelectScalarInt() + .First(); + } + } + }, + ReadTransactionMode); } + + result.Items = list.ToArray(); + return result; } private static AuthenticationInfo Get(IReadOnlyList reader) -- cgit v1.2.3 From 3fd489d1cb85d654b4b32d2ffd901832a38adbe9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 19 Aug 2019 17:03:21 +0200 Subject: Upgrade SQLitePCL to v2 --- .../Data/SqliteDisplayPreferencesRepository.cs | 8 +------- Emby.Server.Implementations/Data/SqliteExtensions.cs | 7 ++----- Emby.Server.Implementations/Data/SqliteUserRepository.cs | 15 +++++---------- .../Emby.Server.Implementations.csproj | 2 +- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 5 files changed, 10 insertions(+), 24 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 77f5d94792..b1c17b92ec 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -215,13 +215,7 @@ namespace Emby.Server.Implementations.Data } private DisplayPreferences Get(IReadOnlyList row) - { - using (var stream = new MemoryStream(row[0].ToBlob())) - { - stream.Position = 0; - return _jsonSerializer.DeserializeFromStream(stream); - } - } + => _jsonSerializer.DeserializeFromString(row.GetString(0)); public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken) { diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index c0f27b25aa..0fb2c10fd3 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -18,10 +18,6 @@ namespace Emby.Server.Implementations.Data connection.RunInTransaction(conn => { - //foreach (var query in queries) - //{ - // conn.Execute(query); - //} conn.ExecuteAll(string.Join(";", queries)); }); } @@ -38,7 +34,8 @@ namespace Emby.Server.Implementations.Data public static Guid ReadGuidFromBlob(this IResultSetValue result) { - return new Guid(result.ToBlob()); + // TODO: Remove ToArray when upgrading to netstandard2.1 + return new Guid(result.ToBlob().ToArray()); } public static string ToDateTimeParamValue(this DateTime dateValue) diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index d6d250fb31..11629b3895 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -35,9 +35,8 @@ namespace Emby.Server.Implementations.Data public string Name => "SQLite"; /// - /// Opens the connection to the database + /// Opens the connection to the database. /// - /// Task. public void Initialize() { using (var connection = GetConnection()) @@ -180,14 +179,10 @@ namespace Emby.Server.Implementations.Data var id = row[0].ToInt64(); var guid = row[1].ReadGuidFromBlob(); - using (var stream = new MemoryStream(row[2].ToBlob())) - { - stream.Position = 0; - var user = _jsonSerializer.DeserializeFromStream(stream); - user.InternalId = id; - user.Id = guid; - return user; - } + var user = _jsonSerializer.DeserializeFromString(row.GetString(2)); + user.InternalId = id; + user.Id = guid; + return user; } /// diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index c78d96d4ab..b48193c58d 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -34,7 +34,7 @@ - + diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index ec7c026e59..e872834779 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -43,7 +43,7 @@ - + -- cgit v1.2.3 From fdc24ec2eeffec4334c705631439de72cf6c8bdd Mon Sep 17 00:00:00 2001 From: SL1288 Date: Mon, 19 Aug 2019 18:40:39 +0200 Subject: Fix LocalTrailers playback. --- Emby.Server.Implementations/Dto/DtoService.cs | 2 +- MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 10 ++++++---- MediaBrowser.Controller/Entities/BaseItem.cs | 8 ++++++++ 3 files changed, 15 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 1a7f10634b..97ddfff530 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1079,7 +1079,7 @@ namespace Emby.Server.Implementations.Dto allExtras = item.GetExtras().ToArray(); } - dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer); + dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer) + item.GetTrailers().Count(); } // Add EpisodeInfo diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index a9b06095da..6eaa1b692f 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -366,11 +366,13 @@ namespace MediaBrowser.Api.UserLibrary var dtoOptions = GetDtoOptions(_authContext, request); - var dtos = item.GetExtras(new[] { ExtraType.Trailer }) - .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) - .ToArray(); + var dtosExtras = item.GetExtras(new[] { ExtraType.Trailer }) + .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); + + var dtosTrailers = item.GetTrailers() + .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); - return ToOptimizedResult(dtos); + return ToOptimizedResult(dtosExtras.Concat(dtosTrailers).ToArray()); } /// diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 2ae856b020..38e4db3cf7 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2883,6 +2883,14 @@ namespace MediaBrowser.Controller.Entities return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null && extraTypes.Contains(i.ExtraType.Value)).OrderBy(i => i.SortName); } + public IEnumerable GetTrailers() + { + if (this is IHasTrailers) + return ((IHasTrailers)this).LocalTrailerIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName); + else + return Array.Empty(); + } + public IEnumerable GetDisplayExtras() { return GetExtras(DisplayExtraTypes); -- cgit v1.2.3 From 6be68a3656c30b03b7c0eeee06c83ad7d96f91bd Mon Sep 17 00:00:00 2001 From: Matzi24GR Date: Thu, 15 Aug 2019 13:58:23 +0000 Subject: Translated using Weblate (Greek) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/ --- Emby.Server.Implementations/Localization/Core/el.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index db7ebb0c01..3589a48938 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -3,7 +3,7 @@ "AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}", "Application": "Εφαρμογή", "Artists": "Καλλιτέχνες", - "AuthenticationSucceededWithUserName": "{0} επιτυχείς σύνδεση", + "AuthenticationSucceededWithUserName": "Ο χρήστης {0} επαληθεύτηκε με επιτυχία", "Books": "Βιβλία", "CameraImageUploadedFrom": "Μια νέα εικόνα κάμερας έχει αποσταλεί από {0}", "Channels": "Κανάλια", @@ -15,9 +15,9 @@ "Favorites": "Αγαπημένα", "Folders": "Φάκελοι", "Genres": "Είδη", - "HeaderAlbumArtists": "Άλμπουμ Καλλιτεχνών", + "HeaderAlbumArtists": "Καλλιτέχνες του Άλμπουμ", "HeaderCameraUploads": "Μεταφορτώσεις Κάμερας", - "HeaderContinueWatching": "Συνεχίστε να παρακολουθείτε", + "HeaderContinueWatching": "Συνεχίστε την παρακολούθηση", "HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ", "HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες", "HeaderFavoriteEpisodes": "Αγαπημένα Επεισόδια", @@ -27,7 +27,7 @@ "HeaderNextUp": "Επόμενο", "HeaderRecordingGroups": "Γκρουπ Εγγραφών", "HomeVideos": "Προσωπικά βίντεο", - "Inherit": "Inherit", + "Inherit": "Κληρονόμηση", "ItemAddedWithName": "{0} προστέθηκε στη βιβλιοθήκη", "ItemRemovedWithName": "{0} διαγράφηκε από τη βιβλιοθήκη", "LabelIpAddressValue": "Διεύθυνση IP: {0}", @@ -92,6 +92,6 @@ "UserStartedPlayingItemWithValues": "{0} παίζει {1} σε {2}", "UserStoppedPlayingItemWithValues": "{0} τελείωσε να παίζει {1} σε {2}", "ValueHasBeenAddedToLibrary": "{0} προστέθηκαν στη βιβλιοθήκη πολυμέσων σας", - "ValueSpecialEpisodeName": "Special - {0}", + "ValueSpecialEpisodeName": "Σπέσιαλ - {0}", "VersionNumber": "Έκδοση {0}" } -- cgit v1.2.3 From 143a408342783de7246c72ba3c9221c796fd47b3 Mon Sep 17 00:00:00 2001 From: Βασίλης Μουρατίδης Date: Thu, 15 Aug 2019 14:41:58 +0000 Subject: Translated using Weblate (Greek) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/ --- Emby.Server.Implementations/Localization/Core/el.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index 3589a48938..90fe030066 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -27,7 +27,7 @@ "HeaderNextUp": "Επόμενο", "HeaderRecordingGroups": "Γκρουπ Εγγραφών", "HomeVideos": "Προσωπικά βίντεο", - "Inherit": "Κληρονόμηση", + "Inherit": "Inherit", "ItemAddedWithName": "{0} προστέθηκε στη βιβλιοθήκη", "ItemRemovedWithName": "{0} διαγράφηκε από τη βιβλιοθήκη", "LabelIpAddressValue": "Διεύθυνση IP: {0}", -- cgit v1.2.3 From d2c69e77337c8266035c70f4b487daa984aaeccd Mon Sep 17 00:00:00 2001 From: Matzi24GR Date: Thu, 15 Aug 2019 14:42:08 +0000 Subject: Translated using Weblate (Greek) Currently translated at 95.7% (90 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/ --- Emby.Server.Implementations/Localization/Core/el.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index 90fe030066..3589a48938 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -27,7 +27,7 @@ "HeaderNextUp": "Επόμενο", "HeaderRecordingGroups": "Γκρουπ Εγγραφών", "HomeVideos": "Προσωπικά βίντεο", - "Inherit": "Inherit", + "Inherit": "Κληρονόμηση", "ItemAddedWithName": "{0} προστέθηκε στη βιβλιοθήκη", "ItemRemovedWithName": "{0} διαγράφηκε από τη βιβλιοθήκη", "LabelIpAddressValue": "Διεύθυνση IP: {0}", -- cgit v1.2.3 From 7d4bb28d187abcdd04d6dcee3f8d10b62f4623e2 Mon Sep 17 00:00:00 2001 From: Axel Gabriel Calle Granda <97carmine@protonmail.com> Date: Sat, 10 Aug 2019 22:16:13 +0000 Subject: Translated using Weblate (Spanish) Currently translated at 95.7% (90 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/ --- Emby.Server.Implementations/Localization/Core/es.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index f03184d5ba..c787949676 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -23,7 +23,7 @@ "HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteShows": "Series favoritas", "HeaderFavoriteSongs": "Canciones favoritas", - "HeaderLiveTV": "TV en directo", + "HeaderLiveTV": "Televisión en directo", "HeaderNextUp": "Siguiendo", "HeaderRecordingGroups": "Grupos de grabación", "HomeVideos": "Vídeos caseros", @@ -79,7 +79,7 @@ "SubtitlesDownloadedForItem": "Descargar subtítulos para {0}", "Sync": "Sincronizar", "System": "Sistema", - "TvShows": "Series de TV", + "TvShows": "Programas de televisión", "User": "Usuario", "UserCreatedWithName": "El usuario {0} ha sido creado", "UserDeletedWithName": "El usuario {0} ha sido borrado", -- cgit v1.2.3 From ba0997a8dbfa768eceb88d5e3d464d3fdef649e9 Mon Sep 17 00:00:00 2001 From: Pafzedog Date: Fri, 16 Aug 2019 08:49:21 +0000 Subject: Translated using Weblate (French) Currently translated at 95.7% (90 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index e434b7605b..6bfedb712c 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -17,14 +17,14 @@ "Genres": "Genres", "HeaderAlbumArtists": "Artistes de l'album", "HeaderCameraUploads": "Photos transférées", - "HeaderContinueWatching": "Continuer à regarder", + "HeaderContinueWatching": "Reprendre", "HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteArtists": "Artistes favoris", "HeaderFavoriteEpisodes": "Épisodes favoris", "HeaderFavoriteShows": "Séries favorites", "HeaderFavoriteSongs": "Chansons favorites", "HeaderLiveTV": "TV en direct", - "HeaderNextUp": "En Cours", + "HeaderNextUp": "À suivre", "HeaderRecordingGroups": "Groupes d'enregistrements", "HomeVideos": "Vidéos personnelles", "Inherit": "Hériter", -- cgit v1.2.3 From f255788383dead8f172c9e585bc589247164382f Mon Sep 17 00:00:00 2001 From: Odd Stråbø Date: Mon, 12 Aug 2019 10:41:06 +0000 Subject: Translated using Weblate (Norwegian Bokmål) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 95.7% (90 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nb_NO/ --- Emby.Server.Implementations/Localization/Core/nb.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index dbda794ad7..daa3f58800 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -1,11 +1,11 @@ { "Albums": "Album", - "AppDeviceValues": "App:{0}, Enhet {1}", + "AppDeviceValues": "App:{0}, Enhet: {1}", "Application": "Applikasjon", "Artists": "Artister", "AuthenticationSucceededWithUserName": "{0} vellykkede autentisert", "Books": "Bøker", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "Et nytt kamerabilde er lastet opp fra {0}", "Channels": "Kanaler", "ChapterNameValue": "Kapittel {0}", "Collections": "Samlinger", -- cgit v1.2.3 From 5dd332b63d423f7c6a4b70cb826a35a957c4e6fa Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 16 Aug 2019 18:52:16 +0200 Subject: Attempt to fix #1391 --- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 44 ++++++++++++---------- Emby.Server.Implementations/Net/SocketFactory.cs | 3 +- 2 files changed, 26 insertions(+), 21 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index fbbab07f8b..820ba1c36f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -1,5 +1,4 @@ using System; -using System.Buffers; using System.Collections.Generic; using System.IO; using System.Net; @@ -82,7 +81,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - var udpClient = _socketFactory.CreateUdpSocket(localPort); + var udpClient = new UdpClient(localPort, AddressFamily.InterNetwork); var hdHomerunManager = new HdHomerunManager(); try @@ -133,7 +132,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun await taskCompletionSource.Task.ConfigureAwait(false); } - private Task StartStreaming(MediaBrowser.Model.Net.ISocket udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) + private Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) { return Task.Run(async () => { @@ -162,28 +161,37 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun }); } - private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) + private async Task CopyTo(UdpClient udpClient, string file, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) { - byte[] buffer = ArrayPool.Shared.Rent(StreamDefaults.DefaultCopyToBufferSize); - try + var resolved = false; + + using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read)) { - using (var source = _socketFactory.CreateNetworkStream(udpClient, false)) - using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read)) + while (true) { - 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(); + using (var timeOutSource = new CancellationTokenSource()) + using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken, + timeOutSource.Token)) { - cancellationToken.ThrowIfCancellationRequested(); + var resTask = udpClient.ReceiveAsync(); + if (await Task.WhenAny(resTask, Task.Delay(30000, linkedSource.Token)).ConfigureAwait(false) != resTask) + { + resTask.Dispose(); + break; + } - currentCancellationToken = cancellationToken; + // We don't want all these delay tasks to keep running + timeOutSource.Cancel(); + var res = await resTask.ConfigureAwait(false); + var buffer = res.Buffer; - read -= RtpHeaderBytes; + var read = buffer.Length - RtpHeaderBytes; if (read > 0) { - await fileStream.WriteAsync(buffer, RtpHeaderBytes, read).ConfigureAwait(false); + fileStream.Write(buffer, RtpHeaderBytes, read); } if (!resolved) @@ -195,10 +203,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } } - finally - { - ArrayPool.Shared.Return(buffer); - } } } } diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index cb53ce50c7..0870db003f 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -19,7 +19,8 @@ namespace Emby.Server.Implementations.Net throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); } - var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); + var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); -- cgit v1.2.3 From dc194015c28c9c0b9347cb5c84b38f494fb1f0dd Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 16 Aug 2019 19:00:32 +0200 Subject: Remove unused args --- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 15 ++++++++++++--- .../LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 4 ---- 2 files changed, 12 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 85754ca8ba..da98f3e58b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -584,9 +584,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Logger, Config.ApplicationPaths, _appHost, - _socketFactory, _networkManager, _streamHelper); + } var enableHttpStream = true; @@ -601,9 +601,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { httpUrl += "?transcode=" + profile; } + mediaSource.Path = httpUrl; - return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper); + return new SharedHttpStream( + mediaSource, + info, + streamId, + FileSystem, + _httpClient, + Logger, + Config.ApplicationPaths, + _appHost, + _streamHelper); } return new HdHomerunUdpStream( @@ -616,7 +626,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Logger, Config.ApplicationPaths, _appHost, - _socketFactory, _networkManager, _streamHelper); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 820ba1c36f..eafa86d54e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -21,8 +21,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private const int RtpHeaderBytes = 12; private readonly IServerApplicationHost _appHost; - private readonly MediaBrowser.Model.Net.ISocketFactory _socketFactory; - private readonly IHdHomerunChannelCommands _channelCommands; private readonly int _numTuners; private readonly INetworkManager _networkManager; @@ -37,13 +35,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, - MediaBrowser.Model.Net.ISocketFactory socketFactory, INetworkManager networkManager, IStreamHelper streamHelper) : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper) { _appHost = appHost; - _socketFactory = socketFactory; _networkManager = networkManager; OriginalStreamId = originalStreamId; _channelCommands = channelCommands; -- cgit v1.2.3 From efc4805233fe8a42215198db0baa0f68e012c1f8 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 28 Aug 2019 14:45:46 +0200 Subject: Fix login --- .../Library/DefaultAuthenticationProvider.cs | 3 ++- MediaBrowser.Api/UserService.cs | 2 +- MediaBrowser.Model/Cryptography/PasswordHash.cs | 16 ++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index b07244fda1..2282b8efb6 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(resolvedUser)); } - // As long as jellyfin supports passwordless users, we need this little block here to accomodate + // As long as jellyfin supports passwordless users, we need this little block here to accommodate if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password)) { return Task.FromResult(new ProviderAuthenticationResult @@ -105,6 +105,7 @@ namespace Emby.Server.Implementations.Library public Task ChangePassword(User user, string newPassword) { ConvertPasswordFormat(user); + // This is needed to support changing a no password user to a password user if (string.IsNullOrEmpty(user.Password)) { diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index f08d070ca6..0192805b85 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -418,7 +418,7 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } - catch(SecurityException e) + catch (SecurityException e) { // rethrow adding IP address to message throw new SecurityException($"[{Request.RemoteIp}] {e.Message}"); diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 4bcf0c117f..6e66f20883 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -69,6 +69,13 @@ namespace MediaBrowser.Model.Cryptography } } + public PasswordHash(ICryptoProvider cryptoProvider) + { + _id = cryptoProvider.DefaultHashMethod; + _salt = cryptoProvider.GenerateSalt(); + _hash = Array.Empty(); + } + public string Id { get => _id; set => _id = value; } public Dictionary Parameters { get => _parameters; set => _parameters = value; } @@ -77,13 +84,6 @@ namespace MediaBrowser.Model.Cryptography public byte[] Hash { get => _hash; set => _hash = value; } - public PasswordHash(ICryptoProvider cryptoProvider) - { - _id = cryptoProvider.DefaultHashMethod; - _salt = cryptoProvider.GenerateSalt(); - _hash = Array.Empty(); - } - // TODO: move this class and use the HexHelper class public static byte[] ConvertFromByteString(string byteString) { @@ -127,7 +127,7 @@ namespace MediaBrowser.Model.Cryptography str.Append(_id); SerializeParameters(str); - if (_salt.Length == 0) + if (_salt.Length != 0) { str.Append('$'); str.Append(ConvertToByteString(_salt)); -- cgit v1.2.3 From 21ff63c371bfdb70e0a80f39412b762df05890f6 Mon Sep 17 00:00:00 2001 From: dkanada Date: Thu, 29 Aug 2019 00:14:50 -0700 Subject: move the transcode path --- .../Configuration/ServerConfigurationManager.cs | 8 +- .../Tasks/DeleteTranscodeFileTask.cs | 155 ++++++++++++++++++++ .../Tasks/DeleteTranscodingTempTask.cs | 156 --------------------- .../Tasks/RefreshMediaLibraryTask.cs | 13 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 3 +- 5 files changed, 166 insertions(+), 169 deletions(-) create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs delete mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index c4fa68cac4..c7f92b80b6 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -66,7 +66,7 @@ namespace Emby.Server.Implementations.Configuration { base.AddParts(factories); - UpdateTranscodingTempPath(); + UpdateTranscodePath(); } /// @@ -87,13 +87,13 @@ namespace Emby.Server.Implementations.Configuration /// /// Updates the transcoding temporary path. /// - private void UpdateTranscodingTempPath() + private void UpdateTranscodePath() { var encodingConfig = this.GetConfiguration("encoding"); ((ServerApplicationPaths)ApplicationPaths).TranscodingTempPath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ? null : - Path.Combine(encodingConfig.TranscodingTempPath, "transcoding-temp"); + Path.Combine(encodingConfig.TranscodingTempPath, "transcodes"); } protected override void OnNamedConfigurationUpdated(string key, object configuration) @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Configuration if (string.Equals(key, "encoding", StringComparison.OrdinalIgnoreCase)) { - UpdateTranscodingTempPath(); + UpdateTranscodePath(); } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs new file mode 100644 index 0000000000..c343a7d482 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks +{ + /// + /// Deletes all transcoding temp files + /// + public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask + { + /// + /// Gets or sets the application paths. + /// + /// The application paths. + private ServerApplicationPaths ApplicationPaths { get; set; } + + private readonly ILogger _logger; + + private readonly IFileSystem _fileSystem; + + /// + /// Initializes a new instance of the class. + /// + public DeleteTranscodeFileTask(ServerApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem) + { + ApplicationPaths = appPaths; + _logger = logger; + _fileSystem = fileSystem; + } + + /// + /// Creates the triggers that define when the task will run + /// + /// IEnumerable{BaseTaskTrigger}. + public IEnumerable GetDefaultTriggers() => new List(); + + /// + /// Returns the task to be executed + /// + /// The cancellation token. + /// The progress. + /// Task. + public Task Execute(CancellationToken cancellationToken, IProgress progress) + { + var minDateModified = DateTime.UtcNow.AddDays(-1); + progress.Report(50); + + try + { + DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodingTempPath, minDateModified, progress); + } + catch (DirectoryNotFoundException) + { + // No biggie here. Nothing to delete + } + + return Task.CompletedTask; + } + + + /// + /// Deletes the transcoded temp files from directory with a last write time less than a given date + /// + /// The task cancellation token. + /// The directory. + /// The min date modified. + /// The progress. + private void DeleteTempFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress progress) + { + var filesToDelete = _fileSystem.GetFiles(directory, true) + .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) + .ToList(); + + var index = 0; + + foreach (var file in filesToDelete) + { + double percent = index; + percent /= filesToDelete.Count; + + progress.Report(100 * percent); + + cancellationToken.ThrowIfCancellationRequested(); + + DeleteFile(file.FullName); + + index++; + } + + DeleteEmptyFolders(directory); + + progress.Report(100); + } + + private void DeleteEmptyFolders(string parent) + { + foreach (var directory in _fileSystem.GetDirectoryPaths(parent)) + { + DeleteEmptyFolders(directory); + if (!_fileSystem.GetFileSystemEntryPaths(directory).Any()) + { + try + { + Directory.Delete(directory, false); + } + catch (UnauthorizedAccessException ex) + { + _logger.LogError(ex, "Error deleting directory {path}", directory); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting directory {path}", directory); + } + } + } + } + + private void DeleteFile(string path) + { + try + { + _fileSystem.DeleteFile(path); + } + catch (UnauthorizedAccessException ex) + { + _logger.LogError(ex, "Error deleting file {path}", path); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting file {path}", path); + } + } + + public string Name => "Transcoding temp cleanup"; + + public string Description => "Deletes transcoding temp files older than 24 hours."; + + public string Category => "Maintenance"; + + public string Key => "DeleteTranscodingTempFiles"; + + public bool IsHidden => false; + + public bool IsEnabled => false; + + public bool IsLogged => true; + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs deleted file mode 100644 index ad9b56535d..0000000000 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodingTempTask.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.ScheduledTasks.Tasks -{ - /// - /// Deletes all transcoding temp files - /// - public class DeleteTranscodingTempTask : IScheduledTask, IConfigurableScheduledTask - { - /// - /// Gets or sets the application paths. - /// - /// The application paths. - protected ServerApplicationPaths ApplicationPaths { get; set; } - - - private readonly ILogger _logger; - - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - public DeleteTranscodingTempTask(ServerApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem) - { - ApplicationPaths = appPaths; - _logger = logger; - _fileSystem = fileSystem; - } - - /// - /// Creates the triggers that define when the task will run - /// - /// IEnumerable{BaseTaskTrigger}. - public IEnumerable GetDefaultTriggers() => new List(); - - /// - /// Returns the task to be executed - /// - /// The cancellation token. - /// The progress. - /// Task. - public Task Execute(CancellationToken cancellationToken, IProgress progress) - { - var minDateModified = DateTime.UtcNow.AddDays(-1); - progress.Report(50); - - try - { - DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodingTempPath, minDateModified, progress); - } - catch (DirectoryNotFoundException) - { - // No biggie here. Nothing to delete - } - - return Task.CompletedTask; - } - - - /// - /// Deletes the transcoded temp files from directory with a last write time less than a given date - /// - /// The task cancellation token. - /// The directory. - /// The min date modified. - /// The progress. - private void DeleteTempFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress progress) - { - var filesToDelete = _fileSystem.GetFiles(directory, true) - .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) - .ToList(); - - var index = 0; - - foreach (var file in filesToDelete) - { - double percent = index; - percent /= filesToDelete.Count; - - progress.Report(100 * percent); - - cancellationToken.ThrowIfCancellationRequested(); - - DeleteFile(file.FullName); - - index++; - } - - DeleteEmptyFolders(directory); - - progress.Report(100); - } - - private void DeleteEmptyFolders(string parent) - { - foreach (var directory in _fileSystem.GetDirectoryPaths(parent)) - { - DeleteEmptyFolders(directory); - if (!_fileSystem.GetFileSystemEntryPaths(directory).Any()) - { - try - { - Directory.Delete(directory, false); - } - catch (UnauthorizedAccessException ex) - { - _logger.LogError(ex, "Error deleting directory {path}", directory); - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting directory {path}", directory); - } - } - } - } - - private void DeleteFile(string path) - { - try - { - _fileSystem.DeleteFile(path); - } - catch (UnauthorizedAccessException ex) - { - _logger.LogError(ex, "Error deleting file {path}", path); - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting file {path}", path); - } - } - - public string Name => "Transcoding temp cleanup"; - - public string Description => "Deletes transcoding temp files older than 24 hours."; - - public string Category => "Maintenance"; - - public string Key => "DeleteTranscodingTempFiles"; - - public bool IsHidden => false; - - public bool IsEnabled => false; - - public bool IsLogged => true; - } -} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index 1a3d85ad7f..3e6d251c91 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -10,7 +10,7 @@ using MediaBrowser.Model.Tasks; namespace Emby.Server.Implementations.ScheduledTasks { /// - /// Class RefreshMediaLibraryTask + /// Class RefreshMediaLibraryTask. /// public class RefreshMediaLibraryTask : IScheduledTask { @@ -31,15 +31,14 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// /// IEnumerable{BaseTaskTrigger}. public IEnumerable GetDefaultTriggers() { - return new[] { - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(12).Ticks} + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(12).Ticks }; } @@ -60,7 +59,7 @@ namespace Emby.Server.Implementations.ScheduledTasks public string Name => "Scan media library"; - public string Description => "Scans your media library and refreshes metatata based on configuration."; + public string Description => "Scans your media library for new files and refreshes metadata."; public string Category => "Library"; diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index a22eaaa514..6a8d03f6d5 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -278,7 +278,7 @@ namespace MediaBrowser.Providers.Manager var currentOptions = options; var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name); - var typeFetcherOrder = typeOptions == null ? null : typeOptions.ImageFetcherOrder; + var typeFetcherOrder = typeOptions?.ImageFetcherOrder; return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, options, refreshOptions, includeDisabled)) .OrderBy(i => @@ -287,7 +287,6 @@ namespace MediaBrowser.Providers.Manager if (!(i is ILocalImageProvider)) { var fetcherOrder = typeFetcherOrder ?? currentOptions.ImageFetcherOrder; - var index = Array.IndexOf(fetcherOrder, i.Name); if (index != -1) -- cgit v1.2.3 From 0aaaaab7a0a1d4931bf59ea757761cfe0af7f441 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 29 Aug 2019 20:25:56 +0200 Subject: Fix invalid arg for --- .../LiveTv/EmbyTV/EncodedRecorder.cs | 46 +++++++--------------- 1 file changed, 14 insertions(+), 32 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 9a9bae2151..75efa7ec83 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -309,44 +309,26 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { _hasExited = true; - DisposeLogStream(); + _logFileStream?.Dispose(); + _logFileStream = null; - try - { - var exitCode = process.ExitCode; + var exitCode = process.ExitCode; - _logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {path}", exitCode, _targetPath); + _logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath); - if (exitCode == 0) - { - _taskCompletionSource.TrySetResult(true); - } - else - { - _taskCompletionSource.TrySetException(new Exception(string.Format("Recording for {path} failed. Exit code {ExitCode}", _targetPath, exitCode))); - } - } - catch + if (exitCode == 0) { - _logger.LogError("FFMpeg recording exited with an error for {path}.", _targetPath); - _taskCompletionSource.TrySetException(new Exception(string.Format("Recording for {path} failed", _targetPath))); + _taskCompletionSource.TrySetResult(true); } - } - - private void DisposeLogStream() - { - if (_logFileStream != null) + else { - try - { - _logFileStream.Dispose(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error disposing recording log stream"); - } - - _logFileStream = null; + _taskCompletionSource.TrySetException( + new Exception( + string.Format( + CultureInfo.InvariantCulture, + "Recording for {0} failed. Exit code {1}", + _targetPath, + exitCode))); } } -- cgit v1.2.3 From e4f893a0eb955d43e7ef4c99bef8d4bfeb61a771 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 29 Aug 2019 22:28:33 +0200 Subject: More warning fixes --- Emby.Notifications/Notifications.cs | 37 ++--- .../Activity/ActivityLogEntryPoint.cs | 149 ++++++++++++++++----- .../Data/SqliteItemRepository.cs | 6 +- Emby.Server.Implementations/Dto/DtoService.cs | 3 +- .../LiveTv/EmbyTV/EncodedRecorder.cs | 29 ++-- .../Sorting/ArtistComparer.cs | 24 +--- MediaBrowser.Api/Music/AlbumsService.cs | 11 +- MediaBrowser.Controller/Entities/Audio/Audio.cs | 37 +---- .../Entities/Audio/IHasAlbumArtist.cs | 27 +++- .../Entities/Audio/MusicAlbum.cs | 38 ++---- MediaBrowser.Controller/Entities/MusicVideo.cs | 7 +- MediaBrowser.Controller/Providers/AlbumInfo.cs | 2 +- .../Providers/MusicVideoInfo.cs | 4 +- MediaBrowser.Controller/Providers/SongInfo.cs | 7 +- MediaBrowser.Model/Dto/BaseItemDto.cs | 2 +- MediaBrowser.Model/Search/SearchHint.cs | 4 +- .../Books/AudioBookMetadataService.cs | 25 +++- MediaBrowser.Providers/Manager/ProviderUtils.cs | 8 +- .../Music/AlbumMetadataService.cs | 42 ++++-- .../Music/AudioMetadataService.cs | 18 ++- .../Music/MusicVideoMetadataService.cs | 25 +++- 21 files changed, 303 insertions(+), 202 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Notifications/Notifications.cs b/Emby.Notifications/Notifications.cs index ec08fd1935..7aa1e7ae89 100644 --- a/Emby.Notifications/Notifications.cs +++ b/Emby.Notifications/Notifications.cs @@ -209,47 +209,48 @@ namespace Emby.Notifications public static string GetItemName(BaseItem item) { var name = item.Name; - var episode = item as Episode; - if (episode != null) + if (item is Episode episode) { if (episode.IndexNumber.HasValue) { - name = string.Format("Ep{0} - {1}", episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), name); + name = string.Format( + CultureInfo.InvariantCulture, + "Ep{0} - {1}", + episode.IndexNumber.Value, + name); } if (episode.ParentIndexNumber.HasValue) { - name = string.Format("S{0}, {1}", episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture), name); + name = string.Format( + CultureInfo.InvariantCulture, + "S{0}, {1}", + episode.ParentIndexNumber.Value, + name); } } - var hasSeries = item as IHasSeries; - if (hasSeries != null) + if (item is IHasSeries hasSeries) { name = hasSeries.SeriesName + " - " + name; } - var hasAlbumArtist = item as IHasAlbumArtist; - if (hasAlbumArtist != null) + if (item is IHasAlbumArtist hasAlbumArtist) { var artists = hasAlbumArtist.AlbumArtists; - if (artists.Length > 0) + if (artists.Count > 0) { name = artists[0] + " - " + name; } } - else + else if (item is IHasArtist hasArtist) { - var hasArtist = item as IHasArtist; - if (hasArtist != null) - { - var artists = hasArtist.Artists; + var artists = hasArtist.Artists; - if (artists.Length > 0) - { - name = artists[0] + " - " + name; - } + if (artists.Count > 0) + { + name = artists[0] + " - " + name; } } diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index fb4ffd74bc..1514402d68 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -96,7 +96,10 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("CameraImageUploadedFrom"), e.Argument.Device.Name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("CameraImageUploadedFrom"), + e.Argument.Device.Name), Type = NotificationType.CameraImageUploaded.ToString() }); } @@ -105,7 +108,10 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("UserLockedOutWithName"), + e.Argument.Name), Type = NotificationType.UserLockedOut.ToString(), UserId = e.Argument.Id }); @@ -115,7 +121,11 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), + e.Provider, + Notifications.Notifications.GetItemName(e.Item)), Type = "SubtitleDownloadFailure", ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture), ShortOverview = e.Exception.Message @@ -178,7 +188,12 @@ namespace Emby.Server.Implementations.Activity CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("UserStartedPlayingItemWithValues"), + user.Name, + GetItemName(item), + e.DeviceName), Type = GetPlaybackNotificationType(item.MediaType), UserId = user.Id }); @@ -193,7 +208,7 @@ namespace Emby.Server.Implementations.Activity name = item.SeriesName + " - " + name; } - if (item.Artists != null && item.Artists.Length > 0) + if (item.Artists != null && item.Artists.Count > 0) { name = item.Artists[0] + " - " + name; } @@ -238,21 +253,31 @@ namespace Emby.Server.Implementations.Activity if (string.IsNullOrEmpty(session.UserName)) { - name = string.Format(_localization.GetLocalizedString("DeviceOfflineWithName"), session.DeviceName); + name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("DeviceOfflineWithName"), + session.DeviceName); // Causing too much spam for now return; } else { - name = string.Format(_localization.GetLocalizedString("UserOfflineFromDevice"), session.UserName, session.DeviceName); + name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("UserOfflineFromDevice"), + session.UserName, + session.DeviceName); } CreateLogEntry(new ActivityLogEntry { Name = name, Type = "SessionEnded", - ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), session.RemoteEndPoint), + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("LabelIpAddressValue"), + session.RemoteEndPoint), UserId = session.UserId }); } @@ -263,9 +288,15 @@ namespace Emby.Server.Implementations.Activity CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), user.Name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("AuthenticationSucceededWithUserName"), + user.Name), Type = "AuthenticationSucceeded", - ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), e.Argument.SessionInfo.RemoteEndPoint), + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("LabelIpAddressValue"), + e.Argument.SessionInfo.RemoteEndPoint), UserId = user.Id }); } @@ -274,9 +305,15 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("FailedLoginAttemptWithUserName"), + e.Argument.Username), Type = "AuthenticationFailed", - ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), e.Argument.RemoteEndPoint), + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("LabelIpAddressValue"), + e.Argument.RemoteEndPoint), Severity = LogLevel.Error }); } @@ -285,7 +322,10 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("UserPolicyUpdatedWithName"), e.Argument.Name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("UserPolicyUpdatedWithName"), + e.Argument.Name), Type = "UserPolicyUpdated", UserId = e.Argument.Id }); @@ -295,7 +335,10 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("UserDeletedWithName"), + e.Argument.Name), Type = "UserDeleted" }); } @@ -304,7 +347,10 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("UserPasswordChangedWithName"), + e.Argument.Name), Type = "UserPasswordChanged", UserId = e.Argument.Id }); @@ -314,7 +360,10 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("UserCreatedWithName"), + e.Argument.Name), Type = "UserCreated", UserId = e.Argument.Id }); @@ -327,21 +376,31 @@ namespace Emby.Server.Implementations.Activity if (string.IsNullOrEmpty(session.UserName)) { - name = string.Format(_localization.GetLocalizedString("DeviceOnlineWithName"), session.DeviceName); + name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("DeviceOnlineWithName"), + session.DeviceName); // Causing too much spam for now return; } else { - name = string.Format(_localization.GetLocalizedString("UserOnlineFromDevice"), session.UserName, session.DeviceName); + name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("UserOnlineFromDevice"), + session.UserName, + session.DeviceName); } CreateLogEntry(new ActivityLogEntry { Name = name, Type = "SessionStarted", - ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), session.RemoteEndPoint), + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("LabelIpAddressValue"), + session.RemoteEndPoint), UserId = session.UserId }); } @@ -350,9 +409,15 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("PluginUpdatedWithName"), + e.Argument.Item1.Name), Type = NotificationType.PluginUpdateInstalled.ToString(), - ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.Item2.versionStr), + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("VersionNumber"), + e.Argument.Item2.versionStr), Overview = e.Argument.Item2.description }); } @@ -361,7 +426,10 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("PluginUninstalledWithName"), + e.Argument.Name), Type = NotificationType.PluginUninstalled.ToString() }); } @@ -370,9 +438,15 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("PluginInstalledWithName"), + e.Argument.name), Type = NotificationType.PluginInstalled.ToString(), - ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.versionStr) + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("VersionNumber"), + e.Argument.versionStr) }); } @@ -382,9 +456,15 @@ namespace Emby.Server.Implementations.Activity CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("NameInstallFailed"), installationInfo.Name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("NameInstallFailed"), + installationInfo.Name), Type = NotificationType.InstallationFailed.ToString(), - ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), installationInfo.Version), + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("VersionNumber"), + installationInfo.Version), Overview = e.Exception.Message }); } @@ -401,7 +481,10 @@ namespace Emby.Server.Implementations.Activity } var time = result.EndTimeUtc - result.StartTimeUtc; - var runningTime = string.Format(_localization.GetLocalizedString("LabelRunningTimeValue"), ToUserFriendlyString(time)); + var runningTime = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("LabelRunningTimeValue"), + ToUserFriendlyString(time)); if (result.Status == TaskCompletionStatus.Failed) { @@ -419,7 +502,10 @@ namespace Emby.Server.Implementations.Activity CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("ScheduledTaskFailedWithName"), + task.Name), Type = NotificationType.TaskFailed.ToString(), Overview = string.Join(Environment.NewLine, vals), ShortOverview = runningTime, @@ -534,8 +620,11 @@ namespace Emby.Server.Implementations.Activity /// The name of this item (singular form) private static string CreateValueString(int value, string description) { - return string.Format("{0:#,##0} {1}", - value, value == 1 ? description : string.Format("{0}s", description)); + return string.Format( + CultureInfo.InvariantCulture, + "{0:#,##0} {1}", + value, + value == 1 ? description : string.Format(CultureInfo.InvariantCulture, "{0}s", description)); } } } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 9d983307f2..c3789eef2f 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -979,7 +979,7 @@ namespace Emby.Server.Implementations.Data } string artists = null; - if (item is IHasArtist hasArtists && hasArtists.Artists.Length > 0) + if (item is IHasArtist hasArtists && hasArtists.Artists.Count > 0) { artists = string.Join("|", hasArtists.Artists); } @@ -987,7 +987,7 @@ namespace Emby.Server.Implementations.Data string albumArtists = null; if (item is IHasAlbumArtist hasAlbumArtists - && hasAlbumArtists.AlbumArtists.Length > 0) + && hasAlbumArtists.AlbumArtists.Count > 0) { albumArtists = string.Join("|", hasAlbumArtists.AlbumArtists); } @@ -5731,7 +5731,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { if (isSubsequentRow) { - insertText.Append(","); + insertText.Append(','); } insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0})", i.ToString(CultureInfo.InvariantCulture)); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 97ddfff530..6da1026188 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -886,8 +886,7 @@ namespace Emby.Server.Implementations.Dto //} } - var hasArtist = item as IHasArtist; - if (hasArtist != null) + if (item is IHasArtist hasArtist) { dto.Artists = hasArtist.Artists; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 9a9bae2151..fbcc66f513 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -87,8 +87,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV CreateNoWindow = true, UseShellExecute = false, - // Must consume both stdout and stderr or deadlocks may occur - //RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = true, @@ -120,9 +118,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV cancellationToken.Register(Stop); - // MUST read both stdout and stderr asynchronously or a deadlock may occurr - //process.BeginOutputReadLine(); - onStarted(); // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback @@ -138,11 +133,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV string videoArgs; if (EncodeVideo(mediaSource)) { - var maxBitrate = 25000000; + const int MaxBitrate = 25000000; videoArgs = string.Format( - "-codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41", - GetOutputSizeParam(), - maxBitrate.ToString(CultureInfo.InvariantCulture)); + CultureInfo.InvariantCulture, + "-codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41", + GetOutputSizeParam(), + MaxBitrate); } else { @@ -151,18 +147,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV videoArgs += " -fflags +genpts"; - var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks); - durationParam = string.Empty; - var flags = new List(); if (mediaSource.IgnoreDts) { flags.Add("+igndts"); } + if (mediaSource.IgnoreIndex) { flags.Add("+ignidx"); } + if (mediaSource.GenPtsInput) { flags.Add("+genpts"); @@ -172,11 +167,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (flags.Count > 0) { - inputModifier += " -fflags " + string.Join("", flags.ToArray()); + inputModifier += " -fflags " + string.Join(string.Empty, flags); } - var videoStream = mediaSource.VideoStream; - if (mediaSource.ReadAtNativeFramerate) { inputModifier += " -re"; @@ -200,13 +193,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var outputParam = string.Empty; - var commandLineArgs = string.Format("-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4}{6} -y \"{1}\"", + var commandLineArgs = string.Format( + CultureInfo.InvariantCulture, + "-i \"{0}\" {2} -map_metadata -1 -threads 0 {3}{4}{5} -y \"{1}\"", inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, - durationParam, outputParam); return inputModifier + " " + commandLineArgs; @@ -257,7 +251,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { _logger.LogInformation("Stopping ffmpeg recording process for {path}", _targetPath); - //process.Kill(); _process.StandardInput.WriteLine("q"); } catch (Exception ex) diff --git a/Emby.Server.Implementations/Sorting/ArtistComparer.cs b/Emby.Server.Implementations/Sorting/ArtistComparer.cs index 9d5befc9a5..756d3c5b6c 100644 --- a/Emby.Server.Implementations/Sorting/ArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/ArtistComparer.cs @@ -7,16 +7,14 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class ArtistComparer + /// Class ArtistComparer. /// public class ArtistComparer : IBaseItemComparer { - /// - /// Compares the specified x. - /// - /// The x. - /// The y. - /// System.Int32. + /// + public string Name => ItemSortBy.Artist; + + /// public int Compare(BaseItem x, BaseItem y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); @@ -29,20 +27,12 @@ namespace Emby.Server.Implementations.Sorting /// System.String. private static string GetValue(BaseItem x) { - var audio = x as Audio; - - if (audio == null) + if (!(x is Audio audio)) { return string.Empty; } - return audio.Artists.Length == 0 ? null : audio.Artists[0]; + return audio.Artists.Count == 0 ? null : audio.Artists[0]; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.Artist; } } diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index c48bcde5c1..2cd3a1003c 100644 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -104,16 +104,15 @@ namespace MediaBrowser.Api.Music var album2 = (MusicAlbum)item2; var artists1 = album1 - .AllArtists + .GetAllArtists() .DistinctNames() .ToList(); - var artists2 = album2 - .AllArtists - .DistinctNames() - .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); + var artists2 = new HashSet( + album2.GetAllArtists().DistinctNames(), + StringComparer.OrdinalIgnoreCase); - return points + artists1.Where(artists2.ContainsKey).Sum(i => 5); + return points + artists1.Where(artists2.Contains).Sum(i => 5); } } } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 13a6fe44a9..67b21068ae 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; @@ -19,15 +20,13 @@ namespace MediaBrowser.Controller.Entities.Audio IHasLookupInfo, IHasMediaSources { - /// - /// Gets or sets the artist. - /// - /// The artist. + /// [IgnoreDataMember] - public string[] Artists { get; set; } + public IReadOnlyList Artists { get; set; } + /// [IgnoreDataMember] - public string[] AlbumArtists { get; set; } + public IReadOnlyList AlbumArtists { get; set; } public Audio() { @@ -63,30 +62,6 @@ namespace MediaBrowser.Controller.Entities.Audio return IsFileProtocol; } - [IgnoreDataMember] - public string[] AllArtists - { - get - { - var list = new string[AlbumArtists.Length + Artists.Length]; - - var index = 0; - foreach (var artist in AlbumArtists) - { - list[index] = artist; - index++; - } - foreach (var artist in Artists) - { - list[index] = artist; - index++; - } - - return list; - - } - } - [IgnoreDataMember] public MusicAlbum AlbumEntity => FindParent(); @@ -125,7 +100,7 @@ namespace MediaBrowser.Controller.Entities.Audio songKey = Album + "-" + songKey; } - var albumArtist = AlbumArtists.Length == 0 ? null : AlbumArtists[0]; + var albumArtist = AlbumArtists.FirstOrDefault(); if (!string.IsNullOrEmpty(albumArtist)) { songKey = albumArtist + "-" + songKey; diff --git a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs index a269b34863..056f31f780 100644 --- a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs @@ -1,14 +1,35 @@ +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities.Audio { public interface IHasAlbumArtist { - string[] AlbumArtists { get; set; } + IReadOnlyList AlbumArtists { get; set; } } public interface IHasArtist { - string[] AllArtists { get; } + /// + /// Gets or sets the artists. + /// + /// The artists. + IReadOnlyList Artists { get; set; } + } + + public static class Extentions + { + public static IEnumerable GetAllArtists(this T item) + where T : IHasArtist, IHasAlbumArtist + { + foreach (var i in item.AlbumArtists) + { + yield return i; + } - string[] Artists { get; set; } + foreach (var i in item.Artists) + { + yield return i; + } + } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 5b2939b759..edf6ffa213 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -18,8 +18,11 @@ namespace MediaBrowser.Controller.Entities.Audio /// public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo, IMetadataContainer { - public string[] AlbumArtists { get; set; } - public string[] Artists { get; set; } + /// + public IReadOnlyList AlbumArtists { get; set; } + + /// + public IReadOnlyList Artists { get; set; } public MusicAlbum() { @@ -41,8 +44,7 @@ namespace MediaBrowser.Controller.Entities.Audio var parents = GetParents(); foreach (var parent in parents) { - var artist = parent as MusicArtist; - if (artist != null) + if (parent is MusicArtist artist) { return artist; } @@ -63,30 +65,7 @@ namespace MediaBrowser.Controller.Entities.Audio public override bool SupportsCumulativeRunTimeTicks => true; [IgnoreDataMember] - public string[] AllArtists - { - get - { - var list = new string[AlbumArtists.Length + Artists.Length]; - - var index = 0; - foreach (var artist in AlbumArtists) - { - list[index] = artist; - index++; - } - foreach (var artist in Artists) - { - list[index] = artist; - index++; - } - - return list; - } - } - - [IgnoreDataMember] - public string AlbumArtist => AlbumArtists.Length == 0 ? null : AlbumArtists[0]; + public string AlbumArtist => AlbumArtists.FirstOrDefault(); [IgnoreDataMember] public override bool SupportsPeople => false; @@ -216,8 +195,7 @@ namespace MediaBrowser.Controller.Entities.Audio private async Task RefreshArtists(MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { - var all = AllArtists; - foreach (var i in all) + foreach (var i in this.GetAllArtists()) { // This should not be necessary but we're seeing some cases of it if (string.IsNullOrEmpty(i)) diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index 5bf082b7ee..94fe11e9da 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; @@ -8,17 +9,15 @@ namespace MediaBrowser.Controller.Entities { public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasLookupInfo { + /// [IgnoreDataMember] - public string[] Artists { get; set; } + public IReadOnlyList Artists { get; set; } public MusicVideo() { Artists = Array.Empty(); } - [IgnoreDataMember] - public string[] AllArtists => Artists; - public override UnratedItem GetBlockUnratedType() { return UnratedItem.Music; diff --git a/MediaBrowser.Controller/Providers/AlbumInfo.cs b/MediaBrowser.Controller/Providers/AlbumInfo.cs index b0b443fc01..ac6b86c1d8 100644 --- a/MediaBrowser.Controller/Providers/AlbumInfo.cs +++ b/MediaBrowser.Controller/Providers/AlbumInfo.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Providers /// Gets or sets the album artist. /// /// The album artist. - public string[] AlbumArtists { get; set; } + public IReadOnlyList AlbumArtists { get; set; } /// /// Gets or sets the artist provider ids. diff --git a/MediaBrowser.Controller/Providers/MusicVideoInfo.cs b/MediaBrowser.Controller/Providers/MusicVideoInfo.cs index 194b264849..9835351fc8 100644 --- a/MediaBrowser.Controller/Providers/MusicVideoInfo.cs +++ b/MediaBrowser.Controller/Providers/MusicVideoInfo.cs @@ -1,7 +1,9 @@ +using System.Collections.Generic; + namespace MediaBrowser.Controller.Providers { public class MusicVideoInfo : ItemLookupInfo { - public string[] Artists { get; set; } + public IReadOnlyList Artists { get; set; } } } diff --git a/MediaBrowser.Controller/Providers/SongInfo.cs b/MediaBrowser.Controller/Providers/SongInfo.cs index 61e9501307..50615b0bd3 100644 --- a/MediaBrowser.Controller/Providers/SongInfo.cs +++ b/MediaBrowser.Controller/Providers/SongInfo.cs @@ -1,12 +1,15 @@ using System; +using System.Collections.Generic; namespace MediaBrowser.Controller.Providers { public class SongInfo : ItemLookupInfo { - public string[] AlbumArtists { get; set; } + public IReadOnlyList AlbumArtists { get; set; } + public string Album { get; set; } - public string[] Artists { get; set; } + + public IReadOnlyList Artists { get; set; } public SongInfo() { diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index b382d9d4ab..db32fedfc2 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -386,7 +386,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the artists. /// /// The artists. - public string[] Artists { get; set; } + public IReadOnlyList Artists { get; set; } /// /// Gets or sets the artist items. diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs index 8a187f18ef..8f4824903f 100644 --- a/MediaBrowser.Model/Search/SearchHint.cs +++ b/MediaBrowser.Model/Search/SearchHint.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace MediaBrowser.Model.Search { @@ -111,6 +112,7 @@ namespace MediaBrowser.Model.Search /// /// The album. public string Album { get; set; } + public Guid AlbumId { get; set; } /// @@ -123,7 +125,7 @@ namespace MediaBrowser.Model.Search /// Gets or sets the artists. /// /// The artists. - public string[] Artists { get; set; } + public IReadOnlyList Artists { get; set; } /// /// Gets or sets the song count. diff --git a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs index 4820e12ab9..0062d5ab3b 100644 --- a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs +++ b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs @@ -11,14 +11,31 @@ namespace MediaBrowser.Providers.Books { public class AudioBookMetadataService : MetadataService { - protected override void MergeData(MetadataResult source, MetadataResult target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) + public AudioBookMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger logger, + IProviderManager providerManager, + IFileSystem fileSystem, + IUserDataManager userDataManager, + ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) + { + } + + /// + protected override void MergeData( + MetadataResult source, + MetadataResult target, + MetadataFields[] lockedFields, + bool replaceData, + bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); var sourceItem = source.Item; var targetItem = target.Item; - if (replaceData || targetItem.Artists.Length == 0) + if (replaceData || targetItem.Artists.Count == 0) { targetItem.Artists = sourceItem.Artists; } @@ -28,9 +45,5 @@ namespace MediaBrowser.Providers.Books targetItem.Album = sourceItem.Album; } } - - public AudioBookMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 2031449d99..e8ed990ed5 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -252,12 +252,10 @@ namespace MediaBrowser.Providers.Manager private static void MergeAlbumArtist(BaseItem source, BaseItem target, MetadataFields[] lockedFields, bool replaceData) { - var sourceHasAlbumArtist = source as IHasAlbumArtist; - var targetHasAlbumArtist = target as IHasAlbumArtist; - - if (sourceHasAlbumArtist != null && targetHasAlbumArtist != null) + if (source is IHasAlbumArtist sourceHasAlbumArtist + && target is IHasAlbumArtist targetHasAlbumArtist) { - if (replaceData || targetHasAlbumArtist.AlbumArtists.Length == 0) + if (replaceData || targetHasAlbumArtist.AlbumArtists.Count == 0) { targetHasAlbumArtist.AlbumArtists = sourceHasAlbumArtist.AlbumArtists; } diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs index 33a6c2fa36..4e59b4119b 100644 --- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs +++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs @@ -15,12 +15,34 @@ namespace MediaBrowser.Providers.Music { public class AlbumMetadataService : MetadataService { + public AlbumMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger logger, + IProviderManager providerManager, + IFileSystem fileSystem, + IUserDataManager userDataManager, + ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) + { + } + + /// + protected override bool EnableUpdatingPremiereDateFromChildren => true; + + /// + protected override bool EnableUpdatingGenresFromChildren => true; + + /// + protected override bool EnableUpdatingStudiosFromChildren => true; + + /// protected override IList GetChildrenForMetadataUpdates(MusicAlbum item) { return item.GetRecursiveChildren(i => i is Audio) .ToList(); } + /// protected override ItemUpdateType UpdateMetadataFromChildren(MusicAlbum item, IList children, bool isFullRefresh, ItemUpdateType currentUpdateType) { var updateType = base.UpdateMetadataFromChildren(item, children, isFullRefresh, currentUpdateType); @@ -50,12 +72,6 @@ namespace MediaBrowser.Providers.Music return updateType; } - protected override bool EnableUpdatingPremiereDateFromChildren => true; - - protected override bool EnableUpdatingGenresFromChildren => true; - - protected override bool EnableUpdatingStudiosFromChildren => true; - private ItemUpdateType SetAlbumArtistFromSongs(MusicAlbum item, IEnumerable /// All concrete types. - public Type[] AllConcreteTypes { get; protected set; } + private Type[] _allConcreteTypes; /// - /// The disposable parts + /// The disposable parts. /// private readonly List _disposableParts = new List(); @@ -499,7 +499,7 @@ namespace Emby.Server.Implementations { var currentType = typeof(T); - return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i)); + return _allConcreteTypes.Where(i => currentType.IsAssignableFrom(i)); } /// @@ -1010,9 +1010,11 @@ namespace Emby.Server.Implementations .Select(x => Assembly.LoadFrom(x)) .SelectMany(x => x.ExportedTypes) .Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType) - .ToList(); + .ToArray(); - types.AddRange(types); + int oldLen = _allConcreteTypes.Length; + Array.Resize(ref _allConcreteTypes, oldLen + types.Length); + types.CopyTo(_allConcreteTypes, oldLen); var plugins = types.Where(x => x.IsAssignableFrom(typeof(IPlugin))) .Select(CreateInstanceSafe) @@ -1022,8 +1024,8 @@ namespace Emby.Server.Implementations .Where(x => x != null) .ToArray(); - int oldLen = _plugins.Length; - Array.Resize(ref _plugins, _plugins.Length + plugins.Length); + oldLen = _plugins.Length; + Array.Resize(ref _plugins, oldLen + plugins.Length); plugins.CopyTo(_plugins, oldLen); var entries = types.Where(x => x.IsAssignableFrom(typeof(IServerEntryPoint))) @@ -1140,7 +1142,7 @@ namespace Emby.Server.Implementations { Logger.LogInformation("Loading assemblies"); - AllConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray(); + _allConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray(); } private IEnumerable GetTypes(IEnumerable assemblies) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index e872834779..8c57ee4534 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -49,7 +49,6 @@ - -- cgit v1.2.3 From 1616f24cee15095b22092c63b5627662bf47befc Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 1 Sep 2019 18:39:23 +0200 Subject: Fix more warnings --- Emby.Photos/PhotoProvider.cs | 4 +- .../MediaEncoder/EncodingManager.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 775 ++++++++++++--------- .../MediaEncoding/MediaEncoderHelpers.cs | 17 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- .../MediaInfo/VideoImageProvider.cs | 2 +- MediaBrowser.XbmcMetadata/EntryPoint.cs | 16 +- MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs | 39 +- MediaBrowser.sln | 8 +- 9 files changed, 492 insertions(+), 373 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs index 99a635e608..1591609ab0 100644 --- a/Emby.Photos/PhotoProvider.cs +++ b/Emby.Photos/PhotoProvider.cs @@ -170,8 +170,8 @@ namespace Emby.Photos } } - const ItemUpdateType result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport; - return Task.FromResult(result); + const ItemUpdateType Result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport; + return Task.FromResult(Result); } } } diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 52d07d784d..840aca7a6b 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -139,7 +139,7 @@ namespace Emby.Server.Implementations.MediaEncoder var protocol = MediaProtocol.File; - var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, Array.Empty()); + var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, null, Array.Empty()); Directory.CreateDirectory(Path.GetDirectoryName(path)); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5665e72cfe..963091673e 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; @@ -22,8 +23,17 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; private readonly ISubtitleEncoder _subtitleEncoder; - // private readonly IApplicationPaths _appPaths; - // private readonly IAssemblyInfo _assemblyInfo; + + private static readonly string[] _videoProfiles = new[] + { + "ConstrainedBaseline", + "Baseline", + "Extended", + "Main", + "High", + "ProgressiveHigh", + "ConstrainedHigh" + }; public EncodingHelper(IMediaEncoder mediaEncoder, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder) { @@ -33,14 +43,10 @@ namespace MediaBrowser.Controller.MediaEncoding } public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) - { - return GetH264OrH265Encoder("libx264", "h264", state, encodingOptions); - } + => GetH264OrH265Encoder("libx264", "h264", state, encodingOptions); public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) - { - return GetH264OrH265Encoder("libx265", "hevc", state, encodingOptions); - } + => GetH264OrH265Encoder("libx265", "hevc", state, encodingOptions); private string GetH264OrH265Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptions encodingOptions) { @@ -64,19 +70,17 @@ namespace MediaBrowser.Controller.MediaEncoding }; if (!string.IsNullOrEmpty(hwType) - && encodingOptions.EnableHardwareEncoding && codecMap.ContainsKey(hwType)) + && encodingOptions.EnableHardwareEncoding + && codecMap.ContainsKey(hwType) + && CheckVaapi(state, hwType, encodingOptions)) { - if (CheckVaapi(state, hwType, encodingOptions)) - { - var preferredEncoder = codecMap[hwType]; + var preferredEncoder = codecMap[hwType]; - if (_mediaEncoder.SupportsEncoder(preferredEncoder)) - { - return preferredEncoder; - } + if (_mediaEncoder.SupportsEncoder(preferredEncoder)) + { + return preferredEncoder; } } - } // Avoid performing a second attempt when the first one @@ -106,15 +110,13 @@ namespace MediaBrowser.Controller.MediaEncoding { var videoStream = state.VideoStream; - if (videoStream != null) + // vaapi will throw an error with this input + // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99. + if (string.Equals(videoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) { - // vaapi will throw an error with this input - // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99. - if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) - { - return false; - } + return false; } + return true; } @@ -127,23 +129,27 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { - if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || - string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) { return GetH265Encoder(state, encodingOptions); } + if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) { return GetH264Encoder(state, encodingOptions); } + if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase)) { return "libvpx"; } + if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase)) { return "wmv2"; } + if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase)) { return "libtheora"; @@ -162,9 +168,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// System.String. public string GetUserAgentParam(EncodingJobInfo state) { - string useragent = null; - - state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); + state.RemoteHttpHeaders.TryGetValue("User-Agent", out string useragent); if (!string.IsNullOrEmpty(useragent)) { @@ -193,50 +197,62 @@ namespace MediaBrowser.Controller.MediaEncoding { return null; } + if (string.Equals(container, "wmv", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "mpg", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "rec", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "ogm", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "divx", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "tp", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "rmvb", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "rtp", StringComparison.OrdinalIgnoreCase)) { return null; @@ -264,10 +280,12 @@ namespace MediaBrowser.Controller.MediaEncoding { return null; } + if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase)) { return null; @@ -292,30 +310,37 @@ namespace MediaBrowser.Controller.MediaEncoding { return "mp3"; } + if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase)) { return "aac"; } + if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase)) { return "wma"; } + if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } + if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } + if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } + if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } + if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; @@ -337,14 +362,17 @@ namespace MediaBrowser.Controller.MediaEncoding { return "wmv"; } + if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) { return "vpx"; } + if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) { return "theora"; } + if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase)) { return "h264"; @@ -355,19 +383,9 @@ namespace MediaBrowser.Controller.MediaEncoding public int GetVideoProfileScore(string profile) { - var list = new[] - { - "ConstrainedBaseline", - "Baseline", - "Extended", - "Main", - "High", - "ProgressiveHigh", - "ConstrainedHigh" - }; - // strip spaces because they may be stripped out on the query string - return Array.FindIndex(list, t => string.Equals(t, profile.Replace(" ", ""), StringComparison.OrdinalIgnoreCase)); + profile = profile.Replace(" ", ""); + return Array.FindIndex(_videoProfiles, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); } public string GetInputPathArgument(EncodingJobInfo state) @@ -375,14 +393,19 @@ namespace MediaBrowser.Controller.MediaEncoding var protocol = state.InputProtocol; var mediaPath = state.MediaPath ?? string.Empty; - var inputPath = new[] { mediaPath }; - - if (state.IsInputVideo) + string[] inputPath; + if (state.IsInputVideo + && !(state.VideoType == VideoType.Iso && state.IsoMount == null)) { - if (!(state.VideoType == VideoType.Iso && state.IsoMount == null)) - { - inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames); - } + inputPath = MediaEncoderHelpers.GetInputArgument( + _fileSystem, + mediaPath, + state.IsoMount, + state.PlayableStreamFileNames); + } + else + { + inputPath = new[] { mediaPath }; } return _mediaEncoder.GetInputArgument(inputPath, protocol); @@ -401,18 +424,22 @@ namespace MediaBrowser.Controller.MediaEncoding { return "aac -strict experimental"; } + if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) { return "libmp3lame"; } + if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase)) { return "libvorbis"; } + if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase)) { return "wmav2"; } + if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) { return "libopus"; @@ -426,54 +453,59 @@ namespace MediaBrowser.Controller.MediaEncoding /// public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions) { - var request = state.BaseRequest; - - var arg = string.Format("-i {0}", GetInputPathArgument(state)); + var arg = new StringBuilder(); - if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) + if (state.IsVideoRequest + && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { - if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hwOutputFormat = "vaapi"; + + if (hasGraphicalSubs) { - if (state.VideoStream != null && state.VideoStream.Width.HasValue) - { - // This is hacky but not sure how to get the exact subtitle resolution - int height = Convert.ToInt32((double)state.VideoStream.Width.Value / 16.0 * 9.0); + hwOutputFormat = "yuv420p"; + } - arg += string.Format(" -canvas_size {0}:{1}", state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture), height.ToString(CultureInfo.InvariantCulture)); - } + arg.Append("-hwaccel vaapi -hwaccel_output_format ") + .Append(hwOutputFormat) + .Append(" -vaapi_device ") + .Append(encodingOptions.VaapiDevice) + .Append(' '); + } - var subtitlePath = state.SubtitleStream.Path; + arg.Append("-i ") + .Append(GetInputPathArgument(state)); - if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase)) - { - var idxFile = Path.ChangeExtension(subtitlePath, ".idx"); - if (File.Exists(idxFile)) - { - subtitlePath = idxFile; - } - } + if (state.SubtitleStream != null + && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode + && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) + { + if (state.VideoStream != null && state.VideoStream.Width.HasValue) + { + // This is hacky but not sure how to get the exact subtitle resolution + int height = Convert.ToInt32(state.VideoStream.Width.Value / 16.0 * 9.0); - arg += " -i \"" + subtitlePath + "\""; + arg.Append(" -canvas_size ") + .Append(state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture)) + .Append(':') + .Append(height.ToString(CultureInfo.InvariantCulture)); } - } - if (state.IsVideoRequest) - { - if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) - { - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - var hwOutputFormat = "vaapi"; + var subtitlePath = state.SubtitleStream.Path; - if (hasGraphicalSubs) + if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase)) + { + var idxFile = Path.ChangeExtension(subtitlePath, ".idx"); + if (File.Exists(idxFile)) { - hwOutputFormat = "yuv420p"; + subtitlePath = idxFile; } - - arg = "-hwaccel vaapi -hwaccel_output_format " + hwOutputFormat + " -vaapi_device " + encodingOptions.VaapiDevice + " " + arg; } + + arg.Append(" -i \"").Append(subtitlePath).Append('\"'); } - return arg.Trim(); + return arg.ToString(); } /// @@ -485,16 +517,16 @@ namespace MediaBrowser.Controller.MediaEncoding { var codec = stream.Codec ?? string.Empty; - return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || - codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; + return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 + || codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; } public bool IsH265(MediaStream stream) { var codec = stream.Codec ?? string.Empty; - return codec.IndexOf("265", StringComparison.OrdinalIgnoreCase) != -1 || - codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1; + return codec.IndexOf("265", StringComparison.OrdinalIgnoreCase) != -1 + || codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1; } public string GetBitStreamArgs(MediaStream stream) @@ -523,27 +555,38 @@ namespace MediaBrowser.Controller.MediaEncoding { // With vpx when crf is used, b:v becomes a max rate // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. - return string.Format(" -maxrate:v {0} -bufsize:v {1} -b:v {0}", bitrate.Value.ToString(_usCulture), (bitrate.Value * 2).ToString(_usCulture)); + return string.Format( + CultureInfo.InvariantCulture, + " -maxrate:v {0} -bufsize:v {1} -b:v {0}", + bitrate.Value, + bitrate.Value * 2); } if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase)) { - return string.Format(" -b:v {0}", bitrate.Value.ToString(_usCulture)); + return string.Format( + CultureInfo.InvariantCulture, + " -b:v {0}", + bitrate.Value); } if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase)) { // h264 - return string.Format(" -maxrate {0} -bufsize {1}", - bitrate.Value.ToString(_usCulture), - (bitrate.Value * 2).ToString(_usCulture)); + return string.Format( + CultureInfo.InvariantCulture, + " -maxrate {0} -bufsize {1}", + bitrate.Value, + bitrate.Value * 2); } // h264 - return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}", - bitrate.Value.ToString(_usCulture), - (bitrate.Value * 2).ToString(_usCulture)); + return string.Format( + CultureInfo.InvariantCulture, + " -b:v {0} -maxrate {0} -bufsize {1}", + bitrate.Value, + bitrate.Value * 2); } return string.Empty; @@ -576,7 +619,7 @@ namespace MediaBrowser.Controller.MediaEncoding // hls always copies timestamps var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive ? string.Empty - : string.Format(",setpts=PTS -{0}/TB", seconds.ToString(_usCulture)); + : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds); // TODO // var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf"); @@ -684,6 +727,7 @@ namespace MediaBrowser.Controller.MediaEncoding { encodeCrf = encodingOptions.H265Crf; } + if (encodeCrf >= 0 && encodeCrf <= 51) { param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture); @@ -695,12 +739,11 @@ namespace MediaBrowser.Controller.MediaEncoding { defaultCrf = "28"; } + param += " -crf " + defaultCrf; } } - - // h264 (h264_qsv) - else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) // h264 (h264_qsv) { string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" }; @@ -716,10 +759,8 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -look_ahead 0"; } - - // h264 (h264_nvenc) - else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) { switch (encodingOptions.EncoderPreset) { @@ -750,9 +791,7 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - - // webm - else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm { // Values 0-3, 0 being highest quality but slower var profileScore = 0; @@ -778,18 +817,14 @@ namespace MediaBrowser.Controller.MediaEncoding qmin, qmax); } - else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase)) { param += "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2"; } - - // asf/wmv - else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase)) // asf/wmv { param += "-qmin 2"; } - else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase)) { param += "-mbd 2"; @@ -805,21 +840,21 @@ namespace MediaBrowser.Controller.MediaEncoding var targetVideoCodec = state.ActualOutputVideoCodec; - var request = state.BaseRequest; var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault(); // vaapi does not support Baseline profile, force Constrained Baseline in this case, // which is compatible (and ugly) - if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && - profile != null && profile.ToLowerInvariant().Contains("baseline")) + if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + && profile != null + && profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1) { - profile = "constrained_baseline"; + profile = "constrained_baseline"; } if (!string.IsNullOrEmpty(profile)) { - if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { // not supported by h264_omx param += " -profile:v " + profile; @@ -834,9 +869,9 @@ namespace MediaBrowser.Controller.MediaEncoding // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 - if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { switch (level) { @@ -872,11 +907,11 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - // nvenc doesn't decode with param -level set ?! - else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) { - // todo param += ""; + // nvenc doesn't decode with param -level set ?! + // TODO: } else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)) { @@ -894,10 +929,10 @@ namespace MediaBrowser.Controller.MediaEncoding // todo } - if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { param = "-pix_fmt yuv420p " + param; } @@ -919,12 +954,10 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - if (videoStream.IsInterlaced) + if (videoStream.IsInterlaced + && state.DeInterlace(videoStream.Codec, false)) { - if (state.DeInterlace(videoStream.Codec, false)) - { - return false; - } + return false; } if (videoStream.IsAnamorphic ?? false) @@ -936,24 +969,23 @@ namespace MediaBrowser.Controller.MediaEncoding } // Can't stream copy if we're burning in subtitles - if (request.SubtitleStreamIndex.HasValue) + if (request.SubtitleStreamIndex.HasValue + && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { - if (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) - { - return false; - } + return false; } - if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + && videoStream.IsAVC.HasValue + && !videoStream.IsAVC.Value + && request.RequireAvc) { - if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value && request.RequireAvc) - { - return false; - } + return false; } // Source and target codecs must match - if (string.IsNullOrEmpty(videoStream.Codec) || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(videoStream.Codec) + || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase)) { return false; } @@ -983,21 +1015,17 @@ namespace MediaBrowser.Controller.MediaEncoding } // Video width must fall within requested value - if (request.MaxWidth.HasValue) + if (request.MaxWidth.HasValue + && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value)) { - if (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value) - { - return false; - } + return false; } // Video height must fall within requested value - if (request.MaxHeight.HasValue) + if (request.MaxHeight.HasValue + && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value)) { - if (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value) - { - return false; - } + return false; } // Video framerate must fall within requested value @@ -1013,12 +1041,10 @@ namespace MediaBrowser.Controller.MediaEncoding } // Video bitrate must fall within requested value - if (request.VideoBitRate.HasValue) + if (request.VideoBitRate.HasValue + && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value)) { - if (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value) - { - return false; - } + return false; } var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec); @@ -1031,35 +1057,31 @@ namespace MediaBrowser.Controller.MediaEncoding } var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec); - if (maxRefFrames.HasValue) + if (maxRefFrames.HasValue + && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value) { - if (videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value) - { - return false; - } + return false; } // If a specific level was requested, the source must match or be less than var level = state.GetRequestedLevel(videoStream.Codec); - if (!string.IsNullOrEmpty(level)) + if (!string.IsNullOrEmpty(level) + && double.TryParse(level, NumberStyles.Any, _usCulture, out var requestLevel)) { - if (double.TryParse(level, NumberStyles.Any, _usCulture, out var requestLevel)) + if (!videoStream.Level.HasValue) { - if (!videoStream.Level.HasValue) - { - //return false; - } + //return false; + } - if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel) - { - return false; - } + if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel) + { + return false; } } - if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase) && - string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase) && - !(videoStream.IsAVC ?? false)) + if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase) + && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase) + && !(videoStream.IsAVC ?? false)) { // see Coach S01E01 - Kelly and the Professor(0).avi return false; @@ -1068,7 +1090,7 @@ namespace MediaBrowser.Controller.MediaEncoding return request.EnableAutoStreamCopy; } - public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, string[] supportedAudioCodecs) + public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable supportedAudioCodecs) { var request = state.BaseRequest; @@ -1078,16 +1100,16 @@ namespace MediaBrowser.Controller.MediaEncoding } var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec); - if (maxBitDepth.HasValue) + if (maxBitDepth.HasValue + && audioStream.BitDepth.HasValue + && audioStream.BitDepth.Value > maxBitDepth.Value) { - if (audioStream.BitDepth.HasValue && audioStream.BitDepth.Value > maxBitDepth.Value) - { - return false; - } + return false; } // Source and target codecs must match - if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(audioStream.Codec) + || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase)) { return false; } @@ -1100,6 +1122,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return false; } + if (audioStream.Channels.Value > channels.Value) { return false; @@ -1113,6 +1136,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return false; } + if (audioStream.SampleRate.Value > request.AudioSampleRate.Value) { return false; @@ -1126,6 +1150,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return false; } + if (audioStream.BitRate.Value > request.AudioBitRate.Value) { return false; @@ -1141,17 +1166,17 @@ namespace MediaBrowser.Controller.MediaEncoding if (videoStream != null) { - var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue && - request.Height.Value > videoStream.Height.Value && request.Width.HasValue && videoStream.Width.HasValue && - request.Width.Value > videoStream.Width.Value; + var isUpscaling = request.Height.HasValue + && videoStream.Height.HasValue + && request.Height.Value > videoStream.Height.Value + && request.Width.HasValue + && videoStream.Width.HasValue + && request.Width.Value > videoStream.Width.Value; // Don't allow bitrate increases unless upscaling - if (!isUpscaling) + if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue) { - if (bitrate.HasValue && videoStream.BitRate.HasValue) - { - bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value); - } + bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value); } } @@ -1179,7 +1204,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (sourceBitrate <= 3000000) { - sourceBitrate = Convert.ToInt32(sourceBitrate * 2); + sourceBitrate *= 2; } var bitrate = Math.Min(sourceBitrate, requestedBitrate); @@ -1189,12 +1214,13 @@ namespace MediaBrowser.Controller.MediaEncoding private static double GetVideoBitrateScaleFactor(string codec) { - if (StringHelper.EqualsIgnoreCase(codec, "h265") || - StringHelper.EqualsIgnoreCase(codec, "hevc") || - StringHelper.EqualsIgnoreCase(codec, "vp9")) + if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)) { return .5; } + return 1; } @@ -1221,9 +1247,7 @@ namespace MediaBrowser.Controller.MediaEncoding scaleFactor = Math.Max(scaleFactor, 2); } - var newBitrate = scaleFactor * bitrate; - - return Convert.ToInt32(newBitrate); + return Convert.ToInt32(scaleFactor * bitrate); } public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream) @@ -1235,7 +1259,6 @@ namespace MediaBrowser.Controller.MediaEncoding // Don't encode any higher than this return Math.Min(384000, request.AudioBitRate.Value); - //return Math.Min(currentBitrate, request.AudioBitRate.Value); } return null; @@ -1248,12 +1271,14 @@ namespace MediaBrowser.Controller.MediaEncoding var filters = new List(); // Boost volume to 200% when downsampling from 6ch to 2ch - if (channels.HasValue && channels.Value <= 2) + if (channels.HasValue + && channels.Value <= 2 + && state.AudioStream != null + && state.AudioStream.Channels.HasValue + && state.AudioStream.Channels.Value > 5 + && !encodingOptions.DownMixAudioBoost.Equals(1)) { - if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !encodingOptions.DownMixAudioBoost.Equals(1)) - { - filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture)); - } + filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture)); } var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive; @@ -1261,12 +1286,16 @@ namespace MediaBrowser.Controller.MediaEncoding { var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds; - filters.Add(string.Format("asetpts=PTS-{0}/TB", Math.Round(seconds).ToString(_usCulture))); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "asetpts=PTS-{0}/TB", + Math.Round(seconds))); } if (filters.Count > 0) { - return "-af \"" + string.Join(",", filters.ToArray()) + "\""; + return "-af \"" + string.Join(",", filters) + "\""; } return string.Empty; @@ -1283,18 +1312,17 @@ namespace MediaBrowser.Controller.MediaEncoding { var request = state.BaseRequest; - var inputChannels = audioStream == null - ? null - : audioStream.Channels; + var inputChannels = audioStream?.Channels; if (inputChannels <= 0) { inputChannels = null; } - int? transcoderChannelLimit = null; var codec = outputAudioCodec ?? string.Empty; + + int? transcoderChannelLimit; if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1) { // wmav2 currently only supports two channel output @@ -1343,6 +1371,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return val2; } + if (!val2.HasValue) { return val1; @@ -1416,7 +1445,10 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.VideoStream != null) { - args += string.Format("-map 0:{0}", state.VideoStream.Index); + args += string.Format( + CultureInfo.InvariantCulture, + "-map 0:{0}", + state.VideoStream.Index); } else { @@ -1426,7 +1458,10 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null) { - args += string.Format(" -map 0:{0}", state.AudioStream.Index); + args += string.Format( + CultureInfo.InvariantCulture, + " -map 0:{0}", + state.AudioStream.Index); } else @@ -1441,7 +1476,10 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (subtitleMethod == SubtitleDeliveryMethod.Embed) { - args += string.Format(" -map 0:{0}", state.SubtitleStream.Index); + args += string.Format( + CultureInfo.InvariantCulture, + " -map 0:{0}", + state.SubtitleStream.Index); } else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) { @@ -1493,7 +1531,10 @@ namespace MediaBrowser.Controller.MediaEncoding var request = state.BaseRequest; // Add resolution params, if specified - if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) + if (request.Width.HasValue + || request.Height.HasValue + || request.MaxHeight.HasValue + || request.MaxWidth.HasValue) { outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"'); @@ -1515,12 +1556,15 @@ namespace MediaBrowser.Controller.MediaEncoding } } - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0) + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + && outputSizeParam.Length == 0) { outputSizeParam = ",format=nv12|vaapi,hwupload"; // Add parameters to use VAAPI with burn-in subttiles (GH issue #642) - if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { + if (state.SubtitleStream != null + && state.SubtitleStream.IsTextSubtitleStream + && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { outputSizeParam += ",hwmap=mode=read+write+direct"; } } @@ -1529,7 +1573,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue) { - videoSizeParam = string.Format("scale={0}:{1}", state.VideoStream.Width.Value.ToString(_usCulture), state.VideoStream.Height.Value.ToString(_usCulture)); + videoSizeParam = string.Format( + CultureInfo.InvariantCulture, + "scale={0}:{1}", + state.VideoStream.Width.Value, + state.VideoStream.Height.Value); videoSizeParam += ":force_original_aspect_ratio=decrease"; } @@ -1542,15 +1590,18 @@ namespace MediaBrowser.Controller.MediaEncoding ? 0 : state.SubtitleStream.Index; - return string.Format(" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"", - mapPrefix.ToString(_usCulture), - subtitleStreamIndex.ToString(_usCulture), - state.VideoStream.Index.ToString(_usCulture), + return string.Format( + CultureInfo.InvariantCulture, + " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"", + mapPrefix, + subtitleStreamIndex, + state.VideoStream.Index, outputSizeParam, videoSizeParam); } - private ValueTuple GetFixedOutputSize(int? videoWidth, + private (int? width, int? height) GetFixedOutputSize( + int? videoWidth, int? videoHeight, int? requestedWidth, int? requestedHeight, @@ -1559,11 +1610,11 @@ namespace MediaBrowser.Controller.MediaEncoding { if (!videoWidth.HasValue && !requestedWidth.HasValue) { - return new ValueTuple(null, null); + return (null, null); } if (!videoHeight.HasValue && !requestedHeight.HasValue) { - return new ValueTuple(null, null); + return (null, null); } decimal inputWidth = Convert.ToDecimal(videoWidth ?? requestedWidth); @@ -1583,7 +1634,7 @@ namespace MediaBrowser.Controller.MediaEncoding outputWidth = 2 * Math.Truncate(outputWidth / 2); outputHeight = 2 * Math.Truncate(outputHeight / 2); - return new ValueTuple(Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); + return (Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); } public List GetScalingFilters(int? videoWidth, @@ -1597,9 +1648,17 @@ namespace MediaBrowser.Controller.MediaEncoding int? requestedMaxHeight) { var filters = new List(); - var fixedOutputSize = GetFixedOutputSize(videoWidth, videoHeight, requestedWidth, requestedHeight, requestedMaxWidth, requestedMaxHeight); - - if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && fixedOutputSize.Item1.HasValue && fixedOutputSize.Item2.HasValue) + var (width, height) = GetFixedOutputSize( + videoWidth, + videoHeight, + requestedWidth, + requestedHeight, + requestedMaxWidth, + requestedMaxHeight); + + if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + && width.HasValue + && height.HasValue) { // Work around vaapi's reduced scaling features var scaler = "scale_vaapi"; @@ -1607,15 +1666,26 @@ namespace MediaBrowser.Controller.MediaEncoding // Given the input dimensions (inputWidth, inputHeight), determine the output dimensions // (outputWidth, outputHeight). The user may request precise output dimensions or maximum // output dimensions. Output dimensions are guaranteed to be even. - var outputWidth = fixedOutputSize.Item1.Value; - var outputHeight = fixedOutputSize.Item2.Value; + var outputWidth = width.Value; + var outputHeight = height.Value; - if (!videoWidth.HasValue || outputWidth != videoWidth.Value || !videoHeight.HasValue || outputHeight != videoHeight.Value) + if (!videoWidth.HasValue + || outputWidth != videoWidth.Value + || !videoHeight.HasValue + || outputHeight != videoHeight.Value) { - filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(_usCulture), outputHeight.ToString(_usCulture))); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "{0}=w={1}:h={2}", + scaler, + outputWidth, + outputHeight)); } } - else if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1 && fixedOutputSize.Item1.HasValue && fixedOutputSize.Item2.HasValue) + else if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1 + && width.HasValue + && height.HasValue) { // Nothing to do, it's handled as an input resize filter } @@ -1631,7 +1701,12 @@ namespace MediaBrowser.Controller.MediaEncoding var widthParam = requestedWidth.Value.ToString(_usCulture); var heightParam = requestedHeight.Value.ToString(_usCulture); - filters.Add(string.Format("scale=trunc({0}/64)*64:trunc({1}/2)*2", widthParam, heightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc({0}/64)*64:trunc({1}/2)*2", + widthParam, + heightParam)); } else { @@ -1647,11 +1722,21 @@ namespace MediaBrowser.Controller.MediaEncoding if (isExynosV4L2) { - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/64)*64:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/64)*64:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", + maxWidthParam, + maxHeightParam)); } else { - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", + maxWidthParam, + maxHeightParam)); } } @@ -1667,7 +1752,11 @@ namespace MediaBrowser.Controller.MediaEncoding { var widthParam = requestedWidth.Value.ToString(_usCulture); - filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale={0}:trunc(ow/a/2)*2", + widthParam)); } } @@ -1678,11 +1767,19 @@ namespace MediaBrowser.Controller.MediaEncoding if (isExynosV4L2) { - filters.Add(string.Format("scale=trunc(oh*a/64)*64:{0}", heightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(oh*a/64)*64:{0}", + heightParam)); } else { - filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(oh*a/2)*2:{0}", + heightParam)); } } @@ -1693,11 +1790,19 @@ namespace MediaBrowser.Controller.MediaEncoding if (isExynosV4L2) { - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/64)*64:trunc(ow/dar/2)*2", maxWidthParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/64)*64:trunc(ow/dar/2)*2", + maxWidthParam)); } else { - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", + maxWidthParam)); } } @@ -1708,11 +1813,19 @@ namespace MediaBrowser.Controller.MediaEncoding if (isExynosV4L2) { - filters.Add(string.Format("scale=trunc(oh*a/64)*64:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(oh*a/64)*64:min(max(iw/dar\\,ih)\\,{0})", + maxHeightParam)); } else { - filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", + maxHeightParam)); } } } @@ -1722,8 +1835,8 @@ namespace MediaBrowser.Controller.MediaEncoding private string GetFixedSizeScalingFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight) { - var widthParam = requestedWidth.ToString(_usCulture); - var heightParam = requestedHeight.ToString(_usCulture); + var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture); + var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture); string filter = null; @@ -1765,13 +1878,14 @@ namespace MediaBrowser.Controller.MediaEncoding } } - return string.Format(filter, widthParam, heightParam); + return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam); } /// /// If we're going to put a fixed size on the command line, this will calculate it /// - public string GetOutputSizeParam(EncodingJobInfo state, + public string GetOutputSizeParam( + EncodingJobInfo state, EncodingOptions options, string outputVideoCodec, bool allowTimeStampCopy = true) @@ -1790,8 +1904,9 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwdownload"); // If transcoding from 10 bit, transform colour spaces too - if ( !string.IsNullOrEmpty(videoStream.PixelFormat) && videoStream.PixelFormat.IndexOf( "p10", StringComparison.OrdinalIgnoreCase ) != -1 - && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase ) ) + if (!string.IsNullOrEmpty(videoStream.PixelFormat) + && videoStream.PixelFormat.IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1 + && string.Equals(outputVideoCodec,"libx264", StringComparison.OrdinalIgnoreCase)) { filters.Add("format=p010le"); filters.Add("format=nv12"); @@ -1800,7 +1915,6 @@ namespace MediaBrowser.Controller.MediaEncoding { filters.Add("format=nv12"); } - } if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) @@ -1809,15 +1923,16 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwupload"); } - if (state.DeInterlace("h264", true) && string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + if (state.DeInterlace("h264", true) + && string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { - filters.Add(string.Format("deinterlace_vaapi")); + filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi")); } - if ((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)) && - !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + if ((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)) + && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { - var inputFramerate = videoStream == null ? null : videoStream.RealFrameRate; + var inputFramerate = videoStream?.RealFrameRate; // If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle if (string.Equals(options.DeinterlaceMethod, "bobandweave", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30) @@ -1830,11 +1945,11 @@ namespace MediaBrowser.Controller.MediaEncoding } } - var inputWidth = videoStream == null ? null : videoStream.Width; - var inputHeight = videoStream == null ? null : videoStream.Height; + var inputWidth = videoStream?.Width; + var inputHeight = videoStream?.Height; var threeDFormat = state.MediaSource.Video3DFormat; - var videoDecoder = this.GetHardwareAcceleratedVideoDecoder(state, options); + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options); filters.AddRange(GetScalingFilters(inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight)); @@ -1852,6 +1967,7 @@ namespace MediaBrowser.Controller.MediaEncoding { filters.Add("hwmap"); } + if (allowTimeStampCopy) { output += " -copyts"; @@ -1860,7 +1976,10 @@ namespace MediaBrowser.Controller.MediaEncoding if (filters.Count > 0) { - output += string.Format(" -vf \"{0}\"", string.Join(",", filters.ToArray())); + output += string.Format( + CultureInfo.InvariantCulture, + " -vf \"{0}\"", + string.Join(",", filters)); } return output; @@ -1908,7 +2027,8 @@ namespace MediaBrowser.Controller.MediaEncoding } } - if (state.AudioStream != null && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)) + if (state.AudioStream != null + && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)) { state.OutputAudioCodec = "copy"; } @@ -1925,14 +2045,10 @@ namespace MediaBrowser.Controller.MediaEncoding } public static string GetProbeSizeArgument(int numInputFiles) - { - return numInputFiles > 1 ? "-probesize 1G" : ""; - } + => numInputFiles > 1 ? "-probesize 1G" : ""; public static string GetAnalyzeDurationArgument(int numInputFiles) - { - return numInputFiles > 1 ? "-analyzeduration 200M" : ""; - } + => numInputFiles > 1 ? "-analyzeduration 200M" : ""; public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions) { @@ -2000,18 +2116,22 @@ namespace MediaBrowser.Controller.MediaEncoding { flags.Add("+igndts"); } + if (state.IgnoreInputIndex) { flags.Add("+ignidx"); } + if (state.GenPtsInput || string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { flags.Add("+genpts"); } + if (state.DiscardCorruptFramesInput) { flags.Add("+discardcorrupt"); } + if (state.EnableFastSeekInput) { flags.Add("+fastseek"); @@ -2019,24 +2139,33 @@ namespace MediaBrowser.Controller.MediaEncoding if (flags.Count > 0) { - inputModifier += " -fflags " + string.Join("", flags.ToArray()); + inputModifier += " -fflags " + string.Join(string.Empty, flags); } - var videoDecoder = this.GetHardwareAcceleratedVideoDecoder(state, encodingOptions); + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions); if (!string.IsNullOrEmpty(videoDecoder)) { inputModifier += " " + videoDecoder; - var videoStream = state.VideoStream; - var inputWidth = videoStream == null ? null : videoStream.Width; - var inputHeight = videoStream == null ? null : videoStream.Height; - var request = state.BaseRequest; + if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1) + { + var videoStream = state.VideoStream; + var inputWidth = videoStream?.Width; + var inputHeight = videoStream?.Height; + var request = state.BaseRequest; - var fixedOutputSize = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); + var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); - if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1 && fixedOutputSize.Item1.HasValue && fixedOutputSize.Item2.HasValue) - { - inputModifier += string.Format(" -resize {0}x{1}", fixedOutputSize.Item1.Value.ToString(_usCulture), fixedOutputSize.Item2.Value.ToString(_usCulture)); + if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1 + && width.HasValue + && height.HasValue) + { + inputModifier += string.Format( + CultureInfo.InvariantCulture, + " -resize {0}x{1}", + width.Value, + height.Value); + } } } @@ -2045,9 +2174,9 @@ namespace MediaBrowser.Controller.MediaEncoding var outputVideoCodec = GetVideoEncoder(state, encodingOptions); // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking - if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) && - state.TranscodingType != TranscodingJobType.Progressive && - state.EnableBreakOnNonKeyFrames(outputVideoCodec)) + if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) + && state.TranscodingType != TranscodingJobType.Progressive + && state.EnableBreakOnNonKeyFrames(outputVideoCodec)) { inputModifier += " -noaccurate_seek"; } @@ -2071,14 +2200,16 @@ namespace MediaBrowser.Controller.MediaEncoding } - public void AttachMediaSourceInfo(EncodingJobInfo state, - MediaSourceInfo mediaSource, - string requestedUrl) + public void AttachMediaSourceInfo( + EncodingJobInfo state, + MediaSourceInfo mediaSource, + string requestedUrl) { if (state == null) { throw new ArgumentNullException(nameof(state)); } + if (mediaSource == null) { throw new ArgumentNullException(nameof(mediaSource)); @@ -2136,15 +2267,16 @@ namespace MediaBrowser.Controller.MediaEncoding state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; - if (state.ReadInputAtNativeFramerate || - mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)) + if (state.ReadInputAtNativeFramerate + || mediaSource.Protocol == MediaProtocol.File + && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)) { state.InputVideoSync = "-1"; state.InputAudioSync = "1"; } - if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase) || - string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase) + || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase)) { // Seeing some stuttering when transcoding wma to audio-only HLS state.InputAudioSync = "1"; @@ -2256,7 +2388,7 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } - return this.GetHardwareAcceleratedVideoDecoder(state.MediaSource.VideoType ?? VideoType.VideoFile, state.VideoStream, encodingOptions); + return GetHardwareAcceleratedVideoDecoder(state.MediaSource.VideoType ?? VideoType.VideoFile, state.VideoStream, encodingOptions); } public string GetHardwareAcceleratedVideoDecoder(VideoType videoType, MediaStream videoStream, EncodingOptions encodingOptions) @@ -2269,9 +2401,9 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } - if (videoStream != null && - !string.IsNullOrEmpty(videoStream.Codec) && - !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) + if (videoStream != null + && !string.IsNullOrEmpty(videoStream.Codec) + && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) { if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { @@ -2469,11 +2601,7 @@ namespace MediaBrowser.Controller.MediaEncoding codec = format; } - var args = " -codec:s:0 " + codec; - - args += " -disposition:s:0 default"; - - return args; + return " -codec:s:0 " + codec + " -disposition:s:0 default"; } public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultPreset) @@ -2484,8 +2612,8 @@ namespace MediaBrowser.Controller.MediaEncoding var format = string.Empty; var keyFrame = string.Empty; - if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase) && - state.BaseRequest.Context == EncodingContext.Streaming) + if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase) + && state.BaseRequest.Context == EncodingContext.Streaming) { // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js format = " -f mp4 -movflags frag_keyframe+empty_moov"; @@ -2495,7 +2623,9 @@ namespace MediaBrowser.Controller.MediaEncoding var inputModifier = GetInputModifier(state, encodingOptions); - return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"", + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"", inputModifier, GetInputArgument(state, encodingOptions), keyFrame, @@ -2505,8 +2635,7 @@ namespace MediaBrowser.Controller.MediaEncoding GetProgressiveVideoAudioArguments(state, encodingOptions), GetSubtitleEmbedArguments(state), format, - outputPath - ).Trim(); + outputPath).Trim(); } public string GetOutputFFlags(EncodingJobInfo state) @@ -2519,7 +2648,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (flags.Count > 0) { - return " -fflags " + string.Join("", flags.ToArray()); + return " -fflags " + string.Join("", flags); } return string.Empty; @@ -2536,9 +2665,9 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - if (state.VideoStream != null && - string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && - !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) + if (state.VideoStream != null + && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) + && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { string bitStreamArgs = GetBitStreamArgs(state.VideoStream); if (!string.IsNullOrEmpty(bitStreamArgs)) @@ -2559,8 +2688,10 @@ namespace MediaBrowser.Controller.MediaEncoding } else { - var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"", - 5.ToString(_usCulture)); + var keyFrameArg = string.Format( + CultureInfo.InvariantCulture, + " -force_key_frames \"expr:gte(t,n_forced*{0})\"", + 5); args += keyFrameArg; @@ -2581,6 +2712,7 @@ namespace MediaBrowser.Controller.MediaEncoding { args += " -copyts"; } + args += " -avoid_negative_ts disabled -start_at_zero"; } @@ -2681,39 +2813,22 @@ namespace MediaBrowser.Controller.MediaEncoding } } - var albumCoverInput = string.Empty; - var mapArgs = string.Empty; - var metadata = string.Empty; - var vn = string.Empty; - - var hasArt = !string.IsNullOrEmpty(state.AlbumCoverPath); - hasArt = false; - - if (hasArt) - { - albumCoverInput = " -i \"" + state.AlbumCoverPath + "\""; - mapArgs = " -map 0:a -map 1:v -c:1:v copy"; - metadata = " -metadata:s:v title=\"Album cover\" -metadata:s:v comment=\"Cover(Front)\""; - } - else - { - vn = " -vn"; - } - var threads = GetNumberOfThreads(state, encodingOptions, null); var inputModifier = GetInputModifier(state, encodingOptions); - return string.Format("{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"", + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"", inputModifier, GetInputArgument(state, encodingOptions), threads, - vn, - string.Join(" ", audioTranscodeParams.ToArray()), + " -vn", + string.Join(" ", audioTranscodeParams), outputPath, - metadata, - albumCoverInput, - mapArgs).Trim(); + string.Empty, + string.Empty, + string.Empty).Trim(); } } diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs index 7f842c1d01..5cedc3d576 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs @@ -1,8 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Controller.MediaEncoding { @@ -16,31 +16,26 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// The file system. /// The video path. - /// The protocol. /// The iso mount. /// The playable stream file names. - /// System.String[][]. - public static string[] GetInputArgument(IFileSystem fileSystem, string videoPath, MediaProtocol protocol, IIsoMount isoMount, string[] playableStreamFileNames) + /// string[]. + public static string[] GetInputArgument(IFileSystem fileSystem, string videoPath, IIsoMount isoMount, IReadOnlyCollection playableStreamFileNames) { - if (playableStreamFileNames.Length > 0) + if (playableStreamFileNames.Count > 0) { if (isoMount == null) { return GetPlayableStreamFiles(fileSystem, videoPath, playableStreamFileNames); } + return GetPlayableStreamFiles(fileSystem, isoMount.MountedPath, playableStreamFileNames); } return new[] { videoPath }; } - private static string[] GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, string[] filenames) + private static string[] GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, IEnumerable filenames) { - if (filenames.Length == 0) - { - return new string[] { }; - } - var allFiles = fileSystem .GetFilePaths(rootPath, true) .ToArray(); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a8874b6d0c..75bb960c34 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -303,7 +303,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters; - var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.MediaSource.Path, request.MediaSource.Protocol, request.MountedIso, request.PlayableStreamFileNames); + var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.MediaSource.Path, request.MountedIso, request.PlayableStreamFileNames); var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length); string analyzeDuration; diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index d80084acf5..e0b23108f0 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.MediaInfo { var protocol = item.PathProtocol ?? MediaProtocol.File; - var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, item.Path, protocol, null, item.GetPlayableStreamFileNames(_mediaEncoder)); + var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, item.Path, null, item.GetPlayableStreamFileNames(_mediaEncoder)); var mediaStreams = item.GetMediaStreams(); diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs index 992991a7ed..fe4d50efaa 100644 --- a/MediaBrowser.XbmcMetadata/EntryPoint.cs +++ b/MediaBrowser.XbmcMetadata/EntryPoint.cs @@ -16,27 +16,30 @@ namespace MediaBrowser.XbmcMetadata { private readonly IUserDataManager _userDataManager; private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; private readonly IProviderManager _providerManager; private readonly IConfigurationManager _config; - public EntryPoint(IUserDataManager userDataManager, ILibraryManager libraryManager, ILogger logger, IProviderManager providerManager, IConfigurationManager config) + public EntryPoint( + IUserDataManager userDataManager, + ILogger logger, + IProviderManager providerManager, + IConfigurationManager config) { _userDataManager = userDataManager; - _libraryManager = libraryManager; _logger = logger; _providerManager = providerManager; _config = config; } + /// public Task RunAsync() { - _userDataManager.UserDataSaved += _userDataManager_UserDataSaved; + _userDataManager.UserDataSaved += OnUserDataSaved; return Task.CompletedTask; } - void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e) + private void OnUserDataSaved(object sender, UserDataSaveEventArgs e) { if (e.SaveReason == UserDataSaveReason.PlaybackFinished || e.SaveReason == UserDataSaveReason.TogglePlayed || e.SaveReason == UserDataSaveReason.UpdateUserRating) { @@ -47,9 +50,10 @@ namespace MediaBrowser.XbmcMetadata } } + /// public void Dispose() { - _userDataManager.UserDataSaved -= _userDataManager_UserDataSaved; + _userDataManager.UserDataSaved -= OnUserDataSaved; } private void SaveMetadataForItem(BaseItem item, ItemUpdateType updateReason) diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs index 430b931999..a1905bf26c 100644 --- a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs @@ -15,16 +15,30 @@ namespace MediaBrowser.XbmcMetadata.Savers { public class AlbumNfoSaver : BaseNfoSaver { + public AlbumNfoSaver( + IFileSystem fileSystem, + IServerConfigurationManager configurationManager, + ILibraryManager libraryManager, + IUserManager userManager, + IUserDataManager userDataManager, + ILogger logger) + : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) + { + } + + /// protected override string GetLocalSavePath(BaseItem item) { return Path.Combine(item.Path, "album.nfo"); } + /// protected override string GetRootElementName(BaseItem item) { return "album"; } + /// public override bool IsEnabledFor(BaseItem item, ItemUpdateType updateType) { if (!item.SupportsLocalMetadata) @@ -35,6 +49,7 @@ namespace MediaBrowser.XbmcMetadata.Savers return item is MusicAlbum && updateType >= MinimumUpdateType; } + /// protected override void WriteCustomElements(BaseItem item, XmlWriter writer) { var album = (MusicAlbum)item; @@ -52,8 +67,6 @@ namespace MediaBrowser.XbmcMetadata.Savers AddTracks(album.Tracks, writer); } - private readonly CultureInfo UsCulture = new CultureInfo("en-US"); - private void AddTracks(IEnumerable tracks, XmlWriter writer) { foreach (var track in tracks.OrderBy(i => i.ParentIndexNumber ?? 0).ThenBy(i => i.IndexNumber ?? 0)) @@ -62,7 +75,7 @@ namespace MediaBrowser.XbmcMetadata.Savers if (track.IndexNumber.HasValue) { - writer.WriteElementString("position", track.IndexNumber.Value.ToString(UsCulture)); + writer.WriteElementString("position", track.IndexNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (!string.IsNullOrEmpty(track.Name)) @@ -81,21 +94,19 @@ namespace MediaBrowser.XbmcMetadata.Savers } } + /// protected override List GetTagsUsed(BaseItem item) { var list = base.GetTagsUsed(item); - list.AddRange(new string[] - { - "track", - "artist", - "albumartist" - }); - return list; - } + list.AddRange( + new string[] + { + "track", + "artist", + "albumartist" + }); - public AlbumNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) - : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) - { + return list; } } } diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 3ed86d65c7..39839e2736 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.3 @@ -41,8 +41,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" @@ -141,10 +139,6 @@ Global {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU -- cgit v1.2.3 From ee637e8fecbcefe429babbbbd1325bce7c3fe991 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 2 Sep 2019 08:19:29 +0200 Subject: Fix warnings, improve performance (#1665) * Fix warnings, improve performance `QueryResult.Items` is now a `IReadOnlyList` so we don't need to allocate a new `Array` when we have a `List` (and `Items` shouldn't need to be mutable anyway) * Update Providers .csproj to latest C# * Remove extra newline from DtoService.cs * Remove extra newline from UserLibraryService.cs --- Emby.Dlna/ContentDirectory/ControlHandler.cs | 11 ++- .../Activity/ActivityRepository.cs | 2 +- .../Data/SqliteItemRepository.cs | 10 +- Emby.Server.Implementations/Dto/DtoService.cs | 110 ++++++++++----------- .../HttpServer/Security/AuthorizationContext.cs | 17 +++- .../Library/LibraryManager.cs | 5 +- .../Library/UserViewManager.cs | 30 +++--- .../LiveTv/LiveTvManager.cs | 18 ++-- .../Services/ResponseHelper.cs | 2 +- .../Services/ServiceExec.cs | 39 ++++---- .../Services/UrlExtensions.cs | 14 ++- MediaBrowser.Api/LiveTv/LiveTvService.cs | 2 +- MediaBrowser.Api/Movies/MoviesService.cs | 2 +- MediaBrowser.Api/PlaylistService.cs | 6 +- MediaBrowser.Api/SuggestionsService.cs | 5 - MediaBrowser.Api/TvShowsService.cs | 6 +- MediaBrowser.Api/UserLibrary/ItemsService.cs | 5 - MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 17 +++- MediaBrowser.Api/UserService.cs | 10 +- .../Extensions/CollectionExtensions.cs | 17 ++++ MediaBrowser.Common/Net/CustomHeaderNames.cs | 2 +- MediaBrowser.Controller/Dto/IDtoService.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 8 +- MediaBrowser.Controller/Entities/Extensions.cs | 9 +- MediaBrowser.Controller/Entities/Folder.cs | 18 ++-- MediaBrowser.Controller/Entities/IHasTrailers.cs | 70 +++++++++++-- MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 10 +- MediaBrowser.Controller/Entities/Movies/Movie.cs | 7 +- MediaBrowser.Controller/Entities/TV/Episode.cs | 7 +- MediaBrowser.Controller/Entities/TV/Series.cs | 7 +- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 4 +- MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 2 +- MediaBrowser.Model/Dto/BaseItemDto.cs | 2 +- MediaBrowser.Model/Dto/RecommendationDto.cs | 3 +- MediaBrowser.Model/Querying/QueryResult.cs | 7 +- MediaBrowser.Providers/Manager/ProviderUtils.cs | 38 ++++--- .../MediaBrowser.Providers.csproj | 2 +- 37 files changed, 308 insertions(+), 218 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 4f8c89e485..d22fc21774 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -289,7 +289,7 @@ namespace Emby.Dlna.ContentDirectory var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount); totalCount = childrenResult.TotalRecordCount; - provided = childrenResult.Items.Length; + provided = childrenResult.Items.Count; foreach (var i in childrenResult.Items) { @@ -309,6 +309,7 @@ namespace Emby.Dlna.ContentDirectory } } } + writer.WriteFullEndElement(); //writer.WriteEndDocument(); } @@ -386,7 +387,7 @@ namespace Emby.Dlna.ContentDirectory totalCount = childrenResult.TotalRecordCount; - provided = childrenResult.Items.Length; + provided = childrenResult.Items.Count; var dlnaOptions = _config.GetDlnaConfiguration(); @@ -677,7 +678,7 @@ namespace Emby.Dlna.ContentDirectory return new QueryResult { - Items = list.ToArray(), + Items = list, TotalRecordCount = list.Count }; } @@ -755,7 +756,7 @@ namespace Emby.Dlna.ContentDirectory return new QueryResult { - Items = list.ToArray(), + Items = list, TotalRecordCount = list.Count }; } @@ -860,7 +861,7 @@ namespace Emby.Dlna.ContentDirectory return new QueryResult { - Items = list.ToArray(), + Items = list, TotalRecordCount = list.Count }; } diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index 541b23afd4..ffaeaa541a 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -247,7 +247,7 @@ namespace Emby.Server.Implementations.Activity ReadTransactionMode); } - result.Items = list.ToArray(); + result.Items = list; return result; } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index c3789eef2f..2f083dda4a 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2746,7 +2746,7 @@ namespace Emby.Server.Implementations.Data var returnList = GetItemList(query); return new QueryResult { - Items = returnList.ToArray(), + Items = returnList, TotalRecordCount = returnList.Count }; } @@ -2883,7 +2883,7 @@ namespace Emby.Server.Implementations.Data } LogQueryTime("GetItems", commandText, now); - result.Items = list.ToArray(); + result.Items = list; return result; } @@ -3161,7 +3161,7 @@ namespace Emby.Server.Implementations.Data var returnList = GetItemIdsList(query); return new QueryResult { - Items = returnList.ToArray(), + Items = returnList, TotalRecordCount = returnList.Count }; } @@ -3281,7 +3281,7 @@ namespace Emby.Server.Implementations.Data LogQueryTime("GetItemIds", commandText, now); - result.Items = list.ToArray(); + result.Items = list; return result; } @@ -5520,7 +5520,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type result.TotalRecordCount = list.Count; } - result.Items = list.ToArray(); + result.Items = list; return result; } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 6da1026188..75192a8f1a 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -80,27 +80,25 @@ namespace Emby.Server.Implementations.Dto return GetBaseItemDto(item, options, user, owner); } - public BaseItemDto[] GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null) - => GetBaseItemDtos(items, items.Count, options, user, owner); - - public BaseItemDto[] GetBaseItemDtos(IEnumerable items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null) + /// + public IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null) { - var returnItems = new BaseItemDto[itemCount]; - var programTuples = new List>(); - var channelTuples = new List>(); + var returnItems = new BaseItemDto[items.Count]; + var programTuples = new List<(BaseItem, BaseItemDto)>(); + var channelTuples = new List<(BaseItemDto, LiveTvChannel)>(); - var index = 0; - foreach (var item in items) + for (int index = 0; index < items.Count; index++) { + var item = items[index]; var dto = GetBaseItemDtoInternal(item, options, user, owner); if (item is LiveTvChannel tvChannel) { - channelTuples.Add(new Tuple(dto, tvChannel)); + channelTuples.Add((dto, tvChannel)); } else if (item is LiveTvProgram) { - programTuples.Add(new Tuple(item, dto)); + programTuples.Add((item, dto)); } if (item is IItemByName byName) @@ -121,7 +119,6 @@ namespace Emby.Server.Implementations.Dto } returnItems[index] = dto; - index++; } if (programTuples.Count > 0) @@ -140,33 +137,32 @@ namespace Emby.Server.Implementations.Dto public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { var dto = GetBaseItemDtoInternal(item, options, user, owner); - var tvChannel = item as LiveTvChannel; - if (tvChannel != null) + if (item is LiveTvChannel tvChannel) { - var list = new List> { new Tuple(dto, tvChannel) }; + var list = new List<(BaseItemDto, LiveTvChannel)>(1) { (dto, tvChannel) }; _livetvManager().AddChannelInfo(list, options, user); } else if (item is LiveTvProgram) { - var list = new List> { new Tuple(item, dto) }; + var list = new List<(BaseItem, BaseItemDto)>(1) { (item, dto) }; var task = _livetvManager().AddInfoToProgramDto(list, options.Fields, user); Task.WaitAll(task); } - var byName = item as IItemByName; - - if (byName != null) + if (item is IItemByName itemByName + && options.ContainsField(ItemFields.ItemCounts)) { - if (options.ContainsField(ItemFields.ItemCounts)) - { - SetItemByNameInfo(item, dto, GetTaggedItems(byName, user, new DtoOptions(false) - { - EnableImages = false - - }), user); - } - - return dto; + SetItemByNameInfo( + item, + dto, + GetTaggedItems( + itemByName, + user, + new DtoOptions(false) + { + EnableImages = false + }), + user); } return dto; @@ -174,12 +170,12 @@ namespace Emby.Server.Implementations.Dto private static IList GetTaggedItems(IItemByName byName, User user, DtoOptions options) { - return byName.GetTaggedItems(new InternalItemsQuery(user) - { - Recursive = true, - DtoOptions = options - - }); + return byName.GetTaggedItems( + new InternalItemsQuery(user) + { + Recursive = true, + DtoOptions = options + }); } private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) @@ -222,8 +218,7 @@ namespace Emby.Server.Implementations.Dto AttachUserSpecificInfo(dto, item, user, options); } - var hasMediaSources = item as IHasMediaSources; - if (hasMediaSources != null) + if (item is IHasMediaSources hasMediaSources) { if (options.ContainsField(ItemFields.MediaSources)) { @@ -769,14 +764,12 @@ namespace Emby.Server.Implementations.Dto dto.CriticRating = item.CriticRating; - var hasDisplayOrder = item as IHasDisplayOrder; - if (hasDisplayOrder != null) + if (item is IHasDisplayOrder hasDisplayOrder) { dto.DisplayOrder = hasDisplayOrder.DisplayOrder; } - var hasCollectionType = item as IHasCollectionType; - if (hasCollectionType != null) + if (item is IHasCollectionType hasCollectionType) { dto.CollectionType = hasCollectionType.CollectionType; } @@ -1073,17 +1066,24 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.LocalTrailerCount)) { + int trailerCount = 0; if (allExtras == null) { allExtras = item.GetExtras().ToArray(); } - dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer) + item.GetTrailers().Count(); + trailerCount += allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer); + + if (item is IHasTrailers hasTrailers) + { + trailerCount += hasTrailers.GetTrailerCount(); + } + + dto.LocalTrailerCount = trailerCount; } // Add EpisodeInfo - var episode = item as Episode; - if (episode != null) + if (item is Episode episode) { dto.IndexNumberEnd = episode.IndexNumberEnd; dto.SeriesName = episode.SeriesName; @@ -1101,7 +1101,7 @@ namespace Emby.Server.Implementations.Dto Series episodeSeries = null; - //if (options.ContainsField(ItemFields.SeriesPrimaryImage)) + if (options.ContainsField(ItemFields.SeriesPrimaryImage)) { episodeSeries = episodeSeries ?? episode.Series; if (episodeSeries != null) @@ -1121,8 +1121,7 @@ namespace Emby.Server.Implementations.Dto } // Add SeriesInfo - var series = item as Series; - if (series != null) + if (item is Series series) { dto.AirDays = series.AirDays; dto.AirTime = series.AirTime; @@ -1130,8 +1129,7 @@ namespace Emby.Server.Implementations.Dto } // Add SeasonInfo - var season = item as Season; - if (season != null) + if (item is Season season) { dto.SeriesName = season.SeriesName; dto.SeriesId = season.SeriesId; @@ -1147,7 +1145,7 @@ namespace Emby.Server.Implementations.Dto } } - //if (options.ContainsField(ItemFields.SeriesPrimaryImage)) + if (options.ContainsField(ItemFields.SeriesPrimaryImage)) { series = series ?? season.Series; if (series != null) @@ -1157,14 +1155,12 @@ namespace Emby.Server.Implementations.Dto } } - var musicVideo = item as MusicVideo; - if (musicVideo != null) + if (item is MusicVideo musicVideo) { SetMusicVideoProperties(dto, musicVideo); } - var book = item as Book; - if (book != null) + if (item is Book book) { SetBookProperties(dto, book); } @@ -1204,8 +1200,7 @@ namespace Emby.Server.Implementations.Dto } } - var photo = item as Photo; - if (photo != null) + if (item is Photo photo) { SetPhotoProperties(dto, photo); } @@ -1224,8 +1219,7 @@ namespace Emby.Server.Implementations.Dto private BaseItem GetImageDisplayParent(BaseItem currentItem, BaseItem originalItem) { - var musicAlbum = currentItem as MusicAlbum; - if (musicAlbum != null) + if (currentItem is MusicAlbum musicAlbum) { var artist = musicAlbum.GetMusicArtist(new DtoOptions(false)); if (artist != null) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 276312a300..4574486049 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; @@ -89,7 +90,7 @@ namespace Emby.Server.Implementations.HttpServer.Security AccessToken = token }); - var tokenInfo = result.Items.Length > 0 ? result.Items[0] : null; + var tokenInfo = result.Items.Count > 0 ? result.Items[0] : null; if (tokenInfo != null) { @@ -190,17 +191,23 @@ namespace Emby.Server.Implementations.HttpServer.Security /// Dictionary{System.StringSystem.String}. private Dictionary GetAuthorization(string authorizationHeader) { - if (authorizationHeader == null) return null; + if (authorizationHeader == null) + { + return null; + } var parts = authorizationHeader.Split(new[] { ' ' }, 2); // There should be at least to parts - if (parts.Length != 2) return null; + if (parts.Length != 2) + { + return null; + } var acceptedNames = new[] { "MediaBrowser", "Emby" }; // It has to be a digest request - if (!acceptedNames.Contains(parts[0] ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase)) { return null; } @@ -232,7 +239,7 @@ namespace Emby.Server.Implementations.HttpServer.Security return value; } - return System.Net.WebUtility.HtmlEncode(value); + return WebUtility.HtmlEncode(value); } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 30ff855cc2..36934f65f0 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1441,7 +1441,7 @@ namespace Emby.Server.Implementations.Library return new QueryResult { - Items = list.ToArray() + Items = list }; } @@ -1977,8 +1977,7 @@ namespace Emby.Server.Implementations.Library public LibraryOptions GetLibraryOptions(BaseItem item) { - var collectionFolder = item as CollectionFolder; - if (collectionFolder == null) + if (!(item is CollectionFolder collectionFolder)) { collectionFolder = GetCollectionFolders(item) .OfType() diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 71f16ac3e9..4d79cae139 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -224,7 +224,7 @@ namespace Emby.Server.Implementations.Library return list; } - private List GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options) + private IReadOnlyList GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options) { var parentId = request.ParentId; @@ -236,24 +236,22 @@ namespace Emby.Server.Implementations.Library if (!parentId.Equals(Guid.Empty)) { var parentItem = _libraryManager.GetItemById(parentId); - var parentItemChannel = parentItem as Channel; - if (parentItemChannel != null) + if (parentItem is Channel parentItemChannel) { - return _channelManager.GetLatestChannelItemsInternal(new InternalItemsQuery(user) - { - ChannelIds = new[] { parentId }, - IsPlayed = request.IsPlayed, - StartIndex = request.StartIndex, - Limit = request.Limit, - IncludeItemTypes = request.IncludeItemTypes, - EnableTotalRecordCount = false - - - }, CancellationToken.None).Result.Items.ToList(); + return _channelManager.GetLatestChannelItemsInternal( + new InternalItemsQuery(user) + { + ChannelIds = new[] { parentId }, + IsPlayed = request.IsPlayed, + StartIndex = request.StartIndex, + Limit = request.Limit, + IncludeItemTypes = request.IncludeItemTypes, + EnableTotalRecordCount = false + }, + CancellationToken.None).Result.Items; } - var parent = parentItem as Folder; - if (parent != null) + if (parentItem is Folder parent) { parents.Add(parent); } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1e5198dd6c..ee975e19a5 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -881,7 +881,7 @@ namespace Emby.Server.Implementations.LiveTv } var programList = _libraryManager.QueryItems(internalQuery).Items; - var totalCount = programList.Length; + var totalCount = programList.Count; var orderedPrograms = programList.Cast().OrderBy(i => i.StartDate.Date); @@ -969,8 +969,8 @@ namespace Emby.Server.Implementations.LiveTv var timers = new Dictionary>(); var seriesTimers = new Dictionary>(); - TimerInfo[] timerList = null; - SeriesTimerInfo[] seriesTimerList = null; + IReadOnlyList timerList = null; + IReadOnlyList seriesTimerList = null; foreach (var programTuple in programs) { @@ -1296,6 +1296,7 @@ namespace Emby.Server.Implementations.LiveTv } private const int MaxGuideDays = 14; + private double GetGuideDays() { var config = GetConfiguration(); @@ -1340,6 +1341,7 @@ namespace Emby.Server.Implementations.LiveTv excludeItemTypes.Add(typeof(Movie).Name); } } + if (query.IsSeries.HasValue) { if (query.IsSeries.Value) @@ -1351,10 +1353,12 @@ namespace Emby.Server.Implementations.LiveTv excludeItemTypes.Add(typeof(Episode).Name); } } + if (query.IsSports ?? false) { genres.Add("Sports"); } + if (query.IsKids ?? false) { genres.Add("Kids"); @@ -1400,20 +1404,20 @@ namespace Emby.Server.Implementations.LiveTv if (query.IsInProgress ?? false) { - //TODO Fix The co-variant conversion between Video[] and BaseItem[], this can generate runtime issues. + // TODO: Fix The co-variant conversion between Video[] and BaseItem[], this can generate runtime issues. result.Items = result .Items .OfType