浏览代码

Merged with latest master

Jim Cartlidge 4 年之前
父节点
当前提交
fcd1b2f0e4
共有 73 个文件被更改,包括 247 次插入257 次删除
  1. 5 1
      .vscode/launch.json
  2. 1 0
      CONTRIBUTORS.md
  3. 4 1
      Emby.Dlna/PlayTo/PlayToController.cs
  4. 11 5
      Emby.Dlna/Server/DescriptionXmlBuilder.cs
  5. 0 51
      Emby.Server.Implementations/Browser/BrowserLauncher.cs
  6. 0 3
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  7. 3 2
      Emby.Server.Implementations/Dto/DtoService.cs
  8. 0 83
      Emby.Server.Implementations/EntryPoints/StartupWizard.cs
  9. 0 1
      Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
  10. 0 5
      Emby.Server.Implementations/IStartupOptions.cs
  11. 0 8
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
  12. 1 0
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  13. 13 0
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  14. 26 8
      Emby.Server.Implementations/Localization/Core/af.json
  15. 5 5
      Emby.Server.Implementations/Localization/Core/th.json
  16. 1 1
      Emby.Server.Implementations/Localization/Core/vi.json
  17. 7 1
      Emby.Server.Implementations/Updates/InstallationManager.cs
  18. 1 1
      Jellyfin.Api/Controllers/ItemLookupController.cs
  19. 9 1
      Jellyfin.Api/Controllers/PackageController.cs
  20. 3 3
      Jellyfin.Api/Controllers/UserController.cs
  21. 2 2
      Jellyfin.Api/Jellyfin.Api.csproj
  22. 16 0
      Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs
  23. 2 2
      Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
  24. 3 6
      Jellyfin.Server/Jellyfin.Server.csproj
  25. 12 0
      Jellyfin.Server/Properties/launchSettings.json
  26. 0 4
      Jellyfin.Server/StartupOptions.cs
  27. 2 0
      MediaBrowser.Controller/Channels/InternalChannelFeatures.cs
  28. 0 1
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  29. 2 0
      MediaBrowser.Controller/Entities/BaseItem.cs
  30. 0 1
      MediaBrowser.Controller/Entities/Folder.cs
  31. 0 2
      MediaBrowser.Controller/Entities/IHasMediaSources.cs
  32. 0 1
      MediaBrowser.Controller/Entities/Photo.cs
  33. 0 1
      MediaBrowser.Controller/Entities/TV/Series.cs
  34. 0 1
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  35. 0 1
      MediaBrowser.Controller/IO/FileData.cs
  36. 2 0
      MediaBrowser.Controller/Library/ILibraryManager.cs
  37. 2 0
      MediaBrowser.Controller/Library/IMediaSourceManager.cs
  38. 1 0
      MediaBrowser.Controller/Library/ItemResolveArgs.cs
  39. 1 0
      MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
  40. 0 1
      MediaBrowser.Controller/LiveTv/ITunerHost.cs
  41. 1 0
      MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs
  42. 7 0
      MediaBrowser.Controller/LiveTv/ProgramInfo.cs
  43. 1 0
      MediaBrowser.Controller/LiveTv/RecordingInfo.cs
  44. 1 0
      MediaBrowser.Controller/LiveTv/TimerInfo.cs
  45. 1 3
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  46. 2 0
      MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
  47. 1 0
      MediaBrowser.Controller/Persistence/IItemRepository.cs
  48. 17 27
      MediaBrowser.Controller/Providers/DirectoryService.cs
  49. 1 0
      MediaBrowser.Controller/Resolvers/IItemResolver.cs
  50. 0 1
      MediaBrowser.Controller/Session/SessionInfo.cs
  51. 13 1
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  52. 0 1
      MediaBrowser.Model/Dlna/StreamInfo.cs
  53. 8 0
      MediaBrowser.Model/Dto/BaseItemDto.cs
  54. 2 0
      MediaBrowser.Model/Entities/MediaStream.cs
  55. 4 0
      MediaBrowser.Model/Entities/MetadataProvider.cs
  56. 1 0
      MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
  57. 0 1
      MediaBrowser.Model/Providers/RemoteImageInfo.cs
  58. 0 1
      MediaBrowser.Model/Providers/RemoteSearchResult.cs
  59. 1 0
      MediaBrowser.Model/Querying/ItemFields.cs
  60. 3 0
      MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
  61. 3 0
      MediaBrowser.Model/Session/PlaybackProgressInfo.cs
  62. 2 0
      MediaBrowser.Model/Sync/SyncCategory.cs
  63. 4 1
      MediaBrowser.Model/System/SystemInfo.cs
  64. 1 0
      MediaBrowser.Model/Tasks/IConfigurableScheduledTask.cs
  65. 10 0
      MediaBrowser.Model/Updates/PackageInfo.cs
  66. 1 1
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  67. 1 0
      MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
  68. 4 0
      MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
  69. 17 10
      MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
  70. 0 1
      MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs
  71. 0 1
      MediaBrowser.Providers/TV/DummySeasonProvider.cs
  72. 4 3
      README.md
  73. 1 2
      tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs

+ 5 - 1
.vscode/launch.json

@@ -11,7 +11,11 @@
             "cwd": "${workspaceFolder}/Jellyfin.Server",
             "console": "internalConsole",
             "stopAtEntry": false,
