From c6188e26afa0034c5c255a19b2fc71aa42311e26 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 18 Feb 2019 22:47:02 +0100 Subject: [PATCH 001/131] Got to start somewhere --- .../ApplicationHost.cs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) 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)); } /// From 09921a00aaad31c0ea4a0650e8d0ddb890dca735 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Fri, 22 Mar 2019 00:01:23 -0700 Subject: [PATCH 002/131] made password resets an interface and per user --- .../ApplicationHost.cs | 2 +- .../Library/DefaultPasswordResetProvider.cs | 118 +++++++++++ .../Library/UserManager.cs | 192 ++++++------------ MediaBrowser.Api/Session/SessionsService.cs | 11 + .../Authentication/IPasswordResetProvider.cs | 20 ++ .../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 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() + UsesIdForConfigurationPath = true }; } - 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) + 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() { From 758e35baba95278fb3b55a89dc9295e6f6dad5ac Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sun, 24 Mar 2019 00:30:16 -0700 Subject: [PATCH 003/131] greaterthen/lessthen reversal fix --- .../Library/DefaultPasswordResetProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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; } } } -} +} From 414a318a0d422a893401d1ecf84526043df36210 Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 11:59:40 +0100 Subject: [PATCH 004/131] WAN Address should use public ports instead of local ports. https://github.com/jellyfin/jellyfin/issues/601#issuecomment-475941080 --- .../ApplicationHost.cs | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) 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); From 69cc5814d85733f5bec9cac03e78caa1324406fe Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 12:11:46 +0100 Subject: [PATCH 005/131] Change WAN IP behaviour: Use ServerConfiguration.WanDdns if set in configuration. --- Emby.Server.Implementations/ApplicationHost.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 { From 4ffec8ad260fb8829ea220137934596de19a1c1b Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 12:19:10 +0100 Subject: [PATCH 006/131] Fix build, missing changes. --- Emby.Server.Implementations/ApplicationHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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); From f30af9cd5f484dad763eb67b5b7076cd03bd59df Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Mar 2019 16:47:42 +0100 Subject: [PATCH 007/131] Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From cf36aaef2b1724931b94bf12b95d2b6c43f68133 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Mar 2019 16:47:48 +0100 Subject: [PATCH 008/131] Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)); } From 598b1c99660547d7c8e3b253c4664a6faa78a03d Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Mar 2019 16:47:59 +0100 Subject: [PATCH 009/131] Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)); } From 7ebb043249d9c13c3bf2cc41c52d71c4e73f40f9 Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 16:50:39 +0100 Subject: [PATCH 010/131] Removed comment, renamed methods consistently. --- Emby.Server.Implementations/ApplicationHost.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) 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) { From 030fcaac156e9d75d4e7e84b0bea1291b91c0079 Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 17:02:03 +0100 Subject: [PATCH 011/131] Proper access to configuration objects --- Emby.Server.Implementations/ApplicationHost.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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) From d18252542d81e1c279b10af0198be30a8b94edea Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 17:11:21 +0100 Subject: [PATCH 012/131] Also add the WAN switch to the public system info. --- Emby.Server.Implementations/ApplicationHost.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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, From fb7f29de18bc6089e247041c7aa15b2ad7677339 Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 18:33:21 +0100 Subject: [PATCH 013/131] Format the WAN API Url correctly with https and Port. --- Emby.Server.Implementations/ApplicationHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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); From 26fe4040bfc9ef5f9e723e3c9a410fb24fb8b9b1 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sun, 24 Mar 2019 11:40:00 -0700 Subject: [PATCH 014/131] fixes some usings --- .../Library/DefaultPasswordResetProvider.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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 { From 4e2841f0d747a9501d454fab7c7df5ce4ff86890 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Mar 2019 11:41:03 -0700 Subject: [PATCH 015/131] 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(-) 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(); From 86772bd7bdd570264565c0078ddc66964860f389 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Sun, 24 Mar 2019 12:17:32 -0700 Subject: [PATCH 016/131] removes needless dictionary --- Emby.Server.Implementations/Library/UserManager.cs | 2 -- 1 file changed, 2 deletions(-) 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 From 087d4153aed8a82651a057ddf898dea7f97ded4a Mon Sep 17 00:00:00 2001 From: Phlogi Date: Sun, 24 Mar 2019 21:47:18 +0100 Subject: [PATCH 017/131] Fix check for available WAN address. --- Emby.Server.Implementations/ApplicationHost.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) 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 { From 2c4c56d6d638ee22617c000719aabcef5b949d32 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:17:40 +0100 Subject: [PATCH 018/131] Formatting update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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); } From 89f2dfd78a5c73c9d9d5cc832ff0427fa732ae3e Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:17:53 +0100 Subject: [PATCH 019/131] Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From 3474568ce2864d05fc47575fed3bfe5c7b21a435 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:18:04 +0100 Subject: [PATCH 020/131] Update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From f7e7d726880f87c18e799420ee6eb0ab42d86a47 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:18:18 +0100 Subject: [PATCH 021/131] Formatting update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From e36d424b5f61e2785c26af49db7d120d66933935 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:18:47 +0100 Subject: [PATCH 022/131] Formatting update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 { From 6480cfcc87b65331b15bf787633f978b30e2e829 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Mar 2019 10:19:08 +0100 Subject: [PATCH 023/131] Formatting update Emby.Server.Implementations/ApplicationHost.cs Co-Authored-By: Phlogi --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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); } From fc8de8aeadaa74b691ec909f9481da9a6d90fd2e Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 25 Mar 2019 17:27:24 +0100 Subject: [PATCH 024/131] Check if disposed first --- Emby.Dlna/PlayTo/PlayToController.cs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 67d5cfef42..0808c77d11 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -102,9 +102,10 @@ namespace Emby.Dlna.PlayTo { _sessionManager.ReportSessionEnded(_session.Id); } - catch + catch (Exception ex) { // Could throw if the session is already gone + _logger.LogError(ex, "Error reporting the end of session {Id}", _session.Id); } } @@ -112,20 +113,14 @@ namespace Emby.Dlna.PlayTo { var info = e.Argument; - info.Headers.TryGetValue("NTS", out string nts); - - if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty; - - if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty; - - if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 && - !_disposed) + if (!_disposed + && info.Headers.TryGetValue("USN", out string usn) + && usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 + && (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 + || (info.Headers.TryGetValue("NT", out string nt) + && nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1))) { - if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 || - nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1) - { - OnDeviceUnavailable(); - } + OnDeviceUnavailable(); } } From d623f616fa01abf5c0d0625eaf5c17c4acf86803 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 25 Mar 2019 17:32:27 +0100 Subject: [PATCH 025/131] Improved dispose method --- Emby.Dlna/PlayTo/PlayToController.cs | 40 ++++++++++++++++++---------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 0808c77d11..c58f16438b 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -607,22 +607,34 @@ namespace Emby.Dlna.PlayTo public void Dispose() { - if (!_disposed) - { - _disposed = true; - - _device.PlaybackStart -= _device_PlaybackStart; - _device.PlaybackProgress -= _device_PlaybackProgress; - _device.PlaybackStopped -= _device_PlaybackStopped; - _device.MediaChanged -= _device_MediaChanged; - //_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft; - _device.OnDeviceUnavailable = null; - - _device.Dispose(); - } + Dispose(true); + GC.SuppressFinalize(this); } - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _device.Dispose(); + } + + _device.PlaybackStart -= _device_PlaybackStart; + _device.PlaybackProgress -= _device_PlaybackProgress; + _device.PlaybackStopped -= _device_PlaybackStopped; + _device.MediaChanged -= _device_MediaChanged; + _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft; + _device.OnDeviceUnavailable = null; + _device = null; + + _disposed = true; + } + + private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) { From 4c8f8cf64cc81fb5f1bb32aeb8349ce38badf457 Mon Sep 17 00:00:00 2001 From: Phlogi Date: Mon, 25 Mar 2019 21:34:55 +0100 Subject: [PATCH 026/131] Removed trailing spaces, renamed get wan IP function. --- Emby.Server.Implementations/ApplicationHost.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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) From 1b03f078b92021b8f3c2e7c148f1e67debd27fac Mon Sep 17 00:00:00 2001 From: Phlogi Date: Mon, 25 Mar 2019 21:43:50 +0100 Subject: [PATCH 027/131] No need to assign empty string. --- Emby.Server.Implementations/ApplicationHost.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 { From b44a70ff368f228fc27a184e2139d288bada85cc Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 25 Mar 2019 22:25:32 +0100 Subject: [PATCH 028/131] Simplify/remove/clean code * Remove useless runtime check (we only support one) * Remove unused args * Remove a global constant And ofc fix some warnings ;) --- .../ApplicationHost.cs | 18 ++++------- .../Cryptography/CryptographyProvider.cs | 3 +- .../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 - .../Extensions/BaseExtensions.cs | 10 +++--- jellyfin.ruleset | 3 ++ 14 files changed, 50 insertions(+), 73 deletions(-) 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 @@ + + + From 122cba2aa755df7d7c3e63aaa441f639b126b55c Mon Sep 17 00:00:00 2001 From: Phlogi Date: Mon, 25 Mar 2019 22:26:05 +0100 Subject: [PATCH 029/131] Correct use of local variable wanAddress. --- Emby.Server.Implementations/ApplicationHost.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 { 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: [PATCH 030/131] Apply minor suggestions from code review Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Library/DefaultPasswordResetProvider.cs | 223 +++++++++--------- .../Library/UserManager.cs | 2 +- 2 files changed, 113 insertions(+), 112 deletions(-) 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) From 6be8624373bba6cf25a659390874613a4ea6ba79 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 25 Mar 2019 22:17:23 -0700 Subject: [PATCH 031/131] async improvements and post reset cleanups --- .../Library/DefaultPasswordResetProvider.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) 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) { From a332092769cec5a3b17b7fb49b2d7c66bfe289bd Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 Mar 2019 19:20:40 +0100 Subject: [PATCH 032/131] Reduce complexity http routes --- .../ApplicationHost.cs | 37 +----- .../HttpServer/HttpListenerHost.cs | 50 +++----- .../Services/ServiceController.cs | 62 ++++------ .../Services/ServiceHandler.cs | 107 +++++++++--------- .../Services/SwaggerService.cs | 20 ++-- Jellyfin.Server/Program.cs | 5 +- 6 files changed, 111 insertions(+), 170 deletions(-) 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("/")) - 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 (restPath.Path[0] != '/') { - pathsAtFirstMatch = new List(); - RestPathMap[restPath.FirstMatchHashKey] = pathsAtFirstMatch; + 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)) + { + 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 serviceType = httpHost.GetServiceTypeByRequest(requestType); - var service = appHost.CreateInstance(serviceType); - - //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; + switch (format) + { + case "json": return "application/json"; + case "xml": return "application/xml"; + default: return null; + } } - public RestPath GetRestPath(string httpMethod, string pathInfo) + public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, IResponse httpRes, ILogger logger, CancellationToken cancellationToken) { - if (this.RestPath == null) - { - this.RestPath = FindMatchingRestPath(httpMethod, pathInfo, out string contentType); - - if (contentType != null) - ResponseContentType = contentType; - } - 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) - { - 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; }); From 7343e07fe587b4817f5e80b68359d3193674e6ab Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 Mar 2019 19:31:06 +0100 Subject: [PATCH 033/131] Fix build error --- MediaBrowser.Controller/IServerApplicationHost.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 22797aa0d9..81b9ff0a57 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -83,8 +83,6 @@ namespace MediaBrowser.Controller void EnableLoopback(string appName); - string PackageRuntime { get; } - WakeOnLanInfo[] GetWakeOnLanInfo(); string ExpandVirtualPath(string path); From 157a86d0f139d149083036e6ea962e0fd7d77057 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 27 Mar 2019 12:43:46 +0100 Subject: [PATCH 034/131] Remove dead code --- .../Services/ServiceHandler.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) 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; From b07c146fd96d9ed7676adffda0333ec85f0c05b6 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 27 Mar 2019 16:17:18 -0700 Subject: [PATCH 035/131] 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(-) 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); } From 5e8496bc593399f062169c90b1820c1b8b75a73e Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 27 Mar 2019 22:46:25 -0700 Subject: [PATCH 036/131] minor fixes and usings --- .../Library/DefaultPasswordResetProvider.cs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) 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) { From 48b50a22a43dde00c795fb01521fcd731c323de7 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 28 Mar 2019 08:15:53 -0700 Subject: [PATCH 037/131] switched to a hexa string with crypto random backing --- .../Library/DefaultPasswordResetProvider.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) 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 From 3001f21f8d8592586add36661a0be0679e427d63 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 28 Mar 2019 19:11:05 +0100 Subject: [PATCH 038/131] Hacky fix for a hacky issue --- Emby.Server.Implementations/ApplicationHost.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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) { From 427a3e9b087e57b8308b2c53aaefd663cc3c2a68 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Thu, 28 Mar 2019 18:21:25 -0400 Subject: [PATCH 039/131] Use new libexecdir location for jellyfin-ffmpeg From commit d6bb1f3c in jellyfin-ffmpeg, which moves the installed binaries from /usr/share to /usr/lib on the next release. --- deployment/debian-package-x64/pkg-src/conf/jellyfin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin b/deployment/debian-package-x64/pkg-src/conf/jellyfin index bc00c37e20..c6e595f15a 100644 --- a/deployment/debian-package-x64/pkg-src/conf/jellyfin +++ b/deployment/debian-package-x64/pkg-src/conf/jellyfin @@ -22,7 +22,7 @@ JELLYFIN_CACHE_DIR="/var/cache/jellyfin" 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_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg" # [OPTIONAL] run Jellyfin as a headless service #JELLYFIN_SERVICE_OPT="--service" From 41df562419d8f1681a9720ab1c62ffb9ad0f96cb Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 28 Mar 2019 23:19:56 +0100 Subject: [PATCH 040/131] 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 --- .../IO/FileRefresher.cs | 6 +- .../IO/LibraryMonitor.cs | 125 +++--- .../IO/ManagedFileSystem.cs | 68 ++-- .../IO/StreamHelper.cs | 74 +--- .../IO/ThrottledStream.cs | 355 ------------------ 5 files changed, 82 insertions(+), 546 deletions(-) delete mode 100644 Emby.Server.Implementations/IO/ThrottledStream.cs 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.Length > 3 && filePath[1] == ':' && filePath[2] == '/') return filePath; //absolute local path + if (filePath.Contains("://")) + { + return filePath; // stream + } + + 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 - } -} From b56031b9f3ccfd4a8ac0413657f45645fe2e0f1e Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 28 Mar 2019 20:49:11 -0700 Subject: [PATCH 041/131] fix byte string --- .../Library/DefaultPasswordResetProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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); From 2d396cb589722bf8a950f80abb6d6137fe084a52 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Fri, 29 Mar 2019 07:10:49 -0700 Subject: [PATCH 042/131] adds readonly to properties --- .../Library/DefaultPasswordResetProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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) { From f911fda34fdc1af76dc475c5af042ff6b44262ab Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 29 Mar 2019 20:34:42 +0100 Subject: [PATCH 043/131] Merge ifs --- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 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: [PATCH 044/131] Remove dashes from pins --- .../Library/DefaultPasswordResetProvider.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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"; From f0fbd0232cd2367dda26f3f895926c1d0f742bdd Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Fri, 29 Mar 2019 19:13:01 -0400 Subject: [PATCH 045/131] Correct bad quote characters --- .../Library/DefaultPasswordResetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 1a540f1cf7c0f585fd80bc28d12caa56778c878c Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 30 Mar 2019 11:50:46 -0400 Subject: [PATCH 046/131] Add Ubuntu armhf (Raspberry Pi) build A pretty-much direct copy of the Debian armhf build infrastructure. --- build.yaml | 1 + .../ubuntu-package-armhf/Dockerfile.amd64 | 42 +++++++++++++++++++ .../ubuntu-package-armhf/Dockerfile.armhf | 34 +++++++++++++++ deployment/ubuntu-package-armhf/clean.sh | 29 +++++++++++++ .../ubuntu-package-armhf/dependencies.txt | 1 + .../ubuntu-package-armhf/docker-build.sh | 20 +++++++++ deployment/ubuntu-package-armhf/package.sh | 42 +++++++++++++++++++ deployment/ubuntu-package-armhf/pkg-src | 1 + 8 files changed, 170 insertions(+) create mode 100644 deployment/ubuntu-package-armhf/Dockerfile.amd64 create mode 100644 deployment/ubuntu-package-armhf/Dockerfile.armhf create mode 100755 deployment/ubuntu-package-armhf/clean.sh create mode 100644 deployment/ubuntu-package-armhf/dependencies.txt create mode 100755 deployment/ubuntu-package-armhf/docker-build.sh create mode 100755 deployment/ubuntu-package-armhf/package.sh create mode 120000 deployment/ubuntu-package-armhf/pkg-src diff --git a/build.yaml b/build.yaml index b0d2502d53..289c1caddc 100644 --- a/build.yaml +++ b/build.yaml @@ -6,6 +6,7 @@ packages: - debian-package-x64 - debian-package-armhf - ubuntu-package-x64 + - ubuntu-package-armhf - fedora-package-x64 - centos-package-x64 - linux-x64 diff --git a/deployment/ubuntu-package-armhf/Dockerfile.amd64 b/deployment/ubuntu-package-armhf/Dockerfile.amd64 new file mode 100644 index 0000000000..3e2f9defca --- /dev/null +++ b/deployment/ubuntu-package-armhf/Dockerfile.amd64 @@ -0,0 +1,42 @@ +FROM ubuntu:bionic +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-armhf +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV ARCH=amd64 + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4ced-81e6-286620b390ab/8ab938cf6f5e83b2221630354160ef21/dotnet-sdk-2.2.104-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + +# Prepare the cross-toolchain +RUN dpkg --add-architecture armhf \ + && apt-get update \ + && apt-get install -y cross-gcc-dev \ + && TARGET_LIST="armhf" cross-gcc-gensource 6 \ + && cd cross-gcc-packages-amd64/cross-gcc-6-armhf \ + && apt-get install -y gcc-6-source libstdc++6-armhf-cross binutils-arm-linux-gnueabihf bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:armhf linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf libfontconfig1-dev:armhf libfreetype6-dev:armhf liblttng-ust0:armhf libstdc++6:armhf + +# Link to docker-build script +RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh + +# Link to Debian source dir; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian + +VOLUME ${ARTIFACT_DIR}/ + +COPY . ${SOURCE_DIR}/ + +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/ubuntu-package-armhf/Dockerfile.armhf b/deployment/ubuntu-package-armhf/Dockerfile.armhf new file mode 100644 index 0000000000..72c4647241 --- /dev/null +++ b/deployment/ubuntu-package-armhf/Dockerfile.armhf @@ -0,0 +1,34 @@ +FROM ubuntu:bionic +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-armhf +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV ARCH=armhf + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev liblttng-ust0 + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget https://download.visualstudio.microsoft.com/download/pr/d9f37b73-df8d-4dfa-a905-b7648d3401d0/6312573ac13d7a8ddc16e4058f7d7dc5/dotnet-sdk-2.2.104-linux-arm.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + +# Link to docker-build script +RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh + +# Link to Debian source dir; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian + +VOLUME ${ARTIFACT_DIR}/ + +COPY . ${SOURCE_DIR}/ + +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/ubuntu-package-armhf/clean.sh b/deployment/ubuntu-package-armhf/clean.sh new file mode 100755 index 0000000000..c92c7fdec6 --- /dev/null +++ b/deployment/ubuntu-package-armhf/clean.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +keep_artifacts="${1}" + +WORKDIR="$( pwd )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-ubuntu-build" + +rm -rf "${package_temporary_dir}" &>/dev/null \ + || sudo rm -rf "${package_temporary_dir}" &>/dev/null + +rm -rf "${output_dir}" &>/dev/null \ + || sudo rm -rf "${output_dir}" &>/dev/null + +if [[ ${keep_artifacts} == 'n' ]]; then + docker_sudo="" + if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo=sudo + fi + ${docker_sudo} docker image rm ${image_name} --force +fi diff --git a/deployment/ubuntu-package-armhf/dependencies.txt b/deployment/ubuntu-package-armhf/dependencies.txt new file mode 100644 index 0000000000..bdb9670965 --- /dev/null +++ b/deployment/ubuntu-package-armhf/dependencies.txt @@ -0,0 +1 @@ +docker diff --git a/deployment/ubuntu-package-armhf/docker-build.sh b/deployment/ubuntu-package-armhf/docker-build.sh new file mode 100755 index 0000000000..45e68f0c6b --- /dev/null +++ b/deployment/ubuntu-package-armhf/docker-build.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Builds the DEB inside the Docker container + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image +sed -i '/dotnet-sdk-2.2,/d' debian/control + +# Build DEB +export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} +dpkg-buildpackage -us -uc -aarmhf + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/deb +mv /jellyfin_* ${ARTIFACT_DIR}/deb/ diff --git a/deployment/ubuntu-package-armhf/package.sh b/deployment/ubuntu-package-armhf/package.sh new file mode 100755 index 0000000000..fb03652cd5 --- /dev/null +++ b/deployment/ubuntu-package-armhf/package.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +ARCH="$( arch )" +WORKDIR="$( pwd )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-ubuntu_armhf-build" + +# Determine if sudo should be used for Docker +if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo="sudo" +else + docker_sudo="" +fi + +# Determine which Dockerfile to use +case $ARCH in + 'x86_64') + DOCKERFILE="Dockerfile.amd64" + ;; + 'armv7l') + DOCKERFILE="Dockerfile.armhf" + ;; +esac + +# Set up the build environment Docker image +${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} +# Build the DEBs and copy out to ${package_temporary_dir} +${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" +# Correct ownership on the DEBs (as current user, then as root if that fails) +chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ + || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null +# Move the DEBs to the output directory +mkdir -p "${output_dir}" +mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/ubuntu-package-armhf/pkg-src b/deployment/ubuntu-package-armhf/pkg-src new file mode 120000 index 0000000000..4c695fea17 --- /dev/null +++ b/deployment/ubuntu-package-armhf/pkg-src @@ -0,0 +1 @@ +../debian-package-x64/pkg-src \ No newline at end of file From 1596e93cc12cb7e2a1c974eb36b22e835036f582 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 30 Mar 2019 11:57:10 -0400 Subject: [PATCH 047/131] Fix up the Ubuntu repository definitions --- deployment/ubuntu-package-armhf/Dockerfile.amd64 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/deployment/ubuntu-package-armhf/Dockerfile.amd64 b/deployment/ubuntu-package-armhf/Dockerfile.amd64 index 3e2f9defca..c7575bacaa 100644 --- a/deployment/ubuntu-package-armhf/Dockerfile.amd64 +++ b/deployment/ubuntu-package-armhf/Dockerfile.amd64 @@ -22,11 +22,16 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4 && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet # Prepare the cross-toolchain -RUN dpkg --add-architecture armhf \ +RUN rm /etc/apt/sources.list \ + && export CODENAME="$( lsb_release -c -s )" \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse\ndeb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse\ndeb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse\ndeb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse\ndeb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse\ndeb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse\ndeb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >/etc/apt/sources.list.d/armhf.list \ + && dpkg --add-architecture armhf \ && apt-get update \ && apt-get install -y cross-gcc-dev \ && TARGET_LIST="armhf" cross-gcc-gensource 6 \ && cd cross-gcc-packages-amd64/cross-gcc-6-armhf \ + && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \ && apt-get install -y gcc-6-source libstdc++6-armhf-cross binutils-arm-linux-gnueabihf bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:armhf linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf libfontconfig1-dev:armhf libfreetype6-dev:armhf liblttng-ust0:armhf libstdc++6:armhf # Link to docker-build script From 3375ca5a8cfad159e61f4d8dcd91df26d3c1e71e Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 30 Mar 2019 12:19:49 -0400 Subject: [PATCH 048/131] Split lists echoes into separate lines --- deployment/ubuntu-package-armhf/Dockerfile.amd64 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/deployment/ubuntu-package-armhf/Dockerfile.amd64 b/deployment/ubuntu-package-armhf/Dockerfile.amd64 index c7575bacaa..ef0735e427 100644 --- a/deployment/ubuntu-package-armhf/Dockerfile.amd64 +++ b/deployment/ubuntu-package-armhf/Dockerfile.amd64 @@ -24,8 +24,14 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4 # Prepare the cross-toolchain RUN rm /etc/apt/sources.list \ && export CODENAME="$( lsb_release -c -s )" \ - && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse\ndeb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse\ndeb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse\ndeb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >/etc/apt/sources.list.d/amd64.list \ - && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse\ndeb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse\ndeb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse\ndeb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >/etc/apt/sources.list.d/armhf.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ + && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ + && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ + && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ && dpkg --add-architecture armhf \ && apt-get update \ && apt-get install -y cross-gcc-dev \ From 1d9133a5e871c84ca5cea5a7608c34378dc6663f Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 30 Mar 2019 12:42:16 -0400 Subject: [PATCH 049/131] Simplify bump_version and remove changelogs Make this a lot simpler, use a reference to the release page in the package changelogs instead of a full list. --- bump_version | 133 +++++++++++---------------------------------------- 1 file changed, 27 insertions(+), 106 deletions(-) diff --git a/bump_version b/bump_version index a63fbf7353..d2560ff1e2 100755 --- a/bump_version +++ b/bump_version @@ -9,7 +9,7 @@ usage() { echo -e "bump_version - increase the shared version and generate changelogs" echo -e "" echo -e "Usage:" - echo -e " $ bump_version [-b/--web-branch ] " + echo -e " $ bump_version " echo -e "" echo -e "The web_branch defaults to the same branch name as the current main branch." echo -e "This helps facilitate releases where both branches would be called release-X.Y.Z" @@ -22,14 +22,9 @@ if [[ -z $1 ]]; then fi shared_version_file="./SharedVersion.cs" +build_file="./build.yaml" -# Parse branch option -if [[ $1 == '-b' || $1 == '--web-branch' ]]; then - web_branch="$2" - shift 2 -else - web_branch="$( git branch 2>/dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/' )" -fi +web_branch="$( git branch 2>/dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/' )" # Initialize submodules git submodule update --init --recursive @@ -47,22 +42,11 @@ if ! git diff-index --quiet HEAD --; then fi git fetch --all -# If this is an official branch name, fetch it from origin -official_branches_regex="^master$|^dev$|^release-.*$|^hotfix-.*$" -if [[ ${web_branch} =~ ${official_branches_regex} ]]; then - git checkout origin/${web_branch} || { - echo "ERROR: 'jellyfin-web' branch 'origin/${web_branch}' is invalid." - exit 1 - } -# Otherwise, just check out the local branch (for testing, etc.) -else - git checkout ${web_branch} || { - echo "ERROR: 'jellyfin-web' branch '${web_branch}' is invalid." - exit 1 - } -fi +git checkout origin/${web_branch} popd +git add MediaBrowser.WebDashboard/jellyfin-web + new_version="$1" # Parse the version from the AssemblyVersion @@ -73,91 +57,32 @@ old_version="$( # Set the shared version to the specified new_version old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars -sed -i "s/${old_version_sed}/${new_version}/g" ${shared_version_file} +new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )" +sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file} -declare -a pr_merges_since_last_master -declare changelog_string_github -declare changelog_string_deb -declare changelog_string_yum +old_version="$( + grep "version:" ${build_file} \ + | sed -E 's/version: "([0-9\.]+)"/\1/' +)" -# Build up a changelog from merge commits -for repo in ./ MediaBrowser.WebDashboard/jellyfin-web/; do - last_master_merge_commit="" - pr_merges_since_last_master=() - git_show_details="" - pull_request_id="" - pull_request_description="" - changelog_strings_repo_github="" - changelog_strings_repo_deb="" - changelog_strings_repo_yum="" +old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars +new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )" +sed -i "s/${old_version_sed}/${new_version}/g" ${build_file} - case $repo in - *jellyfin-web*) - repo_name="jellyfin-web" - ;; - *) - repo_name="jellyfin" - ;; - esac - - pushd ${repo} - - # Find the last release commit, so we know what's happened since - last_master_branch="release-${old_version}" - last_master_merge_commit="$( - git log --merges --pretty=oneline \ - | grep -F "${last_master_branch}" \ - | awk '{ print $1 }' \ - || true # Don't die here with errexit - )" - if [[ -z ${last_master_merge_commit} ]]; then - # This repo has no last proper commit, so just skip it - popd - continue - fi - # Get all the PR merge commits since the last master merge commit in `jellyfin` - pr_merges_since_last_master+=( $( - git log --merges --pretty=oneline ${last_master_merge_commit}..HEAD \ - | grep -F "Merge pull request" \ - | awk '{ print $1 }' - ) ) - - for commit_hash in ${pr_merges_since_last_master[@]}; do - git_show_details="$( git show ${commit_hash} )" - pull_request_id="$( - awk ' - /Merge pull request/{ print $4 } - { next } - ' <<<"${git_show_details}" - )" - pull_request_description="$( - awk ' - /^[a-zA-Z]/{ next } - /^ Merge/{ next } - /^$/{ next } - { print $0 } - ' <<<"${git_show_details}" - )" - pull_request_description="$( sed ':a;N;$!ba;s/\n//g; s/ \+//g' <<<"${pull_request_description}" )" - changelog_strings_repo_github="${changelog_strings_repo_github}\n* ${pull_request_id}: ${pull_request_description}" - changelog_strings_repo_deb="${changelog_strings_repo_deb}\n * $( sed 's/#/PR/' <<<"${pull_request_id}" ) ${pull_request_description}" - changelog_strings_repo_yum="${changelog_strings_repo_yum}\n- $( sed 's/#/PR/' <<<"${pull_request_id}" ) ${pull_request_description}" - done - - changelog_string_github="${changelog_string_github}\n#### ${repo_name}:\n$( echo -e "${changelog_strings_repo_github}" | sort -nk2 )\n" - changelog_string_deb="${changelog_string_deb}\n * ${repo_name}:$( echo -e "${changelog_strings_repo_deb}" | sort -nk2 )" - changelog_string_yum="${changelog_string_yum}\n- ${repo_name}:$( echo -e "${changelog_strings_repo_yum}" | sort -nk2 )" - - popd -done +if [[ ${new_version} == *"-"* ]]; then + new_version_deb="$( sed 's/-/~/g' <<<"${new_version}" )" +else + new_version_deb="${new_version}-1" +fi # Write out a temporary Debian changelog with our new stuff appended and some templated formatting debian_changelog_file="deployment/debian-package-x64/pkg-src/changelog" debian_changelog_temp="$( mktemp )" # Create new temp file with our changelog echo -e "### DEBIAN PACKAGE CHANGELOG: Verify this file looks correct or edit accordingly, then delete this line, write, and exit. -jellyfin (${new_version}-1) unstable; urgency=medium -${changelog_string_deb} +jellyfin (${new_version_deb}) unstable; urgency=medium + + * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version} -- Jellyfin Packaging Team $( date --rfc-2822 ) " >> ${debian_changelog_temp} @@ -180,13 +105,14 @@ pushd ${fedora_spec_temp_dir} # Split out the stuff before and after changelog csplit jellyfin.spec "/^%changelog/" # produces xx00 xx01 # Update the version in xx00 -sed -i "s/${old_version_sed}/${new_version}/g" xx00 +sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00 # Remove the header from xx01 sed -i '/^%changelog/d' xx01 # Create new temp file with our changelog echo -e "### YUM SPEC CHANGELOG: Verify this file looks correct or edit accordingly, then delete this line, write, and exit. %changelog -* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team ${changelog_string_yum}" >> ${fedora_changelog_temp} +* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team +- New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version}" >> ${fedora_changelog_temp} cat xx01 >> ${fedora_changelog_temp} # Edit the file to verify $EDITOR ${fedora_changelog_temp} @@ -199,10 +125,5 @@ mv ${fedora_spec_temp} ${fedora_spec_file} rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir} # Stage the changed files for commit -git add ${shared_version_file} ${debian_changelog_file} ${fedora_spec_file} +git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} git status - -# Write out the GitHub-formatted changelog for the merge request/release pages -echo "" -echo "=== The GitHub-formatted changelog follows ===" -echo -e "${changelog_string_github}" From 31aa6c486cdeb851dd7ef7c66a82a76302cc632f Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 30 Mar 2019 12:42:33 -0400 Subject: [PATCH 050/131] Get the version string from build.yaml For the purposes of packaging, this makes more sense, since we can include additional appends to this version (e.g. `-rcX`) when we can't in the SharedVersion file. The previous commit to the bump_version script sets this as well. --- deployment/common.build.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deployment/common.build.sh b/deployment/common.build.sh index d028e3a668..000872ea91 100755 --- a/deployment/common.build.sh +++ b/deployment/common.build.sh @@ -17,12 +17,12 @@ DEFAULT_PKG_DIR="pkg-dist" DEFAULT_DOCKERFILE="Dockerfile" DEFAULT_ARCHIVE_CMD="tar -xvzf" -# Parse the version from the AssemblyVersion +# Parse the version from the build.yaml version get_version() ( local ROOT=${1-$DEFAULT_ROOT} - grep "AssemblyVersion" ${ROOT}/SharedVersion.cs \ - | sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/' + grep "version:" ${ROOT}/build.yaml \ + | sed -E 's/version: "([0-9\.]+.*)"/\1/' ) # Run a build From 891a03c038e8ef451d6db3361fefcbf964a7065e Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 30 Mar 2019 12:58:39 -0400 Subject: [PATCH 051/131] Remove superfluous variable declaration --- bump_version | 1 - 1 file changed, 1 deletion(-) diff --git a/bump_version b/bump_version index d2560ff1e2..b118af54b9 100755 --- a/bump_version +++ b/bump_version @@ -66,7 +66,6 @@ old_version="$( )" old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars -new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )" sed -i "s/${old_version_sed}/${new_version}/g" ${build_file} if [[ ${new_version} == *"-"* ]]; then From f27477da26097ab4cca22c64e5f236150975ff47 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 30 Mar 2019 15:47:34 -0400 Subject: [PATCH 052/131] Bump version to 10.3.0 and update submodule --- MediaBrowser.WebDashboard/jellyfin-web | 2 +- SharedVersion.cs | 4 ++-- build.yaml | 2 +- deployment/debian-package-x64/pkg-src/changelog | 6 ++++++ deployment/fedora-package-x64/pkg-src/jellyfin.spec | 4 +++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.WebDashboard/jellyfin-web b/MediaBrowser.WebDashboard/jellyfin-web index ec5a3b6e5e..9677981344 160000 --- a/MediaBrowser.WebDashboard/jellyfin-web +++ b/MediaBrowser.WebDashboard/jellyfin-web @@ -1 +1 @@ -Subproject commit ec5a3b6e5efb6041153b92818aee562f20ee994d +Subproject commit 9677981344e57e8f84ca664cad13da1f89f4254f diff --git a/SharedVersion.cs b/SharedVersion.cs index 785ba93018..3a0263bd67 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.2.2")] -[assembly: AssemblyFileVersion("10.2.2")] +[assembly: AssemblyVersion("10.3.0")] +[assembly: AssemblyFileVersion("10.3.0")] diff --git a/build.yaml b/build.yaml index 289c1caddc..34d356dacf 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ --- # We just wrap `build` so this is really it name: "jellyfin" -version: "10.2.2" +version: "10.3.0-rc1" packages: - debian-package-x64 - debian-package-armhf diff --git a/deployment/debian-package-x64/pkg-src/changelog b/deployment/debian-package-x64/pkg-src/changelog index 349e8787f6..da9151af1a 100644 --- a/deployment/debian-package-x64/pkg-src/changelog +++ b/deployment/debian-package-x64/pkg-src/changelog @@ -1,3 +1,9 @@ +jellyfin (10.3.0~rc1) unstable; urgency=medium + + * New upstream version 10.3.0-rc1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc1 + + -- Jellyfin Packaging Team Sat, 30 Mar 2019 15:47:24 -0400 + jellyfin (10.2.2-1) unstable; urgency=medium * jellyfin: diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec index e24bd2fcb1..77c291c872 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec @@ -7,7 +7,7 @@ %endif Name: jellyfin -Version: 10.2.2 +Version: 10.3.0 Release: 1%{?dist} Summary: The Free Software Media Browser License: GPLv2 @@ -140,6 +140,8 @@ fi %systemd_postun_with_restart jellyfin.service %changelog +* Sat Mar 30 2019 Jellyfin Packaging Team +- New upstream version 10.3.0-rc1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc1 * Thu Feb 28 2019 Jellyfin Packaging Team - jellyfin: - PR968 Release 10.2.z copr autobuild From f2e2065fd4a879c8583f487e161e09909ed704b2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 31 Mar 2019 15:24:18 +0200 Subject: [PATCH 053/131] Remove unused dependency for Emby.Naming --- Emby.Naming/Emby.Naming.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index e344e7811e..c448ec0ce6 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -10,7 +10,7 @@ - + From e37ccd6ec0c5ac58cc2cc39e53cd7fb268ad04de Mon Sep 17 00:00:00 2001 From: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> Date: Sun, 31 Mar 2019 10:32:56 -0700 Subject: [PATCH 054/131] Updates windows installer default lib location You can use the emby import to move an existing library this way. --- deployment/windows/install-jellyfin.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deployment/windows/install-jellyfin.ps1 b/deployment/windows/install-jellyfin.ps1 index b6e00e0568..689dedb4a9 100644 --- a/deployment/windows/install-jellyfin.ps1 +++ b/deployment/windows/install-jellyfin.ps1 @@ -58,7 +58,7 @@ function Elevate-Window { if($Quiet.IsPresent -or $Quiet -eq $true){ if([string]::IsNullOrEmpty($JellyfinLibraryLocation)){ - $Script:JellyfinDataDir = "$env:AppData\jellyfin\" + $Script:JellyfinDataDir = "$env:LOCALAPPDATA\jellyfin\" }else{ $Script:JellyfinDataDir = $JellyfinLibraryLocation } @@ -82,7 +82,7 @@ if($Quiet.IsPresent -or $Quiet -eq $true){ }else{ $Script:InstallServiceAsUser = $true $Script:UserCredentials = $ServiceUser - $Script:JellyfinDataDir = "C:\Users\$($Script:UserCredentials.UserName)\Appdata\Roaming\jellyfin\"} + $Script:JellyfinDataDir = "$env:HOMEDRIVE\Users\$($Script:UserCredentials.UserName)\Appdata\Roaming\jellyfin\"} if($CreateDesktopShorcut.IsPresent -or $CreateDesktopShorcut -eq $true) {$Script:CreateShortcut = $true}else{$Script:CreateShortcut = $false} if($MigrateEmbyLibrary.IsPresent -or $MigrateEmbyLibrary -eq $true){$Script:MigrateLibrary = $true}else{$Script:MigrateLibrary = $false} if($LaunchJellyfin.IsPresent -or $LaunchJellyfin -eq $true){$Script:StartJellyfin = $true}else{$Script:StartJellyfin = $false} @@ -131,7 +131,7 @@ if($Quiet.IsPresent -or $Quiet -eq $true){ Add-Type -AssemblyName System.Windows.Forms [System.Windows.Forms.Application]::EnableVisualStyles() -$Script:JellyFinDataDir = "$env:AppData\jellyfin\" +$Script:JellyFinDataDir = "$env:LOCALAPPDATA\jellyfin\" $Script:DefaultJellyfinInstallDirectory = "$env:Appdata\jellyfin\" $Script:defaultEmbyDataDir = "$env:Appdata\Emby-Server\" $Script:InstallAsService = $False @@ -392,7 +392,7 @@ $ServiceUserBox.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDow $GUIElementsCollection += $ServiceUserBox $MigrateLibraryCheck = New-Object system.Windows.Forms.CheckBox -$MigrateLibraryCheck.text = "Import Emby Library" +$MigrateLibraryCheck.text = "Import Emby/Old JF Library" $MigrateLibraryCheck.AutoSize = $false $MigrateLibraryCheck.width = 160 $MigrateLibraryCheck.height = 20 @@ -401,7 +401,7 @@ $MigrateLibraryCheck.Font = 'Microsoft Sans Serif,10' $GUIElementsCollection += $MigrateLibraryCheck $LibraryMigrationLabel = New-Object system.Windows.Forms.Label -$LibraryMigrationLabel.text = "Emby Library Path" +$LibraryMigrationLabel.text = "Emby/Old JF Library Path" $LibraryMigrationLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft $LibraryMigrationLabel.AutoSize = $false $LibraryMigrationLabel.width = 120 From 816d8a0216989497f6c43da7b9f64539e63bc5bf Mon Sep 17 00:00:00 2001 From: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> Date: Sun, 31 Mar 2019 10:34:49 -0700 Subject: [PATCH 055/131] Update install-jellyfin.ps1 --- deployment/windows/install-jellyfin.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/windows/install-jellyfin.ps1 b/deployment/windows/install-jellyfin.ps1 index 689dedb4a9..0cd7f5236f 100644 --- a/deployment/windows/install-jellyfin.ps1 +++ b/deployment/windows/install-jellyfin.ps1 @@ -82,7 +82,7 @@ if($Quiet.IsPresent -or $Quiet -eq $true){ }else{ $Script:InstallServiceAsUser = $true $Script:UserCredentials = $ServiceUser - $Script:JellyfinDataDir = "$env:HOMEDRIVE\Users\$($Script:UserCredentials.UserName)\Appdata\Roaming\jellyfin\"} + $Script:JellyfinDataDir = "$env:HOMEDRIVE\Users\$($Script:UserCredentials.UserName)\Appdata\Local\jellyfin\"} if($CreateDesktopShorcut.IsPresent -or $CreateDesktopShorcut -eq $true) {$Script:CreateShortcut = $true}else{$Script:CreateShortcut = $false} if($MigrateEmbyLibrary.IsPresent -or $MigrateEmbyLibrary -eq $true){$Script:MigrateLibrary = $true}else{$Script:MigrateLibrary = $false} if($LaunchJellyfin.IsPresent -or $LaunchJellyfin -eq $true){$Script:StartJellyfin = $true}else{$Script:StartJellyfin = $false} From 2f33e99006fd479ac9134ede80cbb990948d1261 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 2 Apr 2019 18:17:50 +0200 Subject: [PATCH 056/131] Speed up DeepCopy --- .../Entities/BaseItemExtensions.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs index 9c955a7247..815239be24 100644 --- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs +++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs @@ -64,21 +64,31 @@ namespace MediaBrowser.Controller.Entities where T : BaseItem where TU : BaseItem { - var sourceProps = typeof(T).GetProperties().Where(x => x.CanRead).ToList(); - var destProps = typeof(TU).GetProperties() - .Where(x => x.CanWrite) - .ToList(); + var destProps = typeof(TU).GetProperties().Where(x => x.CanWrite).ToList(); - foreach (var sourceProp in sourceProps) + foreach (var sourceProp in typeof(T).GetProperties()) { - if (destProps.Any(x => x.Name == sourceProp.Name)) + // We should be able to write to the property + // for both the source and destination type + // This is only false when the derived type hides the base member + // (which we shouldn't copy anyway) + if (!sourceProp.CanRead || !sourceProp.CanWrite) { - var p = destProps.First(x => x.Name == sourceProp.Name); - p.SetValue(dest, sourceProp.GetValue(source, null), null); + continue; } - } + var v = sourceProp.GetValue(source); + if (v == null) + { + continue; + } + var p = destProps.Find(x => x.Name == sourceProp.Name); + if (p != null) + { + p.SetValue(dest, v); + } + } } /// @@ -93,7 +103,5 @@ namespace MediaBrowser.Controller.Entities source.DeepCopy(dest); return dest; } - - } } From 38fcd31917af1d904ea61de0f90918d44122d2e4 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 2 Apr 2019 18:19:19 -0400 Subject: [PATCH 057/131] 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(-) 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); From d75324afc93129e9237607eda3d39248ad9719ec Mon Sep 17 00:00:00 2001 From: Andrew Rabert Date: Wed, 3 Apr 2019 01:21:28 -0400 Subject: [PATCH 058/131] Update Dockerfiles * Use new dotnet image paths * Update jellyfin-web to 10.3.0-rc1 --- Dockerfile | 8 ++++---- Dockerfile.arm | 6 +++--- Dockerfile.arm64 | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5794bdde1e..dbbed535f8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -ARG DOTNET_VERSION=2 +ARG DOTNET_VERSION=2.2 -FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder +FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder WORKDIR /repo COPY . . ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 @@ -8,7 +8,7 @@ RUN bash -c "source deployment/common.build.sh && \ build_jellyfin Jellyfin.Server Release linux-x64 /jellyfin" FROM jellyfin/ffmpeg as ffmpeg -FROM microsoft/dotnet:${DOTNET_VERSION}-runtime +FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION} # libfontconfig1 is required for Skia RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y \ @@ -21,7 +21,7 @@ RUN apt-get update \ COPY --from=ffmpeg / / COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.2.2 +ARG JELLYFIN_WEB_VERSION=10.3.0-rc1 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm b/Dockerfile.arm index 1497da0ef7..6bd5b576f1 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -8,7 +8,7 @@ FROM alpine as qemu_extract COPY --from=qemu /usr/bin qemu-arm-static.tar.gz RUN tar -xzvf qemu-arm-static.tar.gz -FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder +FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder WORKDIR /repo COPY . . ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 @@ -21,7 +21,7 @@ RUN bash -c "source deployment/common.build.sh && \ build_jellyfin Jellyfin.Server Release linux-arm /jellyfin" -FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm32v7 +FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7 COPY --from=qemu_extract qemu-arm-static /usr/bin RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ @@ -30,7 +30,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.2.2 +ARG JELLYFIN_WEB_VERSION=10.3.0-rc1 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index f4658a055c..3be2fd4f14 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -9,7 +9,7 @@ COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz RUN tar -xzvf qemu-aarch64-static.tar.gz -FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder +FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder WORKDIR /repo COPY . . ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 @@ -22,7 +22,7 @@ RUN bash -c "source deployment/common.build.sh && \ build_jellyfin Jellyfin.Server Release linux-arm64 /jellyfin" -FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm64v8 +FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8 COPY --from=qemu_extract qemu-aarch64-static /usr/bin RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ @@ -31,7 +31,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.2.2 +ARG JELLYFIN_WEB_VERSION=10.3.0-rc1 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web From 05a4161fd388d7ecda2f1b4a6ec6d02ee7b4488e Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Wed, 3 Apr 2019 19:43:02 -0400 Subject: [PATCH 059/131] 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. --- .../ApplicationHost.cs | 2 +- .../Updates/InstallationManager.cs | 41 ++++++++++++++++--- 2 files changed, 36 insertions(+), 7 deletions(-) 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); From 608fd873de6118198c2ac882b2859da3d0caff72 Mon Sep 17 00:00:00 2001 From: Andrew Rabert Date: Wed, 3 Apr 2019 22:58:52 -0400 Subject: [PATCH 060/131] Optimize images with image_optim --- Emby.Dlna/Images/logo120.png | Bin 7522 -> 6201 bytes Emby.Dlna/Images/logo240.png | Bin 18287 -> 13339 bytes Emby.Dlna/Images/logo48.png | Bin 2554 -> 2263 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Emby.Dlna/Images/logo120.png b/Emby.Dlna/Images/logo120.png index 321c4772920b3b5e81c61a63f06d5bb2100514f2..14f6c8d5f66010de7b3f132c554e3ab53d49001e 100644 GIT binary patch literal 6201 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 XU2ajqLA}4W00000NkvXXu0mjfyde4m literal 7522 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 diff --git a/Emby.Dlna/Images/logo240.png b/Emby.Dlna/Images/logo240.png index 64c8281299ec62baed2ba1722ae637783272885a..ff50314d44a8d2b9ae1a23690a95c3983293f8a3 100644 GIT binary patch literal 13339 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 diff --git a/Emby.Dlna/Images/logo48.png b/Emby.Dlna/Images/logo48.png index b8fc145647099cc15f8492378c17ab555d4fb163..d6b5fd1df1adedfbd025b73121436668d98cfd2f 100644 GIT binary patch delta 2252 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 Hu0mjf+c4ez From 09505e0988ec53538c3ecc3d243b1d1a5edac8c6 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Thu, 4 Apr 2019 01:54:31 -0400 Subject: [PATCH 061/131] 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(-) 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); } From 754e76a61bbf4bbda5a4a1073737c07bf28f5f3a Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Thu, 4 Apr 2019 02:34:23 -0400 Subject: [PATCH 062/131] Add TODO to remove string target --- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 ++ 1 file changed, 2 insertions(+) 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); From f5f7de64de2a15164ef6b62cb8da844dd823067d Mon Sep 17 00:00:00 2001 From: John Taylor Date: Sat, 6 Apr 2019 13:40:19 -0400 Subject: [PATCH 063/131] Use TLS 1.2 to download NSSM --- deployment/windows/build-jellyfin.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deployment/windows/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1 index 2c83f264c2..2999912b3a 100644 --- a/deployment/windows/build-jellyfin.ps1 +++ b/deployment/windows/build-jellyfin.ps1 @@ -26,7 +26,10 @@ function Build-JellyFin { Write-Error "arm only supported with Windows 8 or higher" exit } - dotnet publish -c $BuildType -r "$windowsversion-$Architecture" MediaBrowser.sln -o $InstallLocation -v $DotNetVerbosity + Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture" + Write-Verbose "InstallLocation: $InstallLocation" + Write-Verbose "DotNetVerbosity: $DotNetVerbosity" + dotnet publish -c $BuildType -r `"$windowsversion-$Architecture`" MediaBrowser.sln -o $InstallLocation -v $DotNetVerbosity } function Install-FFMPEG { @@ -73,6 +76,7 @@ function Install-NSSM { Write-Warning "NSSM will not be installed" }else{ Write-Verbose "Downloading NSSM" + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest -Uri https://nssm.cc/ci/nssm-2.24-101-g897c7ad.zip -UseBasicParsing -OutFile "$tempdir/nssm.zip" | Write-Verbose } From 1af9c047fbc0283f7abfb4b98918454258dfb348 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 7 Apr 2019 19:51:45 -0400 Subject: [PATCH 064/131] 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. --- .../Library/UserManager.cs | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) 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); } - return true; + if(authenticationResult.Username != username) + { + _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); + username = authenticationResult.Username; + } + + 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) From f96d1e9e690f2f0e83d153e4ae8c2f4d810e7e2e Mon Sep 17 00:00:00 2001 From: DrPandemic Date: Sun, 7 Apr 2019 22:26:27 -0400 Subject: [PATCH 065/131] Fix README documentation link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1f635bdd25..6a206eacf6 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ For more information about the project, please see our [about page](https://jell

