From 65a9d618ccfb5e015eb2dccf437e8795ac5350d5 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 22 Feb 2020 15:04:52 +0900 Subject: [PATCH 001/104] add config options for musicbrainz --- .../ConfigurationOptions.cs | 1 - .../Library/LibraryManager.cs | 3 +- .../Entities/Audio/MusicArtist.cs | 1 + MediaBrowser.Controller/Entities/Folder.cs | 8 +- .../MediaBrowser.Providers.csproj | 5 + .../Music/MusicExternalIds.cs | 95 ---------------- .../MusicBrainz/AlbumProvider.cs} | 45 ++++---- .../MusicBrainz/ArtistProvider.cs} | 19 +++- .../Configuration/PluginConfiguration.cs | 16 +++ .../MusicBrainz/Configuration/config.html | 69 ++++++++++++ .../MusicBrainz/MusicBrainzExternalIds.cs | 102 ++++++++++++++++++ .../Plugins/MusicBrainz/Plugin.cs | 35 ++++++ 12 files changed, 273 insertions(+), 126 deletions(-) rename MediaBrowser.Providers/{Music/MusicBrainzAlbumProvider.cs => Plugins/MusicBrainz/AlbumProvider.cs} (96%) rename MediaBrowser.Providers/{Music/MusicBrainzArtistProvider.cs => Plugins/MusicBrainz/ArtistProvider.cs} (94%) create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzExternalIds.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 2ea7ff6e91..d0f3d67230 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -8,7 +8,6 @@ namespace Emby.Server.Implementations public static Dictionary Configuration => new Dictionary { { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, - { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" } }; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 55566ed517..8a66ffdaac 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -944,7 +944,6 @@ namespace Emby.Server.Implementations.Library IncludeItemTypes = new[] { typeof(T).Name }, Name = name, DtoOptions = options - }).Cast() .OrderBy(i => i.IsAccessedByName ? 1 : 0) .Cast() @@ -1080,7 +1079,7 @@ namespace Emby.Server.Implementations.Library var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => progress.Report(pct * pct * 0.96)); + innerProgress.RegisterAction(pct => progress.Report(pct * 0.96)); // Validate the entire media library await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index efe0d3cf70..5e3056ccb0 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -198,6 +198,7 @@ namespace MediaBrowser.Controller.Entities.Audio return true; } } + return base.RequiresRefresh(); } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 07fbe60350..a892be7a97 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -322,10 +322,10 @@ namespace MediaBrowser.Controller.Entities ProviderManager.OnRefreshProgress(this, 5); } - //build a dictionary of the current children we have now by Id so we can compare quickly and easily + // Build a dictionary of the current children we have now by Id so we can compare quickly and easily var currentChildren = GetActualChildrenDictionary(); - //create a list for our validated children + // Create a list for our validated children var newItems = new List(); cancellationToken.ThrowIfCancellationRequested(); @@ -391,7 +391,7 @@ namespace MediaBrowser.Controller.Entities var folder = this; innerProgress.RegisterAction(p => { - double newPct = .80 * p + 10; + double newPct = 0.80 * p + 10; progress.Report(newPct); ProviderManager.OnRefreshProgress(folder, newPct); }); @@ -421,7 +421,7 @@ namespace MediaBrowser.Controller.Entities var folder = this; innerProgress.RegisterAction(p => { - double newPct = .10 * p + 90; + double newPct = 0.10 * p + 90; progress.Report(newPct); if (recursive) { diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 5593c5036c..48c16b6433 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -24,4 +24,9 @@ true + + + + + diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index 585c98af9e..b431c1e41f 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -5,101 +5,6 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Providers.Music { - public class MusicBrainzReleaseGroupExternalId : IExternalId - { - /// - public string Name => "MusicBrainz Release Group"; - - /// - public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/release-group/{0}"; - - /// - public bool Supports(IHasProviderIds item) - => item is Audio || item is MusicAlbum; - } - - public class MusicBrainzAlbumArtistExternalId : IExternalId - { - /// - public string Name => "MusicBrainz Album Artist"; - - /// - public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) - => item is Audio; - } - - public class MusicBrainzAlbumExternalId : IExternalId - { - /// - public string Name => "MusicBrainz Album"; - - /// - public string Key => MetadataProviders.MusicBrainzAlbum.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/release/{0}"; - - /// - public bool Supports(IHasProviderIds item) - => item is Audio || item is MusicAlbum; - } - - public class MusicBrainzArtistExternalId : IExternalId - { - /// - public string Name => "MusicBrainz"; - - /// - public string Key => MetadataProviders.MusicBrainzArtist.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is MusicArtist; - } - - public class MusicBrainzOtherArtistExternalId : IExternalId - { - /// - public string Name => "MusicBrainz Artist"; - - /// - - public string Key => MetadataProviders.MusicBrainzArtist.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) - => item is Audio || item is MusicAlbum; - } - - public class MusicBrainzTrackId : IExternalId - { - /// - public string Name => "MusicBrainz Track"; - - /// - public string Key => MetadataProviders.MusicBrainzTrack.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/track/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Audio; - } - public class ImvdbId : IExternalId { /// diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs similarity index 96% rename from MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs rename to MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs index 8e71b625ee..0fddb05579 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs @@ -15,7 +15,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -using Microsoft.Extensions.Configuration; +using MediaBrowser.Providers.Plugins.MusicBrainz; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Music @@ -28,7 +28,7 @@ namespace MediaBrowser.Providers.Music /// Be prudent, use a value slightly above the minimun required. /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting /// - private const long MusicBrainzQueryIntervalMs = 1050u; + private readonly long _musicBrainzQueryIntervalMs = 1050u; /// /// For each single MB lookup/search, this is the maximum number of @@ -50,14 +50,14 @@ namespace MediaBrowser.Providers.Music public MusicBrainzAlbumProvider( IHttpClient httpClient, IApplicationHost appHost, - ILogger logger, - IConfiguration configuration) + ILogger logger) { _httpClient = httpClient; _appHost = appHost; _logger = logger; - _musicBrainzBaseUrl = configuration["MusicBrainz:BaseUrl"]; + _musicBrainzBaseUrl = Plugin.Instance.Configuration.Server; + _musicBrainzQueryIntervalMs = Plugin.Instance.Configuration.RateLimit; // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit _stopWatchMusicBrainz.Start(); @@ -74,6 +74,12 @@ namespace MediaBrowser.Providers.Music /// public async Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) { + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return Enumerable.Empty(); + } + var releaseId = searchInfo.GetReleaseId(); var releaseGroupId = searchInfo.GetReleaseGroupId(); @@ -107,8 +113,8 @@ namespace MediaBrowser.Providers.Music url = string.Format( CultureInfo.InvariantCulture, "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"", - WebUtility.UrlEncode(queryName), - WebUtility.UrlEncode(searchInfo.GetAlbumArtist())); + WebUtility.UrlEncode(queryName), + WebUtility.UrlEncode(searchInfo.GetAlbumArtist())); } } @@ -170,7 +176,6 @@ namespace MediaBrowser.Providers.Music } return result; - }); } } @@ -187,6 +192,12 @@ namespace MediaBrowser.Providers.Music Item = new MusicAlbum() }; + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return result; + } + // If we have a release group Id but not a release Id... if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId)) { @@ -456,18 +467,6 @@ namespace MediaBrowser.Providers.Music } case "artist-credit": { - // TODO - - /* - * - - -SARCASTIC+ZOOKEEPER -SARCASTIC+ZOOKEEPER - - - - */ using (var subReader = reader.ReadSubtree()) { var artist = ParseArtistCredit(subReader); @@ -764,10 +763,10 @@ namespace MediaBrowser.Providers.Music { attempts++; - if (_stopWatchMusicBrainz.ElapsedMilliseconds < MusicBrainzQueryIntervalMs) + if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs) { // MusicBrainz is extremely adamant about limiting to one request per second - var delayMs = MusicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; + var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); } @@ -778,7 +777,7 @@ namespace MediaBrowser.Providers.Music response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false); - // We retry a finite number of times, and only whilst MB is indcating 503 (throttling) + // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) } while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs similarity index 94% rename from MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs rename to MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs index 5d675392c9..260a3b6e7b 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs @@ -14,6 +14,7 @@ using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.MusicBrainz; namespace MediaBrowser.Providers.Music { @@ -22,6 +23,12 @@ namespace MediaBrowser.Providers.Music /// public async Task> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken) { + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return Enumerable.Empty(); + } + var musicBrainzId = searchInfo.GetMusicBrainzArtistId(); if (!string.IsNullOrWhiteSpace(musicBrainzId)) @@ -226,6 +233,12 @@ namespace MediaBrowser.Providers.Music Item = new MusicArtist() }; + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return result; + } + var musicBrainzId = id.GetMusicBrainzArtistId(); if (string.IsNullOrWhiteSpace(musicBrainzId)) @@ -237,8 +250,12 @@ namespace MediaBrowser.Providers.Music if (singleResult != null) { musicBrainzId = singleResult.GetProviderId(MetadataProviders.MusicBrainzArtist); - //result.Item.Name = singleResult.Name; result.Item.Overview = singleResult.Overview; + + if (Plugin.Instance.Configuration.ReplaceArtistName) + { + result.Item.Name = singleResult.Name; + } } } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs new file mode 100644 index 0000000000..6bce0b4478 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Providers.Plugins.MusicBrainz +{ + public class PluginConfiguration : BasePluginConfiguration + { + public bool Enable { get; set; } = false; + + public bool ReplaceArtistName { get; set; } = false; + + public string Server { get; set; } = "https://www.musicbrainz.org"; + + public long RateLimit { get; set; } = 1000u; + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html new file mode 100644 index 0000000000..dfffa90655 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html @@ -0,0 +1,69 @@ + + + + MusicBrainz + + +
+
+
+
+
+ +
This can either be a mirror of the official server or a custom server.
+
+
+ +
Span of time between each request in milliseconds.
+
+ + +
+
+ +
+
+
+
+ +
+ + diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzExternalIds.cs new file mode 100644 index 0000000000..0184d42410 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzExternalIds.cs @@ -0,0 +1,102 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Music +{ + public class MusicBrainzReleaseGroupExternalId : IExternalId + { + /// + public string Name => "MusicBrainz Release Group"; + + /// + public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/release-group/{0}"; + + /// + public bool Supports(IHasProviderIds item) + => item is Audio || item is MusicAlbum; + } + + public class MusicBrainzAlbumArtistExternalId : IExternalId + { + /// + public string Name => "MusicBrainz Album Artist"; + + /// + public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) + => item is Audio; + } + + public class MusicBrainzAlbumExternalId : IExternalId + { + /// + public string Name => "MusicBrainz Album"; + + /// + public string Key => MetadataProviders.MusicBrainzAlbum.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/release/{0}"; + + /// + public bool Supports(IHasProviderIds item) + => item is Audio || item is MusicAlbum; + } + + public class MusicBrainzArtistExternalId : IExternalId + { + /// + public string Name => "MusicBrainz"; + + /// + public string Key => MetadataProviders.MusicBrainzArtist.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is MusicArtist; + } + + public class MusicBrainzOtherArtistExternalId : IExternalId + { + /// + public string Name => "MusicBrainz Artist"; + + /// + + public string Key => MetadataProviders.MusicBrainzArtist.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) + => item is Audio || item is MusicAlbum; + } + + public class MusicBrainzTrackId : IExternalId + { + /// + public string Name => "MusicBrainz Track"; + + /// + public string Key => MetadataProviders.MusicBrainzTrack.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/track/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio; + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs new file mode 100644 index 0000000000..2211d513f9 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Providers.Plugins.MusicBrainz +{ + public class Plugin : BasePlugin, IHasWebPages + { + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + public IEnumerable GetPages() + { + yield return new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html" + }; + } + + public override Guid Id => new Guid("8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"); + + public override string Name => "MusicBrainz"; + + public override string Description => "Get artist and album metadata from any MusicBrainz server."; + + public static Plugin Instance { get; private set; } + } +} From 496bdc65f313744732b04079c5654af84a1c6d85 Mon Sep 17 00:00:00 2001 From: Narfinger Date: Sun, 23 Feb 2020 19:45:29 +0900 Subject: [PATCH 002/104] adds names from the episodenumber tests to path tests --- .../TV/EpisodePathParserTest.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs index da6e993100..d959a50661 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs @@ -15,6 +15,24 @@ namespace Jellyfin.Naming.Tests.TV [InlineData("D:\\media\\Foo - S04E011", "Foo", 4, 11)] [InlineData("D:\\media\\Foo\\Foo s01x01", "Foo", 1, 1)] [InlineData("D:\\media\\Foo (2019)\\Season 4\\Foo (2019).S04E03", "Foo (2019)", 4, 3)] + [InlineData("/Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", "Elementary", 2, 3)] + [InlineData("/Season 1/seriesname S01E02 blah.avi", "seriesname", 1, 2)] + [InlineData("/Running Man/Running Man S2017E368.mkv", "Running Man", 2017, 368)] + [InlineData("/Season 1/seriesname 01x02 blah.avi", "seriesname", 1, 2)] + [InlineData("/Season 25/The Simpsons.S25E09.Steal this episode.mp4", "The Simpsons", 25, 9)] + [InlineData("/Season 1/seriesname S01x02 blah.avi", "seriesname", 1, 2)] + [InlineData("/Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", "Elementary", 2, 3)] + [InlineData("/Season 1/seriesname S01xE02 blah.avi", "seriesname", 1, 2)] + [InlineData("/Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", "Elementary", 2, 3)] + [InlineData("/Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", "Elementary", 2, 3)] + [InlineData("/Season 02/Elementary - 02x03-E15 - Ep Name.mp4", "Elementary", 2, 3)] + [InlineData("/Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", "Elementary", 1, 23)] + [InlineData("/The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi", "The Wonder Years", 4, 7)] + // TODO: [InlineData("/Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", "Castle Rock", 2, 1)] + // TODO: [InlineData("/After Life 1x06 Episodio 6 [WEB-DL NF 1080p h264 Dual DD 5.1 Sub].mkv", "After Life", 1, 6)] + // TODO: [InlineData("/Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", "Uchuu Senkan Yamoto 2199", 4, 3)] + // TODO: [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", "The Daily Show", 25, 22)] + // TODO: [InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", "Watchmen (2019)", 1, 3)] public void ParseEpisodesCorrectly(string path, string name, int season, int episode) { NamingOptions o = new NamingOptions(); @@ -32,6 +50,7 @@ namespace Jellyfin.Naming.Tests.TV [InlineData("/media/Foo/[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)] [InlineData("D:\\media\\Foo\\Foo 889", "Foo", 889)] [InlineData("D:\\media\\Foo\\[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)] + // TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", "The Legend of Condor Heroes 2017", 1, 7)] public void ParseEpisodeWithoutSeason(string path, string name, int episode) { NamingOptions o = new NamingOptions(); From a34826008f001fcfee9dabde4e439a0d83e821ea Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 24 Feb 2020 00:22:23 +0900 Subject: [PATCH 003/104] update external ids --- MediaBrowser.Providers/Music/MusicExternalIds.cs | 1 - .../MusicBrainz/{MusicBrainzExternalIds.cs => ExternalIds.cs} | 1 - 2 files changed, 2 deletions(-) rename MediaBrowser.Providers/Plugins/MusicBrainz/{MusicBrainzExternalIds.cs => ExternalIds.cs} (98%) diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index b431c1e41f..628b9a9a10 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -1,5 +1,4 @@ using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs similarity index 98% rename from MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzExternalIds.cs rename to MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs index 0184d42410..fd85465f37 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs @@ -1,4 +1,3 @@ -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; From 19a609a88980b2ac21050eeb991e2abb55675e9c Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 24 Feb 2020 00:24:03 +0900 Subject: [PATCH 004/104] update musicbrainz options --- .../MusicBrainz/Configuration/PluginConfiguration.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs index 6bce0b4478..607ba0cbcb 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs @@ -5,12 +5,12 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz { public class PluginConfiguration : BasePluginConfiguration { - public bool Enable { get; set; } = false; - - public bool ReplaceArtistName { get; set; } = false; - public string Server { get; set; } = "https://www.musicbrainz.org"; public long RateLimit { get; set; } = 1000u; + + public bool Enable { get; set; } + + public bool ReplaceArtistName { get; set; } } } From 940990708e3df623056cc790351e6d5d52af5a0f Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 24 Feb 2020 00:25:27 +0900 Subject: [PATCH 005/104] remove unused assignment --- MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs index 0fddb05579..748c23216c 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.Providers.Music /// Be prudent, use a value slightly above the minimun required. /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting ///
- private readonly long _musicBrainzQueryIntervalMs = 1050u; + private readonly long _musicBrainzQueryIntervalMs; /// /// For each single MB lookup/search, this is the maximum number of From 4ae80a5d56f291a0865dddc5393cf1e3c4b9277b Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 24 Feb 2020 14:35:30 +0900 Subject: [PATCH 006/104] partially fix issue with music scans --- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 5280d455c4..bf10108b8c 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -238,7 +238,7 @@ namespace MediaBrowser.Model.Configuration CodecsUsed = Array.Empty(); PathSubstitutions = Array.Empty(); IgnoreVirtualInterfaces = false; - EnableSimpleArtistDetection = true; + EnableSimpleArtistDetection = false; DisplaySpecialsWithinSeasons = true; EnableExternalContentInSuggestions = true; From 6c6b5d7f280b0d29bf5083387cac4897588e7ef6 Mon Sep 17 00:00:00 2001 From: sharkykh Date: Tue, 25 Feb 2020 23:17:52 +0000 Subject: [PATCH 007/104] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/ --- .../Localization/Core/he.json | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index b08c8966e2..5ef59797d2 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -28,7 +28,7 @@ "HeaderRecordingGroups": "קבוצות הקלטה", "HomeVideos": "סרטונים בייתים", "Inherit": "הורש", - "ItemAddedWithName": "{0} was added to the library", + "ItemAddedWithName": "{0} הוסף לספרייה", "ItemRemovedWithName": "{0} נמחק מהספרייה", "LabelIpAddressValue": "Ip כתובת: {0}", "LabelRunningTimeValue": "משך צפייה: {0}", @@ -36,15 +36,15 @@ "MessageApplicationUpdated": "שרת הJellyfin עודכן", "MessageApplicationUpdatedTo": "שרת הJellyfin עודכן לגרסא {0}", "MessageNamedServerConfigurationUpdatedWithValue": "הגדרת השרת {0} שונתה", - "MessageServerConfigurationUpdated": "Server configuration has been updated", + "MessageServerConfigurationUpdated": "תצורת השרת עודכנה", "MixedContent": "תוכן מעורב", "Movies": "סרטים", "Music": "מוזיקה", "MusicVideos": "Music videos", "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NameSeasonNumber": "עונה {0}", + "NameSeasonUnknown": "עונה לא ידועה", + "NewVersionIsAvailable": "גרסה חדשה של שרת Jellyfin זמינה להורדה.", "NotificationOptionApplicationUpdateAvailable": "Application update available", "NotificationOptionApplicationUpdateInstalled": "Application update installed", "NotificationOptionAudioPlayback": "Audio playback started", @@ -53,10 +53,10 @@ "NotificationOptionInstallationFailed": "התקנה נכשלה", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", + "NotificationOptionPluginInstalled": "התוסף הותקן", + "NotificationOptionPluginUninstalled": "התוסף הוסר", + "NotificationOptionPluginUpdateInstalled": "העדכון לתוסף הותקן", + "NotificationOptionServerRestartRequired": "יש לאתחל את השרת", "NotificationOptionTaskFailed": "Scheduled task failure", "NotificationOptionUserLockedOut": "User locked out", "NotificationOptionVideoPlayback": "Video playback started", @@ -71,15 +71,15 @@ "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.", + "Shows": "סדרות", + "Songs": "שירים", + "StartupEmbyServerIsLoading": "שרת Jellyfin בהליכי טעינה. אנא נסה שנית בעוד זמן קצר.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", "Sync": "סנכרן", "System": "System", - "TvShows": "TV Shows", + "TvShows": "סדרות טלוויזיה", "User": "User", "UserCreatedWithName": "User {0} has been created", "UserDeletedWithName": "User {0} has been deleted", From 9d53fd0e73c70cd275e1a9b958c368b9efd01b76 Mon Sep 17 00:00:00 2001 From: sharkykh Date: Tue, 25 Feb 2020 23:29:27 +0000 Subject: [PATCH 008/104] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/ --- Emby.Server.Implementations/Localization/Core/he.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index 5ef59797d2..f3a7794dae 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -81,9 +81,9 @@ "System": "System", "TvShows": "סדרות טלוויזיה", "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", + "UserCreatedWithName": "המשתמש {0} נוצר", + "UserDeletedWithName": "המשתמש {0} הוסר", + "UserDownloadingItemWithValues": "{0} מוריד את {1}", "UserLockedOutWithName": "User {0} has been locked out", "UserOfflineFromDevice": "{0} has disconnected from {1}", "UserOnlineFromDevice": "{0} is online from {1}", From 107974e3f8ad1c4461bd289ef9cf4d5fb1e619df Mon Sep 17 00:00:00 2001 From: Narfinger Date: Thu, 27 Feb 2020 11:31:57 +0900 Subject: [PATCH 009/104] moves shows tests to Theory and InlineData format --- .../TV/AbsoluteEpisodeNumberTests.cs | 54 +-- .../TV/DailyEpisodeTests.cs | 53 +-- .../TV/EpisodeNumberWithoutSeasonTests.cs | 127 +------ .../TV/EpisodePathParserTest.cs | 20 +- .../TV/EpisodeWithoutSeasonTests.cs | 43 +-- .../TV/MultiEpisodeTests.cs | 153 ++++---- .../TV/SeasonFolderTests.cs | 116 +----- .../TV/SeasonNumberTests.cs | 338 +++--------------- .../TV/SimpleEpisodeTests.cs | 94 +---- 9 files changed, 191 insertions(+), 807 deletions(-) diff --git a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs index 9abbcc7bf0..553d06681b 100644 --- a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs @@ -6,56 +6,22 @@ namespace Jellyfin.Naming.Tests.TV { public class AbsoluteEpisodeNumberTests { - [Fact] - public void TestAbsoluteEpisodeNumber1() - { - Assert.Equal(12, GetEpisodeNumberFromFile(@"The Simpsons/12.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber2() - { - Assert.Equal(12, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 12.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber3() - { - Assert.Equal(82, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 82.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber4() - { - Assert.Equal(112, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 112.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber5() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/Foo_ep_02.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber6() - { - Assert.Equal(889, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 889.avi")); - } - - [Fact] - public void TestAbsoluteEpisodeNumber7() - { - Assert.Equal(101, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 101.avi")); - } - - private int? GetEpisodeNumberFromFile(string path) + [Theory] + [InlineData("The Simpsons/12.avi", 12)] + [InlineData("The Simpsons/The Simpsons 12.avi", 12)] + [InlineData("The Simpsons/The Simpsons 82.avi", 82)] + [InlineData("The Simpsons/The Simpsons 112.avi", 112)] + [InlineData("The Simpsons/Foo_ep_02.avi", 2)] + [InlineData("The Simpsons/The Simpsons 889.avi", 889)] + [InlineData("The Simpsons/The Simpsons 101.avi", 101)] + public void GetEpisodeNumberFromFileTest(string path, int episodeNumber) { var options = new NamingOptions(); var result = new EpisodeResolver(options) .Resolve(path, false, null, null, true); - return result.EpisodeNumber; + Assert.Equal(episodeNumber, result.EpisodeNumber); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs index 29daf8cc37..6ecffe80b7 100644 --- a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs @@ -6,52 +6,17 @@ namespace Jellyfin.Naming.Tests.TV { public class DailyEpisodeTests { - [Fact] - public void TestDailyEpisode1() - { - Test(@"/server/anything_1996.11.14.mp4", "anything", 1996, 11, 14); - } - [Fact] - public void TestDailyEpisode2() - { - Test(@"/server/anything_1996-11-14.mp4", "anything", 1996, 11, 14); - } - // FIXME - // [Fact] - public void TestDailyEpisode3() - { - Test(@"/server/anything_14.11.1996.mp4", "anything", 1996, 11, 14); - } - - // FIXME - // [Fact] - public void TestDailyEpisode4() - { - Test(@"/server/A Daily Show - (2015-01-15) - Episode Name - [720p].mkv", "A Daily Show", 2015, 01, 15); - } - - [Fact] - public void TestDailyEpisode5() - { - Test(@"/server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv", "james.corden", 2017, 04, 20); - } - - [Fact] - public void TestDailyEpisode6() - { - Test(@"/server/ABC News 2018_03_24_19_00_00.mkv", "ABC News", 2018, 03, 24); - } - - // FIXME - // [Fact] - public void TestDailyEpisode7() - { - Test(@"/server/Last Man Standing_KTLADT_2018_05_25_01_28_00.wtv", "Last Man Standing", 2018, 05, 25); - } - - private void Test(string path, string seriesName, int? year, int? month, int? day) + [Theory] + [InlineData(@"/server/anything_1996.11.14.mp4", "anything", 1996, 11, 14)] + [InlineData(@"/server/anything_1996-11-14.mp4", "anything", 1996, 11, 14)] + [InlineData(@"/server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv", "james.corden", 2017, 04, 20)] + [InlineData(@"/server/ABC News 2018_03_24_19_00_00.mkv", "ABC News", 2018, 03, 24)] + // TODO: [InlineData(@"/server/anything_14.11.1996.mp4", "anything", 1996, 11, 14)] + // TODO: [InlineData(@"/server/A Daily Show - (2015-01-15) - Episode Name - [720p].mkv", "A Daily Show", 2015, 01, 15)] + // TODO: [InlineData(@"/server/Last Man Standing_KTLADT_2018_05_25_01_28_00.wtv", "Last Man Standing", 2018, 05, 25)] + public void Test(string path, string seriesName, int? year, int? month, int? day) { var options = new NamingOptions(); diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs index 00aa9ee7c2..0c7d9520e2 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs @@ -6,122 +6,31 @@ namespace Jellyfin.Naming.Tests.TV { public class EpisodeNumberWithoutSeasonTests { - [Fact] - public void TestEpisodeNumberWithoutSeason1() - { - Assert.Equal(8, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons.S25E08.Steal this episode.mp4")); - } - [Fact] - public void TestEpisodeNumberWithoutSeason2() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02 - Ep Name.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason3() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason4() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02 - Ep Name.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason5() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02-Ep Name.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason6() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02.EpName.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason7() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason8() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02 Ep Name.avi")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumberWithoutSeason9() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 5 - 02 - Ep Name.avi")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumberWithoutSeason10() - { - Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 5 - 02 Ep Name.avi")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumberWithoutSeason11() - { - Assert.Equal(7, GetEpisodeNumberFromFile(@"Seinfeld/Seinfeld 0807 The Checks.avi")); - Assert.Equal(8, GetSeasonNumberFromFile(@"Seinfeld/Seinfeld 0807 The Checks.avi")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason12() - { - Assert.Equal(7, GetEpisodeNumberFromFile(@"GJ Club (2013)/GJ Club - 07.mkv")); - } - - // FIXME - // [Fact] - public void TestEpisodeNumberWithoutSeason13() - { - // This is not supported anymore after removing the episode number 365+ hack from EpisodePathParser - Assert.Equal(13, GetEpisodeNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 13.mkv")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason14() - { - Assert.Equal(3, GetSeasonNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 317.mkv")); - Assert.Equal(17, GetEpisodeNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 317.mkv")); - } - - [Fact] - public void TestEpisodeNumberWithoutSeason15() - { - Assert.Equal(2017, GetSeasonNumberFromFile(@"Running Man/Running Man S2017E368.mkv")); - } - - private int? GetEpisodeNumberFromFile(string path) + [Theory] + [InlineData(8, @"The Simpsons/The Simpsons.S25E08.Steal this episode.mp4")] + [InlineData(2, @"The Simpsons/The Simpsons - 02 - Ep Name.avi")] + [InlineData(2, @"The Simpsons/02.avi")] + [InlineData(2, @"The Simpsons/02 - Ep Name.avi")] + [InlineData(2, @"The Simpsons/02-Ep Name.avi")] + [InlineData(2, @"The Simpsons/02.EpName.avi")] + [InlineData(2, @"The Simpsons/The Simpsons - 02.avi")] + [InlineData(2, @"The Simpsons/The Simpsons - 02 Ep Name.avi")] + [InlineData(7, @"GJ Club (2013)/GJ Club - 07.mkv")] + [InlineData(17, @"Case Closed (1996-2007)/Case Closed - 317.mkv")] + // TODO: [InlineData(2, @"The Simpsons/The Simpsons 5 - 02 - Ep Name.avi")] + // TODO: [InlineData(2, @"The Simpsons/The Simpsons 5 - 02 Ep Name.avi")] + // TODO: [InlineData(7, @"Seinfeld/Seinfeld 0807 The Checks.avi")] + // This is not supported anymore after removing the episode number 365+ hack from EpisodePathParser + // TODO: [InlineData(13, @"Case Closed (1996-2007)/Case Closed - 13.mkv")] + public void GetEpisodeNumberFromFileTest(int episodeNumber, string path) { var options = new NamingOptions(); var result = new EpisodeResolver(options) .Resolve(path, false); - return result.EpisodeNumber; + Assert.Equal(episodeNumber, result.EpisodeNumber); } - - private int? GetSeasonNumberFromFile(string path) - { - var options = new NamingOptions(); - - var result = new EpisodeResolver(options) - .Resolve(path, false); - - return result.SeasonNumber; - } - } } diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs index d959a50661..4b56067153 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs @@ -33,7 +33,9 @@ namespace Jellyfin.Naming.Tests.TV // TODO: [InlineData("/Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", "Uchuu Senkan Yamoto 2199", 4, 3)] // TODO: [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", "The Daily Show", 25, 22)] // TODO: [InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", "Watchmen (2019)", 1, 3)] + // TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", "The Legend of Condor Heroes 2017", 1, 7)] public void ParseEpisodesCorrectly(string path, string name, int season, int episode) + { NamingOptions o = new NamingOptions(); EpisodePathParser p = new EpisodePathParser(o); @@ -44,23 +46,5 @@ namespace Jellyfin.Naming.Tests.TV Assert.Equal(season, res.SeasonNumber); Assert.Equal(episode, res.EpisodeNumber); } - - [Theory] - [InlineData("/media/Foo/Foo 889", "Foo", 889)] - [InlineData("/media/Foo/[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)] - [InlineData("D:\\media\\Foo\\Foo 889", "Foo", 889)] - [InlineData("D:\\media\\Foo\\[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)] - // TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", "The Legend of Condor Heroes 2017", 1, 7)] - public void ParseEpisodeWithoutSeason(string path, string name, int episode) - { - NamingOptions o = new NamingOptions(); - EpisodePathParser p = new EpisodePathParser(o); - var res = p.Parse(path, true, fillExtendedInfo: true); - - Assert.True(res.Success); - Assert.Equal(name, res.SeriesName); - Assert.Null(res.SeasonNumber); - Assert.Equal(episode, res.EpisodeNumber); - } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs index c2851ccdb1..364eb7ff85 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs @@ -6,42 +6,13 @@ namespace Jellyfin.Naming.Tests.TV { public class EpisodeWithoutSeasonTests { - // FIXME - // [Fact] - public void TestWithoutSeason1() - { - Test(@"/server/anything_ep02.mp4", "anything", null, 2); - } - - // FIXME - // [Fact] - public void TestWithoutSeason2() - { - Test(@"/server/anything_ep_02.mp4", "anything", null, 2); - } - - // FIXME - // [Fact] - public void TestWithoutSeason3() - { - Test(@"/server/anything_part.II.mp4", "anything", null, null); - } - - // FIXME - // [Fact] - public void TestWithoutSeason4() - { - Test(@"/server/anything_pt.II.mp4", "anything", null, null); - } - - // FIXME - // [Fact] - public void TestWithoutSeason5() - { - Test(@"/server/anything_pt_II.mp4", "anything", null, null); - } - - private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber) + // TODO: [Theory] + // TODO: [InlineData(@"/server/anything_ep02.mp4", "anything", null, 2)] + // TODO: [InlineData(@"/server/anything_ep_02.mp4", "anything", null, 2)] + // TODO: [InlineData(@"/server/anything_part.II.mp4", "anything", null, null)] + // TODO: [InlineData(@"/server/anything_pt.II.mp4", "anything", null, null)] + // TODO: [InlineData(@"/server/anything_pt_II.mp4", "anything", null, null)] + public void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber) { var options = new NamingOptions(); diff --git a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs index b15dd6b74c..3513050b64 100644 --- a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs @@ -6,100 +6,75 @@ namespace Jellyfin.Naming.Tests.TV { public class MultiEpisodeTests { - [Fact] - public void TestGetEndingEpisodeNumberFromFile() - { - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/4x01 – 20 Hours in America (1).mkv")); - - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/01x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01E02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01xE02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname 01x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01E02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01xE02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02x03 - 04 Ep Name.mp4")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/My show name 02x03 - 04 Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/02x03-04-15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03-E15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03x04x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4")); - Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4")); - Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 1/S01E23-E24-E26 - The Woman.mp4")); - - - // Four Digits seasons - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/2009x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009E02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009xE02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname 2009x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009x02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009E02 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009xE02 blah.avi")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03-E15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4")); - Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4")); - Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4")); - Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4")); - - // Without season number - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/02 - blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02 - blah 14 blah.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/02 - blah-02 a.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02.avi")); - - Assert.Equal(3, GetEndingEpisodeNumberFromFile(@"Season 1/02-03 - blah.avi")); - Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 2/02-04 - blah 14 blah.avi")); - Assert.Equal(5, GetEndingEpisodeNumberFromFile(@"Season 1/02-05 - blah-02 a.avi")); - Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 2/02-04.avi")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv")); - - // With format specification that must not be detected as ending episode number - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-1080p.mkv")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-720p.mkv")); - Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-720i.mkv")); - Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 1/MOONLIGHTING_s01e01-e04.mkv")); - } - - [Fact] - public void TestGetEndingEpisodeNumberFromFolder() - { - Assert.Equal(4, GetEndingEpisodeNumberFromFolder(@"Season 1/MOONLIGHTING_s01e01-e04")); - } - - private int? GetEndingEpisodeNumberFromFolder(string path) + [Theory] + [InlineData(@"Season 1/4x01 – 20 Hours in America (1).mkv", null)] + [InlineData(@"Season 1/01x02 blah.avi", null)] + [InlineData(@"Season 1/S01x02 blah.avi", null)] + [InlineData(@"Season 1/S01E02 blah.avi", null)] + [InlineData(@"Season 1/S01xE02 blah.avi", null)] + [InlineData(@"Season 1/seriesname 01x02 blah.avi", null)] + [InlineData(@"Season 1/seriesname S01x02 blah.avi", null)] + [InlineData(@"Season 1/seriesname S01E02 blah.avi", null)] + [InlineData(@"Season 1/seriesname S01xE02 blah.avi", null)] + [InlineData(@"Season 2/02x03 - 04 Ep Name.mp4", null)] + [InlineData(@"Season 2/My show name 02x03 - 04 Ep Name.mp4", null)] + [InlineData(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2/02x03-04-15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/02x03-E15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/02x03x04x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", 26)] + [InlineData(@"Season 1/S01E23-E24-E26 - The Woman.mp4", 26)] + // Four Digits seasons + [InlineData(@"Season 2009/2009x02 blah.avi", null)] + [InlineData(@"Season 2009/S2009x02 blah.avi", null)] + [InlineData(@"Season 2009/S2009E02 blah.avi", null)] + [InlineData(@"Season 2009/S2009xE02 blah.avi", null)] + [InlineData(@"Season 2009/seriesname 2009x02 blah.avi", null)] + [InlineData(@"Season 2009/seriesname S2009x02 blah.avi", null)] + [InlineData(@"Season 2009/seriesname S2009E02 blah.avi", null)] + [InlineData(@"Season 2009/seriesname S2009xE02 blah.avi", null)] + [InlineData(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/2009x03-04-15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/2009x03-E15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/2009x03x04x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4", 15)] + [InlineData(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4", 26)] + [InlineData(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4", 26)] + // Without season number + [InlineData(@"Season 1/02 - blah.avi", null)] + [InlineData(@"Season 2/02 - blah 14 blah.avi", null)] + [InlineData(@"Season 1/02 - blah-02 a.avi", null)] + [InlineData(@"Season 2/02.avi", null)] + [InlineData(@"Season 1/02-03 - blah.avi", 3)] + [InlineData(@"Season 2/02-04 - blah 14 blah.avi", 4)] + [InlineData(@"Season 1/02-05 - blah-02 a.avi", 5)] + [InlineData(@"Season 2/02-04.avi", 4)] + [InlineData(@"Season 2 /[HorribleSubs] Hunter X Hunter - 136[720p].mkv", null)] + // With format specification that must not be detected as ending episode number + [InlineData(@"Season 1/series-s09e14-1080p.mkv", null)] + [InlineData(@"Season 1/series-s09e14-720p.mkv", null)] + [InlineData(@"Season 1/series-s09e14-720i.mkv", null)] + [InlineData(@"Season 1/MOONLIGHTING_s01e01-e04.mkv", 4)] + [InlineData(@"Season 1/MOONLIGHTING_s01e01-e04", 4)] + public void TestGetEndingEpisodeNumberFromFile(string filename, int? endingEpisodeNumber) { var options = new NamingOptions(); var result = new EpisodePathParser(options) - .Parse(path, true); + .Parse(filename, false); - return result.EndingEpsiodeNumber; - } - - private int? GetEndingEpisodeNumberFromFile(string path) - { - var options = new NamingOptions(); - - var result = new EpisodePathParser(options) - .Parse(path, false); - - return result.EndingEpsiodeNumber; + Assert.Equal(result.EndingEpsiodeNumber, endingEpisodeNumber); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs index df683fc342..078f940b29 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs @@ -5,107 +5,27 @@ namespace Jellyfin.Naming.Tests.TV { public class SeasonFolderTests { - [Fact] - public void TestGetSeasonNumberFromPath1() - { - Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season 1")); - } - - [Fact] - public void TestGetSeasonNumberFromPath2() - { - Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Season 2")); - } - - [Fact] - public void TestGetSeasonNumberFromPath3() - { - Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Season 02")); - } - - [Fact] - public void TestGetSeasonNumberFromPath4() - { - Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season 1")); - } - - [Fact] - public void TestGetSeasonNumberFromPath5() - { - Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Seinfeld/S02")); - } - - [Fact] - public void TestGetSeasonNumberFromPath6() - { - Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Seinfeld/2")); - } - - [Fact] - public void TestGetSeasonNumberFromPath7() - { - Assert.Equal(2009, GetSeasonNumberFromPath(@"/Drive/Season 2009")); - } - - [Fact] - public void TestGetSeasonNumberFromPath8() - { - Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season1")); - } - - [Fact] - public void TestGetSeasonNumberFromPath9() - { - Assert.Equal(4, GetSeasonNumberFromPath(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH")); - } - - [Fact] - public void TestGetSeasonNumberFromPath10() - { - Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Season 7 (2016)")); - } - - [Fact] - public void TestGetSeasonNumberFromPath11() - { - Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Staffel 7 (2016)")); - } - - [Fact] - public void TestGetSeasonNumberFromPath12() - { - Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Stagione 7 (2016)")); - } - - [Fact] - public void TestGetSeasonNumberFromPath14() - { - Assert.Null(GetSeasonNumberFromPath(@"/Drive/Season (8)")); - } - - [Fact] - public void TestGetSeasonNumberFromPath13() - { - Assert.Equal(3, GetSeasonNumberFromPath(@"/Drive/3.Staffel")); - } - - [Fact] - public void TestGetSeasonNumberFromPath15() - { - Assert.Null(GetSeasonNumberFromPath(@"/Drive/s06e05")); - } - - [Fact] - public void TestGetSeasonNumberFromPath16() - { - Assert.Null(GetSeasonNumberFromPath(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv")); - } - - private int? GetSeasonNumberFromPath(string path) + [Theory] + [InlineData(@"/Drive/Season 1", 1)] + [InlineData(@"/Drive/Season 2", 2)] + [InlineData(@"/Drive/Season 02", 2)] + [InlineData(@"/Drive/Seinfeld/S02", 2)] + [InlineData(@"/Drive/Seinfeld/2", 2)] + [InlineData(@"/Drive/Season 2009", 2009)] + [InlineData(@"/Drive/Season1", 1)] + [InlineData(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH", 4)] + [InlineData(@"/Drive/Season 7 (2016)", 7)] + [InlineData(@"/Drive/Staffel 7 (2016)", 7)] + [InlineData(@"/Drive/Stagione 7 (2016)", 7)] + [InlineData(@"/Drive/Season (8)", null)] + [InlineData(@"/Drive/3.Staffel", 3)] + [InlineData(@"/Drive/s06e05", null)] + [InlineData(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv", null)] + public void GetSeasonNumberFromPathTest(string path, int? seasonNumber) { var result = SeasonPathParser.Parse(path, true, true); - return result.SeasonNumber; + Assert.Equal(result.SeasonNumber, seasonNumber); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs index 1df28c974c..9eaf897b9e 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs @@ -10,6 +10,50 @@ namespace Jellyfin.Naming.Tests.TV [Theory] [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 25)] + [InlineData("/Show/Season 02/S02E03 blah.avi", 2)] + [InlineData("Season 1/seriesname S01x02 blah.avi", 1)] + [InlineData("Season 1/S01x02 blah.avi", 1)] + [InlineData("Season 1/seriesname S01xE02 blah.avi", 1)] + [InlineData("Season 1/01x02 blah.avi", 1)] + [InlineData("Season 1/S01E02 blah.avi", 1)] + [InlineData("Season 1/S01xE02 blah.avi", 1)] + [InlineData("Season 1/seriesname 01x02 blah.avi", 1)] + [InlineData("Season 1/seriesname S01E02 blah.avi", 1)] + [InlineData("Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", 2)] + [InlineData("Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4", 2)] + [InlineData("Season 2/02x03-04-15 - Ep Name.mp4", 2)] + [InlineData("Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", 2)] + [InlineData("Season 02/02x03-E15 - Ep Name.mp4", 2)] + [InlineData("Season 02/Elementary - 02x03-E15 - Ep Name.mp4", 2)] + [InlineData("Season 02/02x03 - x04 - x15 - Ep Name.mp4", 2)] + [InlineData("Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", 2)] + [InlineData("Season 02/02x03x04x15 - Ep Name.mp4", 2)] + [InlineData("Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", 2)] + [InlineData("Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", 1)] + [InlineData("Season 1/S01E23-E24-E26 - The Woman.mp4", 1)] + [InlineData("Season 25/The Simpsons.S25E09.Steal this episode.mp4", 25)] + [InlineData("The Simpsons/The Simpsons.S25E09.Steal this episode.mp4", 25)] + [InlineData("2016/Season s2016e1.mp4", 2016)] + [InlineData("2016/Season 2016x1.mp4", 2016)] + [InlineData("Season 2009/2009x02 blah.avi", 2009)] + [InlineData("Season 2009/S2009x02 blah.avi", 2009)] + [InlineData("Season 2009/S2009E02 blah.avi", 2009)] + [InlineData("Season 2009/S2009xE02 blah.avi", 2009)] + [InlineData("Season 2009/seriesname 2009x02 blah.avi", 2009)] + [InlineData("Season 2009/seriesname S2009x02 blah.avi", 2009)] + [InlineData("Season 2009/seriesname S2009E02 blah.avi", 2009)] + [InlineData("Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/2009x03-04-15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/2009x03x04x15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4", 2009)] + [InlineData("Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4", 2009)] + [InlineData("Season 2009/S2009E23-E24-E26 - The Woman.mp4", 2009)] + [InlineData("Series/1-12 - The Woman.mp4", 1)] + [InlineData(@"Running Man/Running Man S2017E368.mkv", 2017)] + [InlineData(@"Case Closed (1996-2007)/Case Closed - 317.mkv", 3)] + // TODO: [InlineData(@"Seinfeld/Seinfeld 0807 The Checks.avi", 8)] public void GetSeasonNumberFromEpisodeFileTest(string path, int? expected) { var result = new EpisodeResolver(_namingOptions) @@ -17,299 +61,5 @@ namespace Jellyfin.Naming.Tests.TV Assert.Equal(expected, result.SeasonNumber); } - - private int? GetSeasonNumberFromEpisodeFile(string path) - { - var result = new EpisodeResolver(_namingOptions) - .Resolve(path, false); - - return result.SeasonNumber; - } - - [Fact] - public void TestSeasonNumber1() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"/Show/Season 02/S02E03 blah.avi")); - } - - [Fact] - public void TestSeasonNumber2() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01x02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber3() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01x02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber4() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01xE02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber5() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/01x02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber6() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01E02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber7() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01xE02 blah.avi")); - } - - // FIXME - // [Fact] - public void TestSeasonNumber8() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname 01x02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber9() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01x02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber10() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01E02 blah.avi")); - } - - [Fact] - public void TestSeasonNumber11() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber12() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber13() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/02x03-04-15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber14() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber15() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03-E15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber16() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber17() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber18() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber19() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber20() - { - Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestSeasonNumber21() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestSeasonNumber22() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestSeasonNumber23() - { - Assert.Equal(25, GetSeasonNumberFromEpisodeFile(@"Season 25/The Simpsons.S25E09.Steal this episode.mp4")); - } - - [Fact] - public void TestSeasonNumber24() - { - Assert.Equal(25, GetSeasonNumberFromEpisodeFile(@"The Simpsons/The Simpsons.S25E09.Steal this episode.mp4")); - } - - [Fact] - public void TestSeasonNumber25() - { - Assert.Equal(2016, GetSeasonNumberFromEpisodeFile(@"2016/Season s2016e1.mp4")); - } - - // FIXME - // [Fact] - public void TestSeasonNumber26() - { - // This convention is not currently supported, just adding in case we want to look at it in the future - Assert.Equal(2016, GetSeasonNumberFromEpisodeFile(@"2016/Season 2016x1.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber1() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber2() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009x02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber3() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber4() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009xE02 blah.avi")); - } - - // FIXME - // [Fact] - public void TestFourDigitSeasonNumber5() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname 2009x02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber6() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname S2009x02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber7() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname S2009E02 blah.avi")); - } - - [Fact] - public void TestFourDigitSeasonNumber8() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber9() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber10() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber11() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber12() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber13() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber14() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber15() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber16() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber17() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber18() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber19() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestFourDigitSeasonNumber20() - { - Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4")); - } - - [Fact] - public void TestNoSeriesFolder() - { - Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Series/1-12 - The Woman.mp4")); - } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs index c9323c218e..de253ce375 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs @@ -6,81 +6,25 @@ namespace Jellyfin.Naming.Tests.TV { public class SimpleEpisodeTests { - [Fact] - public void TestSimpleEpisodePath1() - { - Test(@"/server/anything_s01e02.mp4", "anything", 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath2() - { - Test(@"/server/anything_s1e2.mp4", "anything", 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath3() - { - Test(@"/server/anything_s01.e02.mp4", "anything", 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath4() - { - Test(@"/server/anything_s01_e02.mp4", "anything", 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath5() - { - Test(@"/server/anything_102.mp4", "anything", 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath6() - { - Test(@"/server/anything_1x02.mp4", "anything", 1, 2); - } - - // FIXME - // [Fact] - public void TestSimpleEpisodePath7() - { - Test(@"/server/The Walking Dead 4x01.mp4", "The Walking Dead", 4, 1); - } - - [Fact] - public void TestSimpleEpisodePath8() - { - Test(@"/server/the_simpsons-s02e01_18536.mp4", "the_simpsons", 2, 1); - } - - - [Fact] - public void TestSimpleEpisodePath9() - { - Test(@"/server/Temp/S01E02 foo.mp4", string.Empty, 1, 2); - } - - [Fact] - public void TestSimpleEpisodePath10() - { - Test(@"Series/4-12 - The Woman.mp4", string.Empty, 4, 12); - } - - [Fact] - public void TestSimpleEpisodePath11() - { - Test(@"Series/4x12 - The Woman.mp4", string.Empty, 4, 12); - } - - [Fact] - public void TestSimpleEpisodePath12() - { - Test(@"Series/LA X, Pt. 1_s06e32.mp4", "LA X, Pt. 1", 6, 32); - } - - private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber) + [Theory] + [InlineData("/server/anything_s01e02.mp4", "anything", 1, 2)] + [InlineData("/server/anything_s1e2.mp4", "anything", 1, 2)] + [InlineData("/server/anything_s01.e02.mp4", "anything", 1, 2)] + [InlineData("/server/anything_102.mp4", "anything", 1, 2)] + [InlineData("/server/anything_1x02.mp4", "anything", 1, 2)] + [InlineData("/server/The Walking Dead 4x01.mp4", "The Walking Dead", 4, 1)] + [InlineData("/server/the_simpsons-s02e01_18536.mp4", "the_simpsons", 2, 1)] + [InlineData("/server/Temp/S01E02 foo.mp4", "", 1, 2)] + [InlineData("Series/4-12 - The Woman.mp4", "", 4, 12)] + [InlineData("Series/4x12 - The Woman.mp4", "", 4, 12)] + [InlineData("Series/LA X, Pt. 1_s06e32.mp4", "LA X, Pt. 1", 6, 32)] + [InlineData("[Baz-Bar]Foo - [1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)] + [InlineData(@"/Foo/The.Series.Name.S01E04.WEBRip.x264-Baz[Bar]/the.series.name.s01e04.webrip.x264-Baz[Bar].mkv", "The.Series.Name", 1, 4)] + [InlineData(@"Love.Death.and.Robots.S01.1080p.NF.WEB-DL.DDP5.1.x264-NTG/Love.Death.and.Robots.S01E01.Sonnies.Edge.1080p.NF.WEB-DL.DDP5.1.x264-NTG.mkv", "Love.Death.and.Robots", 1, 1)] + // TODO: [InlineData("[Baz-Bar]Foo - 01 - 12[1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)] + // TODO: [InlineData("E:\\Anime\\Yahari Ore no Seishun Love Comedy wa Machigatteiru\\Yahari Ore no Seishun Love Comedy wa Machigatteiru. Zoku\\Oregairu Zoku 11 - Hayama Hayato Always Renconds to Everyone's Expectations..mkv", "Yahari Ore no Seishun Love Comedy wa Machigatteiru", null, 11)] + // TODO: [InlineData(@"/Library/Series/The Grand Tour (2016)/Season 1/S01E01 The Holy Trinity.mkv", "The Grand Tour", 1, 1)] + public void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber) { var options = new NamingOptions(); From 8e20d2e931b273b54ef369c9b75021ab04118e04 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 27 Feb 2020 14:51:34 +0300 Subject: [PATCH 010/104] Simplify AlphanumericComparer, reduce code duplication --- MediaBrowser.Controller/Entities/BaseItem.cs | 8 +- .../Sorting/AlphanumComparator.cs | 32 +++-- .../Sorting/SortExtensions.cs | 122 +----------------- MediaBrowser.Controller/Sorting/SortHelper.cs | 25 ---- 4 files changed, 22 insertions(+), 165 deletions(-) rename {Emby.Server.Implementations => MediaBrowser.Controller}/Sorting/AlphanumComparator.cs (73%) delete mode 100644 MediaBrowser.Controller/Sorting/SortHelper.cs diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 353c675cb3..09cdfc9eaf 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -387,15 +387,12 @@ namespace MediaBrowser.Controller.Entities while (thisMarker < s1.Length) { - if (thisMarker >= s1.Length) - { - break; - } char thisCh = s1[thisMarker]; var thisChunk = new StringBuilder(); + bool isNumeric = char.IsDigit(thisCh); - while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || SortHelper.InChunk(thisCh, thisChunk[0]))) + while ((thisMarker < s1.Length) && (char.IsDigit(thisCh) == isNumeric)) { thisChunk.Append(thisCh); thisMarker++; @@ -406,7 +403,6 @@ namespace MediaBrowser.Controller.Entities } } - var isNumeric = thisChunk.Length > 0 && char.IsDigit(thisChunk[0]); list.Add(new Tuple(thisChunk, isNumeric)); } diff --git a/Emby.Server.Implementations/Sorting/AlphanumComparator.cs b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs similarity index 73% rename from Emby.Server.Implementations/Sorting/AlphanumComparator.cs rename to MediaBrowser.Controller/Sorting/AlphanumComparator.cs index 2e00c24d75..ad1eaf7bf7 100644 --- a/Emby.Server.Implementations/Sorting/AlphanumComparator.cs +++ b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; using MediaBrowser.Controller.Sorting; -namespace Emby.Server.Implementations.Sorting +namespace MediaBrowser.Controller.Sorting { public class AlphanumComparator : IComparer { @@ -31,8 +31,9 @@ namespace Emby.Server.Implementations.Sorting var thisChunk = new StringBuilder(); var thatChunk = new StringBuilder(); + bool thisNumeric = char.IsDigit(thisCh), thatNumeric = char.IsDigit(thatCh); - while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || SortHelper.InChunk(thisCh, thisChunk[0]))) + while ((thisMarker < s1.Length) && (char.IsDigit(thisCh) == thisNumeric)) { thisChunk.Append(thisCh); thisMarker++; @@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.Sorting } } - while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || SortHelper.InChunk(thatCh, thatChunk[0]))) + while ((thatMarker < s2.Length) && (char.IsDigit(thatCh) == thatNumeric)) { thatChunk.Append(thatCh); thatMarker++; @@ -54,38 +55,35 @@ namespace Emby.Server.Implementations.Sorting } } - int result = 0; + // If both chunks contain numeric characters, sort them numerically - if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0])) + if (thisNumeric && thatNumeric) { - if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk)) - { - return 0; - } - if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk)) + if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk) + || !int.TryParse(thatChunk.ToString(), out thatNumericChunk)) { return 0; } if (thisNumericChunk < thatNumericChunk) { - result = -1; + return -1; } if (thisNumericChunk > thatNumericChunk) { - result = 1; + return 1; } } else { - result = thisChunk.ToString().CompareTo(thatChunk.ToString()); + int result = thisChunk.ToString().CompareTo(thatChunk.ToString()); + if (result != 0) + { + return result; + } } - if (result != 0) - { - return result; - } } return 0; diff --git a/MediaBrowser.Controller/Sorting/SortExtensions.cs b/MediaBrowser.Controller/Sorting/SortExtensions.cs index 111f4f17fc..bc98c58f0c 100644 --- a/MediaBrowser.Controller/Sorting/SortExtensions.cs +++ b/MediaBrowser.Controller/Sorting/SortExtensions.cs @@ -7,137 +7,25 @@ namespace MediaBrowser.Controller.Sorting { public static class SortExtensions { + private static IComparer _comparer = new AlphanumComparator(); public static IEnumerable OrderByString(this IEnumerable list, Func getName) { - return list.OrderBy(getName, new AlphanumComparator()); + return list.OrderBy(getName, _comparer); } public static IEnumerable OrderByStringDescending(this IEnumerable list, Func getName) { - return list.OrderByDescending(getName, new AlphanumComparator()); + return list.OrderByDescending(getName, _comparer); } public static IOrderedEnumerable ThenByString(this IOrderedEnumerable list, Func getName) { - return list.ThenBy(getName, new AlphanumComparator()); + return list.ThenBy(getName, _comparer); } public static IOrderedEnumerable ThenByStringDescending(this IOrderedEnumerable list, Func getName) { - return list.ThenByDescending(getName, new AlphanumComparator()); - } - - private class AlphanumComparator : IComparer - { - private enum ChunkType { Alphanumeric, Numeric }; - - private static bool InChunk(char ch, char otherCh) - { - var type = ChunkType.Alphanumeric; - - if (char.IsDigit(otherCh)) - { - type = ChunkType.Numeric; - } - - if ((type == ChunkType.Alphanumeric && char.IsDigit(ch)) - || (type == ChunkType.Numeric && !char.IsDigit(ch))) - { - return false; - } - - return true; - } - - public static int CompareValues(string s1, string s2) - { - if (s1 == null || s2 == null) - { - return 0; - } - - int thisMarker = 0, thisNumericChunk = 0; - int thatMarker = 0, thatNumericChunk = 0; - - while ((thisMarker < s1.Length) || (thatMarker < s2.Length)) - { - if (thisMarker >= s1.Length) - { - return -1; - } - else if (thatMarker >= s2.Length) - { - return 1; - } - char thisCh = s1[thisMarker]; - char thatCh = s2[thatMarker]; - - var thisChunk = new StringBuilder(); - var thatChunk = new StringBuilder(); - - while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || InChunk(thisCh, thisChunk[0]))) - { - thisChunk.Append(thisCh); - thisMarker++; - - if (thisMarker < s1.Length) - { - thisCh = s1[thisMarker]; - } - } - - while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || InChunk(thatCh, thatChunk[0]))) - { - thatChunk.Append(thatCh); - thatMarker++; - - if (thatMarker < s2.Length) - { - thatCh = s2[thatMarker]; - } - } - - int result = 0; - // If both chunks contain numeric characters, sort them numerically - if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0])) - { - if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk)) - { - return 0; - } - if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk)) - { - return 0; - } - - if (thisNumericChunk < thatNumericChunk) - { - result = -1; - } - - if (thisNumericChunk > thatNumericChunk) - { - result = 1; - } - } - else - { - result = thisChunk.ToString().CompareTo(thatChunk.ToString()); - } - - if (result != 0) - { - return result; - } - } - - return 0; - } - - public int Compare(string x, string y) - { - return CompareValues(x, y); - } + return list.ThenByDescending(getName, _comparer); } } } diff --git a/MediaBrowser.Controller/Sorting/SortHelper.cs b/MediaBrowser.Controller/Sorting/SortHelper.cs deleted file mode 100644 index 05981d9757..0000000000 --- a/MediaBrowser.Controller/Sorting/SortHelper.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace MediaBrowser.Controller.Sorting -{ - public static class SortHelper - { - private enum ChunkType { Alphanumeric, Numeric }; - - public static bool InChunk(char ch, char otherCh) - { - var type = ChunkType.Alphanumeric; - - if (char.IsDigit(otherCh)) - { - type = ChunkType.Numeric; - } - - if ((type == ChunkType.Alphanumeric && char.IsDigit(ch)) - || (type == ChunkType.Numeric && !char.IsDigit(ch))) - { - return false; - } - - return true; - } - } -} From d1670f81808a74865f8c4fb2c2a77ab01eb3bde9 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 27 Feb 2020 16:02:18 +0300 Subject: [PATCH 011/104] Apply suggestions from code review Co-Authored-By: Claus Vium --- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- MediaBrowser.Controller/Sorting/AlphanumComparator.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 09cdfc9eaf..66de080a39 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -392,7 +392,7 @@ namespace MediaBrowser.Controller.Entities var thisChunk = new StringBuilder(); bool isNumeric = char.IsDigit(thisCh); - while ((thisMarker < s1.Length) && (char.IsDigit(thisCh) == isNumeric)) + while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric) { thisChunk.Append(thisCh); thisMarker++; diff --git a/MediaBrowser.Controller/Sorting/AlphanumComparator.cs b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs index ad1eaf7bf7..65dc120ca8 100644 --- a/MediaBrowser.Controller/Sorting/AlphanumComparator.cs +++ b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.Sorting var thatChunk = new StringBuilder(); bool thisNumeric = char.IsDigit(thisCh), thatNumeric = char.IsDigit(thatCh); - while ((thisMarker < s1.Length) && (char.IsDigit(thisCh) == thisNumeric)) + while (thisMarker < s1.Length && char.IsDigit(thisCh) == thisNumeric) { thisChunk.Append(thisCh); thisMarker++; @@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Sorting } } - while ((thatMarker < s2.Length) && (char.IsDigit(thatCh) == thatNumeric)) + while (thatMarker < s2.Length && char.IsDigit(thatCh) == thatNumeric) { thatChunk.Append(thatCh); thatMarker++; From e80444d11b874f539c9a41f7867c8f93a0fbaeec Mon Sep 17 00:00:00 2001 From: dkanada Date: Fri, 28 Feb 2020 01:43:57 +0900 Subject: [PATCH 012/104] use the custom server for external ids --- .../Configuration/PluginConfiguration.cs | 36 ++++++++++++++++--- .../MusicBrainz/Configuration/config.html | 8 ++--- .../Plugins/MusicBrainz/ExternalIds.cs | 25 ++++++------- .../Plugins/MusicBrainz/Plugin.cs | 20 ++++++----- 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs index 607ba0cbcb..bd13cdddd5 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs @@ -1,13 +1,41 @@ -using System.Collections.Generic; -using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Plugins; namespace MediaBrowser.Providers.Plugins.MusicBrainz { public class PluginConfiguration : BasePluginConfiguration { - public string Server { get; set; } = "https://www.musicbrainz.org"; + private string server = Plugin.Instance.DefaultServer; - public long RateLimit { get; set; } = 1000u; + private long rateLimit = Plugin.Instance.DefaultRateLimit; + + public string Server + { + get + { + return server; + } + + set + { + server = value.TrimEnd('/'); + } + } + + public long RateLimit + { + get + { + return rateLimit; + } + + set + { + if (value < 2000u && server == Plugin.Instance.DefaultServer) + { + RateLimit = Plugin.Instance.DefaultRateLimit; + } + } + } public bool Enable { get; set; } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html index dfffa90655..1f02461da2 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html @@ -9,12 +9,12 @@
- -
This can either be a mirror of the official server or a custom server.
+ +
This can be a mirror of the official server or even a custom server.
- -
Span of time between each request in milliseconds.
+ +
Span of time between requests in milliseconds. The official server is limited to one request every two seconds.
public void NotifyPendingRestart() { - Logger.LogInformation("App needs to be restarted."); + _logger.LogInformation("App needs to be restarted."); var changed = !HasPendingRestart; @@ -1278,7 +1273,7 @@ namespace Emby.Server.Implementations if (changed) { - EventHelper.QueueEventIfNotNull(HasPendingRestartChanged, this, EventArgs.Empty, Logger); + EventHelper.QueueEventIfNotNull(HasPendingRestartChanged, this, EventArgs.Empty, _logger); } } @@ -1307,10 +1302,10 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - Logger.LogError(ex, "Error sending server restart notification"); + _logger.LogError(ex, "Error sending server restart notification"); } - Logger.LogInformation("Calling RestartInternal"); + _logger.LogInformation("Calling RestartInternal"); RestartInternal(); }); @@ -1335,11 +1330,11 @@ namespace Emby.Server.Implementations } catch (FileLoadException ex) { - Logger.LogError(ex, "Failed to load assembly {Path}", file); + _logger.LogError(ex, "Failed to load assembly {Path}", file); continue; } - Logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file); + _logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file); yield return plugAss; } } @@ -1474,7 +1469,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - Logger.LogError(ex, "Error getting local Ip address information"); + _logger.LogError(ex, "Error getting local Ip address information"); } return null; @@ -1637,19 +1632,19 @@ namespace Emby.Server.Implementations var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase); _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid); - Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid); + _logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid); return valid; } } } catch (OperationCanceledException) { - Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled"); + _logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled"); throw; } catch (Exception ex) { - Logger.LogDebug(ex, "Ping test result to {0}. Success: {1}", apiUrl, false); + _logger.LogDebug(ex, "Ping test result to {0}. Success: {1}", apiUrl, false); _validAddressResults.AddOrUpdate(apiUrl, false, (k, v) => false); return false; @@ -1679,7 +1674,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - Logger.LogError(ex, "Error sending server shutdown notification"); + _logger.LogError(ex, "Error sending server shutdown notification"); } ShutdownInternal(); @@ -1741,7 +1736,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - Logger.LogError(ex, "Error launching url: {url}", url); + _logger.LogError(ex, "Error launching url: {url}", url); throw; } } @@ -1781,14 +1776,14 @@ namespace Emby.Server.Implementations { var type = GetType(); - Logger.LogInformation("Disposing {Type}", type.Name); + _logger.LogInformation("Disposing {Type}", type.Name); var parts = _disposableParts.Distinct().Where(i => i.GetType() != type).ToList(); _disposableParts.Clear(); foreach (var part in parts) { - Logger.LogInformation("Disposing {Type}", part.GetType().Name); + _logger.LogInformation("Disposing {Type}", part.GetType().Name); try { @@ -1796,7 +1791,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name); + _logger.LogError(ex, "Error disposing {Type}", part.GetType().Name); } } From c49a12dd73541c588e22ff558436652c188badb2 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 3 Mar 2020 23:31:25 +0100 Subject: [PATCH 047/104] Make LoggerFactory private in ApplicationHost and use it to construct loggers with context --- .../ApplicationHost.cs | 90 +++++++++---------- .../SocketSharp/WebSocketSharpListener.cs | 5 +- 2 files changed, 44 insertions(+), 51 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 71cff80af0..9b478772cc 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -118,8 +118,10 @@ namespace Emby.Server.Implementations /// public abstract class ApplicationHost : IServerApplicationHost, IDisposable { - private SqliteUserRepository _userRepository; + private readonly ILoggerFactory _loggerFactory; + private readonly ILogger _logger; + private SqliteUserRepository _userRepository; private SqliteDisplayPreferencesRepository _displayPreferencesRepository; /// @@ -166,7 +168,6 @@ namespace Emby.Server.Implementations /// public bool IsShuttingDown { get; private set; } - private ILogger _logger; private IPlugin[] _plugins; /// @@ -175,12 +176,6 @@ namespace Emby.Server.Implementations /// The plugins. public IReadOnlyList Plugins => _plugins; - /// - /// Gets or sets the logger factory. - /// - /// The logger factory. - public ILoggerFactory LoggerFactory { get; protected set; } - /// /// Gets or sets the application paths. /// @@ -365,6 +360,8 @@ namespace Emby.Server.Implementations INetworkManager networkManager, IConfiguration configuration) { + _loggerFactory = loggerFactory; + _logger = _loggerFactory.CreateLogger("App"); _configuration = configuration; XmlSerializer = new MyXmlSerializer(); @@ -373,12 +370,9 @@ namespace Emby.Server.Implementations networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets; ApplicationPaths = applicationPaths; - LoggerFactory = loggerFactory; FileSystemManager = fileSystem; - ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager); - - _logger = LoggerFactory.CreateLogger("App"); + ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, _loggerFactory, XmlSerializer, FileSystemManager); StartupOptions = options; @@ -447,7 +441,7 @@ namespace Emby.Server.Implementations { if (_deviceId == null) { - _deviceId = new DeviceId(ApplicationPaths, LoggerFactory); + _deviceId = new DeviceId(ApplicationPaths, _loggerFactory); } return _deviceId.Value; @@ -647,7 +641,7 @@ namespace Emby.Server.Implementations var response = context.Response; var localPath = context.Request.Path.ToString(); - var req = new WebSocketSharpRequest(request, response, request.Path, _logger); + var req = new WebSocketSharpRequest(request, response, request.Path, _loggerFactory.CreateLogger()); await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false); } @@ -675,7 +669,7 @@ namespace Emby.Server.Implementations HttpClient = new HttpClientManager.HttpClientManager( ApplicationPaths, - LoggerFactory.CreateLogger(), + _loggerFactory.CreateLogger(), FileSystemManager, () => ApplicationUserAgent); serviceCollection.AddSingleton(HttpClient); @@ -685,7 +679,7 @@ namespace Emby.Server.Implementations IsoManager = new IsoManager(); serviceCollection.AddSingleton(IsoManager); - TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager); + TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, _loggerFactory, FileSystemManager); serviceCollection.AddSingleton(TaskManager); serviceCollection.AddSingleton(XmlSerializer); @@ -712,22 +706,22 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ServerConfigurationManager); - LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory.CreateLogger()); + LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, _loggerFactory.CreateLogger()); await LocalizationManager.LoadAll().ConfigureAwait(false); serviceCollection.AddSingleton(LocalizationManager); serviceCollection.AddSingleton(new BdInfoExaminer(FileSystemManager)); - UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager); + UserDataManager = new UserDataManager(_loggerFactory, ServerConfigurationManager, () => UserManager); serviceCollection.AddSingleton(UserDataManager); _displayPreferencesRepository = new SqliteDisplayPreferencesRepository( - LoggerFactory.CreateLogger(), + _loggerFactory.CreateLogger(), ApplicationPaths, FileSystemManager); serviceCollection.AddSingleton(_displayPreferencesRepository); - ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, LoggerFactory.CreateLogger(), LocalizationManager); + ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, _loggerFactory.CreateLogger(), LocalizationManager); serviceCollection.AddSingleton(ItemRepository); AuthenticationRepository = GetAuthenticationRepository(); @@ -736,7 +730,7 @@ namespace Emby.Server.Implementations _userRepository = GetUserRepository(); UserManager = new UserManager( - LoggerFactory.CreateLogger(), + _loggerFactory.CreateLogger(), _userRepository, XmlSerializer, NetworkManager, @@ -750,7 +744,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(UserManager); MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( - LoggerFactory.CreateLogger(), + _loggerFactory.CreateLogger(), ServerConfigurationManager, FileSystemManager, ProcessFactory, @@ -760,23 +754,23 @@ namespace Emby.Server.Implementations StartupOptions.FFmpegPath); serviceCollection.AddSingleton(MediaEncoder); - LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager, MediaEncoder); + LibraryManager = new LibraryManager(this, _loggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager, MediaEncoder); serviceCollection.AddSingleton(LibraryManager); var musicManager = new MusicManager(LibraryManager); serviceCollection.AddSingleton(musicManager); - LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager); + LibraryMonitor = new LibraryMonitor(_loggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager); serviceCollection.AddSingleton(LibraryMonitor); - serviceCollection.AddSingleton(new SearchEngine(LoggerFactory, LibraryManager, UserManager)); + serviceCollection.AddSingleton(new SearchEngine(_loggerFactory, LibraryManager, UserManager)); CertificateInfo = GetCertificateInfo(true); Certificate = GetCertificate(CertificateInfo); HttpServer = new HttpListenerHost( this, - LoggerFactory.CreateLogger(), + _loggerFactory.CreateLogger(), ServerConfigurationManager, _configuration, NetworkManager, @@ -789,7 +783,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(HttpServer); - ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); + ImageProcessor = new ImageProcessor(_loggerFactory.CreateLogger(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); serviceCollection.AddSingleton(ImageProcessor); TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); @@ -798,23 +792,23 @@ namespace Emby.Server.Implementations DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager); serviceCollection.AddSingleton(DeviceManager); - MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); + MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, _loggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); serviceCollection.AddSingleton(MediaSourceManager); - SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager); + SubtitleManager = new SubtitleManager(_loggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager); serviceCollection.AddSingleton(SubtitleManager); - ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer); + ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, _loggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer); serviceCollection.AddSingleton(ProviderManager); - DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager); + DtoService = new DtoService(_loggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager); serviceCollection.AddSingleton(DtoService); - ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager); + ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, _loggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager); serviceCollection.AddSingleton(ChannelManager); SessionManager = new SessionManager( - LoggerFactory.CreateLogger(), + _loggerFactory.CreateLogger(), UserDataManager, LibraryManager, UserManager, @@ -828,47 +822,47 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(SessionManager); serviceCollection.AddSingleton( - new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this)); + new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, _loggerFactory, JsonSerializer, this)); - CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager); + CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, _loggerFactory, ProviderManager); serviceCollection.AddSingleton(CollectionManager); serviceCollection.AddSingleton(typeof(IPlaylistManager), typeof(PlaylistManager)); - LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager); + LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, _loggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager); serviceCollection.AddSingleton(LiveTvManager); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager); serviceCollection.AddSingleton(UserViewManager); NotificationManager = new NotificationManager( - LoggerFactory.CreateLogger(), + _loggerFactory.CreateLogger(), UserManager, ServerConfigurationManager); serviceCollection.AddSingleton(NotificationManager); serviceCollection.AddSingleton(new DeviceDiscovery(ServerConfigurationManager)); - ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); + ChapterManager = new ChapterManager(LibraryManager, _loggerFactory, ServerConfigurationManager, ItemRepository); serviceCollection.AddSingleton(ChapterManager); - EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); + EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, _loggerFactory, MediaEncoder, ChapterManager, LibraryManager); serviceCollection.AddSingleton(EncodingManager); var activityLogRepo = GetActivityLogRepository(); serviceCollection.AddSingleton(activityLogRepo); - serviceCollection.AddSingleton(new ActivityManager(LoggerFactory, activityLogRepo, UserManager)); + serviceCollection.AddSingleton(new ActivityManager(_loggerFactory, activityLogRepo, UserManager)); var authContext = new AuthorizationContext(AuthenticationRepository, UserManager); serviceCollection.AddSingleton(authContext); serviceCollection.AddSingleton(new SessionContext(UserManager, authContext, SessionManager)); - AuthService = new AuthService(LoggerFactory.CreateLogger(), authContext, ServerConfigurationManager, SessionManager, NetworkManager); + AuthService = new AuthService(_loggerFactory.CreateLogger(), authContext, ServerConfigurationManager, SessionManager, NetworkManager); serviceCollection.AddSingleton(AuthService); SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder( LibraryManager, - LoggerFactory.CreateLogger(), + _loggerFactory.CreateLogger(), ApplicationPaths, FileSystemManager, MediaEncoder, @@ -884,7 +878,7 @@ namespace Emby.Server.Implementations _displayPreferencesRepository.Initialize(); - var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger(), ApplicationPaths); + var userDataRepo = new SqliteUserDataRepository(_loggerFactory.CreateLogger(), ApplicationPaths); SetStaticProperties(); @@ -956,7 +950,7 @@ namespace Emby.Server.Implementations private SqliteUserRepository GetUserRepository() { var repo = new SqliteUserRepository( - LoggerFactory.CreateLogger(), + _loggerFactory.CreateLogger(), ApplicationPaths); repo.Initialize(); @@ -966,7 +960,7 @@ namespace Emby.Server.Implementations private IAuthenticationRepository GetAuthenticationRepository() { - var repo = new AuthenticationRepository(LoggerFactory, ServerConfigurationManager); + var repo = new AuthenticationRepository(_loggerFactory, ServerConfigurationManager); repo.Initialize(); @@ -975,7 +969,7 @@ namespace Emby.Server.Implementations private IActivityRepository GetActivityLogRepository() { - var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager); + var repo = new ActivityRepository(_loggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager); repo.Initialize(); @@ -990,7 +984,7 @@ namespace Emby.Server.Implementations ItemRepository.ImageProcessor = ImageProcessor; // For now there's no real way to inject these properly - BaseItem.Logger = LoggerFactory.CreateLogger("BaseItem"); + BaseItem.Logger = _loggerFactory.CreateLogger("BaseItem"); BaseItem.ConfigurationManager = ServerConfigurationManager; BaseItem.LibraryManager = LibraryManager; BaseItem.ProviderManager = ProviderManager; @@ -1200,7 +1194,7 @@ namespace Emby.Server.Implementations }); } - protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(_logger); + protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(_loggerFactory.CreateLogger()); private CertificateInfo GetCertificateInfo(bool generateCertificate) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 2e12a19fd9..b85750c9b9 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -21,15 +21,14 @@ namespace Emby.Server.Implementations.SocketSharp private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); private CancellationToken _disposeCancellationToken; - public WebSocketSharpListener( - ILogger logger) + public WebSocketSharpListener(ILogger logger) { _logger = logger; - _disposeCancellationToken = _disposeCancellationTokenSource.Token; } public Func ErrorHandler { get; set; } + public Func RequestHandler { get; set; } public Action WebSocketConnected { get; set; } From 6b06a9a91937961eaf0cac610a96a6dd06f678c7 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 3 Mar 2020 23:53:48 +0100 Subject: [PATCH 048/104] Make Logger and LoggerFactory both protected in ApplicationHost --- .../ApplicationHost.cs | 155 +++++++++--------- 1 file changed, 81 insertions(+), 74 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 9b478772cc..78b65e798d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -118,9 +118,6 @@ namespace Emby.Server.Implementations /// public abstract class ApplicationHost : IServerApplicationHost, IDisposable { - private readonly ILoggerFactory _loggerFactory; - private readonly ILogger _logger; - private SqliteUserRepository _userRepository; private SqliteDisplayPreferencesRepository _displayPreferencesRepository; @@ -168,6 +165,16 @@ namespace Emby.Server.Implementations /// public bool IsShuttingDown { get; private set; } + /// + /// Gets the logger factory. + /// + protected ILoggerFactory LoggerFactory { get; } + + /// + /// Gets the logger. + /// + protected ILogger Logger { get; } + private IPlugin[] _plugins; /// @@ -360,8 +367,6 @@ namespace Emby.Server.Implementations INetworkManager networkManager, IConfiguration configuration) { - _loggerFactory = loggerFactory; - _logger = _loggerFactory.CreateLogger("App"); _configuration = configuration; XmlSerializer = new MyXmlSerializer(); @@ -370,9 +375,11 @@ namespace Emby.Server.Implementations networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets; ApplicationPaths = applicationPaths; + LoggerFactory = loggerFactory; FileSystemManager = fileSystem; - ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, _loggerFactory, XmlSerializer, FileSystemManager); + Logger = LoggerFactory.CreateLogger("App"); + ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager); StartupOptions = options; @@ -441,7 +448,7 @@ namespace Emby.Server.Implementations { if (_deviceId == null) { - _deviceId = new DeviceId(ApplicationPaths, _loggerFactory); + _deviceId = new DeviceId(ApplicationPaths, LoggerFactory); } return _deviceId.Value; @@ -479,12 +486,12 @@ namespace Emby.Server.Implementations { try { - _logger.LogDebug("Creating instance of {Type}", type); + Logger.LogDebug("Creating instance of {Type}", type); return ActivatorUtilities.CreateInstance(ServiceProvider, type); } catch (Exception ex) { - _logger.LogError(ex, "Error creating {Type}", type); + Logger.LogError(ex, "Error creating {Type}", type); return null; } } @@ -535,7 +542,7 @@ namespace Emby.Server.Implementations /// . public async Task RunStartupTasksAsync() { - _logger.LogInformation("Running startup tasks"); + Logger.LogInformation("Running startup tasks"); Resolve().AddTasks(GetExports(false)); @@ -543,21 +550,21 @@ namespace Emby.Server.Implementations MediaEncoder.SetFFmpegPath(); - _logger.LogInformation("ServerId: {0}", SystemId); + Logger.LogInformation("ServerId: {0}", SystemId); var entryPoints = GetExports(); var stopWatch = new Stopwatch(); stopWatch.Start(); await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false); - _logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); + Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); - _logger.LogInformation("Core startup complete"); + Logger.LogInformation("Core startup complete"); HttpServer.GlobalResponse = null; stopWatch.Restart(); await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); - _logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); + Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); stopWatch.Stop(); } @@ -570,7 +577,7 @@ namespace Emby.Server.Implementations continue; } - _logger.LogDebug("Starting entry point {Type}", entryPoint.GetType()); + Logger.LogDebug("Starting entry point {Type}", entryPoint.GetType()); yield return entryPoint.RunAsync(); } @@ -604,7 +611,7 @@ namespace Emby.Server.Implementations plugin.Version)); } - _logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString()); + Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString()); } DiscoverTypes(); @@ -641,7 +648,7 @@ namespace Emby.Server.Implementations var response = context.Response; var localPath = context.Request.Path.ToString(); - var req = new WebSocketSharpRequest(request, response, request.Path, _loggerFactory.CreateLogger()); + var req = new WebSocketSharpRequest(request, response, request.Path, LoggerFactory.CreateLogger()); await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false); } @@ -662,14 +669,14 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(JsonSerializer); // TODO: Support for injecting ILogger should be deprecated in favour of ILogger and this removed - serviceCollection.AddSingleton(_logger); + serviceCollection.AddSingleton(Logger); serviceCollection.AddSingleton(FileSystemManager); serviceCollection.AddSingleton(); HttpClient = new HttpClientManager.HttpClientManager( ApplicationPaths, - _loggerFactory.CreateLogger(), + LoggerFactory.CreateLogger(), FileSystemManager, () => ApplicationUserAgent); serviceCollection.AddSingleton(HttpClient); @@ -679,7 +686,7 @@ namespace Emby.Server.Implementations IsoManager = new IsoManager(); serviceCollection.AddSingleton(IsoManager); - TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, _loggerFactory, FileSystemManager); + TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager); serviceCollection.AddSingleton(TaskManager); serviceCollection.AddSingleton(XmlSerializer); @@ -706,22 +713,22 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ServerConfigurationManager); - LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, _loggerFactory.CreateLogger()); + LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory.CreateLogger()); await LocalizationManager.LoadAll().ConfigureAwait(false); serviceCollection.AddSingleton(LocalizationManager); serviceCollection.AddSingleton(new BdInfoExaminer(FileSystemManager)); - UserDataManager = new UserDataManager(_loggerFactory, ServerConfigurationManager, () => UserManager); + UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager); serviceCollection.AddSingleton(UserDataManager); _displayPreferencesRepository = new SqliteDisplayPreferencesRepository( - _loggerFactory.CreateLogger(), + LoggerFactory.CreateLogger(), ApplicationPaths, FileSystemManager); serviceCollection.AddSingleton(_displayPreferencesRepository); - ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, _loggerFactory.CreateLogger(), LocalizationManager); + ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, LoggerFactory.CreateLogger(), LocalizationManager); serviceCollection.AddSingleton(ItemRepository); AuthenticationRepository = GetAuthenticationRepository(); @@ -730,7 +737,7 @@ namespace Emby.Server.Implementations _userRepository = GetUserRepository(); UserManager = new UserManager( - _loggerFactory.CreateLogger(), + LoggerFactory.CreateLogger(), _userRepository, XmlSerializer, NetworkManager, @@ -744,7 +751,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(UserManager); MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( - _loggerFactory.CreateLogger(), + LoggerFactory.CreateLogger(), ServerConfigurationManager, FileSystemManager, ProcessFactory, @@ -754,23 +761,23 @@ namespace Emby.Server.Implementations StartupOptions.FFmpegPath); serviceCollection.AddSingleton(MediaEncoder); - LibraryManager = new LibraryManager(this, _loggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager, MediaEncoder); + LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager, MediaEncoder); serviceCollection.AddSingleton(LibraryManager); var musicManager = new MusicManager(LibraryManager); serviceCollection.AddSingleton(musicManager); - LibraryMonitor = new LibraryMonitor(_loggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager); + LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager); serviceCollection.AddSingleton(LibraryMonitor); - serviceCollection.AddSingleton(new SearchEngine(_loggerFactory, LibraryManager, UserManager)); + serviceCollection.AddSingleton(new SearchEngine(LoggerFactory, LibraryManager, UserManager)); CertificateInfo = GetCertificateInfo(true); Certificate = GetCertificate(CertificateInfo); HttpServer = new HttpListenerHost( this, - _loggerFactory.CreateLogger(), + LoggerFactory.CreateLogger(), ServerConfigurationManager, _configuration, NetworkManager, @@ -783,7 +790,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(HttpServer); - ImageProcessor = new ImageProcessor(_loggerFactory.CreateLogger(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); + ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); serviceCollection.AddSingleton(ImageProcessor); TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); @@ -792,23 +799,23 @@ namespace Emby.Server.Implementations DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager); serviceCollection.AddSingleton(DeviceManager); - MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, _loggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); + MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); serviceCollection.AddSingleton(MediaSourceManager); - SubtitleManager = new SubtitleManager(_loggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager); + SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager); serviceCollection.AddSingleton(SubtitleManager); - ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, _loggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer); + ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer); serviceCollection.AddSingleton(ProviderManager); - DtoService = new DtoService(_loggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager); + DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager); serviceCollection.AddSingleton(DtoService); - ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, _loggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager); + ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager); serviceCollection.AddSingleton(ChannelManager); SessionManager = new SessionManager( - _loggerFactory.CreateLogger(), + LoggerFactory.CreateLogger(), UserDataManager, LibraryManager, UserManager, @@ -822,47 +829,47 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(SessionManager); serviceCollection.AddSingleton( - new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, _loggerFactory, JsonSerializer, this)); + new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this)); - CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, _loggerFactory, ProviderManager); + CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager); serviceCollection.AddSingleton(CollectionManager); serviceCollection.AddSingleton(typeof(IPlaylistManager), typeof(PlaylistManager)); - LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, _loggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager); + LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager); serviceCollection.AddSingleton(LiveTvManager); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager); serviceCollection.AddSingleton(UserViewManager); NotificationManager = new NotificationManager( - _loggerFactory.CreateLogger(), + LoggerFactory.CreateLogger(), UserManager, ServerConfigurationManager); serviceCollection.AddSingleton(NotificationManager); serviceCollection.AddSingleton(new DeviceDiscovery(ServerConfigurationManager)); - ChapterManager = new ChapterManager(LibraryManager, _loggerFactory, ServerConfigurationManager, ItemRepository); + ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); serviceCollection.AddSingleton(ChapterManager); - EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, _loggerFactory, MediaEncoder, ChapterManager, LibraryManager); + EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); serviceCollection.AddSingleton(EncodingManager); var activityLogRepo = GetActivityLogRepository(); serviceCollection.AddSingleton(activityLogRepo); - serviceCollection.AddSingleton(new ActivityManager(_loggerFactory, activityLogRepo, UserManager)); + serviceCollection.AddSingleton(new ActivityManager(LoggerFactory, activityLogRepo, UserManager)); var authContext = new AuthorizationContext(AuthenticationRepository, UserManager); serviceCollection.AddSingleton(authContext); serviceCollection.AddSingleton(new SessionContext(UserManager, authContext, SessionManager)); - AuthService = new AuthService(_loggerFactory.CreateLogger(), authContext, ServerConfigurationManager, SessionManager, NetworkManager); + AuthService = new AuthService(LoggerFactory.CreateLogger(), authContext, ServerConfigurationManager, SessionManager, NetworkManager); serviceCollection.AddSingleton(AuthService); SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder( LibraryManager, - _loggerFactory.CreateLogger(), + LoggerFactory.CreateLogger(), ApplicationPaths, FileSystemManager, MediaEncoder, @@ -878,7 +885,7 @@ namespace Emby.Server.Implementations _displayPreferencesRepository.Initialize(); - var userDataRepo = new SqliteUserDataRepository(_loggerFactory.CreateLogger(), ApplicationPaths); + var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger(), ApplicationPaths); SetStaticProperties(); @@ -930,7 +937,7 @@ namespace Emby.Server.Implementations // localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; if (!localCert.HasPrivateKey) { - _logger.LogError("No private key included in SSL cert {CertificateLocation}.", certificateLocation); + Logger.LogError("No private key included in SSL cert {CertificateLocation}.", certificateLocation); return null; } @@ -938,7 +945,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - _logger.LogError(ex, "Error loading cert from {CertificateLocation}", certificateLocation); + Logger.LogError(ex, "Error loading cert from {CertificateLocation}", certificateLocation); return null; } } @@ -950,7 +957,7 @@ namespace Emby.Server.Implementations private SqliteUserRepository GetUserRepository() { var repo = new SqliteUserRepository( - _loggerFactory.CreateLogger(), + LoggerFactory.CreateLogger(), ApplicationPaths); repo.Initialize(); @@ -960,7 +967,7 @@ namespace Emby.Server.Implementations private IAuthenticationRepository GetAuthenticationRepository() { - var repo = new AuthenticationRepository(_loggerFactory, ServerConfigurationManager); + var repo = new AuthenticationRepository(LoggerFactory, ServerConfigurationManager); repo.Initialize(); @@ -969,7 +976,7 @@ namespace Emby.Server.Implementations private IActivityRepository GetActivityLogRepository() { - var repo = new ActivityRepository(_loggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager); + var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager); repo.Initialize(); @@ -984,7 +991,7 @@ namespace Emby.Server.Implementations ItemRepository.ImageProcessor = ImageProcessor; // For now there's no real way to inject these properly - BaseItem.Logger = _loggerFactory.CreateLogger("BaseItem"); + BaseItem.Logger = LoggerFactory.CreateLogger("BaseItem"); BaseItem.ConfigurationManager = ServerConfigurationManager; BaseItem.LibraryManager = LibraryManager; BaseItem.ProviderManager = ProviderManager; @@ -1117,7 +1124,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - _logger.LogError(ex, "Error getting plugin Id from {PluginName}.", plugin.GetType().FullName); + Logger.LogError(ex, "Error getting plugin Id from {PluginName}.", plugin.GetType().FullName); } } @@ -1128,7 +1135,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; } @@ -1140,7 +1147,7 @@ namespace Emby.Server.Implementations /// protected void DiscoverTypes() { - _logger.LogInformation("Loading assemblies"); + Logger.LogInformation("Loading assemblies"); _allConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray(); } @@ -1156,7 +1163,7 @@ namespace Emby.Server.Implementations } catch (TypeLoadException ex) { - _logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName); + Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName); continue; } @@ -1194,7 +1201,7 @@ namespace Emby.Server.Implementations }); } - protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(_loggerFactory.CreateLogger()); + protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(LoggerFactory.CreateLogger()); private CertificateInfo GetCertificateInfo(bool generateCertificate) { @@ -1248,7 +1255,7 @@ namespace Emby.Server.Implementations if (requiresRestart) { - _logger.LogInformation("App needs to be restarted due to configuration change."); + Logger.LogInformation("App needs to be restarted due to configuration change."); NotifyPendingRestart(); } @@ -1259,7 +1266,7 @@ namespace Emby.Server.Implementations /// public void NotifyPendingRestart() { - _logger.LogInformation("App needs to be restarted."); + Logger.LogInformation("App needs to be restarted."); var changed = !HasPendingRestart; @@ -1267,7 +1274,7 @@ namespace Emby.Server.Implementations if (changed) { - EventHelper.QueueEventIfNotNull(HasPendingRestartChanged, this, EventArgs.Empty, _logger); + EventHelper.QueueEventIfNotNull(HasPendingRestartChanged, this, EventArgs.Empty, Logger); } } @@ -1296,10 +1303,10 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - _logger.LogError(ex, "Error sending server restart notification"); + Logger.LogError(ex, "Error sending server restart notification"); } - _logger.LogInformation("Calling RestartInternal"); + Logger.LogInformation("Calling RestartInternal"); RestartInternal(); }); @@ -1324,11 +1331,11 @@ namespace Emby.Server.Implementations } catch (FileLoadException ex) { - _logger.LogError(ex, "Failed to load assembly {Path}", file); + Logger.LogError(ex, "Failed to load assembly {Path}", file); continue; } - _logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file); + Logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file); yield return plugAss; } } @@ -1463,7 +1470,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - _logger.LogError(ex, "Error getting local Ip address information"); + Logger.LogError(ex, "Error getting local Ip address information"); } return null; @@ -1626,19 +1633,19 @@ namespace Emby.Server.Implementations var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase); _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid); - _logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid); + Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid); return valid; } } } catch (OperationCanceledException) { - _logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled"); + Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled"); throw; } catch (Exception ex) { - _logger.LogDebug(ex, "Ping test result to {0}. Success: {1}", apiUrl, false); + Logger.LogDebug(ex, "Ping test result to {0}. Success: {1}", apiUrl, false); _validAddressResults.AddOrUpdate(apiUrl, false, (k, v) => false); return false; @@ -1668,7 +1675,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - _logger.LogError(ex, "Error sending server shutdown notification"); + Logger.LogError(ex, "Error sending server shutdown notification"); } ShutdownInternal(); @@ -1730,7 +1737,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - _logger.LogError(ex, "Error launching url: {url}", url); + Logger.LogError(ex, "Error launching url: {url}", url); throw; } } @@ -1770,14 +1777,14 @@ namespace Emby.Server.Implementations { var type = GetType(); - _logger.LogInformation("Disposing {Type}", type.Name); + Logger.LogInformation("Disposing {Type}", type.Name); var parts = _disposableParts.Distinct().Where(i => i.GetType() != type).ToList(); _disposableParts.Clear(); foreach (var part in parts) { - _logger.LogInformation("Disposing {Type}", part.GetType().Name); + Logger.LogInformation("Disposing {Type}", part.GetType().Name); try { @@ -1785,7 +1792,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - _logger.LogError(ex, "Error disposing {Type}", part.GetType().Name); + Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name); } } From 9aa259eb95bcb77fd1c1c7c4c115cbf6dcda7286 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 3 Mar 2020 23:56:47 +0100 Subject: [PATCH 049/104] Revert unnecessary ordering changes in ApplicationHost --- Emby.Server.Implementations/ApplicationHost.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 78b65e798d..d51e74a4de 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -165,11 +165,6 @@ namespace Emby.Server.Implementations /// public bool IsShuttingDown { get; private set; } - /// - /// Gets the logger factory. - /// - protected ILoggerFactory LoggerFactory { get; } - /// /// Gets the logger. /// @@ -183,6 +178,11 @@ namespace Emby.Server.Implementations /// The plugins. public IReadOnlyList Plugins => _plugins; + /// + /// Gets the logger factory. + /// + protected ILoggerFactory LoggerFactory { get; } + /// /// Gets or sets the application paths. /// @@ -378,9 +378,10 @@ namespace Emby.Server.Implementations LoggerFactory = loggerFactory; FileSystemManager = fileSystem; - Logger = LoggerFactory.CreateLogger("App"); ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager); + Logger = LoggerFactory.CreateLogger("App"); + StartupOptions = options; ImageEncoder = imageEncoder; From 0e98d8bbadf37e237e32abbc79819005d79212cb Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Tue, 3 Mar 2020 22:51:16 -0500 Subject: [PATCH 050/104] Update contributors list Refreshes the contributors list with the data available from all merged PRs. Includes a huge number of people who had never added themselves manually. Going forward this is probably not needed anymore as we can update this fairly easily. --- CONTRIBUTORS.md | 150 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 121 insertions(+), 29 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e14636a577..af459fa2a0 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,41 +1,133 @@ # Jellyfin Contributors - - [JoshuaBoniface](https://github.com/joshuaboniface) - - [nvllsvm](https://github.com/nvllsvm) - - [JustAMan](https://github.com/JustAMan) - - [dcrdev](https://github.com/dcrdev) - - [EraYaN](https://github.com/EraYaN) - - [flemse](https://github.com/flemse) + - [97carmine](https://github.com/97carmine) + - [Abbe98](https://github.com/Abbe98) + - [agrenott](https://github.com/agrenott) + - [AndreCarvalho](https://github.com/AndreCarvalho) + - [anthonylavado](https://github.com/anthonylavado) + - [Artiume](https://github.com/Artiume) + - [AThomsen](https://github.com/AThomsen) + - [bilde2910](https://github.com/bilde2910) - [bfayers](https://github.com/bfayers) - - [Bond_009](https://github.com/Bond-009) - - [AnthonyLavado](https://github.com/anthonylavado) - - [sparky8251](https://github.com/sparky8251) - - [LeoVerto](https://github.com/LeoVerto) - - [grafixeyehero](https://github.com/grafixeyehero) + - [BnMcG](https://github.com/BnMcG) + - [Bond-009](https://github.com/Bond-009) + - [brianjmurrell](https://github.com/brianjmurrell) + - [bugfixin](https://github.com/bugfixin) + - [chaosinnovator](https://github.com/chaosinnovator) + - [ckcr4lyf](https://github.com/ckcr4lyf) + - [crankdoofus](https://github.com/crankdoofus) + - [crobibero](https://github.com/crobibero) + - [cromefire](https://github.com/cromefire) + - [cryptobank](https://github.com/cryptobank) - [cvium](https://github.com/cvium) - - [wtayl0r](https://github.com/wtayl0r) - - [TtheCreator](https://github.com/Tthecreator) + - [dannymichel](https://github.com/dannymichel) + - [DaveChild](https://github.com/DaveChild) + - [dcrdev](https://github.com/dcrdev) + - [dependabotbot](https://github.com/dependabotbot) + - [dhartung](https://github.com/dhartung) + - [dinki](https://github.com/dinki) - [dkanada](https://github.com/dkanada) - - [LogicalPhallacy](https://github.com/LogicalPhallacy/) - - [RazeLighter777](https://github.com/RazeLighter777) - - [WillWill56](https://github.com/WillWill56) - - [Liggy](https://github.com/Liggy) - - [fruhnow](https://github.com/fruhnow) - - [Lynxy](https://github.com/Lynxy) + - [dlahoti](https://github.com/dlahoti) + - [dmitrylyzo](https://github.com/dmitrylyzo) + - [DMouse10462](https://github.com/DMouse10462) + - [DrPandemic](https://github.com/DrPandemic) + - [EraYaN](https://github.com/EraYaN) + - [escabe](https://github.com/escabe) + - [excelite](https://github.com/excelite) - [fasheng](https://github.com/fasheng) - - [ploughpuff](https://github.com/ploughpuff) - - [pjeanjean](https://github.com/pjeanjean) - - [DrPandemic](https://github.com/drpandemic) - - [joern-h](https://github.com/joern-h) - - [Khinenw](https://github.com/HelloWorld017) + - [ferferga](https://github.com/ferferga) - [fhriley](https://github.com/fhriley) - - [nevado](https://github.com/nevado) - - [mark-monteiro](https://github.com/mark-monteiro) - - [ullmie02](https://github.com/ullmie02) + - [flemse](https://github.com/flemse) + - [Froghut](https://github.com/Froghut) + - [fruhnow](https://github.com/fruhnow) - [geilername](https://github.com/geilername) + - [gnattu](https://github.com/gnattu) + - [grafixeyehero](https://github.com/grafixeyehero) + - [h1nk](https://github.com/h1nk) + - [hawken93](https://github.com/hawken93) + - [HelloWorld017](https://github.com/HelloWorld017) + - [jftuga](https://github.com/jftuga) + - [joern-h](https://github.com/joern-h) + - [joshuaboniface](https://github.com/joshuaboniface) + - [JustAMan](https://github.com/JustAMan) + - [justinfenn](https://github.com/justinfenn) + - [KerryRJ](https://github.com/KerryRJ) + - [Larvitar](https://github.com/Larvitar) + - [LeoVerto](https://github.com/LeoVerto) + - [Liggy](https://github.com/Liggy) + - [LogicalPhallacy](https://github.com/LogicalPhallacy) + - [loli10K](https://github.com/loli10K) + - [lostmypillow](https://github.com/lostmypillow) + - [Lynxy](https://github.com/Lynxy) + - [ManfredRichthofen](https://github.com/ManfredRichthofen) + - [Marenz](https://github.com/Marenz) + - [marius-luca-87](https://github.com/marius-luca-87) + - [mark-monteiro](https://github.com/mark-monteiro) + - [Matt07211](https://github.com/Matt07211) + - [mcarlton00](https://github.com/mcarlton00) + - [mitchfizz05](https://github.com/mitchfizz05) + - [MrTimscampi](https://github.com/MrTimscampi) + - [n8225](https://github.com/n8225) + - [Narfinger](https://github.com/Narfinger) + - [NathanPickard](https://github.com/NathanPickard) + - [neilsb](https://github.com/neilsb) + - [nevado](https://github.com/nevado) + - [Nickbert7](https://github.com/Nickbert7) + - [nvllsvm](https://github.com/nvllsvm) + - [nyanmisaka](https://github.com/nyanmisaka) + - [oddstr13](https://github.com/oddstr13) + - [petermcneil](https://github.com/petermcneil) + - [Phlogi](https://github.com/Phlogi) + - [pjeanjean](https://github.com/pjeanjean) + - [ploughpuff](https://github.com/ploughpuff) - [pR0Ps](https://github.com/pR0Ps) - - [artiume](https://github.com/Artiume) - + - [PrplHaz4](https://github.com/PrplHaz4) + - [RazeLighter777](https://github.com/RazeLighter777) + - [redSpoutnik](https://github.com/redSpoutnik) + - [ringmatter](https://github.com/ringmatter) + - [ryan-hartzell](https://github.com/ryan-hartzell) + - [s0urcelab](https://github.com/s0urcelab) + - [sachk](https://github.com/sachk) + - [sammyrc34](https://github.com/sammyrc34) + - [samuel9554](https://github.com/samuel9554) + - [scheidleon](https://github.com/scheidleon) + - [sebPomme](https://github.com/sebPomme) + - [SenorSmartyPants](https://github.com/SenorSmartyPants) + - [shemanaev](https://github.com/shemanaev) + - [skaro13](https://github.com/skaro13) + - [sl1288](https://github.com/sl1288) + - [sorinyo2004](https://github.com/sorinyo2004) + - [sparky8251](https://github.com/sparky8251) + - [stanionascu](https://github.com/stanionascu) + - [stevehayles](https://github.com/stevehayles) + - [SuperSandro2000](https://github.com/SuperSandro2000) + - [tbraeutigam](https://github.com/tbraeutigam) + - [teacupx](https://github.com/teacupx) + - [Terror-Gene](https://github.com/Terror-Gene) + - [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu) + - [ThibaultNocchi](https://github.com/ThibaultNocchi) + - [thornbill](https://github.com/thornbill) + - [ThreeFive-O](https://github.com/ThreeFive-O) + - [TrisMcC](https://github.com/TrisMcC) + - [trumblejoe](https://github.com/trumblejoe) + - [TtheCreator](https://github.com/TtheCreator) + - [twinkybot](https://github.com/twinkybot) + - [Ullmie02](https://github.com/Ullmie02) + - [Unhelpful](https://github.com/Unhelpful) + - [viaregio](https://github.com/viaregio) + - [vitorsemeano](https://github.com/vitorsemeano) + - [voodoos](https://github.com/voodoos) + - [whooo](https://github.com/whooo) + - [WiiPlayer2](https://github.com/WiiPlayer2) + - [WillWill56](https://github.com/WillWill56) + - [wtayl0r](https://github.com/wtayl0r) + - [Wuerfelbecher](https://github.com/Wuerfelbecher) + - [Wunax](https://github.com/Wunax) + - [WWWesten](https://github.com/WWWesten) + - [WX9yMOXWId](https://github.com/WX9yMOXWId) + - [xosdy](https://github.com/xosdy) + - [XVicarious](https://github.com/XVicarious) + - [YouKnowBlom](https://github.com/YouKnowBlom) # Emby Contributors From 3016e78aef7512a3582dd872ee6e1e592f300734 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Tue, 3 Mar 2020 23:22:29 -0500 Subject: [PATCH 051/104] Remove dependabotbot --- CONTRIBUTORS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index af459fa2a0..4f36249655 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -23,7 +23,6 @@ - [dannymichel](https://github.com/dannymichel) - [DaveChild](https://github.com/DaveChild) - [dcrdev](https://github.com/dcrdev) - - [dependabotbot](https://github.com/dependabotbot) - [dhartung](https://github.com/dhartung) - [dinki](https://github.com/dinki) - [dkanada](https://github.com/dkanada) From 007c5b9f6734fc7a7b49ad488fe3733edf9c3c67 Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 4 Mar 2020 13:06:13 +0300 Subject: [PATCH 052/104] Implement BaseItem.GetHashCode override --- MediaBrowser.Controller/Entities/BaseItem.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 1177f921d5..f2adbf75a2 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2935,5 +2935,10 @@ namespace MediaBrowser.Controller.Entities return Id == item.Id; } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } } } From 7931a7a7d0fc78573b844d2f0e29cdcc259d8881 Mon Sep 17 00:00:00 2001 From: artiume Date: Wed, 4 Mar 2020 17:43:50 -0500 Subject: [PATCH 053/104] update fedora --- deployment/fedora-package-x64/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/fedora-package-x64/Dockerfile b/deployment/fedora-package-x64/Dockerfile index 05a4ef21f9..87120f3a05 100644 --- a/deployment/fedora-package-x64/Dockerfile +++ b/deployment/fedora-package-x64/Dockerfile @@ -1,4 +1,4 @@ -FROM fedora:29 +FROM fedora:31 # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/fedora-package-x64 From ada3f966681070823be3eefae9a0f77a31b1994f Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 5 Mar 2020 00:54:46 +0100 Subject: [PATCH 054/104] Add tests for alpha numeric sorting --- .../Extensions/ShuffleExtensions.cs | 13 +++++- MediaBrowser.sln | 7 +++ .../AlphanumComparatorTests.cs | 43 +++++++++++++++++++ .../Jellyfin.Controller.Tests.csproj | 21 +++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs create mode 100644 tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj diff --git a/MediaBrowser.Common/Extensions/ShuffleExtensions.cs b/MediaBrowser.Common/Extensions/ShuffleExtensions.cs index 5889d09c4b..0432f36b57 100644 --- a/MediaBrowser.Common/Extensions/ShuffleExtensions.cs +++ b/MediaBrowser.Common/Extensions/ShuffleExtensions.cs @@ -16,12 +16,23 @@ namespace MediaBrowser.Common.Extensions /// The list that should get shuffled. /// The type. public static void Shuffle(this IList list) + { + list.Shuffle(_rng); + } + + /// + /// Shuffles the items in a list. + /// + /// The list that should get shuffled. + /// The random number generator to use. + /// The type. + public static void Shuffle(this IList list, Random rng) { int n = list.Count; while (n > 1) { n--; - int k = _rng.Next(n + 1); + int k = rng.Next(n + 1); T value = list[k]; list[k] = list[n]; list[n] = value; diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 50570deecf..1c84622ac0 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -60,6 +60,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Api.Tests", "tests EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -170,6 +172,10 @@ Global {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Release|Any CPU.Build.0 = Release|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {462584F7-5023-4019-9EAC-B98CA458C0A0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -201,5 +207,6 @@ Global {3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection EndGlobal diff --git a/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs b/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs new file mode 100644 index 0000000000..09a66036a0 --- /dev/null +++ b/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Sorting; +using Xunit; + +namespace Jellyfin.Controller.Tests +{ + public class AlphanumComparatorTests + { + private readonly Random _rng = new Random(42); + + [Theory] + [InlineData(null, "", "1", "9", "10", "a", "z")] + [InlineData("50F", "100F", "SR9", "SR100")] + [InlineData("image-1.jpg", "image-02.jpg", "image-4.jpg", "image-9.jpg", "image-10.jpg", "image-11.jpg", "image-22.jpg")] + [InlineData("Hard drive 2GB", "Hard drive 20GB")] + [InlineData("b", "e", "è", "ě", "f", "g", "k")] + [InlineData("123456789", "123456789a", "abc", "abcd")] + [InlineData("12345678912345678912345678913234567891", "123456789123456789123456789132345678912")] + [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567891")] + [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567892")] + [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891a")] + [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891b")] + public void AlphanumComparatorTest(params string?[] strings) + { + var copy = (string?[])strings.Clone(); + if (strings.Length == 2) + { + var tmp = copy[0]; + copy[0] = copy[1]; + copy[1] = tmp; + } + else + { + copy.Shuffle(_rng); + } + + Array.Sort(copy, new AlphanumComparator()); + Assert.True(strings.SequenceEqual(copy)); + } + } +} diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj new file mode 100644 index 0000000000..c63f2e8c63 --- /dev/null +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + false + true + enable + + + + + + + + + + + + + + From be1d4b32c6643af69c27c746578b1994a4a650ff Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 5 Mar 2020 00:57:24 +0100 Subject: [PATCH 055/104] Add speed for alpha numeric sorting --- .../Sorting/AlphanumComparator.cs | 144 +++++++++++------- 1 file changed, 91 insertions(+), 53 deletions(-) diff --git a/MediaBrowser.Controller/Sorting/AlphanumComparator.cs b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs index 65dc120ca8..de7f72d1c2 100644 --- a/MediaBrowser.Controller/Sorting/AlphanumComparator.cs +++ b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs @@ -1,94 +1,132 @@ +#nullable enable + +using System; using System.Collections.Generic; -using System.Text; -using MediaBrowser.Controller.Sorting; namespace MediaBrowser.Controller.Sorting { - public class AlphanumComparator : IComparer + public class AlphanumComparator : IComparer { - public static int CompareValues(string s1, string s2) + public static int CompareValues(string? s1, string? s2) { - if (s1 == null || s2 == null) + if (s1 == null && s2 == null) { return 0; } - - int thisMarker = 0, thisNumericChunk = 0; - int thatMarker = 0, thatNumericChunk = 0; - - while ((thisMarker < s1.Length) || (thatMarker < s2.Length)) + else if (s1 == null) { - if (thisMarker >= s1.Length) - { - return -1; - } - else if (thatMarker >= s2.Length) - { - return 1; - } - char thisCh = s1[thisMarker]; - char thatCh = s2[thatMarker]; + return -1; + } + else if (s2 == null) + { + return 1; + } - var thisChunk = new StringBuilder(); - var thatChunk = new StringBuilder(); - bool thisNumeric = char.IsDigit(thisCh), thatNumeric = char.IsDigit(thatCh); + int len1 = s1.Length; + int len2 = s2.Length; - while (thisMarker < s1.Length && char.IsDigit(thisCh) == thisNumeric) + // Early return for empty strings + if (len1 == 0 && len2 == 0) + { + return 0; + } + else if (len1 == 0) + { + return -1; + } + else if (len2 == 0) + { + return 1; + } + + int pos1 = 0; + int pos2 = 0; + + do + { + int start1 = pos1; + int start2 = pos2; + + bool isNum1 = char.IsDigit(s1[pos1++]); + bool isNum2 = char.IsDigit(s2[pos2++]); + + while (pos1 < len1 && char.IsDigit(s1[pos1]) == isNum1) { - thisChunk.Append(thisCh); - thisMarker++; - - if (thisMarker < s1.Length) - { - thisCh = s1[thisMarker]; - } + pos1++; } - while (thatMarker < s2.Length && char.IsDigit(thatCh) == thatNumeric) + while (pos2 < len2 && char.IsDigit(s2[pos2]) == isNum2) { - thatChunk.Append(thatCh); - thatMarker++; - - if (thatMarker < s2.Length) - { - thatCh = s2[thatMarker]; - } + pos2++; } + var span1 = s1.AsSpan(start1, pos1 - start1); + var span2 = s2.AsSpan(start2, pos2 - start2); - // If both chunks contain numeric characters, sort them numerically - if (thisNumeric && thatNumeric) + if (isNum1 && isNum2) { - if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk) - || !int.TryParse(thatChunk.ToString(), out thatNumericChunk)) - { - return 0; - } - - if (thisNumericChunk < thatNumericChunk) + // Trim leading zeros so we can compare the length + // of the strings to find the largest number + span1 = span1.TrimStart('0'); + span2 = span2.TrimStart('0'); + var span1Len = span1.Length; + var span2Len = span2.Length; + if (span1Len < span2Len) { return -1; } + else if (span1Len > span2Len) + { + return 1; + } + else if (span1Len >= 20) // Number is probably too big for a ulong + { + // Trim all the first digits that are the same + int i = 0; + while (i < span1Len && span1[i] == span2[i]) + { + i++; + } - if (thisNumericChunk > thatNumericChunk) + // If there are no more digits it's the same number + if (i == span1Len) + { + continue; + } + + // Only need to compare the most significant digit + span1 = span1.Slice(i, 1); + span2 = span2.Slice(i, 1); + } + + if (!ulong.TryParse(span1, out var num1) + || !ulong.TryParse(span2, out var num2)) + { + return 0; + } + else if (num1 < num2) + { + return -1; + } + else if (num1 > num2) { return 1; } } else { - int result = thisChunk.ToString().CompareTo(thatChunk.ToString()); + int result = span1.CompareTo(span2, StringComparison.InvariantCulture); if (result != 0) { return result; } } + } while (pos1 < len1 && pos2 < len2); - } - - return 0; + return len1 - len2; } + /// public int Compare(string x, string y) { return CompareValues(x, y); From 375cf212dd7ac84f073dc5b0c1df7944c1a99422 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 5 Mar 2020 12:24:12 +0100 Subject: [PATCH 056/104] Update AlphanumComparatorTests.cs --- tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs b/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs index 09a66036a0..929bb92aa8 100644 --- a/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs +++ b/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs @@ -10,6 +10,7 @@ namespace Jellyfin.Controller.Tests { private readonly Random _rng = new Random(42); + // InlineData is pre-sorted [Theory] [InlineData(null, "", "1", "9", "10", "a", "z")] [InlineData("50F", "100F", "SR9", "SR100")] From 456f571343cfd524fd29e22207a806d5acd7d25a Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 5 Mar 2020 14:25:50 +0300 Subject: [PATCH 057/104] Follow code review suggestions --- MediaBrowser.Controller/Entities/BaseItem.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index f2adbf75a2..8f504ebb0c 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2921,24 +2921,17 @@ namespace MediaBrowser.Controller.Entities public override bool Equals(object obj) { - Logger.LogDebug("Comparing me ({0}) to generic them ({1})", this, obj); - return this.Equals(obj as BaseItem); + return obj is BaseItem baseItem && this.Equals(baseItem); } public bool Equals(BaseItem item) { - Logger.LogDebug("Comparing me ({0}) to specific them ({1})", this, item); - if (item == null) - { - return false; - } - - return Id == item.Id; + return Object.Equals(Id, item?.Id); } public override int GetHashCode() { - return Id.GetHashCode(); + return HashCode.Combine(Id); } } } From f4ccee58010c4420f827ce7f8eb037e2ccec1d82 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 5 Mar 2020 15:03:17 +0300 Subject: [PATCH 058/104] Add inheritdoc comment and squash simple method bodies --- MediaBrowser.Controller/Entities/BaseItem.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 8f504ebb0c..9af8d10698 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2919,19 +2919,16 @@ namespace MediaBrowser.Controller.Entities public virtual bool SupportsExternalTransfer => false; + /// public override bool Equals(object obj) { return obj is BaseItem baseItem && this.Equals(baseItem); } - public bool Equals(BaseItem item) - { - return Object.Equals(Id, item?.Id); - } + /// + public bool Equals(BaseItem item) => Object.Equals(Id, item?.Id); - public override int GetHashCode() - { - return HashCode.Combine(Id); - } + /// + public override int GetHashCode() => HashCode.Combine(Id); } } From acd67c7152cc9a476d5cc9e7a4b95b084bfaeb6e Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 5 Mar 2020 16:22:15 +0300 Subject: [PATCH 059/104] Add tracking of JF version used to run this config previously --- Jellyfin.Server/CoreAppHost.cs | 10 +++++++++ .../BaseApplicationConfiguration.cs | 21 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 8b4b61e290..59285a5109 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -41,6 +41,16 @@ namespace Jellyfin.Server networkManager, configuration) { + var previousVersion = ConfigurationManager.CommonConfiguration.PreviousVersion; + if (ApplicationVersion.CompareTo(previousVersion) > 0) + { + Logger.LogWarning("Version check shows Jellyfin was updated: previous version={0}, current version={1}", previousVersion, ApplicationVersion); + + // TODO: run update routines + + ConfigurationManager.CommonConfiguration.PreviousVersion = ApplicationVersion; + ConfigurationManager.SaveConfiguration(); + } } /// diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs index 6a1a0f0901..cc2541f74f 100644 --- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs +++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs @@ -1,3 +1,6 @@ +using System; +using System.Xml.Serialization; + namespace MediaBrowser.Model.Configuration { /// @@ -25,6 +28,24 @@ namespace MediaBrowser.Model.Configuration /// The cache path. public string CachePath { get; set; } + /// + /// Last known version that was ran using the configuration. + /// + /// The version from previous run. + [XmlIgnore] + public Version PreviousVersion { get; set; } + + /// + /// Stringified PreviousVersion to be stored/loaded, + /// because System.Version itself isn't xml-serializable + /// + /// String value of PreviousVersion + public string PreviousVersionStr + { + get => PreviousVersion?.ToString(); + set => PreviousVersion = Version.Parse(value); + } + /// /// Initializes a new instance of the class. /// From ca585f12b3297f58e8f490b03b64729716eb2f40 Mon Sep 17 00:00:00 2001 From: Andrew Rabert <6550543+nvllsvm@users.noreply.github.com> Date: Thu, 5 Mar 2020 09:41:14 -0500 Subject: [PATCH 060/104] Fix Docker packages (#2499) * Fix Vaapi Intel packages https://github.com/jellyfin/jellyfin/issues/1901#issuecomment-593114951 Still need to compile with the packages to verify it builds properly. Arm builds probably need it too. * Update Dockerfile * Update Dockerfile * Update Dockerfile.arm * Update Dockerfile.arm * Update Dockerfile.arm64 * Update Dockerfile.arm * Update Dockerfile.arm * Update Dockerfile.arm * Update Dockerfile.arm64 * Update Dockerfile.arm64 * Update Dockerfile.arm * Update Dockerfile * shift from curl to git for web install removed the necessity of curl, tar and package availability and using the source directly * Update Dockerfile.arm * Update Dockerfile.arm64 * Update Dockerfile * Update Dockerfile * Update Dockerfile.arm * Update Dockerfile.arm64 * clean up packages and remove unnecessary ARG * Update Dockerfile * Update Dockerfile.arm64 * Update Dockerfile * Update Dockerfile.arm64 * Update Dockerfile * Update Dockerfile.arm * Update Dockerfile.arm64 * Explainations * Update Dockerfile.arm * Update Dockerfile.arm64 --- Dockerfile | 20 +++++++++++++++++--- Dockerfile.arm | 31 ++++++++++++++++++++++++++++--- Dockerfile.arm64 | 21 ++++++++++++++++++--- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1029a4cee1..73ab3d7904 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,13 @@ RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release -- FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg FROM debian:buster-slim +# https://askubuntu.com/questions/972516/debian-frontend-environment-variable +ARG DEBIAN_FRONTEND="noninteractive" +# http://stackoverflow.com/questions/48162574/ddg#49462622 +ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn +# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) +ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" + COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web @@ -31,9 +38,16 @@ COPY --from=web-builder /dist /jellyfin/jellyfin-web # mesa-va-drivers: needed for VAAPI RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y \ - libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl ca-certificates \ - && apt-get clean autoclean \ - && apt-get autoremove \ + libfontconfig1 \ + libgomp1 \ + libva-drm2 \ + mesa-va-drivers \ + openssl \ + ca-certificates \ + vainfo \ + i965-va-driver \ + && apt-get clean autoclean -y\ + && apt-get autoremove -y\ && rm -rf /var/lib/apt/lists/* \ && mkdir -p /cache /config /media \ && chmod 777 /cache /config /media \ diff --git a/Dockerfile.arm b/Dockerfile.arm index 5847de9189..4c7aa6aa70 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -27,10 +27,35 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" FROM multiarch/qemu-user-static:x86_64-arm as qemu FROM arm32v7/debian:buster-slim + +# https://askubuntu.com/questions/972516/debian-frontend-environment-variable +ARG DEBIAN_FRONTEND="noninteractive" +# http://stackoverflow.com/questions/48162574/ddg#49462622 +ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn +# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) +ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" + COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin RUN apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ - libssl-dev ca-certificates \ + && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \ + curl -s https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \ + curl -s https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \ + echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \ + echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \ + apt-get update && \ + apt-get install --no-install-recommends --no-install-suggests -y \ + jellyfin-ffmpeg \ + libssl-dev \ + libfontconfig1 \ + libfreetype6 \ + libomxil-bellagio0 \ + libomxil-bellagio-bin \ + libraspberrypi0 \ + vainfo \ + libva2 \ + && apt-get remove curl gnupg -y \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* \ && mkdir -p /cache /config /media \ && chmod 777 /cache /config /media @@ -44,4 +69,4 @@ VOLUME /cache /config /media ENTRYPOINT ["./jellyfin/jellyfin", \ "--datadir", "/config", \ "--cachedir", "/cache", \ - "--ffmpeg", "/usr/bin/ffmpeg"] + "--ffmpeg", "/usr/lib/jellyfin-ffmpeg"] diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index a9f6c50d9f..9dc6fa7edb 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -26,10 +26,25 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu FROM arm64v8/debian:buster-slim + +# https://askubuntu.com/questions/972516/debian-frontend-environment-variable +ARG DEBIAN_FRONTEND="noninteractive" +# http://stackoverflow.com/questions/48162574/ddg#49462622 +ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn +# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) +ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" + COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin -RUN apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ - libssl-dev ca-certificates \ +RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \ + ffmpeg \ + libssl-dev \ + ca-certificates \ + libfontconfig1 \ + libfreetype6 \ + libomxil-bellagio0 \ + libomxil-bellagio-bin \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* \ && mkdir -p /cache /config /media \ && chmod 777 /cache /config /media From 9eef0e8ca0e3359239ab68fcadbf2d65084f12e6 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 5 Mar 2020 17:41:32 +0300 Subject: [PATCH 061/104] Implement EnableThrottling migration for pre-10.5.0 to 10.5.0 or newer --- Jellyfin.Server/CoreAppHost.cs | 38 ++++++++++---- Jellyfin.Server/Migrations.cs | 92 ++++++++++++++++++++++++++++++++++ Jellyfin.Server/Program.cs | 1 + 3 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 Jellyfin.Server/Migrations.cs diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 59285a5109..cd5a2ce853 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -41,16 +41,6 @@ namespace Jellyfin.Server networkManager, configuration) { - var previousVersion = ConfigurationManager.CommonConfiguration.PreviousVersion; - if (ApplicationVersion.CompareTo(previousVersion) > 0) - { - Logger.LogWarning("Version check shows Jellyfin was updated: previous version={0}, current version={1}", previousVersion, ApplicationVersion); - - // TODO: run update routines - - ConfigurationManager.CommonConfiguration.PreviousVersion = ApplicationVersion; - ConfigurationManager.SaveConfiguration(); - } } /// @@ -67,5 +57,33 @@ namespace Jellyfin.Server /// protected override void ShutdownInternal() => Program.Shutdown(); + + /// + /// Runs the migration routines if necessary. + /// + public void TryMigrate() + { + var previousVersion = ConfigurationManager.CommonConfiguration.PreviousVersion; + switch (ApplicationVersion.CompareTo(previousVersion)) + { + case 1: + Logger.LogWarning("Version check shows Jellyfin was updated: previous version={0}, current version={1}", previousVersion, ApplicationVersion); + + Migrations.Run(this, Logger); + + ConfigurationManager.CommonConfiguration.PreviousVersion = ApplicationVersion; + ConfigurationManager.SaveConfiguration(); + break; + case 0: + // nothing to do, versions match + break; + case -1: + Logger.LogWarning("Version check shows Jellyfin was rolled back, use at your own risk: previous version={0}, current version={1}", previousVersion, ApplicationVersion); + // no "rollback" routines for now + ConfigurationManager.CommonConfiguration.PreviousVersion = ApplicationVersion; + ConfigurationManager.SaveConfiguration(); + break; + } + } } } diff --git a/Jellyfin.Server/Migrations.cs b/Jellyfin.Server/Migrations.cs new file mode 100644 index 0000000000..95fea4ea55 --- /dev/null +++ b/Jellyfin.Server/Migrations.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Configuration; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server +{ + /// + /// The class that knows how migrate between different Jellyfin versions. + /// + internal static class Migrations + { + private static readonly IUpdater[] _migrations = + { + new Pre10_5() + }; + + /// + /// Interface that descibes a migration routine. + /// + private interface IUpdater + { + /// + /// Gets maximum version this Updater applies to. + /// If current version is greater or equal to it, skip the updater. + /// + public abstract Version Maximum { get; } + + /// + /// Execute the migration from version "from". + /// + /// Host that hosts current version. + /// Host logger. + /// Version to migrate from. + /// Whether configuration was changed. + public abstract bool Perform(CoreAppHost host, ILogger logger, Version from); + } + + /// + /// Run all needed migrations. + /// + /// CoreAppHost that hosts current version. + /// AppHost logger. + /// Whether anything was changed. + public static bool Run(CoreAppHost host, ILogger logger) + { + bool updated = false; + var version = host.ServerConfigurationManager.CommonConfiguration.PreviousVersion; + + for (var i = 0; i < _migrations.Length; i++) + { + var updater = _migrations[i]; + if (version.CompareTo(updater.Maximum) >= 0) + { + logger.LogDebug("Skipping updater {0} as current version {1} >= its maximum applicable version {2}", updater, version, updater.Maximum); + continue; + } + + if (updater.Perform(host, logger, version)) + { + updated = true; + } + + version = updater.Maximum; + } + + return updated; + } + + private class Pre10_5 : IUpdater + { + public Version Maximum { get => Version.Parse("10.5.0"); } + + public bool Perform(CoreAppHost host, ILogger logger, Version from) + { + // Set EnableThrottling to false as it wasn't used before, and in 10.5.0 it may introduce issues + var encoding = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration("encoding"); + if (encoding.EnableThrottling) + { + logger.LogInformation("Disabling transcoding throttling during migration"); + encoding.EnableThrottling = false; + + host.ServerConfigurationManager.SaveConfiguration("encoding", encoding); + return true; + } + + return false; + } + } + } +} diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 484e507a23..aa1bdb1691 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -182,6 +182,7 @@ namespace Jellyfin.Server // A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection. appHost.ServiceProvider = host.Services; appHost.FindParts(); + appHost.TryMigrate(); try { From 66e11879efcd2a77476ca9704fa938e89776956c Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 5 Mar 2020 18:21:27 +0300 Subject: [PATCH 062/104] Shuffle migrations in a more manageable structure --- Jellyfin.Server/CoreAppHost.cs | 2 +- Jellyfin.Server/Migrations.cs | 92 ------------------- Jellyfin.Server/Migrations/IUpdater.cs | 26 ++++++ Jellyfin.Server/Migrations/MigrationRunner.cs | 46 ++++++++++ Jellyfin.Server/Migrations/Pre_10_5.cs | 33 +++++++ 5 files changed, 106 insertions(+), 93 deletions(-) delete mode 100644 Jellyfin.Server/Migrations.cs create mode 100644 Jellyfin.Server/Migrations/IUpdater.cs create mode 100644 Jellyfin.Server/Migrations/MigrationRunner.cs create mode 100644 Jellyfin.Server/Migrations/Pre_10_5.cs diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index cd5a2ce853..7f4bd3dea2 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -69,7 +69,7 @@ namespace Jellyfin.Server case 1: Logger.LogWarning("Version check shows Jellyfin was updated: previous version={0}, current version={1}", previousVersion, ApplicationVersion); - Migrations.Run(this, Logger); + Migrations.MigrationRunner.Run(this, Logger); ConfigurationManager.CommonConfiguration.PreviousVersion = ApplicationVersion; ConfigurationManager.SaveConfiguration(); diff --git a/Jellyfin.Server/Migrations.cs b/Jellyfin.Server/Migrations.cs deleted file mode 100644 index 95fea4ea55..0000000000 --- a/Jellyfin.Server/Migrations.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Server -{ - /// - /// The class that knows how migrate between different Jellyfin versions. - /// - internal static class Migrations - { - private static readonly IUpdater[] _migrations = - { - new Pre10_5() - }; - - /// - /// Interface that descibes a migration routine. - /// - private interface IUpdater - { - /// - /// Gets maximum version this Updater applies to. - /// If current version is greater or equal to it, skip the updater. - /// - public abstract Version Maximum { get; } - - /// - /// Execute the migration from version "from". - /// - /// Host that hosts current version. - /// Host logger. - /// Version to migrate from. - /// Whether configuration was changed. - public abstract bool Perform(CoreAppHost host, ILogger logger, Version from); - } - - /// - /// Run all needed migrations. - /// - /// CoreAppHost that hosts current version. - /// AppHost logger. - /// Whether anything was changed. - public static bool Run(CoreAppHost host, ILogger logger) - { - bool updated = false; - var version = host.ServerConfigurationManager.CommonConfiguration.PreviousVersion; - - for (var i = 0; i < _migrations.Length; i++) - { - var updater = _migrations[i]; - if (version.CompareTo(updater.Maximum) >= 0) - { - logger.LogDebug("Skipping updater {0} as current version {1} >= its maximum applicable version {2}", updater, version, updater.Maximum); - continue; - } - - if (updater.Perform(host, logger, version)) - { - updated = true; - } - - version = updater.Maximum; - } - - return updated; - } - - private class Pre10_5 : IUpdater - { - public Version Maximum { get => Version.Parse("10.5.0"); } - - public bool Perform(CoreAppHost host, ILogger logger, Version from) - { - // Set EnableThrottling to false as it wasn't used before, and in 10.5.0 it may introduce issues - var encoding = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration("encoding"); - if (encoding.EnableThrottling) - { - logger.LogInformation("Disabling transcoding throttling during migration"); - encoding.EnableThrottling = false; - - host.ServerConfigurationManager.SaveConfiguration("encoding", encoding); - return true; - } - - return false; - } - } - } -} diff --git a/Jellyfin.Server/Migrations/IUpdater.cs b/Jellyfin.Server/Migrations/IUpdater.cs new file mode 100644 index 0000000000..60d9702567 --- /dev/null +++ b/Jellyfin.Server/Migrations/IUpdater.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations +{ + /// + /// Interface that descibes a migration routine. + /// + internal interface IUpdater + { + /// + /// Gets maximum version this Updater applies to. + /// If current version is greater or equal to it, skip the updater. + /// + public abstract Version Maximum { get; } + + /// + /// Execute the migration from version "from". + /// + /// Host that hosts current version. + /// Host logger. + /// Version to migrate from. + /// Whether configuration was changed. + public abstract bool Perform(CoreAppHost host, ILogger logger, Version from); + } +} diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs new file mode 100644 index 0000000000..ad54fa38e6 --- /dev/null +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations +{ + /// + /// The class that knows how migrate between different Jellyfin versions. + /// + public static class MigrationRunner + { + private static readonly IUpdater[] _migrations = + { + new Pre_10_5() + }; + + /// + /// Run all needed migrations. + /// + /// CoreAppHost that hosts current version. + /// AppHost logger. + /// Whether anything was changed. + public static bool Run(CoreAppHost host, ILogger logger) + { + bool updated = false; + var version = host.ServerConfigurationManager.CommonConfiguration.PreviousVersion; + + for (var i = 0; i < _migrations.Length; i++) + { + var updater = _migrations[i]; + if (version.CompareTo(updater.Maximum) >= 0) + { + logger.LogDebug("Skipping updater {0} as current version {1} >= its maximum applicable version {2}", updater, version, updater.Maximum); + continue; + } + + if (updater.Perform(host, logger, version)) + { + updated = true; + } + + version = updater.Maximum; + } + + return updated; + } + } +} diff --git a/Jellyfin.Server/Migrations/Pre_10_5.cs b/Jellyfin.Server/Migrations/Pre_10_5.cs new file mode 100644 index 0000000000..5389a2ad98 --- /dev/null +++ b/Jellyfin.Server/Migrations/Pre_10_5.cs @@ -0,0 +1,33 @@ +using System; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Configuration; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations +{ + /// + /// Updater that takes care of bringing configuration up to 10.5.0 standards. + /// + internal class Pre_10_5 : IUpdater + { + /// + public Version Maximum { get => Version.Parse("10.5.0"); } + + /// + public bool Perform(CoreAppHost host, ILogger logger, Version from) + { + // Set EnableThrottling to false as it wasn't used before, and in 10.5.0 it may introduce issues + var encoding = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration("encoding"); + if (encoding.EnableThrottling) + { + logger.LogInformation("Disabling transcoding throttling during migration"); + encoding.EnableThrottling = false; + + host.ServerConfigurationManager.SaveConfiguration("encoding", encoding); + return true; + } + + return false; + } + } +} From ecaa7f8014666a474c87481471ce7cda7006165a Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 5 Mar 2020 20:09:33 +0300 Subject: [PATCH 063/104] Improve migration logic --- Jellyfin.Server/CoreAppHost.cs | 28 ----------- ...0_5.cs => DisableTranscodingThrottling.cs} | 11 ++--- .../Migrations/DisableZealousLogging.cs | 29 +++++++++++ Jellyfin.Server/Migrations/IUpdater.cs | 31 ++++++------ .../Migrations/MigrationOptions.cs | 23 +++++++++ Jellyfin.Server/Migrations/MigrationRunner.cs | 49 +++++++++++++------ .../Migrations/MigrationsFactory.cs | 20 ++++++++ .../Migrations/MigrationsListStore.cs | 19 +++++++ Jellyfin.Server/Program.cs | 11 +++-- 9 files changed, 151 insertions(+), 70 deletions(-) rename Jellyfin.Server/Migrations/{Pre_10_5.cs => DisableTranscodingThrottling.cs} (79%) create mode 100644 Jellyfin.Server/Migrations/DisableZealousLogging.cs create mode 100644 Jellyfin.Server/Migrations/MigrationOptions.cs create mode 100644 Jellyfin.Server/Migrations/MigrationsFactory.cs create mode 100644 Jellyfin.Server/Migrations/MigrationsListStore.cs diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 7f4bd3dea2..8b4b61e290 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -57,33 +57,5 @@ namespace Jellyfin.Server /// protected override void ShutdownInternal() => Program.Shutdown(); - - /// - /// Runs the migration routines if necessary. - /// - public void TryMigrate() - { - var previousVersion = ConfigurationManager.CommonConfiguration.PreviousVersion; - switch (ApplicationVersion.CompareTo(previousVersion)) - { - case 1: - Logger.LogWarning("Version check shows Jellyfin was updated: previous version={0}, current version={1}", previousVersion, ApplicationVersion); - - Migrations.MigrationRunner.Run(this, Logger); - - ConfigurationManager.CommonConfiguration.PreviousVersion = ApplicationVersion; - ConfigurationManager.SaveConfiguration(); - break; - case 0: - // nothing to do, versions match - break; - case -1: - Logger.LogWarning("Version check shows Jellyfin was rolled back, use at your own risk: previous version={0}, current version={1}", previousVersion, ApplicationVersion); - // no "rollback" routines for now - ConfigurationManager.CommonConfiguration.PreviousVersion = ApplicationVersion; - ConfigurationManager.SaveConfiguration(); - break; - } - } } } diff --git a/Jellyfin.Server/Migrations/Pre_10_5.cs b/Jellyfin.Server/Migrations/DisableTranscodingThrottling.cs similarity index 79% rename from Jellyfin.Server/Migrations/Pre_10_5.cs rename to Jellyfin.Server/Migrations/DisableTranscodingThrottling.cs index 5389a2ad98..83624bdadd 100644 --- a/Jellyfin.Server/Migrations/Pre_10_5.cs +++ b/Jellyfin.Server/Migrations/DisableTranscodingThrottling.cs @@ -1,6 +1,8 @@ using System; +using System.IO; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations @@ -8,13 +10,13 @@ namespace Jellyfin.Server.Migrations /// /// Updater that takes care of bringing configuration up to 10.5.0 standards. /// - internal class Pre_10_5 : IUpdater + internal class DisableTranscodingThrottling : IUpdater { /// - public Version Maximum { get => Version.Parse("10.5.0"); } + public string Name => "DisableTranscodingThrottling"; /// - public bool Perform(CoreAppHost host, ILogger logger, Version from) + public void Perform(CoreAppHost host, ILogger logger) { // Set EnableThrottling to false as it wasn't used before, and in 10.5.0 it may introduce issues var encoding = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration("encoding"); @@ -24,10 +26,7 @@ namespace Jellyfin.Server.Migrations encoding.EnableThrottling = false; host.ServerConfigurationManager.SaveConfiguration("encoding", encoding); - return true; } - - return false; } } } diff --git a/Jellyfin.Server/Migrations/DisableZealousLogging.cs b/Jellyfin.Server/Migrations/DisableZealousLogging.cs new file mode 100644 index 0000000000..a0a934d4ab --- /dev/null +++ b/Jellyfin.Server/Migrations/DisableZealousLogging.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Configuration; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Serilog; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Jellyfin.Server.Migrations +{ + /// + /// Updater that takes care of bringing configuration up to 10.5.0 standards. + /// + internal class DisableZealousLogging : IUpdater + { + /// + public string Name => "DisableZealousLogging"; + + /// + // This tones down logging from some components + public void Perform(CoreAppHost host, ILogger logger) + { + string configPath = Path.Combine(host.ServerConfigurationManager.ApplicationPaths.ConfigurationDirectoryPath, Program.LoggingConfigFile); + // TODO: fix up the config + throw new NotImplementedException("don't know how to fix logging yet"); + } + } +} diff --git a/Jellyfin.Server/Migrations/IUpdater.cs b/Jellyfin.Server/Migrations/IUpdater.cs index 60d9702567..10ada73d5f 100644 --- a/Jellyfin.Server/Migrations/IUpdater.cs +++ b/Jellyfin.Server/Migrations/IUpdater.cs @@ -3,24 +3,21 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations { + /// + /// Interface that descibes a migration routine. + /// + internal interface IUpdater + { /// - /// Interface that descibes a migration routine. + /// Gets the name of the migration, must be unique. /// - internal interface IUpdater - { - /// - /// Gets maximum version this Updater applies to. - /// If current version is greater or equal to it, skip the updater. - /// - public abstract Version Maximum { get; } + public abstract string Name { get; } - /// - /// Execute the migration from version "from". - /// - /// Host that hosts current version. - /// Host logger. - /// Version to migrate from. - /// Whether configuration was changed. - public abstract bool Perform(CoreAppHost host, ILogger logger, Version from); - } + /// + /// Execute the migration from version "from". + /// + /// Host that hosts current version. + /// Host logger. + public abstract void Perform(CoreAppHost host, ILogger logger); + } } diff --git a/Jellyfin.Server/Migrations/MigrationOptions.cs b/Jellyfin.Server/Migrations/MigrationOptions.cs new file mode 100644 index 0000000000..b96288cc1b --- /dev/null +++ b/Jellyfin.Server/Migrations/MigrationOptions.cs @@ -0,0 +1,23 @@ +namespace Jellyfin.Server.Migrations +{ + /// + /// Configuration part that holds all migrations that were applied. + /// + public class MigrationOptions + { + /// + /// Initializes a new instance of the class. + /// + public MigrationOptions() + { + Applied = System.Array.Empty(); + } + +#pragma warning disable CA1819 // Properties should not return arrays + /// + /// Gets or sets he list of applied migration routine names. + /// + public string[] Applied { get; set; } +#pragma warning restore CA1819 // Properties should not return arrays + } +} diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index ad54fa38e6..04d0378521 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -1,3 +1,6 @@ +using System; +using System.Linq; +using MediaBrowser.Common.Configuration; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations @@ -5,42 +8,56 @@ namespace Jellyfin.Server.Migrations /// /// The class that knows how migrate between different Jellyfin versions. /// - public static class MigrationRunner + public sealed class MigrationRunner { - private static readonly IUpdater[] _migrations = + /// + /// The list of known migrations, in order of applicability. + /// + internal static readonly IUpdater[] Migrations = { - new Pre_10_5() + new DisableTranscodingThrottling(), + new DisableZealousLogging() }; /// /// Run all needed migrations. /// /// CoreAppHost that hosts current version. - /// AppHost logger. - /// Whether anything was changed. - public static bool Run(CoreAppHost host, ILogger logger) + /// Factory for making the logger. + public static void Run(CoreAppHost host, ILoggerFactory loggerFactory) { - bool updated = false; - var version = host.ServerConfigurationManager.CommonConfiguration.PreviousVersion; + var logger = loggerFactory.CreateLogger(); + var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration("migrations"); + var applied = migrationOptions.Applied.ToList(); - for (var i = 0; i < _migrations.Length; i++) + for (var i = 0; i < Migrations.Length; i++) { - var updater = _migrations[i]; - if (version.CompareTo(updater.Maximum) >= 0) + var updater = Migrations[i]; + if (applied.Contains(updater.Name)) { - logger.LogDebug("Skipping updater {0} as current version {1} >= its maximum applicable version {2}", updater, version, updater.Maximum); + logger.LogDebug("Skipping migration {0} as it is already applied", updater.Name); continue; } - if (updater.Perform(host, logger, version)) + try { - updated = true; + updater.Perform(host, logger); + } + catch (Exception ex) + { + logger.LogError(ex, "Cannot apply migration {0}", updater.Name); + continue; } - version = updater.Maximum; + applied.Add(updater.Name); } - return updated; + if (applied.Count > migrationOptions.Applied.Length) + { + logger.LogInformation("Some migrations were run, saving the state"); + migrationOptions.Applied = applied.ToArray(); + host.ServerConfigurationManager.SaveConfiguration("migrations", migrationOptions); + } } } } diff --git a/Jellyfin.Server/Migrations/MigrationsFactory.cs b/Jellyfin.Server/Migrations/MigrationsFactory.cs new file mode 100644 index 0000000000..ed01dc646a --- /dev/null +++ b/Jellyfin.Server/Migrations/MigrationsFactory.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; + +namespace Jellyfin.Server.Migrations +{ + /// + /// A factory that teachs Jellyfin how to find a peristent file which lists all applied migrations. + /// + public class MigrationsFactory : IConfigurationFactory + { + /// + public IEnumerable GetConfigurations() + { + return new[] + { + new MigrationsListStore() + }; + } + } +} diff --git a/Jellyfin.Server/Migrations/MigrationsListStore.cs b/Jellyfin.Server/Migrations/MigrationsListStore.cs new file mode 100644 index 0000000000..d91d602c14 --- /dev/null +++ b/Jellyfin.Server/Migrations/MigrationsListStore.cs @@ -0,0 +1,19 @@ +using MediaBrowser.Common.Configuration; + +namespace Jellyfin.Server.Migrations +{ + /// + /// A configuration that lists all the migration routines that were applied. + /// + public class MigrationsListStore : ConfigurationStore + { + /// + /// Initializes a new instance of the class. + /// + public MigrationsListStore() + { + ConfigurationType = typeof(MigrationOptions); + Key = "migrations"; + } + } +} diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index aa1bdb1691..0271861054 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -38,6 +38,11 @@ namespace Jellyfin.Server /// public static class Program { + /// + /// The name of logging configuration file. + /// + public static readonly string LoggingConfigFile = "logging.json"; + private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); private static ILogger _logger = NullLogger.Instance; @@ -182,7 +187,7 @@ namespace Jellyfin.Server // A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection. appHost.ServiceProvider = host.Services; appHost.FindParts(); - appHost.TryMigrate(); + Migrations.MigrationRunner.Run(appHost, _loggerFactory); try { @@ -438,7 +443,7 @@ namespace Jellyfin.Server private static async Task CreateConfiguration(IApplicationPaths appPaths) { const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json"; - string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, "logging.json"); + string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, LoggingConfigFile); if (!File.Exists(configPath)) { @@ -460,7 +465,7 @@ namespace Jellyfin.Server return new ConfigurationBuilder() .SetBasePath(appPaths.ConfigurationDirectoryPath) .AddInMemoryCollection(ConfigurationOptions.Configuration) - .AddJsonFile("logging.json", false, true) + .AddJsonFile(LoggingConfigFile, false, true) .AddEnvironmentVariables("JELLYFIN_") .Build(); } From ccafebca68fc09040572d0a21420ea9e5d6c1088 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 5 Mar 2020 20:37:49 +0300 Subject: [PATCH 064/104] Extract "migrations" config name to a proper constant --- Jellyfin.Server/Migrations/MigrationRunner.cs | 4 ++-- Jellyfin.Server/Migrations/MigrationsListStore.cs | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 04d0378521..1db99f5961 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -27,7 +27,7 @@ namespace Jellyfin.Server.Migrations public static void Run(CoreAppHost host, ILoggerFactory loggerFactory) { var logger = loggerFactory.CreateLogger(); - var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration("migrations"); + var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration(MigrationsListStore.StoreKey); var applied = migrationOptions.Applied.ToList(); for (var i = 0; i < Migrations.Length; i++) @@ -56,7 +56,7 @@ namespace Jellyfin.Server.Migrations { logger.LogInformation("Some migrations were run, saving the state"); migrationOptions.Applied = applied.ToArray(); - host.ServerConfigurationManager.SaveConfiguration("migrations", migrationOptions); + host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); } } } diff --git a/Jellyfin.Server/Migrations/MigrationsListStore.cs b/Jellyfin.Server/Migrations/MigrationsListStore.cs index d91d602c14..7a1ca66714 100644 --- a/Jellyfin.Server/Migrations/MigrationsListStore.cs +++ b/Jellyfin.Server/Migrations/MigrationsListStore.cs @@ -7,13 +7,18 @@ namespace Jellyfin.Server.Migrations /// public class MigrationsListStore : ConfigurationStore { + /// + /// The name of the configuration in the storage. + /// + public static readonly string StoreKey = "migrations"; + /// /// Initializes a new instance of the class. /// public MigrationsListStore() { ConfigurationType = typeof(MigrationOptions); - Key = "migrations"; + Key = StoreKey; } } } From 55b429e5e816bea33afbd810d5f1e4f560ef0069 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 5 Mar 2020 20:40:17 +0300 Subject: [PATCH 065/104] Moved migration routines to their own directory --- Jellyfin.Server/Migrations/MigrationRunner.cs | 4 ++-- .../Migrations/{ => Routines}/DisableTranscodingThrottling.cs | 2 +- .../Migrations/{ => Routines}/DisableZealousLogging.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename Jellyfin.Server/Migrations/{ => Routines}/DisableTranscodingThrottling.cs (96%) rename Jellyfin.Server/Migrations/{ => Routines}/DisableZealousLogging.cs (95%) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 1db99f5961..8b72cb467e 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -15,8 +15,8 @@ namespace Jellyfin.Server.Migrations /// internal static readonly IUpdater[] Migrations = { - new DisableTranscodingThrottling(), - new DisableZealousLogging() + new Routines.DisableTranscodingThrottling(), + new Routines.DisableZealousLogging() }; /// diff --git a/Jellyfin.Server/Migrations/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs similarity index 96% rename from Jellyfin.Server/Migrations/DisableTranscodingThrottling.cs rename to Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index 83624bdadd..eff6469e20 100644 --- a/Jellyfin.Server/Migrations/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -namespace Jellyfin.Server.Migrations +namespace Jellyfin.Server.Migrations.Routines { /// /// Updater that takes care of bringing configuration up to 10.5.0 standards. diff --git a/Jellyfin.Server/Migrations/DisableZealousLogging.cs b/Jellyfin.Server/Migrations/Routines/DisableZealousLogging.cs similarity index 95% rename from Jellyfin.Server/Migrations/DisableZealousLogging.cs rename to Jellyfin.Server/Migrations/Routines/DisableZealousLogging.cs index a0a934d4ab..501f8f8654 100644 --- a/Jellyfin.Server/Migrations/DisableZealousLogging.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableZealousLogging.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; using Serilog; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Jellyfin.Server.Migrations +namespace Jellyfin.Server.Migrations.Routines { /// /// Updater that takes care of bringing configuration up to 10.5.0 standards. From 216e425cc55e8de1718df76f89c923cdf54de871 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 5 Mar 2020 20:52:00 +0300 Subject: [PATCH 066/104] Fix comment --- Jellyfin.Server/Migrations/IUpdater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/IUpdater.cs b/Jellyfin.Server/Migrations/IUpdater.cs index 10ada73d5f..9b749841cf 100644 --- a/Jellyfin.Server/Migrations/IUpdater.cs +++ b/Jellyfin.Server/Migrations/IUpdater.cs @@ -14,7 +14,7 @@ namespace Jellyfin.Server.Migrations public abstract string Name { get; } /// - /// Execute the migration from version "from". + /// Execute the migration routine. /// /// Host that hosts current version. /// Host logger. From e36c4de9f6bb33fe6ccf3b9c6eae4a2f058424ba Mon Sep 17 00:00:00 2001 From: ferferga Date: Thu, 5 Mar 2020 18:53:04 +0100 Subject: [PATCH 067/104] Replaces uninstaller icon so it's more visible --- deployment/windows/jellyfin.nsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/windows/jellyfin.nsi b/deployment/windows/jellyfin.nsi index 86724b8f49..fada62d981 100644 --- a/deployment/windows/jellyfin.nsi +++ b/deployment/windows/jellyfin.nsi @@ -73,7 +73,7 @@ Unicode True ; TODO: Replace with nice Jellyfin Icons !ifdef UXPATH !define MUI_ICON "${UXPATH}\branding\NSIS\modern-install.ico" ; Installer Icon - !define MUI_UNICON "${UXPATH}\branding\NSIS\modern-uninstall.ico" ; Uninstaller Icon + !define MUI_UNICON "${UXPATH}\branding\NSIS\modern-install.ico" ; Uninstaller Icon !define MUI_HEADERIMAGE !define MUI_HEADERIMAGE_BITMAP "${UXPATH}\branding\NSIS\installer-header.bmp" From d4564d8e29274a854bdc46df3ebcaf0c1e39e906 Mon Sep 17 00:00:00 2001 From: Vasily Date: Fri, 6 Mar 2020 13:22:44 +0300 Subject: [PATCH 068/104] More logging, mark all migrations as applied if setup wizard is not complete --- Jellyfin.Server/Migrations/MigrationRunner.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 8b72cb467e..00a7c3a00d 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -28,6 +28,17 @@ namespace Jellyfin.Server.Migrations { var logger = loggerFactory.CreateLogger(); var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration(MigrationsListStore.StoreKey); + + if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted) + { + // If startup wizard is not finished, this is a fresh install. + // Don't run any migrations, just mark all of them as applied. + logger.LogInformation("Marking all known migrations as applied because this is fresh install"); + migrationOptions.Applied = Migrations.Select(m => m.Name).ToArray(); + host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); + return; + } + var applied = migrationOptions.Applied.ToList(); for (var i = 0; i < Migrations.Length; i++) @@ -35,20 +46,22 @@ namespace Jellyfin.Server.Migrations var updater = Migrations[i]; if (applied.Contains(updater.Name)) { - logger.LogDebug("Skipping migration {0} as it is already applied", updater.Name); + logger.LogDebug("Skipping migration {Name} as it is already applied", updater.Name); continue; } + logger.LogInformation("Applying migration {Name}", updater.Name); try { updater.Perform(host, logger); } catch (Exception ex) { - logger.LogError(ex, "Cannot apply migration {0}", updater.Name); + logger.LogError(ex, "Cannot apply migration {Name}", updater.Name); continue; } + logger.LogInformation("Migration {Name} applied successfully", updater.Name); applied.Add(updater.Name); } From 098d3538e32f4440bf6ed3a14844ff3a3b2a6fca Mon Sep 17 00:00:00 2001 From: Vasily Date: Fri, 6 Mar 2020 17:22:22 +0300 Subject: [PATCH 069/104] Disable logging.json migration as it is not ready yet --- Jellyfin.Server/Migrations/MigrationRunner.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 00a7c3a00d..caaa58ae15 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -15,8 +15,7 @@ namespace Jellyfin.Server.Migrations /// internal static readonly IUpdater[] Migrations = { - new Routines.DisableTranscodingThrottling(), - new Routines.DisableZealousLogging() + new Routines.DisableTranscodingThrottling() }; /// From 5a0f1fe848aa16176ee1f697af8fe91b92c15c54 Mon Sep 17 00:00:00 2001 From: Vasily Date: Fri, 6 Mar 2020 19:01:07 +0300 Subject: [PATCH 070/104] Implement review suggestion --- Jellyfin.Server/Migrations/MigrationRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index caaa58ae15..0274e68a1d 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Server.Migrations var logger = loggerFactory.CreateLogger(); var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration(MigrationsListStore.StoreKey); - if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted) + if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Length == 0) { // If startup wizard is not finished, this is a fresh install. // Don't run any migrations, just mark all of them as applied. From f2fdf50b3b7138f47e1777e31ef97b737775fb63 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 6 Mar 2020 19:07:34 +0100 Subject: [PATCH 071/104] Create separate constants for the two logging file names --- Jellyfin.Server/Program.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 0271861054..970443c8b6 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -39,9 +39,14 @@ namespace Jellyfin.Server public static class Program { /// - /// The name of logging configuration file. + /// The name of logging configuration file containing application defaults. /// - public static readonly string LoggingConfigFile = "logging.json"; + public static readonly string LoggingConfigFileDefault = "logging.default.json"; + + /// + /// The name of the logging configuration file containing user override settings. + /// + public static readonly string LoggingConfigFileUser = "logging.user.json"; private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); @@ -443,7 +448,7 @@ namespace Jellyfin.Server private static async Task CreateConfiguration(IApplicationPaths appPaths) { const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json"; - string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, LoggingConfigFile); + string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, LoggingConfigFileDefault); if (!File.Exists(configPath)) { @@ -465,7 +470,7 @@ namespace Jellyfin.Server return new ConfigurationBuilder() .SetBasePath(appPaths.ConfigurationDirectoryPath) .AddInMemoryCollection(ConfigurationOptions.Configuration) - .AddJsonFile(LoggingConfigFile, false, true) + .AddJsonFile(LoggingConfigFileDefault, false, true) .AddEnvironmentVariables("JELLYFIN_") .Build(); } From 1a9908d094a83b66c1b56e78bc496a1ff056c7d9 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 6 Mar 2020 19:11:42 +0100 Subject: [PATCH 072/104] Add migration to create "logging.user.json" --- Jellyfin.Server/Migrations/MigrationRunner.cs | 3 +- .../Routines/CreateUserLoggingConfigFile.cs | 93 +++++++++++++++++++ .../Routines/DisableZealousLogging.cs | 29 ------ 3 files changed, 95 insertions(+), 30 deletions(-) create mode 100644 Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs delete mode 100644 Jellyfin.Server/Migrations/Routines/DisableZealousLogging.cs diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index caaa58ae15..ac7f3d77ae 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -15,7 +15,8 @@ namespace Jellyfin.Server.Migrations /// internal static readonly IUpdater[] Migrations = { - new Routines.DisableTranscodingThrottling() + new Routines.DisableTranscodingThrottling(), + new Routines.CreateUserLoggingConfigFile() }; /// diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs new file mode 100644 index 0000000000..7a089680e4 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MediaBrowser.Common.Configuration; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// + /// Migration to initialize the user logging configuration file "logging.user.json". + /// If the deprecated logging.json file exists and has a custom config, it will be used as logging.user.json, + /// otherwise a blank file will be created. + /// + internal class CreateUserLoggingConfigFile : IUpdater + { + /// + /// An empty logging JSON configuration, which will be used as the default contents for the user settings config file. + /// + private const string EmptyLoggingConfig = @"{ ""Serilog"": { } }"; + + /// + /// File history for logging.json as existed during this migration creation. The contents for each has been minified. + /// + private readonly List _defaultConfigHistory = new List + { + // 9a6c27947353585391e211aa88b925f81e8cd7b9 + @"{""Serilog"":{""MinimumLevel"":{""Default"":""Information"",""Override"":{""Microsoft"":""Warning"",""System"":""Warning""}},""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + // 71bdcd730705a714ee208eaad7290b7c68df3885 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + // a44936f97f8afc2817d3491615a7cfe1e31c251c + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}", + // 7af3754a11ad5a4284f107997fb5419a010ce6f3 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}", + // 60691349a11f541958e0b2247c9abc13cb40c9fb + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}", + // 65fe243afbcc4b596cf8726708c1965cd34b5f68 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {ThreadId} {SourceContext}: {Message:lj} {NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {ThreadId} {SourceContext}:{Message} {NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + // 96c9af590494aa8137d5a061aaf1e68feee60b67 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}:{Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + }; + + /// + public string Name => "CreateLoggingConfigHeirarchy"; + + /// + public void Perform(CoreAppHost host, ILogger logger) + { + var logDirectory = host.Resolve().ConfigurationDirectoryPath; + var oldConfigPath = Path.Combine(logDirectory, "logging.json"); + var userConfigPath = Path.Combine(logDirectory, Program.LoggingConfigFileUser); + + // Check if there are existing settings in the old "logging.json" file that should be migrated + bool shouldMigrateOldFile = ShouldKeepOldConfig(oldConfigPath); + + // Create the user settings file "logging.user.json" + if (shouldMigrateOldFile) + { + // Use the existing logging.json file + File.Copy(oldConfigPath, userConfigPath); + } + else + { + // Write an empty JSON file + File.WriteAllText(userConfigPath, EmptyLoggingConfig); + } + } + + /// + /// Check if the existing logging.json file should be migrated to logging.user.json. + /// + private bool ShouldKeepOldConfig(string oldConfigPath) + { + // Cannot keep the old logging file if it doesn't exist + if (!File.Exists(oldConfigPath)) + { + return false; + } + + // Check if the existing logging.json file has been modified by the user by comparing it to all the + // versions in our git history. Until now, the file has never been migrated after first creation so users + // could have any version from the git history. + var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath)); + var existingConfigIsUnmodified = _defaultConfigHistory + .Select(historicalConfigText => JToken.Parse(historicalConfigText)) + .Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson)); + + // The existing config file should be kept and used only if it has been modified by the user + return !existingConfigIsUnmodified; + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/DisableZealousLogging.cs b/Jellyfin.Server/Migrations/Routines/DisableZealousLogging.cs deleted file mode 100644 index 501f8f8654..0000000000 --- a/Jellyfin.Server/Migrations/Routines/DisableZealousLogging.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.IO; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Serilog; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Jellyfin.Server.Migrations.Routines -{ - /// - /// Updater that takes care of bringing configuration up to 10.5.0 standards. - /// - internal class DisableZealousLogging : IUpdater - { - /// - public string Name => "DisableZealousLogging"; - - /// - // This tones down logging from some components - public void Perform(CoreAppHost host, ILogger logger) - { - string configPath = Path.Combine(host.ServerConfigurationManager.ApplicationPaths.ConfigurationDirectoryPath, Program.LoggingConfigFile); - // TODO: fix up the config - throw new NotImplementedException("don't know how to fix logging yet"); - } - } -} From 6660006f01aee44ea33d1539000c5e4ea06e1115 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 6 Mar 2020 19:28:36 +0100 Subject: [PATCH 073/104] Load user logging config file into application configuration --- Jellyfin.Server/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 970443c8b6..2590fdb215 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -470,7 +470,8 @@ namespace Jellyfin.Server return new ConfigurationBuilder() .SetBasePath(appPaths.ConfigurationDirectoryPath) .AddInMemoryCollection(ConfigurationOptions.Configuration) - .AddJsonFile(LoggingConfigFileDefault, false, true) + .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true) + .AddJsonFile(LoggingConfigFileUser, optional: true, reloadOnChange: true) .AddEnvironmentVariables("JELLYFIN_") .Build(); } From 4c2b543b307b55b2220472c59396b9b4a604cfb7 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 6 Mar 2020 21:51:50 +0100 Subject: [PATCH 074/104] Rename IUpdater to IMigrationRoutine --- .../{IUpdater.cs => IMigrationRoutine.cs} | 8 ++++---- Jellyfin.Server/Migrations/MigrationRunner.cs | 18 +++++++++--------- .../Routines/CreateUserLoggingConfigFile.cs | 2 +- .../Routines/DisableTranscodingThrottling.cs | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) rename Jellyfin.Server/Migrations/{IUpdater.cs => IMigrationRoutine.cs} (69%) diff --git a/Jellyfin.Server/Migrations/IUpdater.cs b/Jellyfin.Server/Migrations/IMigrationRoutine.cs similarity index 69% rename from Jellyfin.Server/Migrations/IUpdater.cs rename to Jellyfin.Server/Migrations/IMigrationRoutine.cs index 9b749841cf..20a3aa3d6a 100644 --- a/Jellyfin.Server/Migrations/IUpdater.cs +++ b/Jellyfin.Server/Migrations/IMigrationRoutine.cs @@ -4,20 +4,20 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations { /// - /// Interface that descibes a migration routine. + /// Interface that describes a migration routine. /// - internal interface IUpdater + internal interface IMigrationRoutine { /// /// Gets the name of the migration, must be unique. /// - public abstract string Name { get; } + public string Name { get; } /// /// Execute the migration routine. /// /// Host that hosts current version. /// Host logger. - public abstract void Perform(CoreAppHost host, ILogger logger); + public void Perform(CoreAppHost host, ILogger logger); } } diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index ac7f3d77ae..8e786f34ea 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Server.Migrations /// /// The list of known migrations, in order of applicability. /// - internal static readonly IUpdater[] Migrations = + internal static readonly IMigrationRoutine[] Migrations = { new Routines.DisableTranscodingThrottling(), new Routines.CreateUserLoggingConfigFile() @@ -43,26 +43,26 @@ namespace Jellyfin.Server.Migrations for (var i = 0; i < Migrations.Length; i++) { - var updater = Migrations[i]; - if (applied.Contains(updater.Name)) + var migrationRoutine = Migrations[i]; + if (applied.Contains(migrationRoutine.Name)) { - logger.LogDebug("Skipping migration {Name} as it is already applied", updater.Name); + logger.LogDebug("Skipping migration {Name} as it is already applied", migrationRoutine.Name); continue; } - logger.LogInformation("Applying migration {Name}", updater.Name); + logger.LogInformation("Applying migration {Name}", migrationRoutine.Name); try { - updater.Perform(host, logger); + migrationRoutine.Perform(host, logger); } catch (Exception ex) { - logger.LogError(ex, "Cannot apply migration {Name}", updater.Name); + logger.LogError(ex, "Cannot apply migration {Name}", migrationRoutine.Name); continue; } - logger.LogInformation("Migration {Name} applied successfully", updater.Name); - applied.Add(updater.Name); + logger.LogInformation("Migration {Name} applied successfully", migrationRoutine.Name); + applied.Add(migrationRoutine.Name); } if (applied.Count > migrationOptions.Applied.Length) diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 7a089680e4..6dbeb27761 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Server.Migrations.Routines /// If the deprecated logging.json file exists and has a custom config, it will be used as logging.user.json, /// otherwise a blank file will be created. /// - internal class CreateUserLoggingConfigFile : IUpdater + internal class CreateUserLoggingConfigFile : IMigrationRoutine { /// /// An empty logging JSON configuration, which will be used as the default contents for the user settings config file. diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index eff6469e20..db0bef8851 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -10,7 +10,7 @@ namespace Jellyfin.Server.Migrations.Routines /// /// Updater that takes care of bringing configuration up to 10.5.0 standards. /// - internal class DisableTranscodingThrottling : IUpdater + internal class DisableTranscodingThrottling : IMigrationRoutine { /// public string Name => "DisableTranscodingThrottling"; From b373721d299826bafac8cfadeeacbd04fed436dd Mon Sep 17 00:00:00 2001 From: Marcus Schelin Date: Fri, 6 Mar 2020 22:27:05 +0000 Subject: [PATCH 075/104] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/ --- .../Localization/Core/sv.json | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index db4cfde957..b2934545d3 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -1,7 +1,7 @@ { "Albums": "Album", - "AppDeviceValues": "App: {0}, Enhet: {1}", - "Application": "App", + "AppDeviceValues": "Applikation: {0}, Enhet: {1}", + "Application": "Applikation", "Artists": "Artister", "AuthenticationSucceededWithUserName": "{0} har autentiserats", "Books": "Böcker", @@ -16,7 +16,7 @@ "Folders": "Mappar", "Genres": "Genrer", "HeaderAlbumArtists": "Albumartister", - "HeaderCameraUploads": "Kamera Uppladdningar", + "HeaderCameraUploads": "Kamerauppladdningar", "HeaderContinueWatching": "Fortsätt kolla", "HeaderFavoriteAlbums": "Favoritalbum", "HeaderFavoriteArtists": "Favoritartister", @@ -34,9 +34,9 @@ "LabelRunningTimeValue": "Speltid: {0}", "Latest": "Senaste", "MessageApplicationUpdated": "Jellyfin Server har uppdaterats", - "MessageApplicationUpdatedTo": "Jellyfin Server har uppgraderats till {0}", + "MessageApplicationUpdatedTo": "Jellyfin Server har uppdaterats till {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Serverinställningarna {0} har uppdaterats", - "MessageServerConfigurationUpdated": "Server konfigurationen har uppdaterats", + "MessageServerConfigurationUpdated": "Serverkonfigurationen har uppdaterats", "MixedContent": "Blandat innehåll", "Movies": "Filmer", "Music": "Musik", @@ -44,11 +44,11 @@ "NameInstallFailed": "{0} installationen misslyckades", "NameSeasonNumber": "Säsong {0}", "NameSeasonUnknown": "Okänd säsong", - "NewVersionIsAvailable": "En ny version av Jellyfin Server är klar för nedladdning.", + "NewVersionIsAvailable": "En ny version av Jellyfin Server är tillgänglig att hämta.", "NotificationOptionApplicationUpdateAvailable": "Ny programversion tillgänglig", "NotificationOptionApplicationUpdateInstalled": "Programuppdatering installerad", "NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats", - "NotificationOptionAudioPlaybackStopped": "Ljuduppspelning stoppad", + "NotificationOptionAudioPlaybackStopped": "Ljuduppspelning stoppades", "NotificationOptionCameraImageUploaded": "Kamerabild har laddats upp", "NotificationOptionInstallationFailed": "Fel vid installation", "NotificationOptionNewLibraryContent": "Nytt innehåll har lagts till", @@ -60,7 +60,7 @@ "NotificationOptionTaskFailed": "Schemalagd aktivitet har misslyckats", "NotificationOptionUserLockedOut": "Användare har låsts ut", "NotificationOptionVideoPlayback": "Videouppspelning har påbörjats", - "NotificationOptionVideoPlaybackStopped": "Videouppspelning stoppad", + "NotificationOptionVideoPlaybackStopped": "Videouppspelning stoppades", "Photos": "Bilder", "Playlists": "Spellistor", "Plugin": "Tillägg", @@ -69,13 +69,13 @@ "PluginUpdatedWithName": "{0} uppdaterades", "ProviderValue": "Källa: {0}", "ScheduledTaskFailedWithName": "{0} misslyckades", - "ScheduledTaskStartedWithName": "{0} startad", + "ScheduledTaskStartedWithName": "{0} startades", "ServerNameNeedsToBeRestarted": "{0} behöver startas om", "Shows": "Serier", "Songs": "Låtar", - "StartupEmbyServerIsLoading": "Jellyfin server arbetar. Pröva igen inom kort.", + "StartupEmbyServerIsLoading": "Jellyfin Server arbetar. Pröva igen snart.", "SubtitleDownloadFailureForItem": "Nerladdning av undertexter för {0} misslyckades", - "SubtitleDownloadFailureFromForItem": "Undertexter misslyckades att ladda ner {0} för {1}", + "SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} för {1}", "SubtitlesDownloadedForItem": "Undertexter har laddats ner till {0}", "Sync": "Synk", "System": "System", @@ -89,9 +89,9 @@ "UserOnlineFromDevice": "{0} är uppkopplad från {1}", "UserPasswordChangedWithName": "Lösenordet för {0} har ändrats", "UserPolicyUpdatedWithName": "Användarpolicyn har uppdaterats för {0}", - "UserStartedPlayingItemWithValues": "{0} har börjat spela upp {1}", - "UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1}", - "ValueHasBeenAddedToLibrary": "{0} har blivit tillagd till ditt mediabibliotek", + "UserStartedPlayingItemWithValues": "{0} spelar upp {1} på {2}", + "UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1} på {2}", + "ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek", "ValueSpecialEpisodeName": "Specialavsnitt - {0}", "VersionNumber": "Version {0}" } From f3db3cacf6c5ee66128305154daaeaca09de9cb2 Mon Sep 17 00:00:00 2001 From: Terrance M Date: Fri, 6 Mar 2020 22:26:36 +0000 Subject: [PATCH 076/104] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index dd61686145..68134a1510 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -17,14 +17,14 @@ "Genres": "风格", "HeaderAlbumArtists": "专辑作家", "HeaderCameraUploads": "相机上传", - "HeaderContinueWatching": "继续观看", + "HeaderContinueWatching": "继续观影", "HeaderFavoriteAlbums": "收藏的专辑", "HeaderFavoriteArtists": "最爱的艺术家", "HeaderFavoriteEpisodes": "最爱的剧集", "HeaderFavoriteShows": "最爱的节目", "HeaderFavoriteSongs": "最爱的歌曲", "HeaderLiveTV": "电视直播", - "HeaderNextUp": "下一步", + "HeaderNextUp": "接下来", "HeaderRecordingGroups": "录制组", "HomeVideos": "家庭视频", "Inherit": "继承", From e8c593f413f0856934f446db2cc8aca3693df818 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 7 Mar 2020 17:39:55 +0100 Subject: [PATCH 077/104] Add baseURL to attachments --- MediaBrowser.Api/Playback/MediaInfoService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index a44e1720fe..08a7e534f9 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -573,7 +573,8 @@ namespace MediaBrowser.Api.Playback { attachment.DeliveryUrl = string.Format( CultureInfo.InvariantCulture, - "/Videos/{0}/{1}/Attachments/{2}", + "{0}/Videos/{1}/{2}/Attachments/{3}", + ServerConfigurationManager.Configuration.BaseUrl, item.Id, mediaSource.Id, attachment.Index); From e0381c88544e214d9c4ed328b550d938818a7357 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sat, 7 Mar 2020 11:55:02 -0500 Subject: [PATCH 078/104] Set EnableHttps disabled by default Prevents issues on first setup when behind a reverse proxy. Also prevents issues saving the Networking page by default if SSL is not fully configured. --- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index bf10108b8c..c8f18e69e5 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -248,7 +248,7 @@ namespace MediaBrowser.Model.Configuration PublicHttpsPort = DefaultHttpsPort; HttpServerPortNumber = DefaultHttpPort; HttpsPortNumber = DefaultHttpsPort; - EnableHttps = true; + EnableHttps = false; EnableDashboardResponseCaching = true; EnableCaseSensitiveItemIds = true; From 7ecb16a46e44b8e2f8cc23329a2b66dad6aeb2fd Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sat, 7 Mar 2020 18:23:32 +0100 Subject: [PATCH 079/104] do not ignore exceptions during migration execution --- Jellyfin.Server/Migrations/MigrationRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 0274e68a1d..15c1e75aaf 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -57,7 +57,7 @@ namespace Jellyfin.Server.Migrations catch (Exception ex) { logger.LogError(ex, "Cannot apply migration {Name}", updater.Name); - continue; + throw; } logger.LogInformation("Migration {Name} applied successfully", updater.Name); From 1295f6c79bdf76274501838c9e42094e4b1dd3c0 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sat, 7 Mar 2020 20:18:45 +0100 Subject: [PATCH 080/104] Documentation and log message cleanup --- Jellyfin.Server/Migrations/MigrationOptions.cs | 2 +- Jellyfin.Server/Migrations/MigrationRunner.cs | 10 +++++----- Jellyfin.Server/Migrations/MigrationsFactory.cs | 2 +- .../Routines/DisableTranscodingThrottling.cs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationOptions.cs b/Jellyfin.Server/Migrations/MigrationOptions.cs index b96288cc1b..6b7831158f 100644 --- a/Jellyfin.Server/Migrations/MigrationOptions.cs +++ b/Jellyfin.Server/Migrations/MigrationOptions.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Server.Migrations #pragma warning disable CA1819 // Properties should not return arrays /// - /// Gets or sets he list of applied migration routine names. + /// Gets or sets the list of applied migration routine names. /// public string[] Applied { get; set; } #pragma warning restore CA1819 // Properties should not return arrays diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 15c1e75aaf..ca4c79cfd3 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations { /// - /// The class that knows how migrate between different Jellyfin versions. + /// The class that knows which migrations to apply and how to apply them. /// public sealed class MigrationRunner { @@ -45,22 +45,22 @@ namespace Jellyfin.Server.Migrations var updater = Migrations[i]; if (applied.Contains(updater.Name)) { - logger.LogDebug("Skipping migration {Name} as it is already applied", updater.Name); + logger.LogDebug("Skipping migration '{Name}' since it is already applied", updater.Name); continue; } - logger.LogInformation("Applying migration {Name}", updater.Name); + logger.LogInformation("Applying migration '{Name}'", updater.Name); try { updater.Perform(host, logger); } catch (Exception ex) { - logger.LogError(ex, "Cannot apply migration {Name}", updater.Name); + logger.LogError(ex, "Could not apply migration '{Name}'", updater.Name); throw; } - logger.LogInformation("Migration {Name} applied successfully", updater.Name); + logger.LogInformation("Migration '{Name}' applied successfully", updater.Name); applied.Add(updater.Name); } diff --git a/Jellyfin.Server/Migrations/MigrationsFactory.cs b/Jellyfin.Server/Migrations/MigrationsFactory.cs index ed01dc646a..23c1b1ee6f 100644 --- a/Jellyfin.Server/Migrations/MigrationsFactory.cs +++ b/Jellyfin.Server/Migrations/MigrationsFactory.cs @@ -4,7 +4,7 @@ using MediaBrowser.Common.Configuration; namespace Jellyfin.Server.Migrations { /// - /// A factory that teachs Jellyfin how to find a peristent file which lists all applied migrations. + /// A factory that can find a persistent file of the migration configuration, which lists all applied migrations. /// public class MigrationsFactory : IConfigurationFactory { diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index eff6469e20..936c3640e0 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations.Routines { /// - /// Updater that takes care of bringing configuration up to 10.5.0 standards. + /// Disable transcode throttling for all installations since it is currently broken for certain video formats. /// internal class DisableTranscodingThrottling : IUpdater { @@ -18,7 +18,7 @@ namespace Jellyfin.Server.Migrations.Routines /// public void Perform(CoreAppHost host, ILogger logger) { - // Set EnableThrottling to false as it wasn't used before, and in 10.5.0 it may introduce issues + // Set EnableThrottling to false since it wasn't used before and may introduce issues var encoding = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration("encoding"); if (encoding.EnableThrottling) { From 796bdd46df40ce0f964d4c293361205ded3b41cb Mon Sep 17 00:00:00 2001 From: Leo Verto Date: Sat, 7 Mar 2020 20:14:28 +0000 Subject: [PATCH 081/104] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/de/ --- Emby.Server.Implementations/Localization/Core/de.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index 019736c471..69678c2684 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -1,15 +1,15 @@ { "Albums": "Alben", - "AppDeviceValues": "App: {0}, Gerät: {1}", - "Application": "Anwendung", + "AppDeviceValues": "Anw: {0}, Gerät: {1}", + "Application": "Programm", "Artists": "Interpreten", "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet", "Books": "Bücher", - "CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}", + "CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen", "Channels": "Kanäle", "ChapterNameValue": "Kapitel {0}", "Collections": "Sammlungen", - "DeviceOfflineWithName": "{0} wurde getrennt", + "DeviceOfflineWithName": "{0} hat die Verbindung getrennt", "DeviceOnlineWithName": "{0} ist verbunden", "FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}", "Favorites": "Favoriten", @@ -17,7 +17,7 @@ "Genres": "Genres", "HeaderAlbumArtists": "Album-Interpreten", "HeaderCameraUploads": "Kamera-Uploads", - "HeaderContinueWatching": "Weiterschauen", + "HeaderContinueWatching": "Fortsetzen", "HeaderFavoriteAlbums": "Lieblingsalben", "HeaderFavoriteArtists": "Lieblings-Interpreten", "HeaderFavoriteEpisodes": "Lieblingsepisoden", From d38eab50ef086a72471d47835d273354ffa9054f Mon Sep 17 00:00:00 2001 From: sharkykh Date: Sat, 7 Mar 2020 23:13:42 +0000 Subject: [PATCH 082/104] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/ --- .../Localization/Core/he.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index f3a7794dae..556937baf5 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -2,28 +2,28 @@ "Albums": "אלבומים", "AppDeviceValues": "יישום: {0}, מכשיר: {1}", "Application": "אפליקציה", - "Artists": "אמנים", - "AuthenticationSucceededWithUserName": "{0} זוהה בהצלחה", + "Artists": "אומנים", + "AuthenticationSucceededWithUserName": "{0} אומת בהצלחה", "Books": "ספרים", - "CameraImageUploadedFrom": "תמונה חדשה הועלתה מ{0}", + "CameraImageUploadedFrom": "תמונת מצלמה חדשה הועלתה מ {0}", "Channels": "ערוצים", "ChapterNameValue": "פרק {0}", - "Collections": "קולקציות", + "Collections": "אוספים", "DeviceOfflineWithName": "{0} התנתק", "DeviceOnlineWithName": "{0} מחובר", "FailedLoginAttemptWithUserName": "ניסיון כניסה שגוי מ{0}", - "Favorites": "אהובים", + "Favorites": "מועדפים", "Folders": "תיקיות", "Genres": "ז'אנרים", "HeaderAlbumArtists": "אמני האלבום", "HeaderCameraUploads": "העלאות ממצלמה", "HeaderContinueWatching": "המשך לצפות", "HeaderFavoriteAlbums": "אלבומים שאהבתי", - "HeaderFavoriteArtists": "אמנים שאהבתי", - "HeaderFavoriteEpisodes": "פרקים אהובים", - "HeaderFavoriteShows": "תוכניות אהובות", - "HeaderFavoriteSongs": "שירים שאהבתי", - "HeaderLiveTV": "טלוויזיה בשידור חי", + "HeaderFavoriteArtists": "אמנים מועדפים", + "HeaderFavoriteEpisodes": "פרקים מועדפים", + "HeaderFavoriteShows": "סדרות מועדפות", + "HeaderFavoriteSongs": "שירים מועדפים", + "HeaderLiveTV": "שידורים חיים", "HeaderNextUp": "הבא", "HeaderRecordingGroups": "קבוצות הקלטה", "HomeVideos": "סרטונים בייתים", From b4e81f2f0cfceb50d042239771e974e70af0c304 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 7 Mar 2020 21:29:51 +0000 Subject: [PATCH 083/104] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/ --- Emby.Server.Implementations/Localization/Core/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 4423b7f988..1d6fdde21a 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -3,7 +3,7 @@ "AppDeviceValues": "App: {0}, Apparaat: {1}", "Application": "Applicatie", "Artists": "Artiesten", - "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd", + "AuthenticationSucceededWithUserName": "{0} succesvol geauthenticeerd", "Books": "Boeken", "CameraImageUploadedFrom": "Er is een nieuwe foto toegevoegd van {0}", "Channels": "Kanalen", From 01b51388f50433eb0d2e90087148c61c048c9d2f Mon Sep 17 00:00:00 2001 From: IDXK Date: Sat, 7 Mar 2020 21:51:11 +0000 Subject: [PATCH 084/104] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/ --- Emby.Server.Implementations/Localization/Core/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 1d6fdde21a..e22f95ab46 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -9,7 +9,7 @@ "Channels": "Kanalen", "ChapterNameValue": "Hoofdstuk {0}", "Collections": "Verzamelingen", - "DeviceOfflineWithName": "{0} heeft de verbinding verbroken", + "DeviceOfflineWithName": "Verbinding met {0} is verbroken", "DeviceOnlineWithName": "{0} is verbonden", "FailedLoginAttemptWithUserName": "Mislukte aanmeld poging van {0}", "Favorites": "Favorieten", From cbd1e924866cef8aae12ee9afb4342041337f0de Mon Sep 17 00:00:00 2001 From: Tomi Date: Sat, 7 Mar 2020 21:28:08 +0000 Subject: [PATCH 085/104] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fi/ --- .../Localization/Core/fi.json | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index d02f841fd1..a38103d25a 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -17,31 +17,31 @@ "LabelIpAddressValue": "IP-osoite: {0}", "ItemRemovedWithName": "{0} poistettiin kirjastosta", "ItemAddedWithName": "{0} lisättiin kirjastoon", - "Inherit": "Periä", + "Inherit": "Periytyä", "HomeVideos": "Kotivideot", - "HeaderRecordingGroups": "Äänitysryhmät", + "HeaderRecordingGroups": "Nauhoitusryhmät", "HeaderNextUp": "Seuraavaksi", "HeaderFavoriteSongs": "Lempikappaleet", "HeaderFavoriteShows": "Lempisarjat", "HeaderFavoriteEpisodes": "Lempijaksot", - "HeaderCameraUploads": "Kamerasta lähetetyt", + "HeaderCameraUploads": "Kameralataukset", "HeaderFavoriteArtists": "Lempiartistit", "HeaderFavoriteAlbums": "Lempialbumit", "HeaderContinueWatching": "Jatka katsomista", - "HeaderAlbumArtists": "Albumin artistit", - "Genres": "Tyylilaji", + "HeaderAlbumArtists": "Albumin esittäjä", + "Genres": "Tyylilajit", "Folders": "Kansiot", "Favorites": "Suosikit", - "FailedLoginAttemptWithUserName": "Epäonnistunut kirjautumisyritys kohteesta {0}", - "DeviceOnlineWithName": "{0} on yhdistynyt", + "FailedLoginAttemptWithUserName": "Kirjautuminen epäonnistui kohteesta {0}", + "DeviceOnlineWithName": "{0} on yhdistetty", "DeviceOfflineWithName": "{0} on katkaissut yhteytensä", "Collections": "Kokoelmat", "ChapterNameValue": "Luku: {0}", "Channels": "Kanavat", - "CameraImageUploadedFrom": "Uusi kamerakuva on lähetetty kohteesta {0}", + "CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}", "Books": "Kirjat", - "AuthenticationSucceededWithUserName": "{0} todennettu onnistuneesti", - "Artists": "Artistit", + "AuthenticationSucceededWithUserName": "{0} todennus onnistui", + "Artists": "Esiintyjät", "Application": "Sovellus", "AppDeviceValues": "Sovellus: {0}, Laite: {1}", "Albums": "Albumit", @@ -76,21 +76,21 @@ "Shows": "Ohjelmat", "ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen", "ProviderValue": "Palveluntarjoaja: {0}", - "Plugin": "Laajennus", + "Plugin": "Liitännäinen", "NotificationOptionVideoPlaybackStopped": "Videon toistaminen pysäytetty", "NotificationOptionVideoPlayback": "Videon toistaminen aloitettu", "NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos", "NotificationOptionTaskFailed": "Ajastetun tehtävän ongelma", "NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan", - "NotificationOptionPluginUpdateInstalled": "Laajennuksen päivitys asennettu", - "NotificationOptionPluginUninstalled": "Laajennus poistettu", - "NotificationOptionPluginInstalled": "Laajennus asennettu", - "NotificationOptionPluginError": "Ongelma laajennuksessa", - "NotificationOptionNewLibraryContent": "Uusi sisältö lisätty", - "NotificationOptionInstallationFailed": "Asennusvirhe", - "NotificationOptionCameraImageUploaded": "Kameran kuva lisätty", - "NotificationOptionAudioPlaybackStopped": "Äänen toistaminen pysäytetty", - "NotificationOptionAudioPlayback": "Äänen toistaminen aloitettu", + "NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty", + "NotificationOptionPluginUninstalled": "Liitännäinen poistettu", + "NotificationOptionPluginInstalled": "Liitännäinen asennettu", + "NotificationOptionPluginError": "Ongelma liitännäisessä", + "NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty", + "NotificationOptionInstallationFailed": "Asennus epäonnistui", + "NotificationOptionCameraImageUploaded": "Kuva ladattu kamerasta", + "NotificationOptionAudioPlaybackStopped": "Audion toisto pysäytetty", + "NotificationOptionAudioPlayback": "Audion toisto aloitettu", "NotificationOptionApplicationUpdateInstalled": "Ohjelmistopäivitys asennettu", "NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla" } From 26c778eb166ead97f32a736e22d99513f9784654 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 8 Mar 2020 12:10:25 +0900 Subject: [PATCH 086/104] implement option to disable audiodb for now --- .../Plugins/AudioDb/AlbumImageProvider.cs | 3 ++- .../Plugins/AudioDb/AlbumProvider.cs | 11 +++++++++++ .../Plugins/AudioDb/ArtistImageProvider.cs | 3 ++- .../Plugins/AudioDb/ArtistProvider.cs | 6 ++++++ .../AudioDb/Configuration/PluginConfiguration.cs | 2 ++ .../Plugins/AudioDb/Configuration/config.html | 4 ++++ 6 files changed, 27 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs index 85719fa51a..dee2d59f0f 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs @@ -102,6 +102,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb } /// - public bool Supports(BaseItem item) => item is MusicAlbum; + public bool Supports(BaseItem item) + => Plugin.Instance.Configuration.Enable && item is MusicAlbum; } } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs index 7f9f8c628d..1a0e878719 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs @@ -55,6 +55,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb { var result = new MetadataResult(); + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return result; + } + var id = info.GetReleaseGroupId(); if (!string.IsNullOrWhiteSpace(id)) @@ -78,6 +84,11 @@ namespace MediaBrowser.Providers.Plugins.AudioDb private void ProcessResult(MusicAlbum item, Album result, string preferredLanguage) { + if (Plugin.Instance.Configuration.ReplaceAlbumName && !string.IsNullOrWhiteSpace(result.strAlbum)) + { + item.Album = result.strAlbum; + } + if (!string.IsNullOrWhiteSpace(result.strArtist)) { item.AlbumArtists = new string[] { result.strArtist }; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs index 51b0eacfc8..18afd5dd5c 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs @@ -143,6 +143,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb } /// - public bool Supports(BaseItem item) => item is MusicArtist; + public bool Supports(BaseItem item) + => Plugin.Instance.Configuration.Enable && item is MusicArtist; } } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index a51c107df3..df0f3df8fa 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -56,6 +56,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb { var result = new MetadataResult(); + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return result; + } + var id = info.GetMusicBrainzArtistId(); if (!string.IsNullOrWhiteSpace(id)) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs index 90790d26fc..ad3c7eb4bd 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs @@ -5,5 +5,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class PluginConfiguration : BasePluginConfiguration { public bool Enable { get; set; } + + public bool ReplaceAlbumName { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html index 56c32e13ea..c50dba71f7 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html @@ -12,6 +12,10 @@ Enable this provider for metadata searches on artists and albums. +
From acf1698d2b9277afea001555c2bb8e8d90f275ae Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 8 Mar 2020 12:17:49 +0900 Subject: [PATCH 087/104] include audiodb config page in release --- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 48c16b6433..dfe3eb2ef6 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -24,6 +24,11 @@ true + + + + + From f8b391538d9283879e962e0dbffed12dc6024a7d Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 8 Mar 2020 12:19:38 +0900 Subject: [PATCH 088/104] update audiodb config page --- .../Plugins/AudioDb/Configuration/config.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html index c50dba71f7..34494644d4 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html @@ -13,7 +13,7 @@ Enable this provider for metadata searches on artists and albums.
@@ -32,6 +32,7 @@ Dashboard.showLoadingMsg(); ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { $('#enable').checked(config.Enable); + $('#replaceAlbumName').checked(config.ReplaceAlbumName); Dashboard.hideLoadingMsg(); }); @@ -43,6 +44,7 @@ var form = this; ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { config.Enable = $('#enable', form).checked(); + config.ReplaceAlbumName = $('#replaceAlbumName', form).checked(); ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); }); From 86190aa7e995c490509e65159e4c1f76cda61cc3 Mon Sep 17 00:00:00 2001 From: sharkykh Date: Sun, 8 Mar 2020 10:52:43 +0000 Subject: [PATCH 089/104] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/ --- Emby.Server.Implementations/Localization/Core/he.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index 556937baf5..5618719dd3 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -40,8 +40,8 @@ "MixedContent": "תוכן מעורב", "Movies": "סרטים", "Music": "מוזיקה", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", + "MusicVideos": "קליפים", + "NameInstallFailed": "התקנת {0} נכשלה", "NameSeasonNumber": "עונה {0}", "NameSeasonUnknown": "עונה לא ידועה", "NewVersionIsAvailable": "גרסה חדשה של שרת Jellyfin זמינה להורדה.", @@ -89,8 +89,8 @@ "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}", + "UserStartedPlayingItemWithValues": "{0} מנגן את {1} על {2}", + "UserStoppedPlayingItemWithValues": "{0} סיים לנגן את {1} על {2}", "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueSpecialEpisodeName": "מיוחד- {0}", "VersionNumber": "Version {0}" From a0fdceb4bcf672bb87bed9f89921f60e8213080b Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 8 Mar 2020 15:02:42 +0100 Subject: [PATCH 090/104] Throw exception on migration failure to halt application Also save migration configuration after each migration instead of at the end in case an exception is thrown part way through the list --- Jellyfin.Server/Migrations/MigrationRunner.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 8bc29d8ac8..0f9c2a3914 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -59,16 +59,12 @@ namespace Jellyfin.Server.Migrations catch (Exception ex) { logger.LogError(ex, "Could not apply migration {Name}", migrationRoutine.Name); - continue; + throw; } + // Mark the migration as completed logger.LogInformation("Migration {Name} applied successfully", migrationRoutine.Name); applied.Add(migrationRoutine.Name); - } - - if (applied.Count > migrationOptions.Applied.Length) - { - logger.LogInformation("Some migrations were run, saving the state"); migrationOptions.Applied = applied.ToArray(); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); } From 2f0b4cc24c84d98820c8a6755191d4fc1dab74e7 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 8 Mar 2020 15:02:59 +0100 Subject: [PATCH 091/104] Clean up migration logging messages --- Jellyfin.Server/Migrations/MigrationRunner.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 0f9c2a3914..821f51c02f 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -46,11 +46,11 @@ namespace Jellyfin.Server.Migrations var migrationRoutine = Migrations[i]; if (applied.Contains(migrationRoutine.Name)) { - logger.LogDebug("Skipping migration {Name} as it is already applied", migrationRoutine.Name); + logger.LogDebug("Skipping migration '{Name}' since it is already applied", migrationRoutine.Name); continue; } - logger.LogInformation("Applying migration {Name}", migrationRoutine.Name); + logger.LogInformation("Applying migration '{Name}'", migrationRoutine.Name); try { @@ -58,15 +58,16 @@ namespace Jellyfin.Server.Migrations } catch (Exception ex) { - logger.LogError(ex, "Could not apply migration {Name}", migrationRoutine.Name); + logger.LogError(ex, "Could not apply migration '{Name}'", migrationRoutine.Name); throw; } // Mark the migration as completed - logger.LogInformation("Migration {Name} applied successfully", migrationRoutine.Name); + logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name); applied.Add(migrationRoutine.Name); migrationOptions.Applied = applied.ToArray(); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); + logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name); } } } From 8dbb1c92573ba5cf3e4f8d953ffd6083a8ccbde4 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 8 Mar 2020 15:46:13 +0100 Subject: [PATCH 092/104] Use logging.json instead of logging.user.json for override settings --- .../Routines/CreateUserLoggingConfigFile.cs | 45 +++++-------------- Jellyfin.Server/Program.cs | 6 +-- 2 files changed, 14 insertions(+), 37 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 6dbeb27761..834099ea05 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -15,11 +15,6 @@ namespace Jellyfin.Server.Migrations.Routines ///
internal class CreateUserLoggingConfigFile : IMigrationRoutine { - /// - /// An empty logging JSON configuration, which will be used as the default contents for the user settings config file. - /// - private const string EmptyLoggingConfig = @"{ ""Serilog"": { } }"; - /// /// File history for logging.json as existed during this migration creation. The contents for each has been minified. /// @@ -48,46 +43,28 @@ namespace Jellyfin.Server.Migrations.Routines public void Perform(CoreAppHost host, ILogger logger) { var logDirectory = host.Resolve().ConfigurationDirectoryPath; - var oldConfigPath = Path.Combine(logDirectory, "logging.json"); - var userConfigPath = Path.Combine(logDirectory, Program.LoggingConfigFileUser); + var existingConfigPath = Path.Combine(logDirectory, "logging.json"); - // Check if there are existing settings in the old "logging.json" file that should be migrated - bool shouldMigrateOldFile = ShouldKeepOldConfig(oldConfigPath); - - // Create the user settings file "logging.user.json" - if (shouldMigrateOldFile) + // If the existing logging.json config file is unmodified, then 'reset' it by moving it to 'logging.old.json' + // NOTE: This config file has 'reloadOnChange: true', so this change will take effect immediately even though it has already been loaded + if (File.Exists(existingConfigPath) && ExistingConfigUnmodified(existingConfigPath)) { - // Use the existing logging.json file - File.Copy(oldConfigPath, userConfigPath); - } - else - { - // Write an empty JSON file - File.WriteAllText(userConfigPath, EmptyLoggingConfig); + File.Move(existingConfigPath, Path.Combine(logDirectory, "logging.old.json")); } } /// - /// Check if the existing logging.json file should be migrated to logging.user.json. + /// Check if the existing logging.json file has not been modified by the user by comparing it to all the + /// versions in our git history. Until now, the file has never been migrated after first creation so users + /// could have any version from the git history. /// - private bool ShouldKeepOldConfig(string oldConfigPath) + /// does not exist or could not be read. + private bool ExistingConfigUnmodified(string oldConfigPath) { - // Cannot keep the old logging file if it doesn't exist - if (!File.Exists(oldConfigPath)) - { - return false; - } - - // Check if the existing logging.json file has been modified by the user by comparing it to all the - // versions in our git history. Until now, the file has never been migrated after first creation so users - // could have any version from the git history. var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath)); - var existingConfigIsUnmodified = _defaultConfigHistory + return _defaultConfigHistory .Select(historicalConfigText => JToken.Parse(historicalConfigText)) .Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson)); - - // The existing config file should be kept and used only if it has been modified by the user - return !existingConfigIsUnmodified; } } } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 2590fdb215..7c3d0f2771 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -44,9 +44,9 @@ namespace Jellyfin.Server public static readonly string LoggingConfigFileDefault = "logging.default.json"; /// - /// The name of the logging configuration file containing user override settings. + /// The name of the logging configuration file containing the system-specific override settings. /// - public static readonly string LoggingConfigFileUser = "logging.user.json"; + public static readonly string LoggingConfigFileSystem = "logging.json"; private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); @@ -471,7 +471,7 @@ namespace Jellyfin.Server .SetBasePath(appPaths.ConfigurationDirectoryPath) .AddInMemoryCollection(ConfigurationOptions.Configuration) .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true) - .AddJsonFile(LoggingConfigFileUser, optional: true, reloadOnChange: true) + .AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true) .AddEnvironmentVariables("JELLYFIN_") .Build(); } From 72bf920291d1c486aaf66544ccd6eb64c13e254b Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 8 Mar 2020 16:05:31 +0100 Subject: [PATCH 093/104] Use a Guid to uniquely identify migrations instead of a string name Also use a list instead of an array to store executed migrations in the configuration class --- Jellyfin.Server/Migrations/IMigrationRoutine.cs | 7 ++++++- Jellyfin.Server/Migrations/MigrationOptions.cs | 11 ++++++----- Jellyfin.Server/Migrations/MigrationRunner.cs | 11 ++++------- .../Routines/CreateUserLoggingConfigFile.cs | 3 +++ .../Routines/DisableTranscodingThrottling.cs | 3 +++ 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Jellyfin.Server/Migrations/IMigrationRoutine.cs b/Jellyfin.Server/Migrations/IMigrationRoutine.cs index 20a3aa3d6a..eab995d67e 100644 --- a/Jellyfin.Server/Migrations/IMigrationRoutine.cs +++ b/Jellyfin.Server/Migrations/IMigrationRoutine.cs @@ -9,7 +9,12 @@ namespace Jellyfin.Server.Migrations internal interface IMigrationRoutine { /// - /// Gets the name of the migration, must be unique. + /// Gets the unique id for this migration. This should never be modified after the migration has been created. + /// + public Guid Id { get; } + + /// + /// Gets the display name of the migration. /// public string Name { get; } diff --git a/Jellyfin.Server/Migrations/MigrationOptions.cs b/Jellyfin.Server/Migrations/MigrationOptions.cs index 6b7831158f..d95354145e 100644 --- a/Jellyfin.Server/Migrations/MigrationOptions.cs +++ b/Jellyfin.Server/Migrations/MigrationOptions.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; + namespace Jellyfin.Server.Migrations { /// @@ -10,14 +13,12 @@ namespace Jellyfin.Server.Migrations /// public MigrationOptions() { - Applied = System.Array.Empty(); + Applied = new List(); } -#pragma warning disable CA1819 // Properties should not return arrays /// - /// Gets or sets the list of applied migration routine names. + /// Gets the list of applied migration routine names. /// - public string[] Applied { get; set; } -#pragma warning restore CA1819 // Properties should not return arrays + public List Applied { get; } } } diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 821f51c02f..2081143d20 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -29,22 +29,20 @@ namespace Jellyfin.Server.Migrations var logger = loggerFactory.CreateLogger(); var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration(MigrationsListStore.StoreKey); - if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Length == 0) + if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0) { // If startup wizard is not finished, this is a fresh install. // Don't run any migrations, just mark all of them as applied. logger.LogInformation("Marking all known migrations as applied because this is fresh install"); - migrationOptions.Applied = Migrations.Select(m => m.Name).ToArray(); + migrationOptions.Applied.AddRange(Migrations.Select(m => m.Id)); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); return; } - var applied = migrationOptions.Applied.ToList(); - for (var i = 0; i < Migrations.Length; i++) { var migrationRoutine = Migrations[i]; - if (applied.Contains(migrationRoutine.Name)) + if (migrationOptions.Applied.Contains(migrationRoutine.Id)) { logger.LogDebug("Skipping migration '{Name}' since it is already applied", migrationRoutine.Name); continue; @@ -64,8 +62,7 @@ namespace Jellyfin.Server.Migrations // Mark the migration as completed logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name); - applied.Add(migrationRoutine.Name); - migrationOptions.Applied = applied.ToArray(); + migrationOptions.Applied.Add(migrationRoutine.Id); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name); } diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 834099ea05..3bc32c0478 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -36,6 +36,9 @@ namespace Jellyfin.Server.Migrations.Routines @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}:{Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", }; + /// + public Guid Id => Guid.Parse("{EF103419-8451-40D8-9F34-D1A8E93A1679}"); + /// public string Name => "CreateLoggingConfigHeirarchy"; diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index 279e7bbea5..673f0e4155 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -12,6 +12,9 @@ namespace Jellyfin.Server.Migrations.Routines ///
internal class DisableTranscodingThrottling : IMigrationRoutine { + /// + public Guid Id => Guid.Parse("{4124C2CD-E939-4FFB-9BE9-9B311C413638}"); + /// public string Name => "DisableTranscodingThrottling"; From 9e89cbbc3ad451b510a00fd7e214f6b942176f47 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 8 Mar 2020 17:40:30 +0100 Subject: [PATCH 094/104] Store migration names alongside Ids in configuration in order to assist with development/debugging --- Jellyfin.Server/Migrations/MigrationOptions.cs | 4 ++-- Jellyfin.Server/Migrations/MigrationRunner.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationOptions.cs b/Jellyfin.Server/Migrations/MigrationOptions.cs index d95354145e..816dd9ee74 100644 --- a/Jellyfin.Server/Migrations/MigrationOptions.cs +++ b/Jellyfin.Server/Migrations/MigrationOptions.cs @@ -13,12 +13,12 @@ namespace Jellyfin.Server.Migrations ///
public MigrationOptions() { - Applied = new List(); + Applied = new List<(Guid Id, string Name)>(); } /// /// Gets the list of applied migration routine names. /// - public List Applied { get; } + public List<(Guid Id, string Name)> Applied { get; } } } diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 2081143d20..b5ea04dcac 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -33,16 +33,18 @@ namespace Jellyfin.Server.Migrations { // If startup wizard is not finished, this is a fresh install. // Don't run any migrations, just mark all of them as applied. - logger.LogInformation("Marking all known migrations as applied because this is fresh install"); - migrationOptions.Applied.AddRange(Migrations.Select(m => m.Id)); + logger.LogInformation("Marking all known migrations as applied because this is a fresh install"); + migrationOptions.Applied.AddRange(Migrations.Select(m => (m.Id, m.Name))); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); return; } + var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet(); + for (var i = 0; i < Migrations.Length; i++) { var migrationRoutine = Migrations[i]; - if (migrationOptions.Applied.Contains(migrationRoutine.Id)) + if (appliedMigrationIds.Contains(migrationRoutine.Id)) { logger.LogDebug("Skipping migration '{Name}' since it is already applied", migrationRoutine.Name); continue; @@ -62,7 +64,7 @@ namespace Jellyfin.Server.Migrations // Mark the migration as completed logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name); - migrationOptions.Applied.Add(migrationRoutine.Id); + migrationOptions.Applied.Add((migrationRoutine.Id, migrationRoutine.Name)); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name); } From 3741348fb9330a36121ec00b319cb185e2d50baf Mon Sep 17 00:00:00 2001 From: Mednis Date: Sun, 8 Mar 2020 21:24:14 +0000 Subject: [PATCH 095/104] Added translation using Weblate (Latvian) --- Emby.Server.Implementations/Localization/Core/lv.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/lv.json diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/lv.json @@ -0,0 +1 @@ +{} From 3e3c47407b71ffe6281777b50ffe3451daf4bbe2 Mon Sep 17 00:00:00 2001 From: Mednis Date: Sun, 8 Mar 2020 21:25:39 +0000 Subject: [PATCH 096/104] Translated using Weblate (Latvian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lv/ --- .../Localization/Core/lv.json | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json index 0967ef424b..6c48cc0059 100644 --- a/Emby.Server.Implementations/Localization/Core/lv.json +++ b/Emby.Server.Implementations/Localization/Core/lv.json @@ -1 +1,90 @@ -{} +{ + "ServerNameNeedsToBeRestarted": "{0} ir vajadzīgs restarts", + "NotificationOptionTaskFailed": "Plānota uzdevuma kļūme", + "HeaderRecordingGroups": "Ierakstu Grupas", + "UserPolicyUpdatedWithName": "Lietotāju politika atjaunota priekš {0}", + "SubtitleDownloadFailureFromForItem": "Subtitru lejupielāde no {0} priekš {1} neizdevās", + "NotificationOptionVideoPlaybackStopped": "Video atskaņošana apturēta", + "NotificationOptionVideoPlayback": "Video atskaņošana sākta", + "NotificationOptionInstallationFailed": "Instalācija neizdevās", + "AuthenticationSucceededWithUserName": "{0} veiksmīgi autentificējies", + "ValueSpecialEpisodeName": "Speciālais - {0}", + "ScheduledTaskStartedWithName": "{0} iesākts", + "ScheduledTaskFailedWithName": "{0} neizdevās", + "Photos": "Attēli", + "NotificationOptionUserLockedOut": "Lietotājs bloķēts", + "LabelRunningTimeValue": "Garums: {0}", + "Inherit": "Mantot", + "AppDeviceValues": "Lietotne:{0}, Ierīce:{1}", + "VersionNumber": "Versija {0}", + "ValueHasBeenAddedToLibrary": "{0} ir ticis pievienots tavai multvides bibliotēkai", + "UserStoppedPlayingItemWithValues": "{0} ir beidzis atskaņot {1} uz {2}", + "UserStartedPlayingItemWithValues": "{0} atskaņo {1} uz {2}", + "UserPasswordChangedWithName": "Parole nomainīta lietotājam {0}", + "UserOnlineFromDevice": "{0} ir tiešsaistē no {1}", + "UserOfflineFromDevice": "{0} ir atvienojies no {1}", + "UserLockedOutWithName": "Lietotājs {0} ir ticis bloķēts", + "UserDownloadingItemWithValues": "{0} lejupielādē {1}", + "UserDeletedWithName": "Lietotājs {0} ir izdzēsts", + "UserCreatedWithName": "Lietotājs {0} ir ticis izveidots", + "User": "Lietotājs", + "TvShows": "TV Raidījumi", + "Sync": "Sinhronizācija", + "System": "Sistēma", + "SubtitlesDownloadedForItem": "Subtitri lejupielādēti priekš {0}", + "StartupEmbyServerIsLoading": "Jellyfin Serveris lādējas. Lūdzu mēģiniet vēlreiz pēc brīža.", + "Songs": "Dziesmas", + "Shows": "Raidījumi", + "PluginUpdatedWithName": "{0} tika atjaunots", + "PluginUninstalledWithName": "{0} tika noņemts", + "PluginInstalledWithName": "{0} tika uzstādīts", + "Plugin": "Paplašinājums", + "Playlists": "Atskaņošanas Saraksti", + "MixedContent": "Jaukts saturs", + "HomeVideos": "Mājas Video", + "HeaderNextUp": "Nākamais", + "ChapterNameValue": "Nodaļa {0}", + "Application": "Lietotne", + "NotificationOptionServerRestartRequired": "Vajadzīgs servera restarts", + "NotificationOptionPluginUpdateInstalled": "Paplašinājuma atjauninājums uzstādīts", + "NotificationOptionPluginUninstalled": "Paplašinājums noņemts", + "NotificationOptionPluginInstalled": "Paplašinājums uzstādīts", + "NotificationOptionPluginError": "Paplašinājuma kļūda", + "NotificationOptionNewLibraryContent": "Jauns saturs pievienots", + "NotificationOptionCameraImageUploaded": "Kameras attēls augšupielādēts", + "NotificationOptionAudioPlaybackStopped": "Audio atskaņošana apturēta", + "NotificationOptionAudioPlayback": "Audio atskaņošana sākta", + "NotificationOptionApplicationUpdateInstalled": "Lietotnes atjauninājums uzstādīts", + "NotificationOptionApplicationUpdateAvailable": "Lietotnes atjauninājums pieejams", + "NewVersionIsAvailable": "Lejupielādei ir pieejama jauna Jellyfin Server versija.", + "NameSeasonUnknown": "Nezināma Sezona", + "NameSeasonNumber": "Sezona {0}", + "NameInstallFailed": "{0} instalācija neizdevās", + "MusicVideos": "Mūzikas video", + "Music": "Mūzika", + "Movies": "Filmas", + "MessageServerConfigurationUpdated": "Servera konfigurācija ir tikusi atjaunota", + "MessageNamedServerConfigurationUpdatedWithValue": "Servera konfigurācijas sadaļa {0} ir tikusi atjaunota", + "MessageApplicationUpdatedTo": "Jellyfin Server ir ticis atjaunots uz {0}", + "MessageApplicationUpdated": "Jellyfin Server ir ticis atjaunots", + "Latest": "Jaunākais", + "LabelIpAddressValue": "IP adrese: {0}", + "ItemRemovedWithName": "{0} tika noņemts no bibliotēkas", + "ItemAddedWithName": "{0} tika pievienots bibliotēkai", + "HeaderLiveTV": "Tiešraides TV", + "HeaderContinueWatching": "Turpini skatīties", + "HeaderCameraUploads": "Kameras augšupielādes", + "HeaderAlbumArtists": "Albumu izpildītāji", + "Genres": "Žanri", + "Folders": "Mapes", + "Favorites": "Favorīti", + "FailedLoginAttemptWithUserName": "Neizdevies pieslēgšanās mēģinājums no {0}", + "DeviceOnlineWithName": "{0} ir pievienojies", + "DeviceOfflineWithName": "{0} ir atvienojies", + "Collections": "Kolekcijas", + "Channels": "Kanāli", + "CameraImageUploadedFrom": "Jauns kameras attēls ir ticis augšupielādēts no {0}", + "Books": "Grāmatas", + "Artists": "Izpildītāji", + "Albums": "Albumi" +} From c257d6071c3a8dd141d1191062e892d912177d9a Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 8 Mar 2020 20:53:11 -0400 Subject: [PATCH 097/104] Fix curl for Jellyfin GPG key This curl command seems to fail inexplicably with an "unable to get local issuer" error. Use `-k` instead so it doesn't complain. --- Dockerfile.arm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.arm b/Dockerfile.arm index 4c7aa6aa70..07780e27bf 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -38,7 +38,7 @@ ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \ - curl -s https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \ + curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \ curl -s https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \ echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \ echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \ From ad93f3b1468ce4f8a536e2f7b15b769043f32efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Fonseca?= Date: Mon, 9 Mar 2020 10:01:18 +0000 Subject: [PATCH 098/104] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/ --- .../Localization/Core/pt.json | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index ef8d988c87..9ee3c37a8a 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -1,5 +1,5 @@ { - "HeaderLiveTV": "TV ao Vivo", + "HeaderLiveTV": "TV em Directo", "Collections": "Colecções", "Books": "Livros", "Artists": "Artistas", @@ -10,13 +10,13 @@ "HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteEpisodes": "Episódios Favoritos", "HeaderFavoriteShows": "Séries Favoritas", - "HeaderContinueWatching": "Continuar a Ver", + "HeaderContinueWatching": "Continuar a Assistir", "HeaderAlbumArtists": "Artistas do Álbum", "Genres": "Géneros", - "Folders": "Pastas", + "Folders": "Directórios", "Favorites": "Favoritos", "Channels": "Canais", - "UserDownloadingItemWithValues": "{0} está a transferir {1}", + "UserDownloadingItemWithValues": "{0} está a ser transferido {1}", "VersionNumber": "Versão {0}", "ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia", "UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}", @@ -24,12 +24,12 @@ "UserPolicyUpdatedWithName": "A política do utilizador {0} foi alterada", "UserPasswordChangedWithName": "A palavra-passe do utilizador {0} foi alterada", "UserOnlineFromDevice": "{0} ligou-se a partir de {1}", - "UserOfflineFromDevice": "{0} desligou-se a partir de {1}", - "UserLockedOutWithName": "Utilizador {0} bloqueado", - "UserDeletedWithName": "Utilizador {0} removido", - "UserCreatedWithName": "Utilizador {0} criado", + "UserOfflineFromDevice": "{0} desconectou-se a partir de {1}", + "UserLockedOutWithName": "O utilizador {0} foi bloqueado", + "UserDeletedWithName": "O utilizador {0} foi removido", + "UserCreatedWithName": "O utilizador {0} foi criado", "User": "Utilizador", - "TvShows": "Programas", + "TvShows": "Séries", "System": "Sistema", "SubtitlesDownloadedForItem": "Legendas transferidas para {0}", "SubtitleDownloadFailureFromForItem": "Falha na transferência de legendas de {0} para {1}", @@ -38,22 +38,22 @@ "ScheduledTaskStartedWithName": "{0} iniciou", "ScheduledTaskFailedWithName": "{0} falhou", "ProviderValue": "Fornecedor: {0}", - "PluginUpdatedWithName": "{0} foi actualizado", + "PluginUpdatedWithName": "{0} foi atualizado", "PluginUninstalledWithName": "{0} foi desinstalado", "PluginInstalledWithName": "{0} foi instalado", - "Plugin": "Extensão", + "Plugin": "Plugin", "NotificationOptionVideoPlaybackStopped": "Reprodução de vídeo parada", "NotificationOptionVideoPlayback": "Reprodução de vídeo iniciada", "NotificationOptionUserLockedOut": "Utilizador bloqueado", "NotificationOptionTaskFailed": "Falha em tarefa agendada", "NotificationOptionServerRestartRequired": "É necessário reiniciar o servidor", - "NotificationOptionPluginUpdateInstalled": "Extensão actualizada", - "NotificationOptionPluginUninstalled": "Extensão desinstalada", - "NotificationOptionPluginInstalled": "Extensão instalada", - "NotificationOptionPluginError": "Falha na extensão", + "NotificationOptionPluginUpdateInstalled": "Plugin actualizado", + "NotificationOptionPluginUninstalled": "Plugin desinstalado", + "NotificationOptionPluginInstalled": "Plugin instalado", + "NotificationOptionPluginError": "Falha no plugin", "NotificationOptionNewLibraryContent": "Novo conteúdo adicionado", "NotificationOptionInstallationFailed": "Falha de instalação", - "NotificationOptionCameraImageUploaded": "Imagem da câmara enviada", + "NotificationOptionCameraImageUploaded": "Imagem de câmara enviada", "NotificationOptionAudioPlaybackStopped": "Reprodução Parada", "NotificationOptionAudioPlayback": "Reprodução Iniciada", "NotificationOptionApplicationUpdateInstalled": "A actualização da aplicação foi instalada", @@ -66,30 +66,30 @@ "Music": "Música", "MixedContent": "Conteúdo Misto", "MessageServerConfigurationUpdated": "A configuração do servidor foi actualizada", - "MessageNamedServerConfigurationUpdatedWithValue": "Configurações do servidor na secção {0} foram atualizadas", - "MessageApplicationUpdatedTo": "O servidor Jellyfin foi actualizado para a versão {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "As configurações do servidor na secção {0} foram atualizadas", + "MessageApplicationUpdatedTo": "O servidor Jellyfin foi atualizado para a versão {0}", "MessageApplicationUpdated": "O servidor Jellyfin foi actualizado", "Latest": "Mais Recente", "LabelRunningTimeValue": "Duração: {0}", - "LabelIpAddressValue": "Endereço IP: {0}", + "LabelIpAddressValue": "Endereço de IP: {0}", "ItemRemovedWithName": "{0} foi removido da biblioteca", "ItemAddedWithName": "{0} foi adicionado à biblioteca", "Inherit": "Herdar", "HomeVideos": "Vídeos Caseiros", "HeaderRecordingGroups": "Grupos de Gravação", - "ValueSpecialEpisodeName": "Especial - {0}", + "ValueSpecialEpisodeName": "Episódio Especial - {0}", "Sync": "Sincronização", "Songs": "Músicas", "Shows": "Séries", "Playlists": "Listas de Reprodução", "Photos": "Fotografias", "Movies": "Filmes", - "HeaderCameraUploads": "Envios a partir da câmara", - "FailedLoginAttemptWithUserName": "Tentativa de ligação a partir de {0} falhou", - "DeviceOnlineWithName": "{0} ligou-se", - "DeviceOfflineWithName": "{0} desligou-se", + "HeaderCameraUploads": "Carregamentos a partir da câmara", + "FailedLoginAttemptWithUserName": "Tentativa de ligação falhada a partir de {0}", + "DeviceOnlineWithName": "{0} está connectado", + "DeviceOfflineWithName": "{0} desconectou-se", "ChapterNameValue": "Capítulo {0}", - "CameraImageUploadedFrom": "Uma nova imagem de câmara foi enviada a partir de {0}", + "CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}", "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", "Application": "Aplicação", "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}" From 6ec1be219d0f35feffb267e41937ce83c0ebc4c1 Mon Sep 17 00:00:00 2001 From: Nutjob Date: Mon, 9 Mar 2020 14:49:22 +0000 Subject: [PATCH 099/104] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- Emby.Server.Implementations/Localization/Core/it.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index c1c40c18e1..395924af4b 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -5,7 +5,7 @@ "Artists": "Artisti", "AuthenticationSucceededWithUserName": "{0} autenticato con successo", "Books": "Libri", - "CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera {0}", + "CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera da {0}", "Channels": "Canali", "ChapterNameValue": "Capitolo {0}", "Collections": "Collezioni", @@ -18,7 +18,7 @@ "HeaderAlbumArtists": "Artisti dell' Album", "HeaderCameraUploads": "Caricamenti Fotocamera", "HeaderContinueWatching": "Continua a guardare", - "HeaderFavoriteAlbums": "Album preferiti", + "HeaderFavoriteAlbums": "Album Preferiti", "HeaderFavoriteArtists": "Artisti Preferiti", "HeaderFavoriteEpisodes": "Episodi Preferiti", "HeaderFavoriteShows": "Serie TV Preferite", From cc66a40ae349c14921a37c9aa231342bbd57a866 Mon Sep 17 00:00:00 2001 From: Falke Carlsen Date: Mon, 9 Mar 2020 18:02:09 +0000 Subject: [PATCH 100/104] Translated using Weblate (Danish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/da/ --- Emby.Server.Implementations/Localization/Core/da.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index cc8b7dbd53..c421db87d4 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -3,7 +3,7 @@ "AppDeviceValues": "App: {0}, Enhed: {1}", "Application": "Applikation", "Artists": "Kunstnere", - "AuthenticationSucceededWithUserName": "{0} bekræftet med succes", + "AuthenticationSucceededWithUserName": "{0} succesfuldt autentificeret", "Books": "Bøger", "CameraImageUploadedFrom": "Et nyt kamerabillede er blevet uploadet fra {0}", "Channels": "Kanaler", From f6cfdcf7d74741e6dd8719109355285cf14b80dc Mon Sep 17 00:00:00 2001 From: Mednis Date: Mon, 9 Mar 2020 21:08:01 +0000 Subject: [PATCH 101/104] Translated using Weblate (Latvian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lv/ --- Emby.Server.Implementations/Localization/Core/lv.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json index 6c48cc0059..207d5265dd 100644 --- a/Emby.Server.Implementations/Localization/Core/lv.json +++ b/Emby.Server.Implementations/Localization/Core/lv.json @@ -86,5 +86,11 @@ "CameraImageUploadedFrom": "Jauns kameras attēls ir ticis augšupielādēts no {0}", "Books": "Grāmatas", "Artists": "Izpildītāji", - "Albums": "Albumi" + "Albums": "Albumi", + "ProviderValue": "Provider: {0}", + "HeaderFavoriteSongs": "Dziesmu Favorīti", + "HeaderFavoriteShows": "Raidījumu Favorīti", + "HeaderFavoriteEpisodes": "Episožu Favorīti", + "HeaderFavoriteArtists": "Izpildītāju Favorīti", + "HeaderFavoriteAlbums": "Albumu Favorīti" } From 12f2baa3d812d43d055237ef72496cb77ef652ad Mon Sep 17 00:00:00 2001 From: Mednis Date: Mon, 9 Mar 2020 21:55:06 +0000 Subject: [PATCH 102/104] Translated using Weblate (Latvian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lv/ --- Emby.Server.Implementations/Localization/Core/lv.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json index 207d5265dd..8b8d46b2e0 100644 --- a/Emby.Server.Implementations/Localization/Core/lv.json +++ b/Emby.Server.Implementations/Localization/Core/lv.json @@ -72,9 +72,9 @@ "ItemRemovedWithName": "{0} tika noņemts no bibliotēkas", "ItemAddedWithName": "{0} tika pievienots bibliotēkai", "HeaderLiveTV": "Tiešraides TV", - "HeaderContinueWatching": "Turpini skatīties", + "HeaderContinueWatching": "Turpināt Skatīšanos", "HeaderCameraUploads": "Kameras augšupielādes", - "HeaderAlbumArtists": "Albumu izpildītāji", + "HeaderAlbumArtists": "Albumu Izpildītāji", "Genres": "Žanri", "Folders": "Mapes", "Favorites": "Favorīti", From 8999a5f6a2909e139a2b90c9d7b28714e995b69b Mon Sep 17 00:00:00 2001 From: zixaar Date: Tue, 10 Mar 2020 16:19:05 +0000 Subject: [PATCH 103/104] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- .../Localization/Core/ar.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index f0f165b22a..bdc17a47b6 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -2,22 +2,22 @@ "Albums": "ألبومات", "AppDeviceValues": "تطبيق: {0}, جهاز: {1}", "Application": "التطبيق", - "Artists": "الفنان", + "Artists": "الفنانين", "AuthenticationSucceededWithUserName": "{0} سجل الدخول بنجاح", "Books": "كتب", "CameraImageUploadedFrom": "صورة كاميرا جديدة تم رفعها من {0}", "Channels": "القنوات", - "ChapterNameValue": "الباب {0}", + "ChapterNameValue": "فصل {0}", "Collections": "مجموعات", - "DeviceOfflineWithName": "تم قطع اتصال {0}", + "DeviceOfflineWithName": "قُطِع الاتصال بـ{0}", "DeviceOnlineWithName": "{0} متصل", "FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}", - "Favorites": "التفضيلات", + "Favorites": "المفضلة", "Folders": "المجلدات", - "Genres": "أنواع الأفلام", + "Genres": "الأنواع", "HeaderAlbumArtists": "فناني الألبومات", "HeaderCameraUploads": "تحميلات الكاميرا", - "HeaderContinueWatching": "استئناف المشاهدة", + "HeaderContinueWatching": "استئناف", "HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderFavoriteArtists": "الفنانون المفضلون", "HeaderFavoriteEpisodes": "الحلقات المفضلة", @@ -31,20 +31,20 @@ "ItemAddedWithName": "تم إضافة {0} للمكتبة", "ItemRemovedWithName": "تم إزالة {0} من المكتبة", "LabelIpAddressValue": "عنوان الآي بي: {0}", - "LabelRunningTimeValue": "وقت التشغيل: {0}", + "LabelRunningTimeValue": "المدة: {0}", "Latest": "الأحدث", - "MessageApplicationUpdated": "لقد تم تحديث خادم أمبي", + "MessageApplicationUpdated": "لقد تم تحديث خادم Jellyfin", "MessageApplicationUpdatedTo": "تم تحديث سيرفر Jellyfin الى {0}", "MessageNamedServerConfigurationUpdatedWithValue": "تم تحديث إعدادات الخادم في قسم {0}", "MessageServerConfigurationUpdated": "تم تحديث إعدادات الخادم", - "MixedContent": "محتوى مخلوط", + "MixedContent": "محتوى مختلط", "Movies": "الأفلام", "Music": "الموسيقى", "MusicVideos": "الفيديوهات الموسيقية", "NameInstallFailed": "فشل التثبيت {0}", "NameSeasonNumber": "الموسم {0}", "NameSeasonUnknown": "الموسم غير معروف", - "NewVersionIsAvailable": "نسخة حديثة من سيرفر Jellyfin متوفرة للتحميل .", + "NewVersionIsAvailable": "نسخة جديدة من سيرفر Jellyfin متوفرة للتحميل.", "NotificationOptionApplicationUpdateAvailable": "يوجد تحديث للتطبيق", "NotificationOptionApplicationUpdateInstalled": "تم تحديث التطبيق", "NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي", From 97bca5a9008ba94411c45572e690decc58805b85 Mon Sep 17 00:00:00 2001 From: zixaar Date: Tue, 10 Mar 2020 16:35:12 +0000 Subject: [PATCH 104/104] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index bdc17a47b6..24da1aa567 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -49,10 +49,10 @@ "NotificationOptionApplicationUpdateInstalled": "تم تحديث التطبيق", "NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي", "NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي", - "NotificationOptionCameraImageUploaded": "تم رقع صورة الكاميرا", + "NotificationOptionCameraImageUploaded": "تم رفع صورة الكاميرا", "NotificationOptionInstallationFailed": "فشل في التثبيت", - "NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد", - "NotificationOptionPluginError": "فشل في الملحق", + "NotificationOptionNewLibraryContent": "أُضِيفَ محتوى جديد", + "NotificationOptionPluginError": "فشل في الـPlugin", "NotificationOptionPluginInstalled": "تم تثبيت الملحق", "NotificationOptionPluginUninstalled": "تمت إزالة الملحق", "NotificationOptionPluginUpdateInstalled": "تم تثبيت تحديثات الملحق",