-            "internalConsoleOptions": "openOnSessionStart"
+            "internalConsoleOptions": "openOnSessionStart",
+            "serverReadyAction": {
+                "action": "openExternally",
+                "pattern": "Overriding address\\(es\\) \\'(https?:\\S+)\\'",
+            }
         },
         {
             "name": ".NET Core Launch (nowebclient)",

+ 1 - 0
CONTRIBUTORS.md

@@ -103,6 +103,7 @@
  - [sl1288](https://github.com/sl1288)
  - [sorinyo2004](https://github.com/sorinyo2004)
  - [sparky8251](https://github.com/sparky8251)
+ - [spookbits](https://github.com/spookbits)
  - [stanionascu](https://github.com/stanionascu)
  - [stevehayles](https://github.com/stevehayles)
  - [SuperSandro2000](https://github.com/SuperSandro2000)

+ 4 - 1
Emby.Dlna/PlayTo/PlayToController.cs

@@ -881,7 +881,10 @@ namespace Emby.Dlna.PlayTo
                     return null;
                 }
 
-                mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
+                if (_mediaSourceManager != null)
+                {
+                    mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
+                }
 
                 return mediaSource;
             }

+ 11 - 5
Emby.Dlna/Server/DescriptionXmlBuilder.cs

@@ -235,13 +235,13 @@ namespace Emby.Dlna.Server
                     .Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
                     .Append("</serviceId>");
                 builder.Append("<SCPDURL>")
-                    .Append(BuildUrl(service.ScpdUrl))
+                    .Append(BuildUrl(service.ScpdUrl, true))
                     .Append("</SCPDURL>");
                 builder.Append("<controlURL>")
-                    .Append(BuildUrl(service.ControlUrl))
+                    .Append(BuildUrl(service.ControlUrl, true))
                     .Append("</controlURL>");
                 builder.Append("<eventSubURL>")
-                    .Append(BuildUrl(service.EventSubUrl))
+                    .Append(BuildUrl(service.EventSubUrl, true))
                     .Append("</eventSubURL>");
 
                 builder.Append("</service>");
@@ -250,7 +250,13 @@ namespace Emby.Dlna.Server
             builder.Append("</serviceList>");
         }
 
-        private string BuildUrl(string url)
+        /// <summary>
+        /// Builds a valid url for inclusion in the xml.
+        /// </summary>
+        /// <param name="url">Url to include.</param>
+        /// <param name="absoluteUrl">Optional. When set to true, the absolute url is always used.</param>
+        /// <returns>The url to use for the element.</returns>
+        private string BuildUrl(string url, bool absoluteUrl = false)
         {
             if (string.IsNullOrEmpty(url))
             {
@@ -261,7 +267,7 @@ namespace Emby.Dlna.Server
 
             url = "/dlna/" + _serverUdn + "/" + url;
 
-            if (EnableAbsoluteUrls)
+            if (EnableAbsoluteUrls || absoluteUrl)
             {
                 url = _serverAddress.TrimEnd('/') + url;
             }

+ 0 - 51
Emby.Server.Implementations/Browser/BrowserLauncher.cs

@@ -1,51 +0,0 @@
-using System;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.Browser
-{
-    /// <summary>
-    /// Assists in opening application URLs in an external browser.
-    /// </summary>
-    public static class BrowserLauncher
-    {
-        /// <summary>
-        /// Opens the home page of the web client.
-        /// </summary>
-        /// <param name="appHost">The app host.</param>
-        public static void OpenWebApp(IServerApplicationHost appHost)
-        {
-            TryOpenUrl(appHost, "/web/index.html");
-        }
-
-        /// <summary>
-        /// Opens the swagger API page.
-        /// </summary>
-        /// <param name="appHost">The app host.</param>
-        public static void OpenSwaggerPage(IServerApplicationHost appHost)
-        {
-            TryOpenUrl(appHost, "/api-docs/swagger");
-        }
-
-        /// <summary>
-        /// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored.
-        /// </summary>
-        /// <param name="appHost">The application host.</param>
-        /// <param name="relativeUrl">The URL to open, relative to the server base URL.</param>
-        private static void TryOpenUrl(IServerApplicationHost appHost, string relativeUrl)
-        {
-            try
-            {
-                string baseUrl = appHost.GetLocalApiUrl("localhost");
-                appHost.LaunchUrl(baseUrl + relativeUrl);
-            }
-            catch (Exception ex)
-            {
-                var logger = appHost.Resolve<ILogger<IServerApplicationHost>>();
-                logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
-            }
-        }
-    }
-}

+ 0 - 3
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -2263,7 +2263,6 @@ namespace Emby.Server.Implementations.Data
             return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase);
         }
 
-
         private static readonly HashSet<string> _artistExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
         {
             "Series",
@@ -3291,7 +3290,6 @@ namespace Emby.Server.Implementations.Data
                 }
             }
 
-
             var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
 
             var statementTexts = new List<string>();
@@ -6006,7 +6004,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
             }
         }
 
-
         /// <summary>
         /// Gets the chapter.
         /// </summary>

+ 3 - 2
Emby.Server.Implementations/Dto/DtoService.cs

@@ -468,7 +468,6 @@ namespace Emby.Server.Implementations.Dto
                     IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
                     Name = item.Album,
                     Limit = 1
-
                 });
 
                 if (parentAlbumIds.Count > 0)
@@ -1139,6 +1138,7 @@ namespace Emby.Server.Implementations.Dto
                     if (episodeSeries != null)
                     {
                         dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
+                        AttachPrimaryImageAspectRatio(dto, episodeSeries);
                     }
                 }
 
@@ -1185,6 +1185,7 @@ namespace Emby.Server.Implementations.Dto
                     if (series != null)
                     {
                         dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
+                        AttachPrimaryImageAspectRatio(dto, series);
                     }
                 }
             }
@@ -1431,7 +1432,7 @@ namespace Emby.Server.Implementations.Dto
                 return null;
             }
 
-            return width / height;
+            return (double)width / height;
         }
     }
 }

+ 0 - 83
Emby.Server.Implementations/EntryPoints/StartupWizard.cs

@@ -1,83 +0,0 @@
-using System.Threading.Tasks;
-using Emby.Server.Implementations.Browser;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Extensions;
-using MediaBrowser.Controller.Plugins;
-using Microsoft.Extensions.Configuration;
-
-namespace Emby.Server.Implementations.EntryPoints
-{
-    /// <summary>
-    /// Class StartupWizard.
-    /// </summary>
-    public sealed class StartupWizard : IServerEntryPoint
-    {
-        private readonly IServerApplicationHost _appHost;
-        private readonly IConfiguration _appConfig;
-        private readonly IServerConfigurationManager _config;
-        private readonly IStartupOptions _startupOptions;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="StartupWizard"/> class.
-        /// </summary>
-        /// <param name="appHost">The application host.</param>
-        /// <param name="appConfig">The application configuration.</param>
-        /// <param name="config">The configuration manager.</param>
-        /// <param name="startupOptions">The application startup options.</param>
-        public StartupWizard(
-            IServerApplicationHost appHost,
-            IConfiguration appConfig,
-            IServerConfigurationManager config,
-            IStartupOptions startupOptions)
-        {
-            _appHost = appHost;
-            _appConfig = appConfig;
-            _config = config;
-            _startupOptions = startupOptions;
-        }
-
-        /// <inheritdoc />
-        public Task RunAsync()
-        {
-            Run();
-            return Task.CompletedTask;
-        }
-
-        private void Run()
-        {
-            if (!_appHost.CanLaunchWebBrowser)
-            {
-                return;
-            }
-
-            // Always launch the startup wizard if possible when it has not been completed
-            if (!_config.Configuration.IsStartupWizardCompleted && _appConfig.HostWebClient())
-            {
-                BrowserLauncher.OpenWebApp(_appHost);
-                return;
-            }
-
-            // Do nothing if the web app is configured to not run automatically
-            if (!_config.Configuration.AutoRunWebApp || _startupOptions.NoAutoRunWebApp)
-            {
-                return;
-            }
-
-            // Launch the swagger page if the web client is not hosted, otherwise open the web client
-            if (_appConfig.HostWebClient())
-            {
-                BrowserLauncher.OpenWebApp(_appHost);
-            }
-            else
-            {
-                BrowserLauncher.OpenSwaggerPage(_appHost);
-            }
-        }
-
-        /// <inheritdoc />
-        public void Dispose()
-        {
-        }
-    }
-}

+ 0 - 1
Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs

@@ -28,7 +28,6 @@ namespace Emby.Server.Implementations.EntryPoints
         private readonly object _syncLock = new object();
         private Timer _updateTimer;
 
-
         public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IUserManager userManager)
         {
             _userDataManager = userDataManager;

+ 0 - 5
Emby.Server.Implementations/IStartupOptions.cs

@@ -16,11 +16,6 @@ namespace Emby.Server.Implementations
         /// </summary>
         bool IsService { get; }
 
-        /// <summary>
-        /// Gets the value of the --noautorunwebapp command line option.
-        /// </summary>
-        bool NoAutoRunWebApp { get; }
-
         /// <summary>
         /// Gets the value of the --package-name command line option.
         /// </summary>

+ 0 - 8
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs

@@ -874,7 +874,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 public List<Lineup> lineups { get; set; }
             }
 
-
             public class Headends
             {
                 public string headend { get; set; }
@@ -886,8 +885,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 public List<Lineup> lineups { get; set; }
             }
 
