aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs5
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs13
-rw-r--r--Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs2
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs20
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs8
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs8
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs50
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs3
-rw-r--r--MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs63
9 files changed, 113 insertions, 59 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index eaea8844d..484942946 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -770,7 +770,7 @@ namespace Emby.Server.Implementations
var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager);
serviceCollection.AddSingleton<IDisplayPreferencesRepository>(displayPreferencesRepo);
- ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory);
+ ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, LocalizationManager);
serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
AuthenticationRepository = GetAuthenticationRepository();
@@ -870,7 +870,8 @@ namespace Emby.Server.Implementations
() => SubtitleEncoder,
() => MediaSourceManager,
ProcessFactory,
- 5000);
+ 5000,
+ LocalizationManager);
serviceCollection.AddSingleton(MediaEncoder);
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 3ae63279b..088a6694b 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -22,6 +22,7 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
@@ -55,6 +56,8 @@ namespace Emby.Server.Implementations.Data
private readonly IServerConfigurationManager _config;
private IServerApplicationHost _appHost;
+ private readonly ILocalizationManager _localization;
+
public IImageProcessor ImageProcessor { get; set; }
/// <summary>
@@ -64,7 +67,8 @@ namespace Emby.Server.Implementations.Data
IServerConfigurationManager config,
IServerApplicationHost appHost,
IJsonSerializer jsonSerializer,
- ILoggerFactory loggerFactory)
+ ILoggerFactory loggerFactory,
+ ILocalizationManager localization)
: base(loggerFactory.CreateLogger(nameof(SqliteItemRepository)))
{
if (config == null)
@@ -81,6 +85,7 @@ namespace Emby.Server.Implementations.Data
_config = config;
_jsonSerializer = jsonSerializer;
_typeMapper = new TypeMapper();
+ _localization = localization;
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
}
@@ -6187,6 +6192,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
item.ColorTransfer = reader[34].ToString();
}
+ if (item.Type == MediaStreamType.Subtitle){
+ item.localizedUndefined = _localization.GetLocalizedString("Undefined");
+ item.localizedDefault = _localization.GetLocalizedString("Default");
+ item.localizedForced = _localization.GetLocalizedString("Forced");
+ }
+
return item;
}
}
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
index 3ec1f81d3..3d15a8afb 100644
--- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library
PasswordHash readyHash = new PasswordHash(resolvedUser.Password);
byte[] calculatedHash;
string calculatedHashString;
- if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id))
+ if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
{
if (string.IsNullOrEmpty(readyHash.Salt))
{
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index efb1ef4a5..4cf703add 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -216,10 +216,10 @@ namespace Emby.Server.Implementations.Library
public static bool IsValidUsername(string username)
{
- //This is some regex that matches only on unicode "word" characters, as well as -, _ and @
- //In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
- // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)
- return Regex.IsMatch(username, "^[\\w-'._@]*$");
+ // This is some regex that matches only on unicode "word" characters, as well as -, _ and @
+ // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
+ // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.)
+ return Regex.IsMatch(username, @"^[\w\-'._@]*$");
}
private static bool IsValidUsernameCharacter(char i)
@@ -448,11 +448,19 @@ namespace Emby.Server.Implementations.Library
user.Policy.InvalidLoginAttemptCount = newValue;
- var maxCount = user.Policy.IsAdministrator ? 3 : 5;
+ // Check for users without a value here and then fill in the default value
+ // also protect from an always lockout if misconfigured
+ if (user.Policy.LoginAttemptsBeforeLockout == null || user.Policy.LoginAttemptsBeforeLockout == 0)
+ {
+ user.Policy.LoginAttemptsBeforeLockout = user.Policy.IsAdministrator ? 5 : 3;
+ }
+
+ var maxCount = user.Policy.LoginAttemptsBeforeLockout;
var fireLockout = false;
- if (newValue >= maxCount)
+ // -1 can be used to specify no lockout value
+ if (maxCount != -1 && newValue >= maxCount)
{
_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue);
user.Policy.IsDisabled = true;
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index f454c2723..4867c0f85 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -16,6 +16,7 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
@@ -54,6 +55,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
+ private readonly ILocalizationManager _localization;
public MediaEncoder(
ILoggerFactory loggerFactory,
@@ -64,7 +66,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
Func<ISubtitleEncoder> subtitleEncoder,
Func<IMediaSourceManager> mediaSourceManager,
IProcessFactory processFactory,
- int defaultImageExtractionTimeoutMs)
+ int defaultImageExtractionTimeoutMs,
+ ILocalizationManager localization)
{
_logger = loggerFactory.CreateLogger(nameof(MediaEncoder));
_jsonSerializer = jsonSerializer;
@@ -74,6 +77,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
SubtitleEncoder = subtitleEncoder;
_processFactory = processFactory;
DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
+ _localization = localization;
}
/// <summary>
@@ -413,7 +417,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- return new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
+ return new ProbeResultNormalizer(_logger, FileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
}
}
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 2cca410a8..54d02fc9f 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -9,6 +9,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
@@ -20,11 +21,13 @@ namespace MediaBrowser.MediaEncoding.Probing
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
+ private readonly ILocalizationManager _localization;
- public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem)
+ public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, ILocalizationManager localization)
{
_logger = logger;
_fileSystem = fileSystem;
+ _localization = localization;
}
public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType? videoType, bool isAudio, string path, MediaProtocol protocol)
@@ -596,6 +599,9 @@ namespace MediaBrowser.MediaEncoding.Probing
{
stream.Type = MediaStreamType.Subtitle;
stream.Codec = NormalizeSubtitleCodec(stream.Codec);
+ stream.localizedUndefined = _localization.GetLocalizedString("Undefined");
+ stream.localizedDefault = _localization.GetLocalizedString("Default");
+ stream.localizedForced = _localization.GetLocalizedString("Forced");
}
else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index fc346df37..5652962da 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
+using System.Text;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
@@ -65,6 +67,10 @@ namespace MediaBrowser.Model.Entities
}
}
+ public string localizedUndefined { get; set; }
+ public string localizedDefault { get; set; }
+ public string localizedForced { get; set; }
+
public string DisplayTitle
{
get
@@ -128,10 +134,6 @@ namespace MediaBrowser.Model.Entities
if (Type == MediaStreamType.Subtitle)
{
- //if (!string.IsNullOrEmpty(Title))
- //{
- // return AddLanguageIfNeeded(Title);
- //}
var attributes = new List<string>();
@@ -141,22 +143,30 @@ namespace MediaBrowser.Model.Entities
}
else
{
- attributes.Add("Und");
+ attributes.Add(string.IsNullOrEmpty(localizedUndefined) ? "Und" : localizedUndefined);
}
if (IsDefault)
{
- attributes.Add("Default");
+ attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault);
}
if (IsForced)
{
- attributes.Add("Forced");
+ attributes.Add(string.IsNullOrEmpty(localizedForced) ? "Forced" : localizedForced);
}
- string name = string.Join(" ", attributes.ToArray());
+ if (!string.IsNullOrEmpty(Title))
+ {
+ return attributes.AsEnumerable()
+ // keep Tags that are not already in Title
+ .Where(tag => Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1)
+ // attributes concatenation, starting with Title
+ .Aggregate(new StringBuilder(Title), (builder, attr) => builder.Append(" - ").Append(attr))
+ .ToString();
+ }
- return name;
+ return string.Join(" - ", attributes.ToArray());
}
if (Type == MediaStreamType.Video)
@@ -220,28 +230,6 @@ namespace MediaBrowser.Model.Entities
return null;
}
- private string AddLanguageIfNeeded(string title)
- {
- if (!string.IsNullOrEmpty(Language) &&
- !string.Equals(Language, "und", StringComparison.OrdinalIgnoreCase) &&
- !IsLanguageInTitle(title, Language))
- {
- title = StringHelper.FirstToUpper(Language) + " " + title;
- }
-
- return title;
- }
-
- private bool IsLanguageInTitle(string title, string language)
- {
- if (title.IndexOf(Language, StringComparison.OrdinalIgnoreCase) != -1)
- {
- return true;
- }
-
- return false;
- }
-
public string NalLengthSize { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index 27ce23778..5415fd5e8 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -66,6 +66,7 @@ namespace MediaBrowser.Model.Users
public bool EnableAllFolders { get; set; }
public int InvalidLoginAttemptCount { get; set; }
+ public int? LoginAttemptsBeforeLockout { get; set; }
public bool EnablePublicSharing { get; set; }
@@ -104,6 +105,8 @@ namespace MediaBrowser.Model.Users
AccessSchedules = Array.Empty<AccessSchedule>();
+ LoginAttemptsBeforeLockout = -1;
+
EnableAllChannels = true;
EnabledChannels = Array.Empty<string>();
diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
index b716f40f0..3797f9039 100644
--- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
@@ -32,12 +32,21 @@ namespace MediaBrowser.Providers.Music
public readonly string MusicBrainzBaseUrl;
- // The Jellyfin user-agent is unrestricted but source IP must not exceed
- // one request per second, therefore we rate limit to avoid throttling.
- // Be prudent, use a value slightly above the minimun required.
- // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
+ /// <summary>
+ /// The Jellyfin user-agent is unrestricted but source IP must not exceed
+ /// one request per second, therefore we rate limit to avoid throttling.
+ /// Be prudent, use a value slightly above the minimun required.
+ /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
+ /// </summary>
private const long MusicBrainzQueryIntervalMs = 1050u;
+ /// <summary>
+ /// For each single MB lookup/search, this is the maximum number of
+ /// attempts that shall be made whilst receiving a 503 Server
+ /// Unavailable (indicating throttled) response.
+ /// </summary>
+ private const uint MusicBrainzQueryAttempts = 5u;
+
public MusicBrainzAlbumProvider(
IHttpClient httpClient,
IApplicationHost appHost,
@@ -717,19 +726,12 @@ namespace MediaBrowser.Providers.Music
/// <summary>
/// Makes request to MusicBrainz server and awaits a response.
+ /// A 503 Service Unavailable response indicates throttling to maintain a rate limit.
+ /// A number of retries shall be made in order to try and satisfy the request before
+ /// giving up and returning null.
/// </summary>
internal async Task<HttpResponseInfo> GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
{
- if (_stopWatchMusicBrainz.ElapsedMilliseconds < MusicBrainzQueryIntervalMs)
- {
- // MusicBrainz is extremely adamant about limiting to one request per second
- var delayMs = MusicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
- await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false);
- }
-
- _logger.LogDebug("MusicBrainz time since previous request: {0}ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
- _stopWatchMusicBrainz.Restart();
-
var options = new HttpRequestOptions
{
Url = MusicBrainzBaseUrl.TrimEnd('/') + url,
@@ -740,7 +742,38 @@ namespace MediaBrowser.Providers.Music
BufferContent = false
};
- return await _httpClient.SendAsync(options, "GET").ConfigureAwait(false);
+ HttpResponseInfo response;
+ var attempts = 0u;
+
+ do
+ {
+ attempts++;
+
+ if (_stopWatchMusicBrainz.ElapsedMilliseconds < MusicBrainzQueryIntervalMs)
+ {
+ // MusicBrainz is extremely adamant about limiting to one request per second
+ var delayMs = MusicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
+ await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false);
+ }
+
+ // Write time since last request to debug log as evidence we're meeting rate limit
+ // requirement, before resetting stopwatch back to zero.
+ _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
+ _stopWatchMusicBrainz.Restart();
+
+ response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false);
+
+ // We retry a finite number of times, and only whilst MB is indcating 503 (throttling)
+ }
+ while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable);
+
+ // Log error if unable to query MB database due to throttling
+ if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable )
+ {
+ _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.Url);
+ }
+
+ return response;
}
public int Order => 0;