Want to get started? -Choose from Prebuilt Packages or Build from Source, then see our first-time setup guide. +Choose from Prebuilt Packages or Build from Source, then see our quick start guide.

Want to contribute? From c72393c9704d693b3beee51ebd35da0f68d13261 Mon Sep 17 00:00:00 2001 From: Terror-Gene Date: Mon, 8 Apr 2019 14:56:42 +0930 Subject: [PATCH 066/131] Updated Unraid Docker icon Logo was set to use emby, but binhex has since added the jellyfin logo. --- deployment/unraid/docker-templates/jellyfin.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/unraid/docker-templates/jellyfin.xml b/deployment/unraid/docker-templates/jellyfin.xml index 1d97a9f00f..5a5b4bb5c7 100644 --- a/deployment/unraid/docker-templates/jellyfin.xml +++ b/deployment/unraid/docker-templates/jellyfin.xml @@ -46,6 +46,6 @@ http://[IP]:[PORT:8096]/ - https://raw.githubusercontent.com/binhex/docker-templates/master/binhex/images/emby-icon.png + https://raw.githubusercontent.com/binhex/docker-templates/master/binhex/images/jellyfin-icon.png From 7b4e16bb8ffaa95619a5be0f231e163da6747665 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 9 Apr 2019 00:43:25 +0200 Subject: [PATCH 067/131] Disabled dotnet_compat part of pipeline. --- .ci/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 7a92d40889..b3389caba9 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -99,7 +99,7 @@ jobs: pool: vmImage: ubuntu-16.04 dependsOn: main_build - condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber']) # Only execute if the pullrequest numer is defined. (So not for normal CI builds) + condition: false #and(succeeded(), variables['System.PullRequest.PullRequestNumber']) # Only execute if the pullrequest numer is defined. (So not for normal CI builds) strategy: matrix: Naming: From a7e31ef31f0202cefe1fd97a648260c3f4791446 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 4 Apr 2019 23:04:54 -0700 Subject: [PATCH 068/131] applied changes to just also search jellyfin base dir --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4867c0f859..b626600fa4 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -230,6 +230,11 @@ namespace MediaBrowser.MediaEncoding.Encoder /// private string ExistsOnSystemPath(string filename) { + string inJellyfinPath = GetEncoderPathFromDirectory(System.AppContext.BaseDirectory, filename); + if (!string.IsNullOrEmpty(inJellyfinPath)) + { + return inJellyfinPath; + } var values = Environment.GetEnvironmentVariable("PATH"); foreach (var path in values.Split(Path.PathSeparator)) 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: [PATCH 069/131] 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(-) 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(); - } } } From efb14f0b583d570cf73807715ee50956badeaf1d Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Wed, 10 Apr 2019 00:23:39 -0400 Subject: [PATCH 070/131] Bump dockerfile web versions too --- bump_version | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/bump_version b/bump_version index b118af54b9..398caee15c 100755 --- a/bump_version +++ b/bump_version @@ -54,6 +54,7 @@ old_version="$( grep "AssemblyVersion" ${shared_version_file} \ | sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/' )" +echo $old_version # Set the shared version to the specified new_version old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars @@ -62,9 +63,11 @@ sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file} old_version="$( grep "version:" ${build_file} \ - | sed -E 's/version: "([0-9\.]+)"/\1/' + | sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/' )" +echo $old_version +# Set the build.yaml version to the specified new_version old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars sed -i "s/${old_version_sed}/${new_version}/g" ${build_file} @@ -74,6 +77,16 @@ else new_version_deb="${new_version}-1" fi +# Set the Dockerfile web version to the specified new_version +old_version="$( + grep "JELLYFIN_WEB_VERSION=" Dockerfile \ + | sed -E 's/ARG JELLYFIN_WEB_VERSION=([0-9\.]+[-a-z0-9]*)/\1/' +)" +echo $old_version + +old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars +sed -i "s/${old_version_sed}/${new_version}/g" Dockerfile* + # Write out a temporary Debian changelog with our new stuff appended and some templated formatting debian_changelog_file="deployment/debian-package-x64/pkg-src/changelog" debian_changelog_temp="$( mktemp )" @@ -124,5 +137,5 @@ mv ${fedora_spec_temp} ${fedora_spec_file} rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir} # Stage the changed files for commit -git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} +git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} Dockerfile* git status From 65bff1181a3ae739dedbfd647d890630aa2cfba8 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Wed, 10 Apr 2019 00:51:21 -0400 Subject: [PATCH 071/131] Bump version to 10.3.0-rc2 and update submodule --- Dockerfile | 2 +- Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- MediaBrowser.WebDashboard/jellyfin-web | 2 +- build.yaml | 2 +- deployment/debian-package-x64/pkg-src/changelog | 6 ++++++ deployment/fedora-package-x64/pkg-src/jellyfin.spec | 2 ++ 7 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index dbbed535f8..36fc43983e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apt-get update \ COPY --from=ffmpeg / / COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.0-rc1 +ARG JELLYFIN_WEB_VERSION=10.3.0-rc2 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm b/Dockerfile.arm index 6bd5b576f1..c896d83f27 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -30,7 +30,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.0-rc1 +ARG JELLYFIN_WEB_VERSION=10.3.0-rc2 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 3be2fd4f14..09c9dc12f8 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -31,7 +31,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.0-rc1 +ARG JELLYFIN_WEB_VERSION=10.3.0-rc2 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/MediaBrowser.WebDashboard/jellyfin-web b/MediaBrowser.WebDashboard/jellyfin-web index 9677981344..e07edea5bf 160000 --- a/MediaBrowser.WebDashboard/jellyfin-web +++ b/MediaBrowser.WebDashboard/jellyfin-web @@ -1 +1 @@ -Subproject commit 9677981344e57e8f84ca664cad13da1f89f4254f +Subproject commit e07edea5bf22c253fc7ee91f45879d8ee2d1bf17 diff --git a/build.yaml b/build.yaml index 34d356dacf..ccd7ce8f85 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ --- # We just wrap `build` so this is really it name: "jellyfin" -version: "10.3.0-rc1" +version: "10.3.0-rc2" packages: - debian-package-x64 - debian-package-armhf diff --git a/deployment/debian-package-x64/pkg-src/changelog b/deployment/debian-package-x64/pkg-src/changelog index da9151af1a..65880620dc 100644 --- a/deployment/debian-package-x64/pkg-src/changelog +++ b/deployment/debian-package-x64/pkg-src/changelog @@ -1,3 +1,9 @@ +jellyfin (10.3.0~rc2) unstable; urgency=medium + + * New upstream version 10.3.0-rc2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc2 + + -- Jellyfin Packaging Team Wed, 10 Apr 2019 00:51:14 -0400 + jellyfin (10.3.0~rc1) unstable; urgency=medium * New upstream version 10.3.0-rc1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc1 diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec index 77c291c872..581d926fbf 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec @@ -140,6 +140,8 @@ fi %systemd_postun_with_restart jellyfin.service %changelog +* Wed Apr 10 2019 Jellyfin Packaging Team +- New upstream version 10.3.0-rc2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc2 * Sat Mar 30 2019 Jellyfin Packaging Team - New upstream version 10.3.0-rc1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc1 * Thu Feb 28 2019 Jellyfin Packaging Team From f888c4b641218557265148c318074a227a79df9e Mon Sep 17 00:00:00 2001 From: Terror-Gene Date: Thu, 11 Apr 2019 03:19:05 +0930 Subject: [PATCH 072/131] Fix missing Unraid cache mount Cache folder was not mounted outside of the Docker image since its separation from the config folder. Config HostDir was only updated for consistency, previous directory was overridden by unraid into the appdata/appname folder anyway. Name capitalization was corrected as this is only used by new installations & does not affect current installations/updates. --- deployment/unraid/docker-templates/jellyfin.xml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/deployment/unraid/docker-templates/jellyfin.xml b/deployment/unraid/docker-templates/jellyfin.xml index 5a5b4bb5c7..eec9967bbe 100644 --- a/deployment/unraid/docker-templates/jellyfin.xml +++ b/deployment/unraid/docker-templates/jellyfin.xml @@ -3,14 +3,15 @@ https://raw.githubusercontent.com/jellyfin/jellyfin/deployment/unraid/docker-templates/jellyfin.xml False MediaApp:Video MediaApp:Music MediaApp:Photos MediaServer:Video MediaServer:Music MediaServer:Photos - JellyFin + Jellyfin - JellyFin is The Free Software Media Browser Converted By Community Applications Always verify this template (and values) against the dockerhub support page for the container!![br][br] + Jellyfin is The Free Software Media Browser Converted By Community Applications Always verify this template (and values) against the dockerhub support page for the container!![br][br] You can add as many mount points as needed for recordings, movies ,etc. [br][br] [b][span style='color: #E80000;']Directions:[/span][/b][br] - [b]/config[/b] : this is where Jellyfin will store it's databases and configuration.[br][br] + [b]/config[/b] : This is where Jellyfin will store it's databases and configuration.[br][br] [b]Port[/b] : This is the default port for Jellyfin. (Will add ssl port later)[br][br] - [b]Media[/b] : This is the mounting point of your media. When you access it in Jellyfin it will be /media or whatever you chose for a mount point + [b]Media[/b] : This is the mounting point of your media. When you access it in Jellyfin it will be /media or whatever you chose for a mount point[br][br] + [b]Cache[/b] : This is where Jellyfin will store and manage cached files like images to serve to clients. This is not where all images are stored.[br][br] [b]Tip:[/b] You can add more volume mappings if you wish Jellyfin has access to it. @@ -35,7 +36,7 @@ - /mnt/cache/appdata/config + /mnt/user/appdata/jellyfin /config rw @@ -44,6 +45,11 @@ /media rw + + /mnt/user/appdata/jellyfin/cache/ + /cache + rw + http://[IP]:[PORT:8096]/ https://raw.githubusercontent.com/binhex/docker-templates/master/binhex/images/jellyfin-icon.png From a9f790e101804a2ce0a85217c5c5874e0a7cdb91 Mon Sep 17 00:00:00 2001 From: Terror-Gene Date: Thu, 11 Apr 2019 04:00:46 +0930 Subject: [PATCH 073/131] Fix directory capitalization --- deployment/unraid/docker-templates/jellyfin.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/unraid/docker-templates/jellyfin.xml b/deployment/unraid/docker-templates/jellyfin.xml index eec9967bbe..57b4cc5ae1 100644 --- a/deployment/unraid/docker-templates/jellyfin.xml +++ b/deployment/unraid/docker-templates/jellyfin.xml @@ -36,7 +36,7 @@ - /mnt/user/appdata/jellyfin + /mnt/user/appdata/Jellyfin /config rw @@ -46,7 +46,7 @@ rw - /mnt/user/appdata/jellyfin/cache/ + /mnt/user/appdata/Jellyfin/cache/ /cache rw From 5f6ab836dea4341844f1e1e5a1e1d45c7c6621c1 Mon Sep 17 00:00:00 2001 From: VooDooS Date: Thu, 11 Apr 2019 15:35:06 +0200 Subject: [PATCH 074/131] Extend Microsoft.Net.Http.Headers.HeaderNames --- MediaBrowser.Common/Net/MoreHeaderNames.cs | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 MediaBrowser.Common/Net/MoreHeaderNames.cs diff --git a/MediaBrowser.Common/Net/MoreHeaderNames.cs b/MediaBrowser.Common/Net/MoreHeaderNames.cs new file mode 100644 index 0000000000..669646db1e --- /dev/null +++ b/MediaBrowser.Common/Net/MoreHeaderNames.cs @@ -0,0 +1,83 @@ +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 From a6e1b23eb04d620e241e4babbcb6ee0ffbf156a9 Mon Sep 17 00:00:00 2001 From: VooDooS Date: Thu, 11 Apr 2019 16:58:28 +0200 Subject: [PATCH 075/131] Simplify headers use in WSS --- .../SocketSharp/WebSocketSharpRequest.cs | 16 +++++---------- MediaBrowser.Model/Services/IHttpRequest.cs | 20 ------------------- 2 files changed, 5 insertions(+), 31 deletions(-) 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 /// From 56d1050bac3a56249acd7f3b3615f796683e0783 Mon Sep 17 00:00:00 2001 From: VooDooS Date: Thu, 11 Apr 2019 17:13:40 +0200 Subject: [PATCH 076/131] Replace custom ip "normalization" by methods from `IPAddress` --- .../SocketSharp/WebSocketSharpRequest.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) 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; From bb807554e2c33f066402898a3ff79913865d50e3 Mon Sep 17 00:00:00 2001 From: VooDooS Date: Thu, 11 Apr 2019 17:17:48 +0200 Subject: [PATCH 077/131] Replace CRLF injection mitigation by use of .NET ip parsing --- .../SocketSharp/WebSocketSharpRequest.cs | 95 +++---------------- 1 file changed, 11 insertions(+), 84 deletions(-) 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) + IPAddress ip; + + // "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)) { - return remoteIp = temp.ToString(); + if (!IPAddress.TryParse(GetHeader(HeaderNames.XRealIP), out ip)) + { + ip = request.HttpContext.Connection.RemoteIpAddress; + } } - temp = CheckBadChars(GetHeader(HeaderNames.XRealIP).AsSpan()); - if (temp.Length != 0) - { - return remoteIp = NormalizeIp(temp.ToString()).ToString(); - } - - return remoteIp = NormalizeIp(request.HttpContext.Connection.RemoteIpAddress).ToString(); + return remoteIp = NormalizeIp(ip).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; - } - - // 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", 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 (crlf != 0) - { - throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); - } - - 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; From ba12d96d23a53ce16a1da1b2fcf68a301050b858 Mon Sep 17 00:00:00 2001 From: VooDooS Date: Fri, 12 Apr 2019 15:25:18 +0200 Subject: [PATCH 078/131] 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 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 From b2f94c0e4015160ba3102cd6617397fc07c6ba35 Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Mon, 15 Apr 2019 00:21:14 -0400 Subject: [PATCH 079/131] 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(-) 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) From 65f9141764c18b76d97925e531816e8dd383f4bb Mon Sep 17 00:00:00 2001 From: DrPandemic Date: Wed, 10 Apr 2019 22:08:23 -0400 Subject: [PATCH 080/131] Update the help message to indicate the right output folder --- build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build b/build index 51d4b79a20..fb4ff1984a 100755 --- a/build +++ b/build @@ -29,7 +29,7 @@ usage() { echo -e "The web_branch defaults to the same branch name as the current main branch or can be 'local' to not touch the submodule branching." echo -e "To build all platforms, use 'all'." echo -e "To perform all build actions, use 'all'." - echo -e "Build output files are collected at '../jellyfin-build/'." + echo -e "Build output files are collected at '../bin/'." } # Show usage on stderr with exit 1 on argless From 34ab99caf18ee862a1dc29a6afb83253db35c219 Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Tue, 16 Apr 2019 01:16:02 -0400 Subject: [PATCH 081/131] 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(-) 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, From 2f0719a883580b547960904703a39748281bf5e0 Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Tue, 16 Apr 2019 01:38:00 -0400 Subject: [PATCH 082/131] Move the definition of ProductName to the correct class Missed moving this from one class to the other. --- MediaBrowser.Model/System/PublicSystemInfo.cs | 5 +++++ MediaBrowser.Model/System/SystemInfo.cs | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Model/System/PublicSystemInfo.cs b/MediaBrowser.Model/System/PublicSystemInfo.cs index d97eda3523..d6e031e427 100644 --- a/MediaBrowser.Model/System/PublicSystemInfo.cs +++ b/MediaBrowser.Model/System/PublicSystemInfo.cs @@ -25,6 +25,11 @@ namespace MediaBrowser.Model.System ///
/// The version. public string Version { get; set; } + + /// + /// The product name. This is the AssemblyProduct name. + /// + public string ProductName { get; set; } /// /// Gets or sets the operating system. diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 222c10798a..3f73cc4e0b 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -32,10 +32,6 @@ namespace MediaBrowser.Model.System /// The display name of the operating system. public string OperatingSystemDisplayName { get; set; } - /// - /// The product name. This is the AssemblyProduct name. - /// - public string ProductName { get; set; } /// /// Get or sets the package name. From c7fedfbca388d3e43ca266e7a4a6caccfc4bb5c7 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 17 Apr 2019 11:41:14 +0200 Subject: [PATCH 083/131] Fix metadata path save --- .../Configuration/ServerConfigurationManager.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) 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"); } /// From 250e0c75dfaebca54e93be6c11c70cb0d19e589a Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Wed, 17 Apr 2019 22:31:06 -0400 Subject: [PATCH 084/131] Add MethodNotAllowedException with code 405 --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 1 + 1 file changed, 1 insertion(+) 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; } From e790f024c2da2b3104ad698abfbd74fdf273bb9f Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Wed, 17 Apr 2019 22:31:17 -0400 Subject: [PATCH 085/131] Return MethodNotAllowedException if Pw is not set Don't accept pre-hashed (not-plaintext) passwords as the auth provider no longer supports this due to sha1+salting the passwords in the database. --- MediaBrowser.Api/UserService.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index a6849f75f5..0db62098ca 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -379,6 +379,11 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } + if (!request.Pw) + { + throw new MethodNotAllowedException("Hashed-only passwords are not valid for this API."); + } + return Post(new AuthenticateUserByName { Username = user.Name, From ca3bb308b3b20327ee96ea914cdaf02fa51374cd Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Wed, 17 Apr 2019 22:46:26 -0400 Subject: [PATCH 086/131] Add the proper Class too --- .../Extensions/ResourceNotFoundException.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs index f62c65fd7f..9f70ae7d89 100644 --- a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs +++ b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs @@ -26,6 +26,30 @@ namespace MediaBrowser.Common.Extensions } } + /// + /// Class MethodNotAllowedException + /// + public class MethodNotAllowedException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public MethodNotAllowedException() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The message. + public MethodNotAllowedException(string message) + : base(message) + { + + } + } + public class RemoteServiceUnavailableException : Exception { public RemoteServiceUnavailableException() From eaa1ac80133e766a1d3ab4e0f5a07bc48619cd44 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Wed, 17 Apr 2019 22:49:17 -0400 Subject: [PATCH 087/131] Apparently strings can't be !'d --- MediaBrowser.Api/UserService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 0db62098ca..119c423e60 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -379,7 +379,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } - if (!request.Pw) + if (request.Pw == "") { throw new MethodNotAllowedException("Hashed-only passwords are not valid for this API."); } From 8f703f4744b2701843e316210263c8e4cd3256bd Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 18 Apr 2019 13:19:16 +0200 Subject: [PATCH 088/131] Remove unused event Release builds were failing because of this unused event. --- .../Activity/ActivityLogEntryPoint.cs | 14 ------------- .../ApplicationHost.cs | 21 +++++++------------ MediaBrowser.Common/IApplicationHost.cs | 5 ----- 3 files changed, 8 insertions(+), 32 deletions(-) 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. /// From 10f33b027345193f91b91600473222797ae9bef5 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Thu, 18 Apr 2019 09:31:30 -0400 Subject: [PATCH 089/131] Update conditional to be correct --- MediaBrowser.Api/UserService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 119c423e60..7628a2f0fa 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -379,7 +379,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } - if (request.Pw == "") + if (!string.IsNullOrEmpty(request.Password) || string.IsNullOrEmpty(request.Pw)) { throw new MethodNotAllowedException("Hashed-only passwords are not valid for this API."); } From 31ad366aa93e8bc07f6e120320f3abd27d9dfd49 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Thu, 18 Apr 2019 10:24:08 -0400 Subject: [PATCH 090/131] Implemented suggested conditional --- MediaBrowser.Api/UserService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 7628a2f0fa..497800d263 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -379,7 +379,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } - if (!string.IsNullOrEmpty(request.Password) || string.IsNullOrEmpty(request.Pw)) + if (!string.IsNullOrEmpty(request.Password) && string.IsNullOrEmpty(request.Pw)) { throw new MethodNotAllowedException("Hashed-only passwords are not valid for this API."); } @@ -387,7 +387,7 @@ namespace MediaBrowser.Api return Post(new AuthenticateUserByName { Username = user.Name, - Password = request.Password, + Password = null, // This should always be null Pw = request.Pw }); } From 90c04a46403ba158e11c444c7109f5a56f12eff3 Mon Sep 17 00:00:00 2001 From: tinganhsu Date: Fri, 19 Apr 2019 04:32:51 +0000 Subject: [PATCH 091/131] 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 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 @@ +{} From ba684d6d3a4bfc9a0dc3f9de16a3b32bb95da1a1 Mon Sep 17 00:00:00 2001 From: tinganhsu Date: Fri, 19 Apr 2019 04:33:31 +0000 Subject: [PATCH 092/131] 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(-) 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}" +} From d6622818dca410f15764a94c2e19cf9dee1393a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Libor=20Fil=C3=ADpek?= Date: Sat, 13 Apr 2019 12:06:54 +0000 Subject: [PATCH 093/131] 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(-) 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}" } From d31f5229da7049a6cef56c745ae93168f7df24d5 Mon Sep 17 00:00:00 2001 From: Dan Johansen Date: Tue, 9 Apr 2019 07:39:06 +0000 Subject: [PATCH 094/131] 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(-) 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", From 395d2e4917f3c790bb14a1dd43e062b036d98b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=92=CE=B1=CF=83=CE=AF=CE=BB=CE=B7=CF=82=20=CE=9C=CE=BF?= =?UTF-8?q?=CF=85=CF=81=CE=B1=CF=84=CE=AF=CE=B4=CE=B7=CF=82?= Date: Thu, 18 Apr 2019 09:05:44 +0000 Subject: [PATCH 095/131] 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(-) 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": "Σύστημα", From 8e2827cc39388152ae3d86cf5ec57cc1b4b00753 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Tue, 2 Apr 2019 12:41:49 +0000 Subject: [PATCH 096/131] 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(-) 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", From 4886fc467c8cd07fbee2cfc43a2f2c6c71216da1 Mon Sep 17 00:00:00 2001 From: SaddFox Date: Tue, 16 Apr 2019 14:53:26 +0000 Subject: [PATCH 097/131] 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(-) 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}" } From 10cbdc8e8e624301c2edb1598491cd6493b1644a Mon Sep 17 00:00:00 2001 From: Heldenkrieger01 Date: Tue, 9 Apr 2019 17:16:35 +0000 Subject: [PATCH 098/131] 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(-) 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}" } From 4a6243096afa9215392c4b2671c84e76eaf0d0b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Libor=20Fil=C3=ADpek?= Date: Fri, 19 Apr 2019 17:30:29 +0000 Subject: [PATCH 099/131] 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(-) 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", From 9d60cc8c660c47df110d8118975410f86a5a7484 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 19 Apr 2019 13:59:04 -0400 Subject: [PATCH 100/131] Rename Chinese (Traditional) file --- .../Localization/Core/{zh_Hant.json => zh-TW.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Emby.Server.Implementations/Localization/Core/{zh_Hant.json => zh-TW.json} (100%) diff --git a/Emby.Server.Implementations/Localization/Core/zh_Hant.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json similarity index 100% rename from Emby.Server.Implementations/Localization/Core/zh_Hant.json rename to Emby.Server.Implementations/Localization/Core/zh-TW.json From 46c37c0ae8d5b5de02ff6f8208a7b91436f54237 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Fri, 19 Apr 2019 14:25:29 -0400 Subject: [PATCH 101/131] Bump version to 10.3.0 (release) --- Dockerfile | 2 +- Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- MediaBrowser.WebDashboard/jellyfin-web | 2 +- build.yaml | 2 +- deployment/debian-package-x64/pkg-src/changelog | 12 +++--------- deployment/fedora-package-x64/pkg-src/jellyfin.spec | 6 ++---- 7 files changed, 10 insertions(+), 18 deletions(-) diff --git a/Dockerfile b/Dockerfile index 36fc43983e..050f8fc49f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apt-get update \ COPY --from=ffmpeg / / COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.0-rc2 +ARG JELLYFIN_WEB_VERSION=10.3.0 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm b/Dockerfile.arm index c896d83f27..81089b5196 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -30,7 +30,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.0-rc2 +ARG JELLYFIN_WEB_VERSION=10.3.0 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 09c9dc12f8..1ad4dc5a28 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -31,7 +31,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.0-rc2 +ARG JELLYFIN_WEB_VERSION=10.3.0 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/MediaBrowser.WebDashboard/jellyfin-web b/MediaBrowser.WebDashboard/jellyfin-web index e07edea5bf..874b51234e 160000 --- a/MediaBrowser.WebDashboard/jellyfin-web +++ b/MediaBrowser.WebDashboard/jellyfin-web @@ -1 +1 @@ -Subproject commit e07edea5bf22c253fc7ee91f45879d8ee2d1bf17 +Subproject commit 874b51234ee4e1f01e2e7410980a1003f316d6a2 diff --git a/build.yaml b/build.yaml index ccd7ce8f85..47ca589f8b 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ --- # We just wrap `build` so this is really it name: "jellyfin" -version: "10.3.0-rc2" +version: "10.3.0" packages: - debian-package-x64 - debian-package-armhf diff --git a/deployment/debian-package-x64/pkg-src/changelog b/deployment/debian-package-x64/pkg-src/changelog index 65880620dc..3908c277b3 100644 --- a/deployment/debian-package-x64/pkg-src/changelog +++ b/deployment/debian-package-x64/pkg-src/changelog @@ -1,14 +1,8 @@ -jellyfin (10.3.0~rc2) unstable; urgency=medium +jellyfin (10.3.0-1) unstable; urgency=medium - * New upstream version 10.3.0-rc2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc2 + * New upstream version 10.3.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0 - -- Jellyfin Packaging Team Wed, 10 Apr 2019 00:51:14 -0400 - -jellyfin (10.3.0~rc1) unstable; urgency=medium - - * New upstream version 10.3.0-rc1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc1 - - -- Jellyfin Packaging Team Sat, 30 Mar 2019 15:47:24 -0400 + -- Jellyfin Packaging Team Fri, 19 Apr 2019 14:24:29 -0400 jellyfin (10.2.2-1) unstable; urgency=medium diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec index 581d926fbf..36fe78c628 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec @@ -140,10 +140,8 @@ fi %systemd_postun_with_restart jellyfin.service %changelog -* Wed Apr 10 2019 Jellyfin Packaging Team -- New upstream version 10.3.0-rc2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc2 -* Sat Mar 30 2019 Jellyfin Packaging Team -- New upstream version 10.3.0-rc1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc1 +* Fri Apr 19 2019 Jellyfin Packaging Team +- New upstream version 10.3.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0 * Thu Feb 28 2019 Jellyfin Packaging Team - jellyfin: - PR968 Release 10.2.z copr autobuild From f62af07381b633d8e7ddf5787d9048fbbf4e3c85 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 20 Apr 2019 12:18:44 +0200 Subject: [PATCH 102/131] Handle exception when loading unsupported assembly Fixes #1256 --- Emby.Server.Implementations/ApplicationHost.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) 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; } } From 6973182ade7af9173abaf835608327be30b6b162 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 20 Apr 2019 14:02:00 +0200 Subject: [PATCH 103/131] Fix more possible exceptions --- .../ApplicationHost.cs | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) 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; From 764c6d5461ff359f50ab76a60589d871545eec2a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 20 Apr 2019 17:42:11 +0200 Subject: [PATCH 104/131] Fix comparison for empty password migration --- Emby.Server.Implementations/Data/SqliteUserRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } From 5fb4922c6f0d01050bca109a2e066163da5863fa Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 20 Apr 2019 14:24:40 -0400 Subject: [PATCH 105/131] Bump version to 10.3.1 --- Dockerfile | 2 +- Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- SharedVersion.cs | 4 ++-- build.yaml | 2 +- deployment/debian-package-x64/pkg-src/changelog | 6 ++++++ deployment/fedora-package-x64/pkg-src/jellyfin.spec | 4 +++- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 050f8fc49f..3242003e16 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apt-get update \ COPY --from=ffmpeg / / COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.0 +ARG JELLYFIN_WEB_VERSION=10.3.1 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm b/Dockerfile.arm index 81089b5196..3cec9133f1 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -30,7 +30,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.0 +ARG JELLYFIN_WEB_VERSION=10.3.1 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 1ad4dc5a28..476ef2929d 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -31,7 +31,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.0 +ARG JELLYFIN_WEB_VERSION=10.3.1 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/SharedVersion.cs b/SharedVersion.cs index 3a0263bd67..9120f988cf 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.3.0")] -[assembly: AssemblyFileVersion("10.3.0")] +[assembly: AssemblyVersion("10.3.1")] +[assembly: AssemblyFileVersion("10.3.1")] diff --git a/build.yaml b/build.yaml index 47ca589f8b..0f843bf74a 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ --- # We just wrap `build` so this is really it name: "jellyfin" -version: "10.3.0" +version: "10.3.1" packages: - debian-package-x64 - debian-package-armhf diff --git a/deployment/debian-package-x64/pkg-src/changelog b/deployment/debian-package-x64/pkg-src/changelog index 3908c277b3..ce30ca6f6d 100644 --- a/deployment/debian-package-x64/pkg-src/changelog +++ b/deployment/debian-package-x64/pkg-src/changelog @@ -1,3 +1,9 @@ +jellyfin (10.3.1-1) unstable; urgency=medium + + * New upstream version 10.3.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.1 + + -- Jellyfin Packaging Team Sat, 20 Apr 2019 14:24:07 -0400 + jellyfin (10.3.0-1) unstable; urgency=medium * New upstream version 10.3.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0 diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec index 36fe78c628..0d938aef96 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec @@ -7,7 +7,7 @@ %endif Name: jellyfin -Version: 10.3.0 +Version: 10.3.1 Release: 1%{?dist} Summary: The Free Software Media Browser License: GPLv2 @@ -140,6 +140,8 @@ fi %systemd_postun_with_restart jellyfin.service %changelog +* Sat Apr 20 2019 Jellyfin Packaging Team +- New upstream version 10.3.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.1 * Fri Apr 19 2019 Jellyfin Packaging Team - New upstream version 10.3.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0 * Thu Feb 28 2019 Jellyfin Packaging Team From 696a36b4a5ada69f00875afa5e1ff1c596c7a371 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 20 Apr 2019 15:59:50 -0400 Subject: [PATCH 106/131] Update submodule for 10.3.1 --- MediaBrowser.WebDashboard/jellyfin-web | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.WebDashboard/jellyfin-web b/MediaBrowser.WebDashboard/jellyfin-web index 874b51234e..97f6808e12 160000 --- a/MediaBrowser.WebDashboard/jellyfin-web +++ b/MediaBrowser.WebDashboard/jellyfin-web @@ -1 +1 @@ -Subproject commit 874b51234ee4e1f01e2e7410980a1003f316d6a2 +Subproject commit 97f6808e12243bbb9b58344185511a9369913c0b From 08d3a5d2feafe9d54c354028f38d7cfa4e94293c Mon Sep 17 00:00:00 2001 From: bugfixin Date: Sun, 21 Apr 2019 19:29:05 +0000 Subject: [PATCH 107/131] 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(-) 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)); From 28c2ac528d46ba97b920d37300fa814bd6f4a51a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 24 Apr 2019 14:06:54 +0200 Subject: [PATCH 108/131] 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 ++- .../Services/HttpResult.cs | 5 ++ .../Services/ResponseHelper.cs | 79 ++++++++----------- .../BaseProgressiveStreamingService.cs | 53 ++++++++++++- 9 files changed, 113 insertions(+), 59 deletions(-) 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) + switch (result) { - return asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken); - } + 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; - var streamWriter = result as IStreamWriter; - if (streamWriter != null) - { - streamWriter.WriteTo(response.OutputStream); - return Task.CompletedTask; - } + if (bytes.Length > 0) + { + return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); + } - var fileWriter = result as FileWriter; - if (fileWriter != null) - { - return fileWriter.WriteToAsync(response, cancellationToken); - } + return Task.CompletedTask; + case string responseText: + var responseTextAsBytes = Encoding.UTF8.GetBytes(responseText); + response.OriginalResponse.ContentLength = responseTextAsBytes.Length; - var stream = result as Stream; - if (stream != null) - { - return CopyStream(stream, response.OutputStream); - } + if (responseTextAsBytes.Length > 0) + { + return response.OutputStream.WriteAsync(responseTextAsBytes, 0, responseTextAsBytes.Length, cancellationToken); + } - var bytes = result as byte[]; - if (bytes != null) - { - response.ContentType = "application/octet-stream"; - - if (bytes.Length > 0) - { - return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); - } - return Task.CompletedTask; - } - - 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; + } } } From a9337033c1d95d7238e9411abbc7255ef2456f35 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 24 Apr 2019 15:25:22 +0200 Subject: [PATCH 109/131] Fix query time logging --- .../Data/SqliteItemRepository.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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); From 71479286e9b35cd43b78037d551da13cc19e49bd Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 24 Apr 2019 19:38:14 +0200 Subject: [PATCH 110/131] Fix #1234 --- Emby.Server.Implementations/HttpServer/HttpResultFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } } From 844ea9d77ed24bb5b3bc5ec5b23df8a045933759 Mon Sep 17 00:00:00 2001 From: bugfixin Date: Thu, 25 Apr 2019 04:36:28 +0000 Subject: [PATCH 111/131] Don't coalesce empty strings to null in StringMapTypeDeserializer --- .../Services/StringMapTypeDeserializer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) { From a827a2fbccd0532631f4de849e18a9eeff772b5d Mon Sep 17 00:00:00 2001 From: bugfixin Date: Thu, 25 Apr 2019 19:14:33 +0000 Subject: [PATCH 112/131] Remove unreachable code and const trySupportSeek within BaseProgressiveStreamingService --- .../BaseProgressiveStreamingService.cs | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 1c36289c5b..83a3f3e3c6 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -283,8 +283,6 @@ namespace MediaBrowser.Api.Playback.Progressive { state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent); - const bool trySupportSeek = false; - var options = new HttpRequestOptions { Url = state.MediaPath, @@ -293,30 +291,9 @@ namespace MediaBrowser.Api.Playback.Progressive CancellationToken = cancellationTokenSource.Token }; - if (trySupportSeek) - { - if (!string.IsNullOrWhiteSpace(Request.QueryString[HeaderNames.Range])) - { - options.RequestHeaders[HeaderNames.Range] = Request.QueryString[HeaderNames.Range]; - } - } var response = await HttpClient.GetResponse(options).ConfigureAwait(false); - if (trySupportSeek) - { - foreach (var name in new[] { HeaderNames.ContentRange, HeaderNames.AcceptRanges }) - { - var val = response.Headers[name]; - if (!string.IsNullOrWhiteSpace(val)) - { - responseHeaders[name] = val; - } - } - } - else - { - responseHeaders[HeaderNames.AcceptRanges] = "none"; - } + responseHeaders[HeaderNames.AcceptRanges] = "none"; // Seeing cases of -1 here if (response.ContentLength.HasValue && response.ContentLength.Value >= 0) From 2b2a2ed70892e1e2ed55e59408df530fcdc01933 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Mon, 29 Apr 2019 00:56:17 -0400 Subject: [PATCH 113/131] Add arm64 packaging for Debuntu --- build.yaml | 2 + .../debian-package-arm64/Dockerfile.amd64 | 43 +++++++++++++++ .../debian-package-arm64/Dockerfile.arm64 | 34 ++++++++++++ deployment/debian-package-arm64/clean.sh | 29 ++++++++++ .../debian-package-arm64/dependencies.txt | 1 + .../debian-package-arm64/docker-build.sh | 20 +++++++ deployment/debian-package-arm64/package.sh | 42 +++++++++++++++ deployment/debian-package-arm64/pkg-src | 1 + deployment/debian-package-x64/pkg-src/rules | 13 +++-- .../ubuntu-package-arm64/Dockerfile.amd64 | 53 +++++++++++++++++++ .../ubuntu-package-arm64/Dockerfile.arm64 | 34 ++++++++++++ deployment/ubuntu-package-arm64/clean.sh | 29 ++++++++++ .../ubuntu-package-arm64/dependencies.txt | 1 + .../ubuntu-package-arm64/docker-build.sh | 20 +++++++ deployment/ubuntu-package-arm64/package.sh | 42 +++++++++++++++ deployment/ubuntu-package-arm64/pkg-src | 1 + 16 files changed, 362 insertions(+), 3 deletions(-) create mode 100644 deployment/debian-package-arm64/Dockerfile.amd64 create mode 100644 deployment/debian-package-arm64/Dockerfile.arm64 create mode 100755 deployment/debian-package-arm64/clean.sh create mode 100644 deployment/debian-package-arm64/dependencies.txt create mode 100755 deployment/debian-package-arm64/docker-build.sh create mode 100755 deployment/debian-package-arm64/package.sh create mode 120000 deployment/debian-package-arm64/pkg-src create mode 100644 deployment/ubuntu-package-arm64/Dockerfile.amd64 create mode 100644 deployment/ubuntu-package-arm64/Dockerfile.arm64 create mode 100755 deployment/ubuntu-package-arm64/clean.sh create mode 100644 deployment/ubuntu-package-arm64/dependencies.txt create mode 100755 deployment/ubuntu-package-arm64/docker-build.sh create mode 100755 deployment/ubuntu-package-arm64/package.sh create mode 120000 deployment/ubuntu-package-arm64/pkg-src diff --git a/build.yaml b/build.yaml index 0f843bf74a..d6bb106d32 100644 --- a/build.yaml +++ b/build.yaml @@ -5,8 +5,10 @@ version: "10.3.1" packages: - debian-package-x64 - debian-package-armhf + - debian-package-arm64 - ubuntu-package-x64 - ubuntu-package-armhf + - ubuntu-package-arm64 - fedora-package-x64 - centos-package-x64 - linux-x64 diff --git a/deployment/debian-package-arm64/Dockerfile.amd64 b/deployment/debian-package-arm64/Dockerfile.amd64 new file mode 100644 index 0000000000..2cb8bd856e --- /dev/null +++ b/deployment/debian-package-arm64/Dockerfile.amd64 @@ -0,0 +1,43 @@ +FROM debian:9 +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-arm64 +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV ARCH=amd64 + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4ced-81e6-286620b390ab/8ab938cf6f5e83b2221630354160ef21/dotnet-sdk-2.2.104-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + +# Prepare the cross-toolchain +RUN dpkg --add-architecture arm64 \ + && apt-get update \ + && apt-get install -y cross-gcc-dev \ + && TARGET_LIST="arm64" cross-gcc-gensource 6 \ + && cd cross-gcc-packages-amd64/cross-gcc-6-arm64 \ + && apt-get install -y gcc-6-source libstdc++6-arm64-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 libfreetype6-dev:arm64 liblttng-ust0:arm64 libstdc++6:arm64 + +# Link to docker-build script +RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh + +# Link to Debian source dir; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian + +VOLUME ${ARTIFACT_DIR}/ + +COPY . ${SOURCE_DIR}/ + +ENTRYPOINT ["/docker-build.sh"] +#ENTRYPOINT ["/bin/bash"] diff --git a/deployment/debian-package-arm64/Dockerfile.arm64 b/deployment/debian-package-arm64/Dockerfile.arm64 new file mode 100644 index 0000000000..bb9e28d0aa --- /dev/null +++ b/deployment/debian-package-arm64/Dockerfile.arm64 @@ -0,0 +1,34 @@ +FROM debian:9 +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-arm64 +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV ARCH=arm64 + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev liblttng-ust0 + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget https://download.visualstudio.microsoft.com/download/pr/d9f37b73-df8d-4dfa-a905-b7648d3401d0/6312573ac13d7a8ddc16e4058f7d7dc5/dotnet-sdk-2.2.104-linux-arm.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + +# Link to docker-build script +RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh + +# Link to Debian source dir; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian + +VOLUME ${ARTIFACT_DIR}/ + +COPY . ${SOURCE_DIR}/ + +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/debian-package-arm64/clean.sh b/deployment/debian-package-arm64/clean.sh new file mode 100755 index 0000000000..ac143c3d0b --- /dev/null +++ b/deployment/debian-package-arm64/clean.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +keep_artifacts="${1}" + +WORKDIR="$( pwd )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-debian_arm64-build" + +rm -rf "${package_temporary_dir}" &>/dev/null \ + || sudo rm -rf "${package_temporary_dir}" &>/dev/null + +rm -rf "${output_dir}" &>/dev/null \ + || sudo rm -rf "${output_dir}" &>/dev/null + +if [[ ${keep_artifacts} == 'n' ]]; then + docker_sudo="" + if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo=sudo + fi + ${docker_sudo} docker image rm ${image_name} --force +fi diff --git a/deployment/debian-package-arm64/dependencies.txt b/deployment/debian-package-arm64/dependencies.txt new file mode 100644 index 0000000000..bdb9670965 --- /dev/null +++ b/deployment/debian-package-arm64/dependencies.txt @@ -0,0 +1 @@ +docker diff --git a/deployment/debian-package-arm64/docker-build.sh b/deployment/debian-package-arm64/docker-build.sh new file mode 100755 index 0000000000..308f3df15b --- /dev/null +++ b/deployment/debian-package-arm64/docker-build.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Builds the DEB inside the Docker container + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image +sed -i '/dotnet-sdk-2.2,/d' debian/control + +# Build DEB +export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} +dpkg-buildpackage -us -uc -aarm64 + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/deb +mv /jellyfin_* ${ARTIFACT_DIR}/deb/ diff --git a/deployment/debian-package-arm64/package.sh b/deployment/debian-package-arm64/package.sh new file mode 100755 index 0000000000..19f70d7f68 --- /dev/null +++ b/deployment/debian-package-arm64/package.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +ARCH="$( arch )" +WORKDIR="$( pwd )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-debian_arm64-build" + +# Determine if sudo should be used for Docker +if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo="sudo" +else + docker_sudo="" +fi + +# Determine which Dockerfile to use +case $ARCH in + 'x86_64') + DOCKERFILE="Dockerfile.amd64" + ;; + 'armv7l') + DOCKERFILE="Dockerfile.arm64" + ;; +esac + +# Set up the build environment Docker image +${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} +# Build the DEBs and copy out to ${package_temporary_dir} +${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" +# Correct ownership on the DEBs (as current user, then as root if that fails) +chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ + || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null +# Move the DEBs to the output directory +mkdir -p "${output_dir}" +mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/debian-package-arm64/pkg-src b/deployment/debian-package-arm64/pkg-src new file mode 120000 index 0000000000..4c695fea17 --- /dev/null +++ b/deployment/debian-package-arm64/pkg-src @@ -0,0 +1 @@ +../debian-package-x64/pkg-src \ No newline at end of file diff --git a/deployment/debian-package-x64/pkg-src/rules b/deployment/debian-package-x64/pkg-src/rules index 62f75bc6b1..ee41e0e24b 100644 --- a/deployment/debian-package-x64/pkg-src/rules +++ b/deployment/debian-package-x64/pkg-src/rules @@ -6,18 +6,25 @@ SHELL := /bin/bash HOST_ARCH := $(shell arch) BUILD_ARCH := ${DEB_HOST_MULTIARCH} ifeq ($(HOST_ARCH),x86_64) + # Building AMD64 + DOTNETRUNTIME := debian-x64 ifeq ($(BUILD_ARCH),arm-linux-gnueabihf) # Cross-building ARM on AMD64 DOTNETRUNTIME := debian-arm - else - # Building AMD64 - DOTNETRUNTIME := debian-x64 + endif + ifeq ($(BUILD_ARCH),aarch64-linux-gnu) + # Cross-building ARM on AMD64 + DOTNETRUNTIME := debian-arm64 endif endif ifeq ($(HOST_ARCH),armv7l) # Building ARM DOTNETRUNTIME := debian-arm endif +ifeq ($(HOST_ARCH),arm64) + # Building ARM + DOTNETRUNTIME := debian-arm64 +endif export DH_VERBOSE=1 export DOTNET_CLI_TELEMETRY_OPTOUT=1 diff --git a/deployment/ubuntu-package-arm64/Dockerfile.amd64 b/deployment/ubuntu-package-arm64/Dockerfile.amd64 new file mode 100644 index 0000000000..5e51ef0f02 --- /dev/null +++ b/deployment/ubuntu-package-arm64/Dockerfile.amd64 @@ -0,0 +1,53 @@ +FROM ubuntu:bionic +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-arm64 +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV ARCH=amd64 + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4ced-81e6-286620b390ab/8ab938cf6f5e83b2221630354160ef21/dotnet-sdk-2.2.104-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + +# Prepare the cross-toolchain +RUN rm /etc/apt/sources.list \ + && export CODENAME="$( lsb_release -c -s )" \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ + && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ + && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ + && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ + && dpkg --add-architecture arm64 \ + && apt-get update \ + && apt-get install -y cross-gcc-dev \ + && TARGET_LIST="arm64" cross-gcc-gensource 6 \ + && cd cross-gcc-packages-amd64/cross-gcc-6-arm64 \ + && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \ + && apt-get install -y gcc-6-source libstdc++6-arm64-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 libfreetype6-dev:arm64 liblttng-ust0:arm64 libstdc++6:arm64 + +# Link to docker-build script +RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh + +# Link to Debian source dir; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian + +VOLUME ${ARTIFACT_DIR}/ + +COPY . ${SOURCE_DIR}/ + +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/ubuntu-package-arm64/Dockerfile.arm64 b/deployment/ubuntu-package-arm64/Dockerfile.arm64 new file mode 100644 index 0000000000..646679328c --- /dev/null +++ b/deployment/ubuntu-package-arm64/Dockerfile.arm64 @@ -0,0 +1,34 @@ +FROM ubuntu:bionic +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-arm64 +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV ARCH=arm64 + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev liblttng-ust0 + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget https://download.visualstudio.microsoft.com/download/pr/d9f37b73-df8d-4dfa-a905-b7648d3401d0/6312573ac13d7a8ddc16e4058f7d7dc5/dotnet-sdk-2.2.104-linux-arm.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + +# Link to docker-build script +RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh + +# Link to Debian source dir; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian + +VOLUME ${ARTIFACT_DIR}/ + +COPY . ${SOURCE_DIR}/ + +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/ubuntu-package-arm64/clean.sh b/deployment/ubuntu-package-arm64/clean.sh new file mode 100755 index 0000000000..c92c7fdec6 --- /dev/null +++ b/deployment/ubuntu-package-arm64/clean.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +keep_artifacts="${1}" + +WORKDIR="$( pwd )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-ubuntu-build" + +rm -rf "${package_temporary_dir}" &>/dev/null \ + || sudo rm -rf "${package_temporary_dir}" &>/dev/null + +rm -rf "${output_dir}" &>/dev/null \ + || sudo rm -rf "${output_dir}" &>/dev/null + +if [[ ${keep_artifacts} == 'n' ]]; then + docker_sudo="" + if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo=sudo + fi + ${docker_sudo} docker image rm ${image_name} --force +fi diff --git a/deployment/ubuntu-package-arm64/dependencies.txt b/deployment/ubuntu-package-arm64/dependencies.txt new file mode 100644 index 0000000000..bdb9670965 --- /dev/null +++ b/deployment/ubuntu-package-arm64/dependencies.txt @@ -0,0 +1 @@ +docker diff --git a/deployment/ubuntu-package-arm64/docker-build.sh b/deployment/ubuntu-package-arm64/docker-build.sh new file mode 100755 index 0000000000..308f3df15b --- /dev/null +++ b/deployment/ubuntu-package-arm64/docker-build.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Builds the DEB inside the Docker container + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image +sed -i '/dotnet-sdk-2.2,/d' debian/control + +# Build DEB +export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} +dpkg-buildpackage -us -uc -aarm64 + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/deb +mv /jellyfin_* ${ARTIFACT_DIR}/deb/ diff --git a/deployment/ubuntu-package-arm64/package.sh b/deployment/ubuntu-package-arm64/package.sh new file mode 100755 index 0000000000..54fc387509 --- /dev/null +++ b/deployment/ubuntu-package-arm64/package.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +ARCH="$( arch )" +WORKDIR="$( pwd )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-ubuntu_arm64-build" + +# Determine if sudo should be used for Docker +if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo="sudo" +else + docker_sudo="" +fi + +# Determine which Dockerfile to use +case $ARCH in + 'x86_64') + DOCKERFILE="Dockerfile.amd64" + ;; + 'armv7l') + DOCKERFILE="Dockerfile.arm64" + ;; +esac + +# Set up the build environment Docker image +${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} +# Build the DEBs and copy out to ${package_temporary_dir} +${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" +# Correct ownership on the DEBs (as current user, then as root if that fails) +chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ + || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null +# Move the DEBs to the output directory +mkdir -p "${output_dir}" +mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/ubuntu-package-arm64/pkg-src b/deployment/ubuntu-package-arm64/pkg-src new file mode 120000 index 0000000000..4c695fea17 --- /dev/null +++ b/deployment/ubuntu-package-arm64/pkg-src @@ -0,0 +1 @@ +../debian-package-x64/pkg-src \ No newline at end of file From c8a59c83439180ac2b4795d8d09009a30e696e93 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Mon, 29 Apr 2019 23:03:57 -0400 Subject: [PATCH 114/131] Support libssl1.1 for Ubuntu Disco --- deployment/debian-package-x64/pkg-src/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/debian-package-x64/pkg-src/control b/deployment/debian-package-x64/pkg-src/control index d96660590c..4422f0fda4 100644 --- a/deployment/debian-package-x64/pkg-src/control +++ b/deployment/debian-package-x64/pkg-src/control @@ -23,6 +23,6 @@ Depends: at, jellyfin-ffmpeg, libfontconfig1, libfreetype6, - libssl1.0.0 | libssl1.0.2 + libssl1.0.0 | libssl1.0.2 | libssl1.1 Description: Jellyfin is a home media server. It is built on top of other popular open source technologies such as Service Stack, jQuery, jQuery mobile, and Mono. It features a REST-based api with built-in documentation to facilitate client development. We also have client libraries for our api to enable rapid development. From 08ed52eb726f03098bdf3218ee9f9d568880ccd0 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 30 Apr 2019 20:08:59 +0200 Subject: [PATCH 115/131] Make the TvdbEpisodeProvider class Public --- MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs index 5474a7c398..302d40c6b7 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB /// /// Class RemoteEpisodeProvider /// - class TvdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder + public class TvdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder { private readonly IHttpClient _httpClient; private readonly ILogger _logger; From 1df73fdeba0aca5ff2835080659877f0a6722f17 Mon Sep 17 00:00:00 2001 From: bugfixin Date: Tue, 30 Apr 2019 19:16:53 +0000 Subject: [PATCH 116/131] Fix incorrect hasPassword flag when easy pin set --- Emby.Server.Implementations/Library/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 : From 682432f55a4cdb24fb2ca47ec4c8667014be2ad7 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 30 Apr 2019 22:18:40 +0200 Subject: [PATCH 117/131] Iterate over IEnumerable before disposing --- MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs | 2 +- MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index 3797f9039a..179e953f48 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -336,7 +336,7 @@ namespace MediaBrowser.Providers.Music } using (var subReader = reader.ReadSubtree()) { - return ParseReleaseList(subReader); + return ParseReleaseList(subReader).ToList(); } } default: diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs index 59280df897..728f7731af 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs @@ -110,7 +110,7 @@ namespace MediaBrowser.Providers.Music } using (var subReader = reader.ReadSubtree()) { - return ParseArtistList(subReader); + return ParseArtistList(subReader).ToList(); } } default: From 91cd7d2f6b3e9ce1212d9c29519aa2b23731b8e9 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 30 Apr 2019 23:35:39 +0200 Subject: [PATCH 118/131] Limit amount of ffmpeg processes extracting images at once --- .../Encoder/MediaEncoder.cs | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index b626600fa4..a8874b6d0c 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly int DefaultImageExtractionTimeoutMs; private readonly string StartupOptionFFmpegPath; - private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2); private readonly List _runningProcesses = new List(); private readonly ILocalizationManager _localization; @@ -582,19 +582,27 @@ namespace MediaBrowser.MediaEncoding.Encoder { bool ranToCompletion; - StartProcess(processWrapper); - - var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; - if (timeoutMs <= 0) + await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + try { - timeoutMs = DefaultImageExtractionTimeoutMs; + StartProcess(processWrapper); + + var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; + if (timeoutMs <= 0) + { + timeoutMs = DefaultImageExtractionTimeoutMs; + } + + ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false); + + if (!ranToCompletion) + { + StopProcess(processWrapper, 1000); + } } - - ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false); - - if (!ranToCompletion) + finally { - StopProcess(processWrapper, 1000); + _thumbnailResourcePool.Release(); } var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; @@ -625,7 +633,8 @@ namespace MediaBrowser.MediaEncoding.Encoder return time.ToString(@"hh\:mm\:ss\.fff", UsCulture); } - public async Task ExtractVideoImagesOnInterval(string[] inputFiles, + public async Task ExtractVideoImagesOnInterval( + string[] inputFiles, string container, MediaStream videoStream, MediaProtocol protocol, @@ -636,8 +645,6 @@ namespace MediaBrowser.MediaEncoding.Encoder int? maxWidth, CancellationToken cancellationToken) { - var resourcePool = _thumbnailResourcePool; - var inputArgument = GetInputArgument(inputFiles, protocol); var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(UsCulture); @@ -701,7 +708,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.LogInformation(process.StartInfo.FileName + " " + process.StartInfo.Arguments); - await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); bool ranToCompletion = false; @@ -742,7 +749,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } finally { - resourcePool.Release(); + _thumbnailResourcePool.Release(); } var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; From e8196fed7cdc43f83f666af477652a90f41b5961 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 30 Apr 2019 20:18:54 -0400 Subject: [PATCH 119/131] Bump version for 10.3.2 --- Dockerfile | 2 +- Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- MediaBrowser.WebDashboard/jellyfin-web | 2 +- SharedVersion.cs | 4 ++-- build.yaml | 2 +- deployment/debian-package-x64/pkg-src/changelog | 6 ++++++ deployment/fedora-package-x64/pkg-src/jellyfin.spec | 4 +++- 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3242003e16..f414995f79 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apt-get update \ COPY --from=ffmpeg / / COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.1 +ARG JELLYFIN_WEB_VERSION=10.3.2 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm b/Dockerfile.arm index 3cec9133f1..66f731354b 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -30,7 +30,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.1 +ARG JELLYFIN_WEB_VERSION=10.3.2 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 476ef2929d..690d4b6e7a 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -31,7 +31,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.1 +ARG JELLYFIN_WEB_VERSION=10.3.2 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/MediaBrowser.WebDashboard/jellyfin-web b/MediaBrowser.WebDashboard/jellyfin-web index 97f6808e12..1ba58b06b3 160000 --- a/MediaBrowser.WebDashboard/jellyfin-web +++ b/MediaBrowser.WebDashboard/jellyfin-web @@ -1 +1 @@ -Subproject commit 97f6808e12243bbb9b58344185511a9369913c0b +Subproject commit 1ba58b06b3dc28e07abae124cff78aa656fcb7e7 diff --git a/SharedVersion.cs b/SharedVersion.cs index 9120f988cf..700ef45491 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.3.1")] -[assembly: AssemblyFileVersion("10.3.1")] +[assembly: AssemblyVersion("10.3.2")] +[assembly: AssemblyFileVersion("10.3.2")] diff --git a/build.yaml b/build.yaml index d6bb106d32..8ce6a00a80 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ --- # We just wrap `build` so this is really it name: "jellyfin" -version: "10.3.1" +version: "10.3.2" packages: - debian-package-x64 - debian-package-armhf diff --git a/deployment/debian-package-x64/pkg-src/changelog b/deployment/debian-package-x64/pkg-src/changelog index ce30ca6f6d..61977c4e08 100644 --- a/deployment/debian-package-x64/pkg-src/changelog +++ b/deployment/debian-package-x64/pkg-src/changelog @@ -1,3 +1,9 @@ +jellyfin (10.3.2-1) unstable; urgency=medium + + * New upstream version 10.3.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.2 + + -- Jellyfin Packaging Team Tue, 30 Apr 2019 20:18:44 -0400 + jellyfin (10.3.1-1) unstable; urgency=medium * New upstream version 10.3.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.1 diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec index 0d938aef96..58d6435696 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec @@ -7,7 +7,7 @@ %endif Name: jellyfin -Version: 10.3.1 +Version: 10.3.2 Release: 1%{?dist} Summary: The Free Software Media Browser License: GPLv2 @@ -140,6 +140,8 @@ fi %systemd_postun_with_restart jellyfin.service %changelog +* Tue Apr 30 2019 Jellyfin Packaging Team +- New upstream version 10.3.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.2 * Sat Apr 20 2019 Jellyfin Packaging Team - New upstream version 10.3.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.1 * Fri Apr 19 2019 Jellyfin Packaging Team From c1daea0ec7c07675b8a4c3f038be69d94a36a794 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 1 May 2019 07:47:22 +0200 Subject: [PATCH 120/131] Change owner and parent id of extras to the main media item --- MediaBrowser.Controller/Entities/BaseItem.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index e20641c99a..b05e97868f 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1481,7 +1481,10 @@ namespace MediaBrowser.Controller.Entities private async Task RefreshExtras(BaseItem item, MetadataRefreshOptions options, List fileSystemChildren, CancellationToken cancellationToken) { - var newExtras = LoadExtras(fileSystemChildren, options.DirectoryService).Concat(LoadThemeVideos(fileSystemChildren, options.DirectoryService)).Concat(LoadThemeSongs(fileSystemChildren, options.DirectoryService)); + var newExtras = LoadExtras(fileSystemChildren, options.DirectoryService) + .Concat(LoadThemeVideos(fileSystemChildren, options.DirectoryService)) + .Concat(LoadThemeSongs(fileSystemChildren, options.DirectoryService)) + .ToArray(); var newExtraIds = newExtras.Select(i => i.Id).ToArray(); @@ -1493,7 +1496,17 @@ namespace MediaBrowser.Controller.Entities var tasks = newExtras.Select(i => { - return RefreshMetadataForOwnedItem(i, true, new MetadataRefreshOptions(options), cancellationToken); + var subOptions = new MetadataRefreshOptions(options); + if (!i.ExtraType.HasValue || + i.OwnerId != ownerId || + !i.ParentId.Equals(Guid.Empty)) + { + i.OwnerId = ownerId; + i.ParentId = Guid.Empty; + subOptions.ForceSave = true; + } + + return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken); }); await Task.WhenAll(tasks).ConfigureAwait(false); From 3634d367c1809a2ff53b0583bd19b87e860d966e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Thu, 25 Apr 2019 05:33:17 +0200 Subject: [PATCH 121/131] Move artifact chown inside docker to avoid sudo --- deployment/centos-package-x64/docker-build.sh | 1 + deployment/centos-package-x64/package.sh | 3 --- deployment/debian-package-arm64/docker-build.sh | 1 + deployment/debian-package-arm64/package.sh | 5 ++--- deployment/debian-package-armhf/docker-build.sh | 1 + deployment/debian-package-armhf/package.sh | 5 ++--- deployment/debian-package-x64/docker-build.sh | 1 + deployment/debian-package-x64/package.sh | 5 ++--- deployment/fedora-package-x64/docker-build.sh | 1 + deployment/fedora-package-x64/package.sh | 5 ++--- deployment/ubuntu-package-arm64/docker-build.sh | 1 + deployment/ubuntu-package-arm64/package.sh | 5 ++--- deployment/ubuntu-package-armhf/docker-build.sh | 1 + deployment/ubuntu-package-armhf/package.sh | 5 ++--- deployment/ubuntu-package-x64/docker-build.sh | 1 + deployment/ubuntu-package-x64/package.sh | 5 ++--- 16 files changed, 22 insertions(+), 24 deletions(-) diff --git a/deployment/centos-package-x64/docker-build.sh b/deployment/centos-package-x64/docker-build.sh index 3acf1ec0df..cefb1652e9 100755 --- a/deployment/centos-package-x64/docker-build.sh +++ b/deployment/centos-package-x64/docker-build.sh @@ -18,3 +18,4 @@ rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg- # Move the artifacts out mkdir -p ${ARTIFACT_DIR}/rpm mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/ +chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/centos-package-x64/package.sh b/deployment/centos-package-x64/package.sh index 27d686e46f..df5a665808 100755 --- a/deployment/centos-package-x64/package.sh +++ b/deployment/centos-package-x64/package.sh @@ -72,9 +72,6 @@ fi ${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile # Build the RPMs and copy out to ${package_temporary_dir} ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" -# Correct ownership on the RPMs (as current user, then as root if that fails) -chown -R "${current_user}" "${package_temporary_dir}" \ - || sudo chown -R "${current_user}" "${package_temporary_dir}" # Move the RPMs to the output directory mkdir -p "${output_dir}" mv "${package_temporary_dir}"/rpm/* "${output_dir}" diff --git a/deployment/debian-package-arm64/docker-build.sh b/deployment/debian-package-arm64/docker-build.sh index 308f3df15b..cee96e1369 100755 --- a/deployment/debian-package-arm64/docker-build.sh +++ b/deployment/debian-package-arm64/docker-build.sh @@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarm64 # Move the artifacts out mkdir -p ${ARTIFACT_DIR}/deb mv /jellyfin_* ${ARTIFACT_DIR}/deb/ +chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/debian-package-arm64/package.sh b/deployment/debian-package-arm64/package.sh index 19f70d7f68..ce02b1af53 100755 --- a/deployment/debian-package-arm64/package.sh +++ b/deployment/debian-package-arm64/package.sh @@ -30,13 +30,12 @@ case $ARCH in ;; esac +# Prepare temporary package dir +mkdir -p "${package_temporary_dir}" # Set up the build environment Docker image ${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} # Build the DEBs and copy out to ${package_temporary_dir} ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" -# Correct ownership on the DEBs (as current user, then as root if that fails) -chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ - || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null # Move the DEBs to the output directory mkdir -p "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/debian-package-armhf/docker-build.sh b/deployment/debian-package-armhf/docker-build.sh index 45e68f0c6b..56227b5880 100755 --- a/deployment/debian-package-armhf/docker-build.sh +++ b/deployment/debian-package-armhf/docker-build.sh @@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarmhf # Move the artifacts out mkdir -p ${ARTIFACT_DIR}/deb mv /jellyfin_* ${ARTIFACT_DIR}/deb/ +chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/debian-package-armhf/package.sh b/deployment/debian-package-armhf/package.sh index 0ec0dc95cf..4393fb8340 100755 --- a/deployment/debian-package-armhf/package.sh +++ b/deployment/debian-package-armhf/package.sh @@ -30,13 +30,12 @@ case $ARCH in ;; esac +# Prepare temporary package dir +mkdir -p "${package_temporary_dir}" # Set up the build environment Docker image ${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} # Build the DEBs and copy out to ${package_temporary_dir} ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" -# Correct ownership on the DEBs (as current user, then as root if that fails) -chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ - || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null # Move the DEBs to the output directory mkdir -p "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/debian-package-x64/docker-build.sh b/deployment/debian-package-x64/docker-build.sh index 0590be0972..07f726dcc8 100755 --- a/deployment/debian-package-x64/docker-build.sh +++ b/deployment/debian-package-x64/docker-build.sh @@ -17,3 +17,4 @@ dpkg-buildpackage -us -uc # Move the artifacts out mkdir -p ${ARTIFACT_DIR}/deb mv /jellyfin_* ${ARTIFACT_DIR}/deb/ +chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/debian-package-x64/package.sh b/deployment/debian-package-x64/package.sh index d7c3f5809c..2530e253bd 100755 --- a/deployment/debian-package-x64/package.sh +++ b/deployment/debian-package-x64/package.sh @@ -19,13 +19,12 @@ else docker_sudo="" fi +# Prepare temporary package dir +mkdir -p "${package_temporary_dir}" # Set up the build environment Docker image ${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile # Build the DEBs and copy out to ${package_temporary_dir} ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" -# Correct ownership on the DEBs (as current user, then as root if that fails) -chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ - || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null # Move the DEBs to the output directory mkdir -p "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/fedora-package-x64/docker-build.sh b/deployment/fedora-package-x64/docker-build.sh index 3acf1ec0df..cefb1652e9 100755 --- a/deployment/fedora-package-x64/docker-build.sh +++ b/deployment/fedora-package-x64/docker-build.sh @@ -18,3 +18,4 @@ rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg- # Move the artifacts out mkdir -p ${ARTIFACT_DIR}/rpm mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/ +chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/fedora-package-x64/package.sh b/deployment/fedora-package-x64/package.sh index eed29aef3c..e659ee5e9e 100755 --- a/deployment/fedora-package-x64/package.sh +++ b/deployment/fedora-package-x64/package.sh @@ -23,13 +23,12 @@ fi ./create_tarball.sh +# Prepare temporary package dir +mkdir -p "${package_temporary_dir}" # Set up the build environment Docker image ${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile # Build the RPMs and copy out to ${package_temporary_dir} ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" -# Correct ownership on the RPMs (as current user, then as root if that fails) -chown -R "${current_user}" "${package_temporary_dir}" \ - || sudo chown -R "${current_user}" "${package_temporary_dir}" # Move the RPMs to the output directory mkdir -p "${output_dir}" mv "${package_temporary_dir}"/rpm/* "${output_dir}" diff --git a/deployment/ubuntu-package-arm64/docker-build.sh b/deployment/ubuntu-package-arm64/docker-build.sh index 308f3df15b..cee96e1369 100755 --- a/deployment/ubuntu-package-arm64/docker-build.sh +++ b/deployment/ubuntu-package-arm64/docker-build.sh @@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarm64 # Move the artifacts out mkdir -p ${ARTIFACT_DIR}/deb mv /jellyfin_* ${ARTIFACT_DIR}/deb/ +chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/ubuntu-package-arm64/package.sh b/deployment/ubuntu-package-arm64/package.sh index 54fc387509..5a2bf61c86 100755 --- a/deployment/ubuntu-package-arm64/package.sh +++ b/deployment/ubuntu-package-arm64/package.sh @@ -30,13 +30,12 @@ case $ARCH in ;; esac +# Prepare temporary package dir +mkdir -p "${package_temporary_dir}" # Set up the build environment Docker image ${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} # Build the DEBs and copy out to ${package_temporary_dir} ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" -# Correct ownership on the DEBs (as current user, then as root if that fails) -chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ - || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null # Move the DEBs to the output directory mkdir -p "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/ubuntu-package-armhf/docker-build.sh b/deployment/ubuntu-package-armhf/docker-build.sh index 45e68f0c6b..56227b5880 100755 --- a/deployment/ubuntu-package-armhf/docker-build.sh +++ b/deployment/ubuntu-package-armhf/docker-build.sh @@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarmhf # Move the artifacts out mkdir -p ${ARTIFACT_DIR}/deb mv /jellyfin_* ${ARTIFACT_DIR}/deb/ +chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/ubuntu-package-armhf/package.sh b/deployment/ubuntu-package-armhf/package.sh index fb03652cd5..15f55bff20 100755 --- a/deployment/ubuntu-package-armhf/package.sh +++ b/deployment/ubuntu-package-armhf/package.sh @@ -30,13 +30,12 @@ case $ARCH in ;; esac +# Prepare temporary package dir +mkdir -p "${package_temporary_dir}" # Set up the build environment Docker image ${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} # Build the DEBs and copy out to ${package_temporary_dir} ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" -# Correct ownership on the DEBs (as current user, then as root if that fails) -chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ - || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null # Move the DEBs to the output directory mkdir -p "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/ubuntu-package-x64/docker-build.sh b/deployment/ubuntu-package-x64/docker-build.sh index 0590be0972..07f726dcc8 100755 --- a/deployment/ubuntu-package-x64/docker-build.sh +++ b/deployment/ubuntu-package-x64/docker-build.sh @@ -17,3 +17,4 @@ dpkg-buildpackage -us -uc # Move the artifacts out mkdir -p ${ARTIFACT_DIR}/deb mv /jellyfin_* ${ARTIFACT_DIR}/deb/ +chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/ubuntu-package-x64/package.sh b/deployment/ubuntu-package-x64/package.sh index 6d4625a197..32e6d4fd65 100755 --- a/deployment/ubuntu-package-x64/package.sh +++ b/deployment/ubuntu-package-x64/package.sh @@ -19,13 +19,12 @@ else docker_sudo="" fi +# Prepare temporary package dir +mkdir -p "${package_temporary_dir}" # Set up the build environment Docker image ${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile # Build the DEBs and copy out to ${package_temporary_dir} ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" -# Correct ownership on the DEBs (as current user, then as root if that fails) -chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ - || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null # Move the DEBs to the output directory mkdir -p "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}" From b8a09339cd7c14ce4806463c4d62b80f760d93aa Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 2 May 2019 08:14:00 +0200 Subject: [PATCH 122/131] Enforce extras folder structure according to Emby's wiki --- MediaBrowser.Controller/Entities/BaseItem.cs | 67 ++++++++++++-------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index b05e97868f..1c6902b73f 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -82,6 +82,21 @@ namespace MediaBrowser.Controller.Entities public static string ThemeSongsFolderName = "theme-music"; public static string ThemeSongFilename = "theme"; public static string ThemeVideosFolderName = "backdrops"; + public static string ExtrasFolderName = "extras"; + public static string BehindTheScenesFolderName = "behind the scenes"; + public static string DeletedScenesFolderName = "deleted scenes"; + public static string InterviewFolderName = "interviews"; + public static string SceneFolderName = "scenes"; + public static string SampleFolderName = "samples"; + + public static string[] AllExtrasTypesFolderNames = { + ExtrasFolderName, + BehindTheScenesFolderName, + DeletedScenesFolderName, + InterviewFolderName, + SceneFolderName, + SampleFolderName + }; [IgnoreDataMember] public Guid[] ThemeSongIds { get; set; } @@ -1276,16 +1291,15 @@ namespace MediaBrowser.Controller.Entities .Select(item => { // Try to retrieve it from the db. If we don't find it, use the resolved version - var dbItem = LibraryManager.GetItemById(item.Id) as Video; - if (dbItem != null) + if (LibraryManager.GetItemById(item.Id) is Video dbItem) { item = dbItem; } else { // item is new - item.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo; + item.ExtraType = Model.Entities.ExtraType.ThemeVideo; } return item; @@ -1296,33 +1310,37 @@ namespace MediaBrowser.Controller.Entities protected virtual BaseItem[] LoadExtras(List fileSystemChildren, IDirectoryService directoryService) { - var files = fileSystemChildren.Where(i => i.IsDirectory) - .SelectMany(i => FileSystem.GetFiles(i.FullName)); + var extras = new List