-
-
             public class Map
             {
                 public string stationID { get; set; }
@@ -971,9 +968,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 public List<string> date { get; set; }
             }
 
-
-
-
             public class Rating
             {
                 public string body { get; set; }
@@ -1017,8 +1011,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 public string isPremiereOrFinale { get; set; }
             }
 
-
-
             public class MetadataSchedule
             {
                 public string modified { get; set; }

+ 1 - 0
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -2135,6 +2135,7 @@ namespace Emby.Server.Implementations.LiveTv
         }
 
         private bool _disposed = false;
+
         /// <summary>
         /// Releases unmanaged and - optionally - managed resources.
         /// </summary>

+ 13 - 0
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

@@ -563,6 +563,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
         protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
         {
+            var tunerCount = info.TunerCount;
+
+            if (tunerCount > 0)
+            {
+                var tunerHostId = info.Id;
+                var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
+
+                if (liveStreams.Count() >= tunerCount)
+                {
+                    throw new LiveTvConflictException("HDHomeRun simultaneous stream limit has been reached.");
+                }
+            }
+
             var profile = streamId.Split('_')[0];
 
             Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelInfo.Id, streamId, profile);

+ 26 - 8
Emby.Server.Implementations/Localization/Core/af.json

@@ -1,19 +1,19 @@
 {
     "Artists": "Kunstenare",
     "Channels": "Kanale",
-    "Folders": "Fouers",
-    "Favorites": "Gunstelinge",
+    "Folders": "Lêergidse",
+    "Favorites": "Gunstellinge",
     "HeaderFavoriteShows": "Gunsteling Vertonings",
     "ValueSpecialEpisodeName": "Spesiale - {0}",
     "HeaderAlbumArtists": "Album Kunstenaars",
     "Books": "Boeke",
     "HeaderNextUp": "Volgende",
-    "Movies": "Rolprente",
-    "Shows": "Program",
-    "HeaderContinueWatching": "Hou Aan Kyk",
+    "Movies": "Flieks",
+    "Shows": "Televisie Reekse",
+    "HeaderContinueWatching": "Kyk Verder",
     "HeaderFavoriteEpisodes": "Gunsteling Episodes",
     "Photos": "Fotos",
-    "Playlists": "Speellysse",
+    "Playlists": "Snitlyste",
     "HeaderFavoriteArtists": "Gunsteling Kunstenaars",
     "HeaderFavoriteAlbums": "Gunsteling Albums",
     "Sync": "Sinkroniseer",
@@ -23,7 +23,7 @@
     "DeviceOfflineWithName": "{0} is ontkoppel",
     "Collections": "Versamelings",
     "Inherit": "Ontvang",
-    "HeaderLiveTV": "Live TV",
+    "HeaderLiveTV": "Lewendige TV",
     "Application": "Program",
     "AppDeviceValues": "App: {0}, Toestel: {1}",
     "VersionNumber": "Weergawe {0}",
@@ -95,5 +95,23 @@
     "TasksChannelsCategory": "Internet kanale",
     "TasksApplicationCategory": "aansoek",
     "TasksLibraryCategory": "biblioteek",
-    "TasksMaintenanceCategory": "onderhoud"
+    "TasksMaintenanceCategory": "onderhoud",
+    "TaskCleanCacheDescription": "Vee kasregister lêers uit wat nie meer deur die stelsel benodig word nie.",
+    "TaskCleanCache": "Reinig Kasgeheue Lêergids",
+    "TaskDownloadMissingSubtitlesDescription": "Soek aanlyn vir vermiste onderskrifte gebasseer op metadata verstellings.",
+    "TaskDownloadMissingSubtitles": "Laai vermiste onderskrifte af",
+    "TaskRefreshChannelsDescription": "Vervris internet kanaal inligting.",
+    "TaskRefreshChannels": "Vervris Kanale",
+    "TaskCleanTranscodeDescription": "Vee transkodering lêers uit wat ouer is as een dag.",
+    "TaskCleanTranscode": "Reinig Transkoderings Leêrbinder",
+    "TaskUpdatePluginsDescription": "Laai opgedateerde inprop-sagteware af en installeer inprop-sagteware wat verstel is om outomaties op te dateer.",
+    "TaskUpdatePlugins": "Dateer Inprop-Sagteware Op",
+    "TaskRefreshPeopleDescription": "Vervris metadata oor akteurs en regisseurs in u media versameling.",
+    "TaskRefreshPeople": "Vervris Mense",
+    "TaskCleanLogsDescription": "Vee loglêers wat ouer as {0} dae is uit.",
+    "TaskCleanLogs": "Reinig Loglêer Lêervouer",
+    "TaskRefreshLibraryDescription": "Skandeer u media versameling vir nuwe lêers en verfris metadata.",
+    "TaskRefreshLibrary": "Skandeer Media Versameling",
+    "TaskRefreshChapterImagesDescription": "Maak kleinkiekeis (fotos) vir films wat hoofstukke het.",
+    "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde"
 }

+ 5 - 5
Emby.Server.Implementations/Localization/Core/th.json

@@ -20,8 +20,8 @@
     "NotificationOptionCameraImageUploaded": "อัปโหลดภาพถ่ายแล้ว",
     "NotificationOptionAudioPlaybackStopped": "หยุดเล่นเสียง",
     "NotificationOptionAudioPlayback": "เริ่มเล่นเสียง",
-    "NotificationOptionApplicationUpdateInstalled": "ติดตั้งการอัปเดตแอพลิเคชันแล้ว",
-    "NotificationOptionApplicationUpdateAvailable": "มีการอัปเดตแอพลิเคชัน",
+    "NotificationOptionApplicationUpdateInstalled": "ติดตั้งการอัปเดตแอพลิเคชันแล้ว",
+    "NotificationOptionApplicationUpdateAvailable": "มีการอัปเดตแอพลิเคชัน",
     "NewVersionIsAvailable": "เวอร์ชันใหม่ของเซิร์ฟเวอร์ Jellyfin พร้อมให้ดาวน์โหลดแล้ว",
     "NameSeasonUnknown": "ไม่ทราบซีซัน",
     "NameSeasonNumber": "ซีซัน {0}",
@@ -65,8 +65,8 @@
     "Books": "หนังสือ",
     "AuthenticationSucceededWithUserName": "{0} ยืนยันตัวสำเร็จแล้ว",
     "Artists": "ศิลปิน",
-    "Application": "แอพลิเคชัน",
-    "AppDeviceValues": "แอ: {0}, อุปกรณ์: {1}",
+    "Application": "แอพลิเคชัน",
+    "AppDeviceValues": "แอ: {0}, อุปกรณ์: {1}",
     "Albums": "อัลบั้ม",
     "ScheduledTaskStartedWithName": "{0} เริ่มต้น",
     "ScheduledTaskFailedWithName": "{0} ล้มเหลว",
@@ -92,7 +92,7 @@
     "TaskCleanCacheDescription": "ลบไฟล์แคชที่ระบบไม่ต้องการ",
     "TaskCleanCache": "ล้างไดเรกทอรีแคช",
     "TasksChannelsCategory": "ช่องอินเทอร์เน็ต",
