diff options
53 files changed, 227 insertions, 264 deletions
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index b7cd91a5c..486109304 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -340,10 +340,19 @@ namespace Emby.Dlna.PlayTo } var playlist = new PlaylistItem[len]; - playlist[0] = CreatePlaylistItem(items[0], user, command.StartPositionTicks.Value, command.MediaSourceId, command.AudioStreamIndex, command.SubtitleStreamIndex); + + // Not nullable enabled - so this is required. + playlist[0] = CreatePlaylistItem( + items[0], + user, + command.StartPositionTicks ?? 0, + command.MediaSourceId ?? string.Empty, + command.AudioStreamIndex, + command.SubtitleStreamIndex); + for (int i = 1; i < len; i++) { - playlist[i] = CreatePlaylistItem(items[i], user, 0, null, null, null); + playlist[i] = CreatePlaylistItem(items[i], user, 0, string.Empty, null, null); } _logger.LogDebug("{0} - Playlist created", _session.DeviceName); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index d74ea0352..1a47335ad 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -284,13 +285,6 @@ namespace Emby.Server.Implementations fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); - CertificateInfo = new CertificateInfo - { - Path = ServerConfigurationManager.Configuration.CertificatePath, - Password = ServerConfigurationManager.Configuration.CertificatePassword - }; - Certificate = GetCertificate(CertificateInfo); - ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version; ApplicationVersionString = ApplicationVersion.ToString(3); ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString; @@ -456,6 +450,7 @@ namespace Emby.Server.Implementations Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false)); ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; + ConfigurationManager.NamedConfigurationUpdated += OnConfigurationUpdated; _mediaEncoder.SetFFmpegPath(); @@ -505,6 +500,13 @@ namespace Emby.Server.Implementations HttpsPort = NetworkConfiguration.DefaultHttpsPort; } + CertificateInfo = new CertificateInfo + { + Path = networkConfiguration.CertificatePath, + Password = networkConfiguration.CertificatePassword + }; + Certificate = GetCertificate(CertificateInfo); + DiscoverTypes(); RegisterServices(); @@ -912,11 +914,11 @@ namespace Emby.Server.Implementations protected void OnConfigurationUpdated(object sender, EventArgs e) { var requiresRestart = false; + var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration(); // Don't do anything if these haven't been set yet if (HttpPort != 0 && HttpsPort != 0) { - var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration(); // Need to restart if ports have changed if (networkConfiguration.HttpServerPortNumber != HttpPort || networkConfiguration.HttpsPortNumber != HttpsPort) @@ -936,10 +938,7 @@ namespace Emby.Server.Implementations requiresRestart = true; } - var currentCertPath = CertificateInfo?.Path; - var newCertPath = ServerConfigurationManager.Configuration.CertificatePath; - - if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase)) + if (ValidateSslCertificate(networkConfiguration)) { requiresRestart = true; } @@ -953,6 +952,33 @@ namespace Emby.Server.Implementations } /// <summary> + /// Validates the SSL certificate. + /// </summary> + /// <param name="networkConfig">The new configuration.</param> + /// <exception cref="FileNotFoundException">The certificate path doesn't exist.</exception> + private bool ValidateSslCertificate(NetworkConfiguration networkConfig) + { + var newPath = networkConfig.CertificatePath; + + if (!string.IsNullOrWhiteSpace(newPath) + && !string.Equals(CertificateInfo?.Path, newPath, StringComparison.Ordinal)) + { + if (File.Exists(newPath)) + { + return true; + } + + throw new FileNotFoundException( + string.Format( + CultureInfo.InvariantCulture, + "Certificate file '{0}' does not exist.", + newPath)); + } + + return false; + } + + /// <summary> /// Notifies that the kernel that a change has been made that requires a restart. /// </summary> public void NotifyPendingRestart() diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index f05a30a89..7a8ed8c29 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -88,7 +88,6 @@ namespace Emby.Server.Implementations.Configuration var newConfig = (ServerConfiguration)newConfiguration; ValidateMetadataPath(newConfig); - ValidateSslCertificate(newConfig); ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration>(newConfig)); @@ -96,31 +95,6 @@ namespace Emby.Server.Implementations.Configuration } /// <summary> - /// Validates the SSL certificate. - /// </summary> - /// <param name="newConfig">The new configuration.</param> - /// <exception cref="FileNotFoundException">The certificate path doesn't exist.</exception> - private void ValidateSslCertificate(BaseApplicationConfiguration newConfig) - { - var serverConfig = (ServerConfiguration)newConfig; - - var newPath = serverConfig.CertificatePath; - - if (!string.IsNullOrWhiteSpace(newPath) - && !string.Equals(Configuration.CertificatePath, newPath, StringComparison.Ordinal)) - { - if (!File.Exists(newPath)) - { - throw new FileNotFoundException( - string.Format( - CultureInfo.InvariantCulture, - "Certificate file '{0}' does not exist.", - newPath)); - } - } - } - - /// <summary> /// Validates the metadata path. /// </summary> /// <param name="newConfig">The new configuration.</param> diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 91c4648c6..9e9452f32 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -31,7 +31,7 @@ <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" /> - <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.0" /> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" /> diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index d62e2eefe..024404ceb 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -185,11 +185,11 @@ namespace Emby.Server.Implementations.HttpServer.Security updateToken = true; } - authInfo.IsApiKey = true; + authInfo.IsApiKey = false; } else { - authInfo.IsApiKey = false; + authInfo.IsApiKey = true; } if (updateToken) diff --git a/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs b/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs deleted file mode 100644 index d4e790c9a..000000000 --- a/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Jellyfin.Data.Events; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.Library -{ - /// <summary> - /// A library post scan/refresh task for pre-fetching remote images. - /// </summary> - public class ImageFetcherPostScanTask : ILibraryPostScanTask - { - private readonly ILibraryManager _libraryManager; - private readonly IProviderManager _providerManager; - private readonly ILogger<ImageFetcherPostScanTask> _logger; - private readonly SemaphoreSlim _imageFetcherLock; - - private ConcurrentDictionary<Guid, (BaseItem item, ItemUpdateType updateReason)> _queuedItems; - - /// <summary> - /// Initializes a new instance of the <see cref="ImageFetcherPostScanTask"/> class. - /// </summary> - /// <param name="libraryManager">An instance of <see cref="ILibraryManager"/>.</param> - /// <param name="providerManager">An instance of <see cref="IProviderManager"/>.</param> - /// <param name="logger">An instance of <see cref="ILogger{ImageFetcherPostScanTask}"/>.</param> - public ImageFetcherPostScanTask( - ILibraryManager libraryManager, - IProviderManager providerManager, - ILogger<ImageFetcherPostScanTask> logger) - { - _libraryManager = libraryManager; - _providerManager = providerManager; - _logger = logger; - _queuedItems = new ConcurrentDictionary<Guid, (BaseItem item, ItemUpdateType updateReason)>(); - _imageFetcherLock = new SemaphoreSlim(1, 1); - _libraryManager.ItemAdded += OnLibraryManagerItemAddedOrUpdated; - _libraryManager.ItemUpdated += OnLibraryManagerItemAddedOrUpdated; - _providerManager.RefreshCompleted += OnProviderManagerRefreshCompleted; - } - - /// <inheritdoc /> - public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) - { - // Sometimes a library scan will cause this to run twice if there's an item refresh going on. - await _imageFetcherLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var now = DateTime.UtcNow; - var itemGuids = _queuedItems.Keys.ToList(); - - for (var i = 0; i < itemGuids.Count; i++) - { - if (!_queuedItems.TryGetValue(itemGuids[i], out var queuedItem)) - { - continue; - } - - var itemId = queuedItem.item.Id.ToString("N", CultureInfo.InvariantCulture); - var itemType = queuedItem.item.GetType(); - _logger.LogDebug( - "Updating remote images for item {ItemId} with media type {ItemMediaType}", - itemId, - itemType); - try - { - await _libraryManager.UpdateImagesAsync(queuedItem.item, queuedItem.updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to fetch images for {Type} item with id {ItemId}", itemType, itemId); - } - - _queuedItems.TryRemove(queuedItem.item.Id, out _); - } - - if (itemGuids.Count > 0) - { - _logger.LogInformation( - "Finished updating/pre-fetching {NumberOfImages} images. Elapsed time: {TimeElapsed}s.", - itemGuids.Count.ToString(CultureInfo.InvariantCulture), - (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture)); - } - else - { - _logger.LogDebug("No images were updated."); - } - } - finally - { - _imageFetcherLock.Release(); - } - } - - private void OnLibraryManagerItemAddedOrUpdated(object sender, ItemChangeEventArgs itemChangeEventArgs) - { - if (!_queuedItems.ContainsKey(itemChangeEventArgs.Item.Id) && itemChangeEventArgs.Item.ImageInfos.Length > 0) - { - _queuedItems.AddOrUpdate( - itemChangeEventArgs.Item.Id, - (itemChangeEventArgs.Item, itemChangeEventArgs.UpdateReason), - (key, existingValue) => existingValue); - } - } - - private void OnProviderManagerRefreshCompleted(object sender, GenericEventArgs<BaseItem> e) - { - if (!_queuedItems.ContainsKey(e.Argument.Id) && e.Argument.ImageInfos.Length > 0) - { - _queuedItems.AddOrUpdate( - e.Argument.Id, - (e.Argument, ItemUpdateType.None), - (key, existingValue) => existingValue); - } - - // The RefreshCompleted event is a bit awkward in that it seems to _only_ be fired on - // the item that was refreshed regardless of children refreshes. So we take it as a signal - // that the refresh is entirely completed. - Run(null, CancellationToken.None).GetAwaiter().GetResult(); - } - } -} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 5b926b0f4..db27862ce 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -42,7 +42,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Library; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Tasks; using MediaBrowser.Providers.MediaInfo; @@ -1955,9 +1954,12 @@ namespace Emby.Server.Implementations.Library } /// <inheritdoc /> - public Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) + public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - RunMetadataSavers(items, updateReason); + foreach (var item in items) + { + await RunMetadataSavers(item, updateReason).ConfigureAwait(false); + } _itemRepository.SaveItems(items, cancellationToken); @@ -1988,25 +1990,22 @@ namespace Emby.Server.Implementations.Library } } } - - return Task.CompletedTask; } /// <inheritdoc /> public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) => UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken); - public void RunMetadataSavers(IReadOnlyList<BaseItem> items, ItemUpdateType updateReason) + public Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason) { - foreach (var item in items) + if (item.IsFileProtocol) { - if (item.IsFileProtocol) - { - ProviderManager.SaveMetadata(item, updateReason); - } - - item.DateLastSaved = DateTime.UtcNow; + ProviderManager.SaveMetadata(item, updateReason); } + + item.DateLastSaved = DateTime.UtcNow; + + return UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate); } /// <summary> diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 1084ddf74..90e6cc966 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -611,25 +611,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings CancellationToken cancellationToken, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { - try + var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .SendAsync(options, completionOption, cancellationToken).ConfigureAwait(false); + if (response.IsSuccessStatusCode) { - return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, completionOption, cancellationToken).ConfigureAwait(false); + return response; } - catch (HttpRequestException ex) - { - _tokens.Clear(); - if (!ex.StatusCode.HasValue || (int)ex.StatusCode.Value >= 500) - { - enableRetry = false; - } - - if (!enableRetry) - { - throw; - } + // Response is automatically disposed in the calling function, + // so dispose manually if not returning. + response.Dispose(); + if (!enableRetry || (int)response.StatusCode >= 500) + { + throw new HttpRequestException( + string.Format(CultureInfo.InvariantCulture, "Request failed: {0}", response.ReasonPhrase), + null, + response.StatusCode); } + _tokens.Clear(); options.Headers.TryAddWithoutValidation("token", await GetToken(providerInfo, cancellationToken).ConfigureAwait(false)); return await Send(options, false, providerInfo, cancellationToken).ConfigureAwait(false); } @@ -647,6 +647,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + hashedPassword + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json); using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(stream).ConfigureAwait(false); if (string.Equals(root.message, "OK", StringComparison.Ordinal)) @@ -701,6 +702,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings try { using var httpResponse = await Send(options, false, null, cancellationToken).ConfigureAwait(false); + httpResponse.EnsureSuccessStatusCode(); await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var response = httpResponse.Content; var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(stream).ConfigureAwait(false); @@ -709,7 +711,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings } catch (HttpRequestException ex) { - // Apparently we're supposed to swallow this + // SchedulesDirect returns 400 if no lineups are configured. if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.BadRequest) { return false; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 4fe6cbf31..5ef83f274 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -135,6 +135,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(GetApiUrl(info) + "/discover.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken) .ConfigureAwait(false); + response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var discoverResponse = await JsonSerializer.DeserializeAsync<DiscoverResponse>(stream, _jsonOptions, cancellationToken) .ConfigureAwait(false); diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index 66681f025..343e067b7 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -113,5 +113,10 @@ "TasksApplicationCategory": "Aplikacija", "TasksLibraryCategory": "Knjižnica", "TasksMaintenanceCategory": "Vzdrževanje", - "TaskDownloadMissingSubtitlesDescription": "Na podlagi nastavitev metapodatkov poišče manjkajoče podnapise na internetu." + "TaskDownloadMissingSubtitlesDescription": "Na podlagi nastavitev metapodatkov poišče manjkajoče podnapise na internetu.", + "TaskCleanActivityLogDescription": "Počisti zapise v dnevniku aktivnosti starejše od nastavljenega časa.", + "TaskCleanActivityLog": "Počisti dnevnik aktivnosti", + "Undefined": "Nedoločen", + "Forced": "Prisilno", + "Default": "Privzeto" } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b3965fcca..885f65c64 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -128,6 +128,9 @@ namespace Emby.Server.Implementations.Session /// <inheritdoc /> public event EventHandler<SessionEventArgs> SessionActivity; + /// <inheritdoc /> + public event EventHandler<SessionEventArgs> SessionControllerConnected; + /// <summary> /// Gets all connections. /// </summary> @@ -313,6 +316,19 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> + public void OnSessionControllerConnected(SessionInfo info) + { + EventHelper.QueueEventIfNotNull( + SessionControllerConnected, + this, + new SessionEventArgs + { + SessionInfo = info + }, + _logger); + } + + /// <inheritdoc /> public void CloseIfNeeded(SessionInfo session) { if (!session.SessionControllers.Any(i => i.IsSessionActive)) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 169eaefd8..39c369a01 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -133,6 +133,8 @@ namespace Emby.Server.Implementations.Session var controller = (WebSocketController)controllerInfo.Item1; controller.AddWebSocket(connection); + + _sessionManager.OnSessionControllerConnected(session); } /// <summary> diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 348213ee1..1d87036a2 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.SyncPlay _sessionManager = sessionManager; _libraryManager = libraryManager; _logger = loggerFactory.CreateLogger<SyncPlayManager>(); - _sessionManager.SessionStarted += OnSessionManagerSessionStarted; + _sessionManager.SessionControllerConnected += OnSessionControllerConnected; } /// <inheritdoc /> @@ -329,11 +329,11 @@ namespace Emby.Server.Implementations.SyncPlay return; } - _sessionManager.SessionStarted -= OnSessionManagerSessionStarted; + _sessionManager.SessionControllerConnected -= OnSessionControllerConnected; _disposed = true; } - private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e) + private void OnSessionControllerConnected(object sender, SessionEventArgs e) { var session = e.SessionInfo; diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index ef346dd5d..ae2fa3ce1 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -12,7 +12,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Events; -using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Json; using MediaBrowser.Common.Net; @@ -190,6 +189,22 @@ namespace Emby.Server.Implementations.Updates continue; } + for (var i = package.versions.Count - 1; i >= 0; i--) + { + // Remove versions with a target abi that is greater then the current application version. + if (Version.TryParse(package.versions[i].targetAbi, out var targetAbi) + && _applicationHost.ApplicationVersion < targetAbi) + { + package.versions.RemoveAt(i); + } + } + + // Don't add a package that doesn't have any compatible versions. + if (package.versions.Count == 0) + { + continue; + } + var existing = FilterPackages(result, package.name, packageGuid).FirstOrDefault(); if (existing != null) { @@ -407,6 +422,7 @@ namespace Emby.Server.Implementations.Updates using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(new Uri(package.SourceUrl), cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); // CA5351: Do Not Use Broken Cryptographic Algorithms diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 8256e3782..0f0bee4bc 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -133,11 +133,11 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult DeleteUser([FromRoute, Required] Guid userId) + public async Task<ActionResult> DeleteUser([FromRoute, Required] Guid userId) { var user = _userManager.GetUserById(userId); _sessionManager.RevokeUserTokens(user.Id, null); - _userManager.DeleteUser(userId); + await _userManager.DeleteUserAsync(userId).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index da6e5fa2d..b4f2817f7 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -16,7 +16,7 @@ <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" /> - <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.0" /> + <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" /> diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 89d6f4d9b..572038d00 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -41,8 +41,8 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.0" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.1" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.1" /> </ItemGroup> <ItemGroup> diff --git a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs index df420f48a..792e57f6a 100644 --- a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs +++ b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs @@ -28,6 +28,16 @@ namespace Jellyfin.Networking.Configuration public bool RequireHttps { get; set; } /// <summary> + /// Gets or sets the filesystem path of an X.509 certificate to use for SSL. + /// </summary> + public string CertificatePath { get; set; } = string.Empty; + + /// <summary> + /// Gets or sets the password required to access the X.509 certificate data in the file specified by <see cref="CertificatePath"/>. + /// </summary> + public string CertificatePassword { get; set; } = string.Empty; + + /// <summary> /// Gets or sets a value used to specify the URL prefix that your Jellyfin instance can be accessed at. /// </summary> public string BaseUrl @@ -83,7 +93,7 @@ namespace Jellyfin.Networking.Configuration /// </summary> /// <remarks> /// In order for HTTPS to be used, in addition to setting this to true, valid values must also be - /// provided for <see cref="ServerConfiguration.CertificatePath"/> and <see cref="ServerConfiguration.CertificatePassword"/>. + /// provided for <see cref="CertificatePath"/> and <see cref="CertificatePassword"/>. /// </remarks> public bool EnableHttps { get; set; } diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index 7bde4f35b..27360afb0 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -27,7 +27,7 @@ namespace Jellyfin.Server.Implementations.Activity } /// <inheritdoc/> - public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated; + public event EventHandler<GenericEventArgs<ActivityLogEntry>>? EntryCreated; /// <inheritdoc/> public async Task CreateAsync(ActivityLog entry) diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs index ec4a76e7f..0340248bb 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs @@ -86,7 +86,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session return name; } - private static string GetPlaybackNotificationType(string mediaType) + private static string? GetPlaybackNotificationType(string mediaType) { if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs index a0bad29e9..1648b1b47 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs @@ -94,7 +94,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session return name; } - private static string GetPlaybackStoppedNotificationType(string mediaType) + private static string? GetPlaybackStoppedNotificationType(string mediaType) { if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index e663798da..9e4a2065f 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -5,6 +5,7 @@ <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateDocumentationFile>true</GenerateDocumentationFile> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <Nullable>enable</Nullable> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> @@ -25,11 +26,11 @@ <ItemGroup> <PackageReference Include="System.Linq.Async" Version="5.0.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.0"> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.0"> + <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 7f3f83749..39f842354 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs index 662b4bf65..6a78e7ee6 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Linq; using System.Text; diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 334f27f85..9cc1c3e5e 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.IO; diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs index 1fb89c4a6..dbba80c21 100644 --- a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs +++ b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs @@ -1,5 +1,4 @@ -#nullable enable -#pragma warning disable CS1591 +#pragma warning disable CS1591 using System.Threading.Tasks; using Jellyfin.Data.Entities; diff --git a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs index 5f32479e1..c4e4c460a 100644 --- a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs +++ b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Authentication; diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index f684d151d..b76b272cf 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -1,5 +1,4 @@ -#nullable enable -#pragma warning disable CA1307 +#pragma warning disable CA1307 using System; using System.Collections.Concurrent; @@ -220,7 +219,7 @@ namespace Jellyfin.Server.Implementations.Users } /// <inheritdoc/> - public void DeleteUser(Guid userId) + public async Task DeleteUserAsync(Guid userId) { if (!_users.TryGetValue(userId, out var user)) { @@ -246,7 +245,7 @@ namespace Jellyfin.Server.Implementations.Users nameof(userId)); } - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); // Clear all entities related to the user from the database. if (user.ProfileImage != null) @@ -258,10 +257,10 @@ namespace Jellyfin.Server.Implementations.Users dbContext.RemoveRange(user.Preferences); dbContext.RemoveRange(user.AccessSchedules); dbContext.Users.Remove(user); - dbContext.SaveChanges(); + await dbContext.SaveChangesAsync().ConfigureAwait(false); _users.Remove(userId); - _eventManager.Publish(new UserDeletedEventArgs(user)); + await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false); } /// <inheritdoc/> diff --git a/Jellyfin.Server.Implementations/ValueConverters/DateTimeKindValueConverter.cs b/Jellyfin.Server.Implementations/ValueConverters/DateTimeKindValueConverter.cs index 8a510898b..a9a18c823 100644 --- a/Jellyfin.Server.Implementations/ValueConverters/DateTimeKindValueConverter.cs +++ b/Jellyfin.Server.Implementations/ValueConverters/DateTimeKindValueConverter.cs @@ -13,9 +13,9 @@ namespace Jellyfin.Server.Implementations.ValueConverters /// </summary> /// <param name="kind">The kind to specify.</param> /// <param name="mappingHints">The mapping hints.</param> - public DateTimeKindValueConverter(DateTimeKind kind, ConverterMappingHints mappingHints = null) + public DateTimeKindValueConverter(DateTimeKind kind, ConverterMappingHints? mappingHints = null) : base(v => v.ToUniversalTime(), v => DateTime.SpecifyKind(v, kind), mappingHints) { } } -}
\ No newline at end of file +} diff --git a/Jellyfin.Server/Filters/FileResponseFilter.cs b/Jellyfin.Server/Filters/FileResponseFilter.cs index 7ad9466c1..eae9a8004 100644 --- a/Jellyfin.Server/Filters/FileResponseFilter.cs +++ b/Jellyfin.Server/Filters/FileResponseFilter.cs @@ -14,7 +14,8 @@ namespace Jellyfin.Server.Filters { Schema = new OpenApiSchema { - Type = "file" + Type = "string", + Format = "binary" } }; diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 03d06fdff..97fb56ba1 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -40,8 +40,8 @@ <PackageReference Include="CommandLineParser" Version="2.8.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" /> - <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.0" /> - <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.0" /> + <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.1" /> + <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.1" /> <PackageReference Include="prometheus-net" Version="4.0.0" /> <PackageReference Include="prometheus-net.AspNetCore" Version="4.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" /> diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index af4be5a26..dd005b7f4 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -81,6 +81,7 @@ namespace Jellyfin.Server.Migrations.Routines { "unstable", ChromecastVersion.Unstable } }; + var customDisplayPrefs = new HashSet<string>(); var dbFilePath = Path.Combine(_paths.DataPath, DbFilename); using (var connection = SQLite3.Open(dbFilePath, ConnectionFlags.ReadOnly, null)) { @@ -185,7 +186,13 @@ namespace Jellyfin.Server.Migrations.Routines foreach (var (key, value) in dto.CustomPrefs) { - dbContext.Add(new CustomItemDisplayPreferences(displayPreferences.UserId, itemId, displayPreferences.Client, key, value)); + // Custom display preferences can have a key collision. + var indexKey = $"{displayPreferences.UserId}|{itemId}|{displayPreferences.Client}|{key}"; + if (!customDisplayPrefs.Contains(indexKey)) + { + dbContext.Add(new CustomItemDisplayPreferences(displayPreferences.UserId, itemId, displayPreferences.Client, key, value)); + customDisplayPrefs.Add(indexKey); + } } dbContext.Add(displayPreferences); diff --git a/MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs b/MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs index ccf214e3c..bd9600110 100644 --- a/MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; @@ -19,7 +20,7 @@ namespace MediaBrowser.Common.Json.Converters /// <inheritdoc /> public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) { - writer.WriteStringValue(value); + writer.WriteStringValue(value.ToString("N", CultureInfo.InvariantCulture)); } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index d8fad3bfb..1b25fbdbb 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1385,6 +1385,7 @@ namespace MediaBrowser.Controller.Entities new List<FileSystemMetadata>(); var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false); + await LibraryManager.UpdateImagesAsync(this).ConfigureAwait(false); // ensure all image properties in DB are fresh if (ownedItemsChanged) { diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 23f4c00c1..57d04ddfa 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -354,6 +354,11 @@ namespace MediaBrowser.Controller.Entities { await currentChild.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); } + else + { + // metadata is up-to-date; make sure DB has correct images dimensions and hash + await LibraryManager.UpdateImagesAsync(currentChild).ConfigureAwait(false); + } continue; } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 24b101694..6700761fc 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -571,7 +571,7 @@ namespace MediaBrowser.Controller.Library string videoPath, string[] files); - void RunMetadataSavers(IReadOnlyList<BaseItem> items, ItemUpdateType updateReason); + Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason); BaseItem GetParentItem(string parentId, Guid? userId); diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 8fd3b8c34..6e267834b 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -93,7 +93,8 @@ namespace MediaBrowser.Controller.Library /// Deletes the specified user. /// </summary> /// <param name="userId">The id of the user to be deleted.</param> - void DeleteUser(Guid userId); + /// <returns>A task representing the deletion of the user.</returns> + Task DeleteUserAsync(Guid userId); /// <summary> /// Resets the password. diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 9ad8557ce..6c06dcad5 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -47,6 +47,11 @@ namespace MediaBrowser.Controller.Session event EventHandler<SessionEventArgs> SessionActivity; /// <summary> + /// Occurs when [session controller connected]. + /// </summary> + event EventHandler<SessionEventArgs> SessionControllerConnected; + + /// <summary> /// Occurs when [capabilities changed]. /// </summary> event EventHandler<SessionEventArgs> CapabilitiesChanged; @@ -78,6 +83,12 @@ namespace MediaBrowser.Controller.Session /// <param name="user">The user.</param> SessionInfo LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Jellyfin.Data.Entities.User user); + /// <summary> + /// Used to report that a session controller has connected. + /// </summary> + /// <param name="session">The session.</param> + void OnSessionControllerConnected(SessionInfo session); + void UpdateDeviceName(string sessionId, string reportedDeviceName); /// <summary> diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 100756c24..38b333510 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -88,11 +88,11 @@ namespace MediaBrowser.Model.Configuration // The left side of the dot is the platform number, and the right side is the device number on the platform. OpenclDevice = "0.0"; EnableTonemapping = false; - TonemappingAlgorithm = "reinhard"; + TonemappingAlgorithm = "hable"; TonemappingRange = "auto"; TonemappingDesat = 0; TonemappingThreshold = 0.8; - TonemappingPeak = 0; + TonemappingPeak = 100; TonemappingParam = 0; H264Crf = 23; H265Crf = 28; diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 6dbce3067..8b3ca17ca 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -229,7 +229,7 @@ namespace MediaBrowser.Providers.Manager await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false); } - private Task SavePeopleMetadataAsync(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken) + private async Task SavePeopleMetadataAsync(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken) { var personsToSave = new List<BaseItem>(); @@ -239,6 +239,7 @@ namespace MediaBrowser.Providers.Manager if (person.ProviderIds.Count > 0 || !string.IsNullOrWhiteSpace(person.ImageUrl)) { + var itemUpdateType = ItemUpdateType.MetadataDownload; var saveEntity = false; var personEntity = LibraryManager.GetPerson(person.Name); foreach (var id in person.ProviderIds) @@ -261,18 +262,18 @@ namespace MediaBrowser.Providers.Manager 0); saveEntity = true; + itemUpdateType = ItemUpdateType.ImageUpdate; } if (saveEntity) { personsToSave.Add(personEntity); + await LibraryManager.RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); } } } - LibraryManager.RunMetadataSavers(personsToSave, ItemUpdateType.MetadataDownload); LibraryManager.CreateItems(personsToSave, null, CancellationToken.None); - return Task.CompletedTask; } protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) @@ -105,12 +105,6 @@ There are three options to get the files for the web client. 2. Build them from source following the instructions on the [jellyfin-web repository](https://github.com/jellyfin/jellyfin-web) 3. Get the pre-built files from an existing installation of the server. For example, with a Windows server installation the client files are located at `C:\Program Files\Jellyfin\Server\jellyfin-web` -Once you have a copy of the built web client files, you need to copy them into a specific directory. - -> `<repository root>/Mediabrowser.WebDashboard/jellyfin-web` - -As part of the build process, this folder will be copied to the build output directory, where it can be accessed by the server. - ### Running The Server The following instructions will help you get the project up and running via the command line, or your preferred IDE. @@ -133,7 +127,7 @@ To run the server from the command line you can use the `dotnet run` command. Th ```bash cd jellyfin # Move into the repository directory -dotnet run --project Jellyfin.Server # Run the server startup project +dotnet run --project Jellyfin.Server --webdir /absolute/path/to/jellyfin-web/dist # Run the server startup project ``` A second option is to build the project and then run the resulting executable file directly. When running the executable directly you can easily add command line options. Add the `--help` flag to list details on all the supported command line options. diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64 index f0d9188c1..d2f98ca82 100644 --- a/deployment/Dockerfile.debian.amd64 +++ b/deployment/Dockerfile.debian.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-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 diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64 index 8132ee887..ffc94e088 100644 --- a/deployment/Dockerfile.debian.arm64 +++ b/deployment/Dockerfile.debian.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-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 diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf index 31f534838..b25f59329 100644 --- a/deployment/Dockerfile.debian.armhf +++ b/deployment/Dockerfile.debian.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-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 diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64 index 2bedafcc5..2e993c25d 100644 --- a/deployment/Dockerfile.linux.amd64 +++ b/deployment/Dockerfile.linux.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-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 diff --git a/deployment/Dockerfile.macos b/deployment/Dockerfile.macos index d470f9b74..f2bbe7f24 100644 --- a/deployment/Dockerfile.macos +++ b/deployment/Dockerfile.macos @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-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 diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable index d2007c075..603becedf 100644 --- a/deployment/Dockerfile.portable +++ b/deployment/Dockerfile.portable @@ -15,7 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-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 diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index 084159d45..a6c7cc5d4 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-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 diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index c2caf4cf8..3a8005816 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-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 diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index 719b3a85b..22b9e7ea8 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-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 diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64 index e6905c906..b1ca61053 100644 --- a/deployment/Dockerfile.windows.amd64 +++ b/deployment/Dockerfile.windows.amd64 @@ -15,7 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-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 diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 7c552ec06..90222d5c8 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -16,7 +16,7 @@ <PackageReference Include="AutoFixture" Version="4.14.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.14.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.14.0" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.0" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.1" /> <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" /> <PackageReference Include="xunit" Version="2.4.1" /> diff --git a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs index 663cc3c78..1e1cde957 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Text.Json; using MediaBrowser.Common.Json.Converters; using Xunit; @@ -46,7 +47,23 @@ namespace Jellyfin.Common.Tests.Json [Fact] public void Serialize_EmptyGuid_EmptyGuid() { - Assert.Equal($"\"{Guid.Empty}\"", JsonSerializer.Serialize(Guid.Empty, _options)); + Assert.Equal($"\"{Guid.Empty:N}\"", JsonSerializer.Serialize(Guid.Empty, _options)); + } + + [Fact] + public void Serialize_Valid_NoDash_Success() + { + var guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA"); + var str = JsonSerializer.Serialize(guid, _options); + Assert.Equal($"\"{guid:N}\"", str); + } + + [Fact] + public void Serialize_Nullable_Success() + { + Guid? guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA"); + var str = JsonSerializer.Serialize(guid, _options); + Assert.Equal($"\"{guid:N}\"", str); } } } |