-    "TasksApplicationCategory": "แอพลิเคชัน",
+    "TasksApplicationCategory": "แอพลิเคชัน",
     "TasksLibraryCategory": "ไลบรารี",
     "TasksMaintenanceCategory": "ปิดซ่อมบำรุง",
     "VersionNumber": "เวอร์ชัน {0}",

+ 1 - 1
Emby.Server.Implementations/Localization/Core/vi.json

@@ -15,7 +15,7 @@
     "ValueSpecialEpisodeName": "Đặc Biệt - {0}",
     "Albums": "Albums",
     "Artists": "Các Nghệ Sĩ",
-    "TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình thông tin chi tiết.",
+    "TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.",
     "TaskDownloadMissingSubtitles": "Tải xuống phụ đề bị thiếu",
     "TaskRefreshChannelsDescription": "Làm mới thông tin kênh internet.",
     "TaskRefreshChannels": "Làm Mới Kênh",

+ 7 - 1
Emby.Server.Implementations/Updates/InstallationManager.cs

@@ -155,7 +155,12 @@ namespace Emby.Server.Implementations.Updates
             var result = new List<PackageInfo>();
             foreach (RepositoryInfo repository in _config.Configuration.PluginRepositories)
             {
-                result.AddRange(await GetPackages(repository.Url, cancellationToken).ConfigureAwait(true));
+                foreach (var package in await GetPackages(repository.Url, cancellationToken).ConfigureAwait(true))
+                {
+                    package.repositoryName = repository.Name;
+                    package.repositoryUrl = repository.Url;
+                    result.Add(package);
+                }
             }
 
             return result;
@@ -393,6 +398,7 @@ namespace Emby.Server.Implementations.Updates
                     // Ignore any exceptions.
                 }
             }
+
             stream.Position = 0;
             _zipClient.ExtractAllFromZip(stream, targetDir, true);
 

+ 1 - 1
Jellyfin.Api/Controllers/ItemLookupController.cs

@@ -292,7 +292,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="NoContentResult"/>.
         /// </returns>
-        [HttpPost("Items/RemoteSearch/Apply/{id}")]
+        [HttpPost("Items/RemoteSearch/Apply/{itemId}")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> ApplySearchCriteria(

+ 9 - 1
Jellyfin.Api/Controllers/PackageController.cs

@@ -77,6 +77,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="name">Package name.</param>
         /// <param name="assemblyGuid">GUID of the associated assembly.</param>
         /// <param name="version">Optional version. Defaults to latest version.</param>
+        /// <param name="repositoryUrl">Optional. Specify the repository to install from.</param>
         /// <response code="204">Package found.</response>
         /// <response code="404">Package not found.</response>
         /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the package could not be found.</returns>
@@ -87,9 +88,16 @@ namespace Jellyfin.Api.Controllers
         public async Task<ActionResult> InstallPackage(
             [FromRoute, Required] string name,
             [FromQuery] string? assemblyGuid,
-            [FromQuery] string? version)
+            [FromQuery] string? version,
+            [FromQuery] string? repositoryUrl)
         {
             var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
+            if (!string.IsNullOrEmpty(repositoryUrl))
+            {
+                packages = packages.Where(p => p.repositoryUrl.Equals(repositoryUrl, StringComparison.OrdinalIgnoreCase))
+                    .ToList();
+            }
+
             var package = _installationManager.GetCompatibleVersions(
                     packages,
                     name,

+ 3 - 3
Jellyfin.Api/Controllers/UserController.cs

@@ -505,17 +505,17 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Initiates the forgot password process for a local user.
         /// </summary>
-        /// <param name="enteredUsername">The entered username.</param>
+        /// <param name="forgotPasswordRequest">The forgot password request containing the entered username.</param>
         /// <response code="200">Password reset process started.</response>
         /// <returns>A <see cref="Task"/> containing a <see cref="ForgotPasswordResult"/>.</returns>
         [HttpPost("ForgotPassword")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody] string? enteredUsername)
+        public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody, Required] ForgotPasswordDto forgotPasswordRequest)
         {
             var isLocal = HttpContext.IsLocal()
                           || _networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIp());
 
-            var result = await _userManager.StartForgotPasswordProcess(enteredUsername, isLocal).ConfigureAwait(false);
+            var result = await _userManager.StartForgotPasswordProcess(forgotPasswordRequest.EnteredUsername, isLocal).ConfigureAwait(false);
 
             return result;
         }

+ 2 - 2
Jellyfin.Api/Jellyfin.Api.csproj

@@ -17,8 +17,8 @@
     <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.8" />
     <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
     <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.8" />
-    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
-    <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.5.1" />
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
+    <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.6.3" />
   </ItemGroup>
 
   <ItemGroup>

+ 16 - 0
Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs

@@ -0,0 +1,16 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Jellyfin.Api.Models.UserDtos
+{
+    /// <summary>
+    /// Forgot Password request body DTO.
+    /// </summary>
+    public class ForgotPasswordDto
+    {
+        /// <summary>
+        /// Gets or sets the entered username to have its password reset.
+        /// </summary>
+        [Required]
+        public string? EnteredUsername { get; set; }
+    }
+}

+ 2 - 2
Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj

@@ -18,8 +18,8 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="BlurHashSharp" Version="1.1.0" />
-    <PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.1.0" />
+    <PackageReference Include="BlurHashSharp" Version="1.1.1" />
+    <PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.1.1" />
     <PackageReference Include="SkiaSharp" Version="2.80.2" />
     <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.80.2" />
   </ItemGroup>

+ 3 - 6
Jellyfin.Server/Jellyfin.Server.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk.Web">
 
   <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
   <PropertyGroup>
@@ -13,6 +13,7 @@
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <Nullable>enable</Nullable>
+    <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
   </PropertyGroup>
 
   <ItemGroup>
@@ -23,10 +24,6 @@
     <EmbeddedResource Include="Resources/Configuration/*" />
   </ItemGroup>
 
-  <ItemGroup>
-    <FrameworkReference Include="Microsoft.AspNetCore.App" />
-  </ItemGroup>
-
   <!-- Code Analyzers-->
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
     <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
@@ -53,7 +50,7 @@
     <PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
     <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
     <PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
-    <PackageReference Include="Serilog.Sinks.Graylog" Version="2.1.3" />
+    <PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.1" />
     <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.4" />
     <PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.14" />
   </ItemGroup>

+ 12 - 0
Jellyfin.Server/Properties/launchSettings.json

@@ -2,6 +2,8 @@
   "profiles": {
     "Jellyfin.Server": {
       "commandName": "Project",
+      "launchBrowser": true,
+      "applicationUrl": "http://localhost:8096",
       "environmentVariables": {
         "ASPNETCORE_ENVIRONMENT": "Development"
       }
@@ -12,6 +14,16 @@
         "ASPNETCORE_ENVIRONMENT": "Development"
       },
       "commandLineArgs": "--nowebclient"
+    },
+    "Jellyfin.Server (API Docs)": {
+      "commandName": "Project",
+      "launchBrowser": true,
+      "launchUrl": "api-docs/swagger",
+      "applicationUrl": "http://localhost:8096",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      },
+      "commandLineArgs": "--nowebclient"
     }
   }
 }

+ 0 - 4
Jellyfin.Server/StartupOptions.cs

@@ -63,10 +63,6 @@ namespace Jellyfin.Server
         [Option("service", Required = false, HelpText = "Run as headless service.")]
         public bool IsService { get; set; }
 
-        /// <inheritdoc />
-        [Option("noautorunwebapp", Required = false, HelpText = "Run headless if startup wizard is complete.")]
-        public bool NoAutoRunWebApp { get; set; }
-
         /// <inheritdoc />
         [Option("package-name", Required = false, HelpText = "Used when packaging Jellyfin (example, synology).")]
         public string? PackageName { get; set; }

+ 2 - 0
MediaBrowser.Controller/Channels/InternalChannelFeatures.cs

@@ -42,6 +42,7 @@ namespace MediaBrowser.Controller.Channels
         /// Indicates if a sort ascending/descending toggle is supported or not.
         /// </summary>
         public bool SupportsSortOrderToggle { get; set; }
+
         /// <summary>
         /// Gets or sets the automatic refresh levels.
         /// </summary>
@@ -53,6 +54,7 @@ namespace MediaBrowser.Controller.Channels
         /// </summary>
         /// <value>The daily download limit.</value>
         public int? DailyDownloadLimit { get; set; }
+
         /// <summary>
         /// Gets or sets a value indicating whether [supports downloading].
         /// </summary>

+ 0 - 1
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -90,7 +90,6 @@ namespace MediaBrowser.Controller.Entities.Audio
 
             var songKey = IndexNumber.HasValue ? IndexNumber.Value.ToString("0000") : string.Empty;
 
-
             if (ParentIndexNumber.HasValue)
             {
                 songKey = ParentIndexNumber.Value.ToString("0000") + "-" + songKey;

+ 2 - 0
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -197,6 +197,7 @@ namespace MediaBrowser.Controller.Entities
         public virtual bool SupportsRemoteImageDownloading => true;
 
         private string _name;
+
         /// <summary>
         /// Gets or sets the name.
         /// </summary>
@@ -661,6 +662,7 @@ namespace MediaBrowser.Controller.Entities
         }
 
         private string _forcedSortName;
+
         /// <summary>
         /// Gets or sets the name of the forced sort.
         /// </summary>

+ 0 - 1
MediaBrowser.Controller/Entities/Folder.cs

@@ -1386,7 +1386,6 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-
         /// <summary>
         /// Gets the linked children.
         /// </summary>

+ 0 - 2
MediaBrowser.Controller/Entities/IHasMediaSources.cs

@@ -21,7 +21,5 @@ namespace MediaBrowser.Controller.Entities
         List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution);
 
         List<MediaStream> GetMediaStreams();
-
-
     }
 }

+ 0 - 1
MediaBrowser.Controller/Entities/Photo.cs

@@ -16,7 +16,6 @@ namespace MediaBrowser.Controller.Entities
         [JsonIgnore]
         public override Folder LatestItemsIndexContainer => AlbumEntity;
 
-
         [JsonIgnore]
         public PhotoAlbum AlbumEntity
         {

+ 0 - 1
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -450,7 +450,6 @@ namespace MediaBrowser.Controller.Entities.TV
             });
         }
 
-
         protected override bool GetBlockUnratedValue(User user)
         {
             return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series.ToString());

+ 0 - 1
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -258,7 +258,6 @@ namespace MediaBrowser.Controller.Entities
                 IncludeItemTypes = new[] { typeof(Movie).Name },
                 Recursive = true,
                 EnableTotalRecordCount = false
-
             }).Items
                 .SelectMany(i => i.Genres)
                 .DistinctNames()

+ 0 - 1
MediaBrowser.Controller/IO/FileData.cs

@@ -111,5 +111,4 @@ namespace MediaBrowser.Controller.IO
             return returnResult;
         }
     }
-
 }

+ 2 - 0
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -77,6 +77,7 @@ namespace MediaBrowser.Controller.Library
         MusicArtist GetArtist(string name);
 
         MusicArtist GetArtist(string name, DtoOptions options);
+
         /// <summary>
         /// Gets a Studio.
         /// </summary>
@@ -234,6 +235,7 @@ namespace MediaBrowser.Controller.Library
         /// Occurs when [item updated].
         /// </summary>
         event EventHandler<ItemChangeEventArgs> ItemUpdated;
+
         /// <summary>
         /// Occurs when [item removed].
         /// </summary>

+ 2 - 0
MediaBrowser.Controller/Library/IMediaSourceManager.cs

@@ -28,12 +28,14 @@ namespace MediaBrowser.Controller.Library
         /// <param name="itemId">The item identifier.</param>
         /// <returns>IEnumerable&lt;MediaStream&gt;.</returns>
         List<MediaStream> GetMediaStreams(Guid itemId);
+
         /// <summary>
         /// Gets the media streams.
         /// </summary>
         /// <param name="mediaSourceId">The media source identifier.</param>
         /// <returns>IEnumerable&lt;MediaStream&gt;.</returns>
         List<MediaStream> GetMediaStreams(string mediaSourceId);
+
         /// <summary>
         /// Gets the media streams.
         /// </summary>

+ 1 - 0
MediaBrowser.Controller/Library/ItemResolveArgs.cs

@@ -156,6 +156,7 @@ namespace MediaBrowser.Controller.Library
         }
 
         // REVIEW: @bond
+
         /// <summary>
         /// Gets the physical locations.
         /// </summary>

+ 1 - 0
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -231,6 +231,7 @@ namespace MediaBrowser.Controller.LiveTv
         /// Saves the tuner host.
         /// </summary>
         Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
+
         /// <summary>
         /// Saves the listing provider.
         /// </summary>

+ 0 - 1
MediaBrowser.Controller/LiveTv/ITunerHost.cs

@@ -56,7 +56,6 @@ namespace MediaBrowser.Controller.LiveTv
         Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
 
         Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken);
-
     }
 
     public interface IConfigurableTunerHost

+ 1 - 0
MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs

@@ -42,6 +42,7 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value>The tuners.</value>
         public List<LiveTvTunerInfo> Tuners { get; set; }
+
         /// <summary>
         /// Gets or sets a value indicating whether this instance is visible.
         /// </summary>

+ 7 - 0
MediaBrowser.Controller/LiveTv/ProgramInfo.cs

@@ -35,6 +35,7 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value>The overview.</value>
         public string Overview { get; set; }
+
         /// <summary>
         /// Gets or sets the short overview.
         /// </summary>
@@ -169,31 +170,37 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value>The production year.</value>
         public int? ProductionYear { get; set; }
+
         /// <summary>
         /// Gets or sets the home page URL.
         /// </summary>
         /// <value>The home page URL.</value>
         public string HomePageUrl { get; set; }
+
         /// <summary>
         /// Gets or sets the series identifier.
         /// </summary>
         /// <value>The series identifier.</value>
         public string SeriesId { get; set; }
+
         /// <summary>
         /// Gets or sets the show identifier.
         /// </summary>
         /// <value>The show identifier.</value>
         public string ShowId { get; set; }
+
         /// <summary>
         /// Gets or sets the season number.
         /// </summary>
         /// <value>The season number.</value>
         public int? SeasonNumber { get; set; }
+
         /// <summary>
         /// Gets or sets the episode number.
         /// </summary>
         /// <value>The episode number.</value>
         public int? EpisodeNumber { get; set; }
+
         /// <summary>
         /// Gets or sets the etag.
         /// </summary>

+ 1 - 0
MediaBrowser.Controller/LiveTv/RecordingInfo.cs

@@ -187,6 +187,7 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
         public bool? HasImage { get; set; }
+
         /// <summary>
         /// Gets or sets the show identifier.
         /// </summary>

+ 1 - 0
MediaBrowser.Controller/LiveTv/TimerInfo.cs

@@ -113,6 +113,7 @@ namespace MediaBrowser.Controller.LiveTv
 
         // Program properties
         public int? SeasonNumber { get; set; }
+
         /// <summary>
         /// Gets or sets the episode number.
         /// </summary>

+ 1 - 3
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -109,7 +109,6 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
 
             return _mediaEncoder.SupportsHwaccel("vaapi");
-
         }
 
         /// <summary>
@@ -508,6 +507,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                                 arg.Append("-hwaccel qsv ");
                             }
                         }
+
                         // While using SW decoder
                         else
                         {
@@ -1441,7 +1441,6 @@ namespace MediaBrowser.Controller.MediaEncoding
 
             var codec = outputAudioCodec ?? string.Empty;
 
-
             int? transcoderChannelLimit;
             if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
             {
@@ -2511,7 +2510,6 @@ namespace MediaBrowser.Controller.MediaEncoding
             return inputModifier;
         }
 
-
         public void AttachMediaSourceInfo(
             EncodingJobInfo state,
             MediaSourceInfo mediaSource,

+ 2 - 0
MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs

@@ -697,10 +697,12 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// The progressive.
         /// </summary>
         Progressive,
+
         /// <summary>
         /// The HLS.
         /// </summary>
         Hls,
+
         /// <summary>
         /// The dash.
         /// </summary>

+ 1 - 0
MediaBrowser.Controller/Persistence/IItemRepository.cs

@@ -100,6 +100,7 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="query">The query.</param>
         /// <returns>IEnumerable&lt;Guid&gt;.</returns>
         QueryResult<Guid> GetItemIds(InternalItemsQuery query);
+
         /// <summary>
         /// Gets the items.
         /// </summary>

+ 17 - 27
MediaBrowser.Controller/Providers/DirectoryService.cs

@@ -1,6 +1,7 @@
 #pragma warning disable CS1591
 
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
 using MediaBrowser.Model.IO;
@@ -11,11 +12,11 @@ namespace MediaBrowser.Controller.Providers
     {
         private readonly IFileSystem _fileSystem;
 
-        private readonly Dictionary<string, FileSystemMetadata[]> _cache = new Dictionary<string, FileSystemMetadata[]>(StringComparer.OrdinalIgnoreCase);
+        private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new ConcurrentDictionary<string, FileSystemMetadata[]>(StringComparer.OrdinalIgnoreCase);
 
-        private readonly Dictionary<string, FileSystemMetadata> _fileCache = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
+        private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new ConcurrentDictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
 
-        private readonly Dictionary<string, List<string>> _filePathCache = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
+        private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new ConcurrentDictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
 
         public DirectoryService(IFileSystem fileSystem)
         {
@@ -24,14 +25,7 @@ namespace MediaBrowser.Controller.Providers
 
         public FileSystemMetadata[] GetFileSystemEntries(string path)
         {
-            if (!_cache.TryGetValue(path, out FileSystemMetadata[] entries))
-            {
-                entries = _fileSystem.GetFileSystemEntries(path).ToArray();
-
-                _cache[path] = entries;
-            }
-
-            return entries;
+            return _cache.GetOrAdd(path, p => _fileSystem.GetFileSystemEntries(p).ToArray());
         }
 
         public List<FileSystemMetadata> GetFiles(string path)
@@ -51,21 +45,19 @@ namespace MediaBrowser.Controller.Providers
 
         public FileSystemMetadata GetFile(string path)
         {
-            if (!_fileCache.TryGetValue(path, out FileSystemMetadata file))
+            var result = _fileCache.GetOrAdd(path, p =>
             {
-                file = _fileSystem.GetFileInfo(path);
+                var file = _fileSystem.GetFileInfo(p);
+                return file != null && file.Exists ? file : null;
+            });
 
-                if (file != null && file.Exists)
-                {
-                    _fileCache[path] = file;
-                }
-                else
-                {
-                    return null;
-                }
+            if (result == null)
+            {
+                // lets not store null results in the cache
+                _fileCache.TryRemove(path, out _);
             }
 
-            return file;
+            return result;
         }
 
         public IReadOnlyList<string> GetFilePaths(string path)
@@ -73,14 +65,12 @@ namespace MediaBrowser.Controller.Providers
 
         public IReadOnlyList<string> GetFilePaths(string path, bool clearCache)
         {
-            if (clearCache || !_filePathCache.TryGetValue(path, out List<string> result))
+            if (clearCache)
             {
-                result = _fileSystem.GetFilePaths(path).ToList();
-
-                _filePathCache[path] = result;
+                _filePathCache.TryRemove(path, out _);
             }
 
-            return result;
+            return _filePathCache.GetOrAdd(path, p => _fileSystem.GetFilePaths(p).ToList());
         }
     }
 }

+ 1 - 0
MediaBrowser.Controller/Resolvers/IItemResolver.cs

@@ -19,6 +19,7 @@ namespace MediaBrowser.Controller.Resolvers
         /// <param name="args">The args.</param>
         /// <returns>BaseItem.</returns>
         BaseItem ResolvePath(ItemResolveArgs args);
+
         /// <summary>
         /// Gets the priority.
         /// </summary>

+ 0 - 1
MediaBrowser.Controller/Session/SessionInfo.cs

@@ -22,7 +22,6 @@ namespace MediaBrowser.Controller.Session
         private readonly ISessionManager _sessionManager;
         private readonly ILogger _logger;
 
-
         private readonly object _progressLock = new object();
         private Timer _progressTimer;
         private PlaybackProgressInfo _lastProgressInfo;

+ 13 - 1
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -498,7 +498,6 @@ namespace MediaBrowser.Model.Dlna
                 }
             }
 
-
             if (playMethods.Count > 0)
             {
                 transcodeReasons.Clear();
@@ -1431,6 +1430,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.AudioChannels:
                         {
                             if (string.IsNullOrEmpty(qualifier))
@@ -1466,6 +1466,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.IsAvc:
                         {
                             if (!enableNonQualifiedConditions)
@@ -1487,6 +1488,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.IsAnamorphic:
                         {
                             if (!enableNonQualifiedConditions)
@@ -1508,6 +1510,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.IsInterlaced:
                         {
                             if (string.IsNullOrEmpty(qualifier))
@@ -1539,6 +1542,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.AudioProfile:
                     case ProfileConditionValue.Has64BitOffsets:
                     case ProfileConditionValue.PacketLength:
@@ -1550,6 +1554,7 @@ namespace MediaBrowser.Model.Dlna
                             // Not supported yet
                             break;
                         }
+
                     case ProfileConditionValue.RefFrames:
                         {
                             if (string.IsNullOrEmpty(qualifier))
@@ -1585,6 +1590,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.VideoBitDepth:
                         {
                             if (string.IsNullOrEmpty(qualifier))
@@ -1620,6 +1626,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.VideoProfile:
                         {
                             if (string.IsNullOrEmpty(qualifier))
@@ -1643,6 +1650,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.Height:
                         {
                             if (!enableNonQualifiedConditions)
@@ -1668,6 +1676,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.VideoBitrate:
                         {
                             if (!enableNonQualifiedConditions)
@@ -1693,6 +1702,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.VideoFramerate:
                         {
                             if (!enableNonQualifiedConditions)
@@ -1718,6 +1728,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.VideoLevel:
                         {
                             if (string.IsNullOrEmpty(qualifier))
@@ -1743,6 +1754,7 @@ namespace MediaBrowser.Model.Dlna
 
                             break;
                         }
+
                     case ProfileConditionValue.Width:
                         {
                             if (!enableNonQualifiedConditions)

+ 0 - 1
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -276,7 +276,6 @@ namespace MediaBrowser.Model.Dlna
 
             list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
 
-
             if (!item.IsDirectStream)
             {
                 if (item.RequireNonAnamorphic)

+ 8 - 0
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -429,6 +429,7 @@ namespace MediaBrowser.Model.Dto
         /// </summary>
         /// <value>The album id.</value>
         public Guid AlbumId { get; set; }
+
         /// <summary>
         /// Gets or sets the album image tag.
         /// </summary>
@@ -599,11 +600,13 @@ namespace MediaBrowser.Model.Dto
         /// </summary>
         /// <value>The trailer count.</value>
         public int? TrailerCount { get; set; }
+
         /// <summary>
         /// Gets or sets the movie count.
         /// </summary>
         /// <value>The movie count.</value>
         public int? MovieCount { get; set; }
+
         /// <summary>
         /// Gets or sets the series count.
         /// </summary>
@@ -611,16 +614,19 @@ namespace MediaBrowser.Model.Dto
         public int? SeriesCount { get; set; }
 
         public int? ProgramCount { get; set; }
+
         /// <summary>
         /// Gets or sets the episode count.
         /// </summary>
         /// <value>The episode count.</value>
         public int? EpisodeCount { get; set; }
+
         /// <summary>
         /// Gets or sets the song count.
         /// </summary>
         /// <value>The song count.</value>
         public int? SongCount { get; set; }
+
         /// <summary>
         /// Gets or sets the album count.
         /// </summary>
@@ -628,6 +634,7 @@ namespace MediaBrowser.Model.Dto
         public int? AlbumCount { get; set; }
 
         public int? ArtistCount { get; set; }
+
         /// <summary>
         /// Gets or sets the music video count.
         /// </summary>
@@ -768,6 +775,7 @@ namespace MediaBrowser.Model.Dto
         /// </summary>
         /// <value>The timer identifier.</value>
         public string TimerId { get; set; }
+
         /// <summary>
         /// Gets or sets the current program.
         /// </summary>

+ 2 - 0
MediaBrowser.Model/Entities/MediaStream.cs

@@ -451,11 +451,13 @@ namespace MediaBrowser.Model.Entities
         /// </summary>
         /// <value>The method.</value>
         public SubtitleDeliveryMethod? DeliveryMethod { get; set; }
+
         /// <summary>
         /// Gets or sets the delivery URL.
         /// </summary>
         /// <value>The delivery URL.</value>
         public string DeliveryUrl { get; set; }
+
         /// <summary>
         /// Gets or sets a value indicating whether this instance is external URL.
         /// </summary>

+ 4 - 0
MediaBrowser.Model/Entities/MetadataProvider.cs

@@ -11,18 +11,22 @@ namespace MediaBrowser.Model.Entities
         /// The imdb.
         /// </summary>
         Imdb = 2,
+
         /// <summary>
         /// The TMDB.
         /// </summary>
         Tmdb = 3,
+
         /// <summary>
         /// The TVDB.
         /// </summary>
         Tvdb = 4,
+
         /// <summary>
         /// The tvcom.
         /// </summary>
         Tvcom = 5,
+
         /// <summary>
         /// Tmdb Collection Id.
         /// </summary>

+ 1 - 0
MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs

@@ -84,6 +84,7 @@ namespace MediaBrowser.Model.LiveTv
         /// </summary>
         /// <value><c>null</c> if [is kids] contains no value, <c>true</c> if [is kids]; otherwise, <c>false</c>.</value>
         public bool? IsKids { get; set; }
+
         /// <summary>
         /// Gets or sets a value indicating whether this instance is sports.
         /// </summary>

+ 0 - 1
MediaBrowser.Model/Providers/RemoteImageInfo.cs

@@ -68,5 +68,4 @@ namespace MediaBrowser.Model.Providers
         /// <value>The type of the rating.</value>
         public RatingType RatingType { get; set; }
     }
-
 }

+ 0 - 1
MediaBrowser.Model/Providers/RemoteSearchResult.cs

@@ -50,6 +50,5 @@ namespace MediaBrowser.Model.Providers
         public RemoteSearchResult AlbumArtist { get; set; }
 
         public RemoteSearchResult[] Artists { get; set; }
-
     }
 }

+ 1 - 0
MediaBrowser.Model/Querying/ItemFields.cs

@@ -168,6 +168,7 @@ namespace MediaBrowser.Model.Querying
         Studios,
 
         BasicSyncInfo,
+
         /// <summary>
         /// The synchronize information.
         /// </summary>

+ 3 - 0
MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs

@@ -37,16 +37,19 @@ namespace MediaBrowser.Model.Querying
         /// </summary>
         /// <value>The fields.</value>
         public ItemFields[] Fields { get; set; }
+
         /// <summary>
         /// Gets or sets a value indicating whether [enable images].
         /// </summary>
         /// <value><c>null</c> if [enable images] contains no value, <c>true</c> if [enable images]; otherwise, <c>false</c>.</value>
         public bool? EnableImages { get; set; }
+
         /// <summary>
         /// Gets or sets the image type limit.
         /// </summary>
         /// <value>The image type limit.</value>
         public int? ImageTypeLimit { get; set; }
+
         /// <summary>
         /// Gets or sets the enable image types.
         /// </summary>

+ 3 - 0
MediaBrowser.Model/Session/PlaybackProgressInfo.cs

@@ -88,16 +88,19 @@ namespace MediaBrowser.Model.Session
         /// </summary>
         /// <value>The play method.</value>
         public PlayMethod PlayMethod { get; set; }
+
         /// <summary>
         /// Gets or sets the live stream identifier.
         /// </summary>
         /// <value>The live stream identifier.</value>
         public string LiveStreamId { get; set; }
+
         /// <summary>
         /// Gets or sets the play session identifier.
         /// </summary>
         /// <value>The play session identifier.</value>
         public string PlaySessionId { get; set; }
+
         /// <summary>
         /// Gets or sets the repeat mode.
         /// </summary>

+ 2 - 0
MediaBrowser.Model/Sync/SyncCategory.cs

@@ -8,10 +8,12 @@ namespace MediaBrowser.Model.Sync
         /// The latest.
         /// </summary>
         Latest = 0,
+
         /// <summary>
         /// The next up.
         /// </summary>
         NextUp = 1,
+
         /// <summary>
         /// The resume.
         /// </summary>

+ 4 - 1
MediaBrowser.Model/System/SystemInfo.cs

@@ -14,13 +14,16 @@ namespace MediaBrowser.Model.System
     {
         /// <summary>No path to FFmpeg found.</summary>
         NotFound,
+
         /// <summary>Path supplied via command line using switch --ffmpeg.</summary>
         SetByArgument,
+
         /// <summary>User has supplied path via Transcoding UI page.</summary>
         Custom,
+
         /// <summary>FFmpeg tool found on system $PATH.</summary>
         System
-    };
+    }
 
     /// <summary>
     /// Class SystemInfo.

+ 1 - 0
MediaBrowser.Model/Tasks/IConfigurableScheduledTask.cs

@@ -9,6 +9,7 @@ namespace MediaBrowser.Model.Tasks
         /// </summary>
         /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
         bool IsHidden { get; }
+
         /// <summary>
         /// Gets a value indicating whether this instance is enabled.
         /// </summary>

+ 10 - 0
MediaBrowser.Model/Updates/PackageInfo.cs

@@ -52,6 +52,16 @@ namespace MediaBrowser.Model.Updates
         /// <value>The versions.</value>
         public IReadOnlyList<VersionInfo> versions { get; set; }
 
+        /// <summary>
+        /// Gets or sets the repository name.
+        /// </summary>
+        public string repositoryName { get; set; }
+
+        /// <summary>
+        /// Gets or sets the repository url.
+        /// </summary>
+        public string repositoryUrl { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="PackageInfo"/> class.
         /// </summary>

+ 1 - 1
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -21,7 +21,7 @@
     <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.8" />
     <PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
     <PackageReference Include="PlaylistsNET" Version="1.1.2" />
-    <PackageReference Include="TvDbSharper" Version="3.2.1" />
+    <PackageReference Include="TvDbSharper" Version="3.2.2" />
   </ItemGroup>
 
   <PropertyGroup>

+ 1 - 0
MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs

@@ -198,6 +198,7 @@ namespace MediaBrowser.Providers.Music
                                 result.Name = reader.ReadElementContentAsString();
                                 break;
                             }
+
                         case "annotation":
                             {
                                 result.Overview = reader.ReadElementContentAsString();

+ 4 - 0
MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs

@@ -444,6 +444,7 @@ namespace MediaBrowser.Providers.Music
                                     result.Title = reader.ReadElementContentAsString();
                                     break;
                                 }
+
                             case "date":
                                 {
                                     var val = reader.ReadElementContentAsString();
@@ -454,17 +455,20 @@ namespace MediaBrowser.Providers.Music
 
                                     break;
                                 }
+
                             case "annotation":
                                 {
                                     result.Overview = reader.ReadElementContentAsString();
                                     break;
                                 }
+
                             case "release-group":
                                 {
                                     result.ReleaseGroupId = reader.GetAttribute("id");
                                     reader.Skip();
                                     break;
                                 }
+
                             case "artist-credit":
                                 {
                                     using (var subReader = reader.ReadSubtree())

+ 17 - 10
MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs

@@ -57,21 +57,28 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
                 // Process images
                 try
                 {
-                    var episodeInfo = new EpisodeInfo
+                    string episodeTvdbId = null;
+
+                    if (episode.IndexNumber.HasValue && episode.ParentIndexNumber.HasValue)
                     {
-                        IndexNumber = episode.IndexNumber.Value,
-                        ParentIndexNumber = episode.ParentIndexNumber.Value,
-                        SeriesProviderIds = series.ProviderIds,
-                        SeriesDisplayOrder = series.DisplayOrder
-                    };
-                    string episodeTvdbId = await _tvdbClientManager
-                        .GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false);
+                        var episodeInfo = new EpisodeInfo
+                        {
+                            IndexNumber = episode.IndexNumber.Value,
+                            ParentIndexNumber = episode.ParentIndexNumber.Value,
+                            SeriesProviderIds = series.ProviderIds,
+                            SeriesDisplayOrder = series.DisplayOrder
+                        };
+
+                        episodeTvdbId = await _tvdbClientManager
+                            .GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false);
+                    }
+
                     if (string.IsNullOrEmpty(episodeTvdbId))
                     {
                         _logger.LogError(
                             "Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
-                            episodeInfo.ParentIndexNumber,
-                            episodeInfo.IndexNumber,
+                            episode.ParentIndexNumber,
+                            episode.IndexNumber,
                             series.GetProviderId(MetadataProvider.Tvdb));
                         return imageResult;
                     }

+ 0 - 1
MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs

@@ -302,7 +302,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
                 {
                     Url = string.Format(CultureInfo.InvariantCulture, "https://www.youtube.com/watch?v={0}", i.Source),
                     Name = i.Name
-
                 }).ToArray();
             }
         }

+ 0 - 1
MediaBrowser.Providers/TV/DummySeasonProvider.cs

@@ -217,7 +217,6 @@ namespace MediaBrowser.Providers.TV
                     new DeleteOptions
                     {
                         DeleteFileLocation = true
-
                     },
                     false);
 

+ 4 - 3
README.md

@@ -53,18 +53,19 @@ Jellyfin is a Free Software Media System that puts you in control of managing an
 For further details, please see [our documentation page](https://docs.jellyfin.org/). To receive the latest updates, get help with Jellyfin, and join the community, please visit [one of our communication channels](https://docs.jellyfin.org/general/getting-help.html). For more information about the project, please see our [about page](https://docs.jellyfin.org/general/about.html).
 
 <strong>Want to get started?</strong><br/>
-Choose from <a href="https://docs.jellyfin.org/general/administration/installing.html">Prebuilt Packages</a> or <a href="https://docs.jellyfin.org/general/administration/building.html">Build from Source</a>, then see our <a href="https://docs.jellyfin.org/general/quick-start.html">quick start guide</a>.<br/>
+Check out our <a href="https://jellyfin.org/downloads">downloads page</a> or our <a href="https://docs.jellyfin.org/general/administration/installing.html">installation guide</a>, then see our <a href="https://docs.jellyfin.org/general/quick-start.html">quick start guide</a>. You can also <a href="https://docs.jellyfin.org/general/administration/building.html">build from source</a>.<br/>
 
 <strong>Something not working right?</strong><br/>
 Open an <a href="https://docs.jellyfin.org/general/contributing/issues.html">Issue</a> on GitHub.<br/>
 
 <strong>Want to contribute?</strong><br/>
-Check out <a href="https://docs.jellyfin.org/general/contributing/index.html">our documentation for guidelines</a>.<br/>
+Check out our <a href="https://jellyfin.org/contribute">contributing choose-your-own-adventure</a> to see where you can help, then see our <a href="https://docs.jellyfin.org/general/contributing/index.html">contributing guide</a> and our <a href="https://jellyfin.org/docs/general/community-standards">community standards</a>.<br/>
 
 <strong>New idea or improvement?</strong><br/>
 Check out our <a href="https://features.jellyfin.org/?view=most-wanted">feature request hub</a>.<br/>
 
-Most of the translations can be found in the web client but we have several other clients that have missing strings. Translations can be improved very easily from our <a href="https://translate.jellyfin.org/projects/jellyfin/jellyfin-core">Weblate</a> instance. Look through the following graphic to see if your native language could use some work!
+<strong>Don't see Jellyfin in your language?</strong><br/>
+Check out our <a href="https://translate.jellyfin.org">Weblate instance</a> to help translate Jellyfin and its subprojects.<br/>
 
 <a href="https://translate.jellyfin.org/engage/jellyfin/?utm_source=widget">
 <img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-web/multi-auto.svg" alt="Detailed Translation Status"/>

+ 1 - 2
tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs

@@ -45,8 +45,7 @@ namespace Jellyfin.Api.Tests
             // Specify the startup command line options
             var commandLineOpts = new StartupOptions
             {
-                NoWebClient = true,
-                NoAutoRunWebApp = true
+                NoWebClient = true
             };
 
             // Use a temporary directory for the application paths