Browse Source

Merge remote-tracking branch 'upstream/master' into integration-tests

Mark Monteiro 5 years ago
parent
commit
cd98938190
94 changed files with 1103 additions and 1521 deletions
  1. 9 9
      .ci/azure-pipelines-compat.yml
  2. 23 30
      .ci/azure-pipelines-main.yml
  3. 42 18
      .ci/azure-pipelines-test.yml
  4. 6 6
      .ci/azure-pipelines.yml
  5. 5 0
      DvdLib/DvdLib.csproj
  6. 1 0
      Emby.Dlna/ConfigurationExtension.cs
  7. 5 0
      Emby.Dlna/Emby.Dlna.csproj
  8. 6 0
      Emby.Drawing/Emby.Drawing.csproj
  9. 9 47
      Emby.Drawing/ImageProcessor.cs
  10. 5 0
      Emby.Naming/Emby.Naming.csproj
  11. 5 0
      Emby.Notifications/Emby.Notifications.csproj
  12. 6 0
      Emby.Photos/Emby.Photos.csproj
  13. 1 1
      Emby.Photos/PhotoProvider.cs
  14. 5 5
      Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
  15. 188 428
      Emby.Server.Implementations/ApplicationHost.cs
  16. 1 1
      Emby.Server.Implementations/ConfigurationOptions.cs
  17. 13 7
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  18. 3 3
      Emby.Server.Implementations/Devices/DeviceManager.cs
  19. 16 14
      Emby.Server.Implementations/Dto/DtoService.cs
  20. 5 0
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  21. 31 14
      Emby.Server.Implementations/EntryPoints/StartupWizard.cs
  22. 5 4
      Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
  23. 58 49
      Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
  24. 10 27
      Emby.Server.Implementations/HttpServer/Security/AuthService.cs
  25. 26 32
      Emby.Server.Implementations/IO/LibraryMonitor.cs
  26. 1 1
      Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
  27. 100 129
      Emby.Server.Implementations/Library/LibraryManager.cs
  28. 11 11
      Emby.Server.Implementations/Library/MediaSourceManager.cs
  29. 3 4
      Emby.Server.Implementations/Library/SearchEngine.cs
  30. 18 19
      Emby.Server.Implementations/Library/UserDataManager.cs
  31. 14 23
      Emby.Server.Implementations/Library/UserManager.cs
  32. 0 1
      Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
  33. 10 15
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  34. 1 1
      Emby.Server.Implementations/Localization/Core/fa.json
  35. 6 5
      Emby.Server.Implementations/Localization/Core/ja.json
  36. 3 3
      Emby.Server.Implementations/Localization/Core/nl.json
  37. 24 2
      Emby.Server.Implementations/Localization/Core/pt-PT.json
  38. 20 3
      Emby.Server.Implementations/Localization/Core/tr.json
  39. 28 6
      Emby.Server.Implementations/Localization/Core/zh-TW.json
  40. 0 3
      Emby.Server.Implementations/Localization/LocalizationManager.cs
  41. 3 17
      Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
  42. 2 2
      Emby.Server.Implementations/Security/AuthenticationRepository.cs
  43. 1 1
      Emby.Server.Implementations/Session/SessionManager.cs
  44. 35 42
      Emby.Server.Implementations/Updates/InstallationManager.cs
  45. 5 0
      Jellyfin.Api/Jellyfin.Api.csproj
  46. 6 0
      Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
  47. 5 8
      Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs
  48. 28 21
      Jellyfin.Drawing.Skia/SkiaEncoder.cs
  49. 5 5
      Jellyfin.Drawing.Skia/StripCollageBuilder.cs
  50. 2 2
      Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs
  51. 22 5
      Jellyfin.Server/CoreAppHost.cs
  52. 5 0
      Jellyfin.Server/Jellyfin.Server.csproj
  53. 2 23
      Jellyfin.Server/Program.cs
  54. 8 1
      MediaBrowser.Api/Images/ImageService.cs
  55. 5 0
      MediaBrowser.Api/MediaBrowser.Api.csproj
  56. 1 43
      MediaBrowser.Api/PackageService.cs
  57. 1 1
      MediaBrowser.Api/UserService.cs
  58. 2 0
      MediaBrowser.Common/Extensions/ShuffleExtensions.cs
  59. 1 11
      MediaBrowser.Common/IApplicationHost.cs
  60. 5 0
      MediaBrowser.Common/MediaBrowser.Common.csproj
  61. 4 4
      MediaBrowser.Common/Plugins/BasePlugin.cs
  62. 9 13
      MediaBrowser.Common/Updates/IInstallationManager.cs
  63. 1 1
      MediaBrowser.Common/Updates/InstallationEventArgs.cs
  64. 12 6
      MediaBrowser.Controller/Authentication/AuthenticationException.cs
  65. 0 9
      MediaBrowser.Controller/Drawing/IImageProcessor.cs
  66. 5 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  67. 1 1
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  68. 24 8
      MediaBrowser.Controller/Net/SecurityException.cs
  69. 5 0
      MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
  70. 4 13
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  71. 5 0
      MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
  72. 5 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  73. 5 5
      MediaBrowser.Model/Services/IHasRequestFilter.cs
  74. 0 2
      MediaBrowser.Model/System/SystemInfo.cs
  75. 0 29
      MediaBrowser.Model/Updates/CheckForUpdateResult.cs
  76. 3 15
      MediaBrowser.Model/Updates/InstallationInfo.cs
  77. 7 121
      MediaBrowser.Model/Updates/PackageInfo.cs
  78. 0 23
      MediaBrowser.Model/Updates/PackageTargetSystem.cs
  79. 0 23
      MediaBrowser.Model/Updates/PackageVersionClass.cs
  80. 0 96
      MediaBrowser.Model/Updates/PackageVersionInfo.cs
  81. 58 0
      MediaBrowser.Model/Updates/VersionInfo.cs
  82. 32 41
      MediaBrowser.Providers/Manager/ProviderManager.cs
  83. 5 0
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  84. 4 4
      MediaBrowser.Providers/Subtitles/SubtitleManager.cs
  85. 5 0
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  86. 5 0
      MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
  87. 5 0
      RSSDP/RSSDP.csproj
  88. 5 0
      tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
  89. 5 0
      tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
  90. 5 0
      tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
  91. 5 0
      tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
  92. 5 0
      tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
  93. 5 0
      tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
  94. 2 4
      tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs

+ 9 - 9
.ci/azure-pipelines-compat.yml

@@ -1,13 +1,13 @@
 parameters:
-  - name: Packages
-    type: object
-    default: {}
-  - name: LinuxImage
-    type: string
-    default: "ubuntu-latest"
-  - name: DotNetSdkVersion
-    type: string
-    default: 3.1.100
+- name: Packages
+  type: object
+  default: {}
+- name: LinuxImage
+  type: string
+  default: "ubuntu-latest"
+- name: DotNetSdkVersion
+  type: string
+  default: 3.1.100
 
 jobs:
   - job: CompatibilityCheck

+ 23 - 30
.ci/azure-pipelines-main.yml

@@ -20,41 +20,34 @@ jobs:
         submodules: true
         persistCredentials: true
 
-      - task: CmdLine@2
-        displayName: "Clone Web Branch"
-        condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+      - task: DownloadPipelineArtifact@2
+        displayName: "Download Web Branch"
+        condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
         inputs:
-          script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
+          path: '$(Agent.TempDirectory)'
+          artifact: 'jellyfin-web-production'
+          source: 'specific'
+          project: 'jellyfin'
+          pipeline: 'Jellyfin Web'
+          runBranch: variables['Build.SourceBranch']
 
-      - task: CmdLine@2
-        displayName: "Clone Web Target"
-        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
+      - task: DownloadPipelineArtifact@2
+        displayName: "Download Web Target"
+        condition: eq(variables['Build.Reason'], 'PullRequest')
         inputs:
-          script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
+          path: '$(Agent.TempDirectory)'
+          artifact: 'jellyfin-web-production'
+          source: 'specific'
+          project: 'jellyfin'
+          pipeline: 'Jellyfin Web'
+          runBranch: variables['System.PullRequest.TargetBranch']
 
-      - task: NodeTool@0
-        displayName: "Install Node"
-        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+      - task: ExtractFiles@1
+        displayName: "Extract Web Client"
         inputs:
-          versionSpec: "12.x"
-
-      - task: CmdLine@2
-        displayName: "Build Web Client"
-        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
-        inputs:
-          script: yarn install
-          workingDirectory: $(Agent.TempDirectory)/jellyfin-web
-
-      - task: CopyFiles@2
-        displayName: "Copy Web Client"
-        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
-        inputs:
-          sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
-          contents: "**"
-          targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
-          cleanTargetFolder: true
-          overWrite: true
-          flattenFolders: false
+          archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
+          destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
+          cleanDestinationFolder: false
 
       - task: UseDotNet@2
         displayName: "Update DotNet"

+ 42 - 18
.ci/azure-pipelines-test.yml

@@ -1,26 +1,25 @@
 parameters:
-  - name: ImageNames
-    type: object
-    default:
-      Linux: "ubuntu-latest"
-      Windows: "windows-latest"
-      macOS: "macos-latest"
-  - name: TestProjects
-    type: string
-    default: "tests/**/*Tests.csproj"
-  - name: DotNetSdkVersion
-    type: string
-    default: 3.1.100
+- name: ImageNames
+  type: object
+  default:
+    Linux: "ubuntu-latest"
+    Windows: "windows-latest"
+    macOS: "macos-latest"
+- name: TestProjects
+  type: string
+  default: "tests/**/*Tests.csproj"
+- name: DotNetSdkVersion
+  type: string
+  default: 3.1.100
 
 jobs:
-  - job: MainTest
-    displayName: Main Test
+  - job: Test
+    displayName: Test
     strategy:
       matrix:
         ${{ each imageName in parameters.ImageNames }}:
           ${{ imageName.key }}:
             ImageName: ${{ imageName.value }}
-      maxParallel: 3
     pool:
       vmImage: "$(ImageName)"
     steps:
@@ -29,14 +28,30 @@ jobs:
         submodules: true
         persistCredentials: false
 
+      # This is required for the SonarCloud analyzer
+      - task: UseDotNet@2
+        displayName: "Install .NET Core SDK 2.1"
+        condition: eq(variables['ImageName'], 'ubuntu-latest')
+        inputs:
+          packageType: sdk
+          version: '2.1.805'
+
       - task: UseDotNet@2
         displayName: "Update DotNet"
         inputs:
           packageType: sdk
           version: ${{ parameters.DotNetSdkVersion }}
 
+      - task: SonarCloudPrepare@1
+        displayName: 'Prepare analysis on SonarCloud'
+        condition: eq(variables['ImageName'], 'ubuntu-latest')
+        inputs:
+          SonarCloud: 'Sonarcloud for Jellyfin'
+          organization: 'jellyfin'
+          projectKey: 'jellyfin_jellyfin'
+
       - task: DotNetCoreCLI@2
-        displayName: Run .NET Core CLI tests
+        displayName: 'Run CLI Tests'
         inputs:
           command: "test"
           projects: ${{ parameters.TestProjects }}
@@ -45,9 +60,17 @@ jobs:
           testRunTitle: $(Agent.JobName)
           workingDirectory: "$(Build.SourcesDirectory)"
 
+      - task: SonarCloudAnalyze@1
+        displayName: 'Run Code Analysis'
+        condition: eq(variables['ImageName'], 'ubuntu-latest')
+
+      - task: SonarCloudPublish@1
+        displayName: 'Publish Quality Gate Result'
+        condition: eq(variables['ImageName'], 'ubuntu-latest')
+
       - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
         condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
-        displayName: ReportGenerator (merge)
+        displayName: 'Run ReportGenerator'
         inputs:
           reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
           targetdir: "$(Agent.TempDirectory)/merged/"
@@ -56,10 +79,11 @@ jobs:
       ## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
       - task: PublishCodeCoverageResults@1
         condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
-        displayName: Publish Code Coverage
+        displayName: 'Publish Code Coverage'
         inputs:
           codeCoverageTool: "cobertura"
           #summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
           summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
           pathToSources: $(Build.SourcesDirectory)
           failIfCoverageEmpty: true
+

+ 6 - 6
.ci/azure-pipelines.yml

@@ -1,12 +1,12 @@
 name: $(Date:yyyyMMdd)$(Rev:.r)
 
 variables:
-  - name: TestProjects
-    value: "tests/**/*Tests.csproj"
-  - name: RestoreBuildProjects
-    value: "Jellyfin.Server/Jellyfin.Server.csproj"
-  - name: DotNetSdkVersion
-    value: 3.1.100
+- name: TestProjects
+  value: "tests/**/*Tests.csproj"
+- name: RestoreBuildProjects
+  value: "Jellyfin.Server/Jellyfin.Server.csproj"
+- name: DotNetSdkVersion
+  value: 3.1.100
 
 pr:
   autoCancel: true

+ 5 - 0
DvdLib/DvdLib.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{713F42B5-878E-499D-A878-E4C652B1D5E8}</ProjectGuid>
+  </PropertyGroup>
+
   <ItemGroup>
     <Compile Include="..\SharedVersion.cs" />
   </ItemGroup>

+ 1 - 0
Emby.Dlna/ConfigurationExtension.cs

@@ -1,3 +1,4 @@
+#nullable enable
 #pragma warning disable CS1591
 
 using System.Collections.Generic;

+ 5 - 0
Emby.Dlna/Emby.Dlna.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{805844AB-E92F-45E6-9D99-4F6D48D129A5}</ProjectGuid>
+  </PropertyGroup>
+
   <ItemGroup>
     <Compile Include="..\SharedVersion.cs" />
   </ItemGroup>

+ 6 - 0
Emby.Drawing/Emby.Drawing.csproj

@@ -1,10 +1,16 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{08FFF49B-F175-4807-A2B5-73B0EBD9F716}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <Nullable>enable</Nullable>
   </PropertyGroup>
 
   <ItemGroup>

+ 9 - 47
Emby.Drawing/ImageProcessor.cs

@@ -8,7 +8,6 @@ using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Entities;
@@ -33,8 +32,7 @@ namespace Emby.Drawing
         private readonly IFileSystem _fileSystem;
         private readonly IServerApplicationPaths _appPaths;
         private readonly IImageEncoder _imageEncoder;
-        private readonly Func<ILibraryManager> _libraryManager;
-        private readonly Func<IMediaEncoder> _mediaEncoder;
+        private readonly IMediaEncoder _mediaEncoder;
 
         private bool _disposed = false;
 
@@ -45,20 +43,17 @@ namespace Emby.Drawing
         /// <param name="appPaths">The server application paths.</param>
         /// <param name="fileSystem">The filesystem.</param>
         /// <param name="imageEncoder">The image encoder.</param>
-        /// <param name="libraryManager">The library manager.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
         public ImageProcessor(
             ILogger<ImageProcessor> logger,
             IServerApplicationPaths appPaths,
             IFileSystem fileSystem,
             IImageEncoder imageEncoder,
-            Func<ILibraryManager> libraryManager,
-            Func<IMediaEncoder> mediaEncoder)
+            IMediaEncoder mediaEncoder)
         {
             _logger = logger;
             _fileSystem = fileSystem;
             _imageEncoder = imageEncoder;
-            _libraryManager = libraryManager;
             _mediaEncoder = mediaEncoder;
             _appPaths = appPaths;
         }
@@ -121,26 +116,9 @@ namespace Emby.Drawing
         /// <inheritdoc />
         public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
         {
-            if (options == null)
-            {
-                throw new ArgumentNullException(nameof(options));
-            }
-
-            var libraryManager = _libraryManager();
-
             ItemImageInfo originalImage = options.Image;
             BaseItem item = options.Item;
 
-            if (!originalImage.IsLocalFile)
-            {
-                if (item == null)
-                {
-                    item = libraryManager.GetItemById(options.ItemId);
-                }
-
-                originalImage = await libraryManager.ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false);
-            }
-
             string originalImagePath = originalImage.Path;
             DateTime dateModified = originalImage.DateModified;
             ImageDimensions? originalImageSize = null;
@@ -312,10 +290,6 @@ namespace Emby.Drawing
 
         /// <inheritdoc />
         public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
-            => GetImageDimensions(item, info, true);
-
-        /// <inheritdoc />
-        public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem)
         {
             int width = info.Width;
             int height = info.Height;
@@ -332,11 +306,6 @@ namespace Emby.Drawing
             info.Width = size.Width;
             info.Height = size.Height;
 
-            if (updateItem)
-            {
-                _libraryManager().UpdateImages(item);
-            }
-
             return size;
         }
 
@@ -351,19 +320,12 @@ namespace Emby.Drawing
         /// <inheritdoc />
         public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
         {
-            try
+            return GetImageCacheTag(item, new ItemImageInfo
             {
-                return GetImageCacheTag(item, new ItemImageInfo
-                {
-                    Path = chapter.ImagePath,
-                    Type = ImageType.Chapter,
-                    DateModified = chapter.ImageDateModified
-                });
-            }
-            catch
-            {
-                return null;
-            }
+                Path = chapter.ImagePath,
+                Type = ImageType.Chapter,
+                DateModified = chapter.ImageDateModified
+            });
         }
 
         private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
@@ -384,13 +346,13 @@ namespace Emby.Drawing
                 {
                     string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
 
-                    string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png";
+                    string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
                     var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
 
                     var file = _fileSystem.GetFileInfo(outputPath);
                     if (!file.Exists)
                     {
-                        await _mediaEncoder().ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
+                        await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
                         dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
                     }
                     else

+ 5 - 0
Emby.Naming/Emby.Naming.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>

+ 5 - 0
Emby.Notifications/Emby.Notifications.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{2E030C33-6923-4530-9E54-FA29FA6AD1A9}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>

+ 6 - 0
Emby.Photos/Emby.Photos.csproj

@@ -1,4 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
+
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{89AB4548-770D-41FD-A891-8DAFF44F452C}</ProjectGuid>
+  </PropertyGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />

+ 1 - 1
Emby.Photos/PhotoProvider.cs

@@ -160,7 +160,7 @@ namespace Emby.Photos
 
                 try
                 {
-                    var size = _imageProcessor.GetImageDimensions(item, img, false);
+                    var size = _imageProcessor.GetImageDimensions(item, img);
 
                     if (size.Width > 0 && size.Height > 0)
                     {

+ 5 - 5
Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs

@@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.Activity
             });
         }
 
-        private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
+        private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
         {
             CreateLogEntry(new ActivityLogEntry
             {
@@ -434,8 +434,8 @@ namespace Emby.Server.Implementations.Activity
                 ShortOverview = string.Format(
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("VersionNumber"),
-                    e.Argument.Item2.versionStr),
-                Overview = e.Argument.Item2.description
+                    e.Argument.Item2.version),
+                Overview = e.Argument.Item2.changelog
             });
         }
 
@@ -451,7 +451,7 @@ namespace Emby.Server.Implementations.Activity
             });
         }
 
-        private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
+        private void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
         {
             CreateLogEntry(new ActivityLogEntry
             {
@@ -463,7 +463,7 @@ namespace Emby.Server.Implementations.Activity
                 ShortOverview = string.Format(
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("VersionNumber"),
-                    e.Argument.versionStr)
+                    e.Argument.version)
             });
         }
 

+ 188 - 428
Emby.Server.Implementations/ApplicationHost.cs

@@ -86,7 +86,6 @@ using MediaBrowser.Model.Activity;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Cryptography;
 using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
@@ -104,7 +103,6 @@ using MediaBrowser.WebDashboard.Api;
 using MediaBrowser.XbmcMetadata.Providers;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Extensions;
-using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
@@ -121,14 +119,20 @@ namespace Emby.Server.Implementations
         /// </summary>
         private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
 
-        private SqliteUserRepository _userRepository;
-        private SqliteDisplayPreferencesRepository _displayPreferencesRepository;
+        private readonly IFileSystem _fileSystemManager;
+        private readonly INetworkManager _networkManager;
+        private readonly IXmlSerializer _xmlSerializer;
+        private readonly IStartupOptions _startupOptions;
+
+        private IMediaEncoder _mediaEncoder;
+        private ISessionManager _sessionManager;
+        private IHttpServer _httpServer;
+        private IHttpClient _httpClient;
 
         /// <summary>
         /// Gets a value indicating whether this instance can self restart.
         /// </summary>
-        /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
-        public abstract bool CanSelfRestart { get; }
+        public bool CanSelfRestart => _startupOptions.RestartPath != null;
 
         public virtual bool CanLaunchWebBrowser
         {
@@ -139,7 +143,7 @@ namespace Emby.Server.Implementations
                     return false;
                 }
 
-                if (StartupOptions.IsService)
+                if (_startupOptions.IsService)
                 {
                     return false;
                 }
@@ -209,21 +213,6 @@ namespace Emby.Server.Implementations
         /// <value>The configuration manager.</value>
         protected IConfigurationManager ConfigurationManager { get; set; }
 
-        public IFileSystem FileSystemManager { get; set; }
-
-        /// <inheritdoc />
-        public PackageVersionClass SystemUpdateLevel
-        {
-            get
-            {
-#if BETA
-                return PackageVersionClass.Beta;
-#else
-                return PackageVersionClass.Release;
-#endif
-            }
-        }
-
         /// <summary>
         /// Gets or sets the service provider.
         /// </summary>
@@ -245,110 +234,6 @@ namespace Emby.Server.Implementations
         /// <value>The server configuration manager.</value>
         public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager;
 
-        /// <summary>
-        /// Gets or sets the user manager.
-        /// </summary>
-        /// <value>The user manager.</value>
-        public IUserManager UserManager { get; set; }
-
-        /// <summary>
-        /// Gets or sets the library manager.
-        /// </summary>
-        /// <value>The library manager.</value>
-        internal ILibraryManager LibraryManager { get; set; }
-
-        /// <summary>
-        /// Gets or sets the directory watchers.
-        /// </summary>
-        /// <value>The directory watchers.</value>
-        private ILibraryMonitor LibraryMonitor { get; set; }
-
-        /// <summary>
-        /// Gets or sets the provider manager.
-        /// </summary>
-        /// <value>The provider manager.</value>
-        private IProviderManager ProviderManager { get; set; }
-
-        /// <summary>
-        /// Gets or sets the HTTP server.
-        /// </summary>
-        /// <value>The HTTP server.</value>
-        private IHttpServer HttpServer { get; set; }
-
-        private IDtoService DtoService { get; set; }
-
-        public IImageProcessor ImageProcessor { get; set; }
-
-        /// <summary>
-        /// Gets or sets the media encoder.
-        /// </summary>
-        /// <value>The media encoder.</value>
-        private IMediaEncoder MediaEncoder { get; set; }
-
-        private ISubtitleEncoder SubtitleEncoder { get; set; }
-
-        private ISessionManager SessionManager { get; set; }
-
-        private ILiveTvManager LiveTvManager { get; set; }
-
-        public LocalizationManager LocalizationManager { get; set; }
-
-        private IEncodingManager EncodingManager { get; set; }
-
-        private IChannelManager ChannelManager { get; set; }
-
-        /// <summary>
-        /// Gets or sets the user data repository.
-        /// </summary>
-        /// <value>The user data repository.</value>
-        private IUserDataManager UserDataManager { get; set; }
-
-        internal SqliteItemRepository ItemRepository { get; set; }
-
-        private INotificationManager NotificationManager { get; set; }
-
-        private ISubtitleManager SubtitleManager { get; set; }
-
-        private IChapterManager ChapterManager { get; set; }
-
-        private IDeviceManager DeviceManager { get; set; }
-
-        internal IUserViewManager UserViewManager { get; set; }
-
-        private IAuthenticationRepository AuthenticationRepository { get; set; }
-
-        private ITVSeriesManager TVSeriesManager { get; set; }
-
-        private ICollectionManager CollectionManager { get; set; }
-
-        private IMediaSourceManager MediaSourceManager { get; set; }
-
-        /// <summary>
-        /// Gets the installation manager.
-        /// </summary>
-        /// <value>The installation manager.</value>
-        protected IInstallationManager InstallationManager { get; private set; }
-
-        protected IAuthService AuthService { get; private set; }
-
-        public IStartupOptions StartupOptions { get; }
-
-        internal IImageEncoder ImageEncoder { get; private set; }
-
-        protected readonly IXmlSerializer XmlSerializer;
-
-        protected ISocketFactory SocketFactory { get; private set; }
-
-        protected ITaskManager TaskManager { get; private set; }
-
-        public IHttpClient HttpClient { get; private set; }
-
-        protected INetworkManager NetworkManager { get; set; }
-
-        public IJsonSerializer JsonSerializer { get; private set; }
-
-        protected IIsoManager IsoManager { get; private set; }
-
         /// <summary>
         /// Initializes a new instance of the <see cref="ApplicationHost" /> class.
         /// </summary>
@@ -357,29 +242,33 @@ namespace Emby.Server.Implementations
             ILoggerFactory loggerFactory,
             IStartupOptions options,
             IFileSystem fileSystem,
-            IImageEncoder imageEncoder,
             INetworkManager networkManager)
         {
-            XmlSerializer = new MyXmlSerializer();
+            _xmlSerializer = new MyXmlSerializer();
 
-            NetworkManager = networkManager;
+            _networkManager = networkManager;
             networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
 
             ApplicationPaths = applicationPaths;
             LoggerFactory = loggerFactory;
-            FileSystemManager = fileSystem;
+            _fileSystemManager = fileSystem;
 
-            ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager);
+            ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
 
-            Logger = LoggerFactory.CreateLogger("App");
+            Logger = LoggerFactory.CreateLogger<ApplicationHost>();
 
-            StartupOptions = options;
-
-            ImageEncoder = imageEncoder;
+            _startupOptions = options;
 
             fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
 
-            NetworkManager.NetworkChanged += OnNetworkChanged;
+            _networkManager.NetworkChanged += OnNetworkChanged;
+
+            CertificateInfo = new CertificateInfo
+            {
+                Path = ServerConfigurationManager.Configuration.CertificatePath,
+                Password = ServerConfigurationManager.Configuration.CertificatePassword
+            };
+            Certificate = GetCertificate(CertificateInfo);
         }
 
         public string ExpandVirtualPath(string path)
@@ -447,10 +336,7 @@ namespace Emby.Server.Implementations
             }
         }
 
-        /// <summary>
-        /// Gets the name.
-        /// </summary>
-        /// <value>The name.</value>
+        /// <inheritdoc/>
         public string Name => ApplicationProductName;
 
         /// <summary>
@@ -540,7 +426,7 @@ namespace Emby.Server.Implementations
 
             ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
 
-            MediaEncoder.SetFFmpegPath();
+            _mediaEncoder.SetFFmpegPath();
 
             Logger.LogInformation("ServerId: {0}", SystemId);
 
@@ -552,7 +438,7 @@ namespace Emby.Server.Implementations
             Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
 
             Logger.LogInformation("Core startup complete");
-            HttpServer.GlobalResponse = null;
+            _httpServer.GlobalResponse = null;
 
             stopWatch.Restart();
             await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false);
@@ -576,7 +462,7 @@ namespace Emby.Server.Implementations
         }
 
         /// <inheritdoc/>
-        public async Task InitAsync(IServiceCollection serviceCollection, IConfiguration startupConfig)
+        public void Init(IServiceCollection serviceCollection)
         {
             HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
             HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
@@ -588,8 +474,6 @@ namespace Emby.Server.Implementations
                 HttpsPort = ServerConfiguration.DefaultHttpsPort;
             }
 
-            JsonSerializer = new JsonSerializer();
-
             if (Plugins != null)
             {
                 var pluginBuilder = new StringBuilder();
@@ -609,7 +493,7 @@ namespace Emby.Server.Implementations
 
             DiscoverTypes();
 
-            await RegisterServices(serviceCollection, startupConfig).ConfigureAwait(false);
+            RegisterServices(serviceCollection);
         }
 
         public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
@@ -620,7 +504,7 @@ namespace Emby.Server.Implementations
                 return;
             }
 
-            await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
+            await _httpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
         }
 
         public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
@@ -636,14 +520,16 @@ namespace Emby.Server.Implementations
             var localPath = context.Request.Path.ToString();
 
             var req = new WebSocketSharpRequest(request, response, request.Path, LoggerFactory.CreateLogger<WebSocketSharpRequest>());
-            await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
+            await _httpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
         }
 
         /// <summary>
         /// Registers services/resources with the service collection that will be available via DI.
         /// </summary>
-        protected async Task RegisterServices(IServiceCollection serviceCollection, IConfiguration startupConfig)
+        protected virtual void RegisterServices(IServiceCollection serviceCollection)
         {
+            serviceCollection.AddSingleton(_startupOptions);
+
             serviceCollection.AddMemoryCache();
 
             serviceCollection.AddSingleton(ConfigurationManager);
@@ -651,240 +537,169 @@ namespace Emby.Server.Implementations
 
             serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
 
-            serviceCollection.AddSingleton(JsonSerializer);
+            serviceCollection.AddSingleton<IJsonSerializer, JsonSerializer>();
 
-            // TODO: Support for injecting ILogger should be deprecated in favour of ILogger<T> and this removed
-            serviceCollection.AddSingleton<ILogger>(Logger);
+            // TODO: Remove support for injecting ILogger completely
+            serviceCollection.AddSingleton((provider) =>
+            {
+                Logger.LogWarning("Injecting ILogger directly is deprecated and should be replaced with ILogger<T>");
+                return Logger;
+            });
 
-            serviceCollection.AddSingleton(FileSystemManager);
+            serviceCollection.AddSingleton(_fileSystemManager);
             serviceCollection.AddSingleton<TvdbClientManager>();
 
-            HttpClient = new HttpClientManager.HttpClientManager(
-                ApplicationPaths,
-                LoggerFactory.CreateLogger<HttpClientManager.HttpClientManager>(),
-                FileSystemManager,
-                () => ApplicationUserAgent);
-            serviceCollection.AddSingleton(HttpClient);
+            serviceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>();
 
-            serviceCollection.AddSingleton(NetworkManager);
+            serviceCollection.AddSingleton(_networkManager);
 
-            IsoManager = new IsoManager();
-            serviceCollection.AddSingleton(IsoManager);
+            serviceCollection.AddSingleton<IIsoManager, IsoManager>();
 
-            TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager);
-            serviceCollection.AddSingleton(TaskManager);
+            serviceCollection.AddSingleton<ITaskManager, TaskManager>();
 
-            serviceCollection.AddSingleton(XmlSerializer);
+            serviceCollection.AddSingleton(_xmlSerializer);
 
-            serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
+            serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
 
-            var cryptoProvider = new CryptographyProvider();
-            serviceCollection.AddSingleton<ICryptoProvider>(cryptoProvider);
+            serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
 
-            SocketFactory = new SocketFactory();
-            serviceCollection.AddSingleton(SocketFactory);
+            serviceCollection.AddSingleton<ISocketFactory, SocketFactory>();
 
-            serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager));
+            serviceCollection.AddSingleton<IInstallationManager, InstallationManager>();
 
-            serviceCollection.AddSingleton(typeof(IZipClient), typeof(ZipClient));
+            serviceCollection.AddSingleton<IZipClient, ZipClient>();
 
-            serviceCollection.AddSingleton(typeof(IHttpResultFactory), typeof(HttpResultFactory));
+            serviceCollection.AddSingleton<IHttpResultFactory, HttpResultFactory>();
 
             serviceCollection.AddSingleton<IServerApplicationHost>(this);
             serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
 
             serviceCollection.AddSingleton(ServerConfigurationManager);
 
-            LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory.CreateLogger<LocalizationManager>());
-            await LocalizationManager.LoadAll().ConfigureAwait(false);
-            serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
-
-            serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager));
+            serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
 
-            UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager);
-            serviceCollection.AddSingleton(UserDataManager);
+            serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
 
-            _displayPreferencesRepository = new SqliteDisplayPreferencesRepository(
-                LoggerFactory.CreateLogger<SqliteDisplayPreferencesRepository>(),
-                ApplicationPaths,
-                FileSystemManager);
-            serviceCollection.AddSingleton<IDisplayPreferencesRepository>(_displayPreferencesRepository);
+            serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
+            serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
 
-            ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, LoggerFactory.CreateLogger<SqliteItemRepository>(), LocalizationManager);
-            serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
+            serviceCollection.AddSingleton<IDisplayPreferencesRepository, SqliteDisplayPreferencesRepository>();
 
-            AuthenticationRepository = GetAuthenticationRepository();
-            serviceCollection.AddSingleton(AuthenticationRepository);
+            serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
 
-            _userRepository = GetUserRepository();
+            serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
 
-            UserManager = new UserManager(
-                LoggerFactory.CreateLogger<UserManager>(),
-                _userRepository,
-                XmlSerializer,
-                NetworkManager,
-                () => ImageProcessor,
-                () => DtoService,
-                this,
-                JsonSerializer,
-                FileSystemManager,
-                cryptoProvider);
+            serviceCollection.AddSingleton<IUserRepository, SqliteUserRepository>();
 
-            serviceCollection.AddSingleton(UserManager);
+            // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
+            serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
+            serviceCollection.AddSingleton<IUserManager, UserManager>();
 
-            MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
-                LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
-                ServerConfigurationManager,
-                FileSystemManager,
-                LocalizationManager,
-                () => SubtitleEncoder,
-                startupConfig,
-                StartupOptions.FFmpegPath);
-            serviceCollection.AddSingleton(MediaEncoder);
+            // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
+            // TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
+            serviceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
+            serviceCollection.AddSingleton<IMediaEncoder>(provider =>
+                ActivatorUtilities.CreateInstance<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(provider, _startupOptions.FFmpegPath ?? string.Empty));
 
-            LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager, MediaEncoder);
-            serviceCollection.AddSingleton(LibraryManager);
+            // TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
+            serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
+            serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
+            serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
+            serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
 
-            var musicManager = new MusicManager(LibraryManager);
-            serviceCollection.AddSingleton<IMusicManager>(musicManager);
+            serviceCollection.AddSingleton<IMusicManager, MusicManager>();
 
-            LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager);
-            serviceCollection.AddSingleton(LibraryMonitor);
+            serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
 
-            serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager));
-
-            CertificateInfo = GetCertificateInfo(true);
-            Certificate = GetCertificate(CertificateInfo);
+            serviceCollection.AddSingleton<ISearchEngine, SearchEngine>();
 
             serviceCollection.AddSingleton<ServiceController>();
             serviceCollection.AddSingleton<IHttpListener, WebSocketSharpListener>();
             serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>();
 
-            ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger<ImageProcessor>(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
-            serviceCollection.AddSingleton(ImageProcessor);
-
-            TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
-            serviceCollection.AddSingleton(TVSeriesManager);
-
-            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);
-            serviceCollection.AddSingleton(MediaSourceManager);
-
-            SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager);
-            serviceCollection.AddSingleton(SubtitleManager);
-
-            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);
-            serviceCollection.AddSingleton(DtoService);
-
-            ChannelManager = new ChannelManager(
-                UserManager,
-                DtoService,
-                LibraryManager,
-                LoggerFactory.CreateLogger<ChannelManager>(),
-                ServerConfigurationManager,
-                FileSystemManager,
-                UserDataManager,
-                JsonSerializer,
-                ProviderManager);
-            serviceCollection.AddSingleton(ChannelManager);
-
-            SessionManager = new SessionManager(
-                LoggerFactory.CreateLogger<SessionManager>(),
-                UserDataManager,
-                LibraryManager,
-                UserManager,
-                musicManager,
-                DtoService,
-                ImageProcessor,
-                this,
-                AuthenticationRepository,
-                DeviceManager,
-                MediaSourceManager);
-            serviceCollection.AddSingleton(SessionManager);
-
-            serviceCollection.AddSingleton<IDlnaManager>(
-                new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this));
-
-            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);
-            serviceCollection.AddSingleton(LiveTvManager);
-
-            UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
-            serviceCollection.AddSingleton(UserViewManager);
-
-            NotificationManager = new NotificationManager(
-                LoggerFactory.CreateLogger<NotificationManager>(),
-                UserManager,
-                ServerConfigurationManager);
-            serviceCollection.AddSingleton(NotificationManager);
-
-            serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
-
-            ChapterManager = new ChapterManager(ItemRepository);
-            serviceCollection.AddSingleton(ChapterManager);
-
-            EncodingManager = new MediaEncoder.EncodingManager(
-                LoggerFactory.CreateLogger<MediaEncoder.EncodingManager>(),
-                FileSystemManager,
-                MediaEncoder,
-                ChapterManager,
-                LibraryManager);
-            serviceCollection.AddSingleton(EncodingManager);
-
-            var activityLogRepo = GetActivityLogRepository();
-            serviceCollection.AddSingleton(activityLogRepo);
-            serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(activityLogRepo, UserManager));
-
-            var authContext = new AuthorizationContext(AuthenticationRepository, UserManager);
-            serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
-            serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
-
-            AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager);
-            serviceCollection.AddSingleton(AuthService);
-
-            SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(
-                LibraryManager,
-                LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(),
-                ApplicationPaths,
-                FileSystemManager,
-                MediaEncoder,
-                HttpClient,
-                MediaSourceManager);
-            serviceCollection.AddSingleton(SubtitleEncoder);
-
-            serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
-            serviceCollection.AddSingleton<EncodingHelper>();
+            serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
 
-            serviceCollection.AddSingleton(typeof(IAttachmentExtractor), typeof(MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor));
+            serviceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
 
-            _displayPreferencesRepository.Initialize();
+            serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
 
-            var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger<SqliteUserDataRepository>(), ApplicationPaths);
+            serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
 
-            SetStaticProperties();
+            serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
+
+            serviceCollection.AddSingleton<IProviderManager, ProviderManager>();
+
+            // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
+            serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
+            serviceCollection.AddSingleton<IDtoService, DtoService>();
+
+            serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
+
+            serviceCollection.AddSingleton<ISessionManager, SessionManager>();
 
-            ((UserManager)UserManager).Initialize();
+            serviceCollection.AddSingleton<IDlnaManager, DlnaManager>();
 
-            ((UserDataManager)UserDataManager).Repository = userDataRepo;
-            ItemRepository.Initialize(userDataRepo, UserManager);
-            ((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
+            serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
+
+            serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
+
+            serviceCollection.AddSingleton<LiveTvDtoService>();
+            serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
+
+            serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
+
+            serviceCollection.AddSingleton<INotificationManager, NotificationManager>();
+
+            serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
+
+            serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
+
+            serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
+
+            serviceCollection.AddSingleton<IActivityRepository, ActivityRepository>();
+            serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
+
+            serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
+            serviceCollection.AddSingleton<ISessionContext, SessionContext>();
+
+            serviceCollection.AddSingleton<IAuthService, AuthService>();
+
+            serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
+
+            serviceCollection.AddSingleton<IResourceFileManager, ResourceFileManager>();
+            serviceCollection.AddSingleton<EncodingHelper>();
+
+            serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
         }
 
         /// <summary>
         /// Create services registered with the service container that need to be initialized at application startup.
         /// </summary>
-        public void InitializeServices()
+        /// <returns>A task representing the service initialization operation.</returns>
+        public async Task InitializeServices()
         {
-            HttpServer = Resolve<IHttpServer>();
+            var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
+            await localizationManager.LoadAll().ConfigureAwait(false);
+
+            _mediaEncoder = Resolve<IMediaEncoder>();
+            _sessionManager = Resolve<ISessionManager>();
+            _httpServer = Resolve<IHttpServer>();
+            _httpClient = Resolve<IHttpClient>();
+
+            ((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
+            ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
+            ((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
+            ((ActivityRepository)Resolve<IActivityRepository>()).Initialize();
+
+            SetStaticProperties();
+
+            var userManager = (UserManager)Resolve<IUserManager>();
+            userManager.Initialize();
+
+            var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
+            ((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, userManager);
+
+            FindParts();
         }
 
         public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
@@ -953,75 +768,38 @@ namespace Emby.Server.Implementations
             }
         }
 
-        /// <summary>
-        /// Gets the user repository.
-        /// </summary>
-        /// <returns><see cref="Task{SqliteUserRepository}" />.</returns>
-        private SqliteUserRepository GetUserRepository()
-        {
-            var repo = new SqliteUserRepository(
-                LoggerFactory.CreateLogger<SqliteUserRepository>(),
-                ApplicationPaths);
-
-            repo.Initialize();
-
-            return repo;
-        }
-
-        private IAuthenticationRepository GetAuthenticationRepository()
-        {
-            var repo = new AuthenticationRepository(LoggerFactory, ServerConfigurationManager);
-
-            repo.Initialize();
-
-            return repo;
-        }
-
-        private IActivityRepository GetActivityLogRepository()
-        {
-            var repo = new ActivityRepository(LoggerFactory.CreateLogger<ActivityRepository>(), ServerConfigurationManager.ApplicationPaths, FileSystemManager);
-
-            repo.Initialize();
-
-            return repo;
-        }
-
         /// <summary>
         /// Dirty hacks.
         /// </summary>
         private void SetStaticProperties()
         {
-            ItemRepository.ImageProcessor = ImageProcessor;
-
             // For now there's no real way to inject these properly
-            BaseItem.Logger = LoggerFactory.CreateLogger("BaseItem");
+            BaseItem.Logger = Resolve<ILogger<BaseItem>>();
             BaseItem.ConfigurationManager = ServerConfigurationManager;
-            BaseItem.LibraryManager = LibraryManager;
-            BaseItem.ProviderManager = ProviderManager;
-            BaseItem.LocalizationManager = LocalizationManager;
-            BaseItem.ItemRepository = ItemRepository;
-            User.UserManager = UserManager;
-            BaseItem.FileSystem = FileSystemManager;
-            BaseItem.UserDataManager = UserDataManager;
-            BaseItem.ChannelManager = ChannelManager;
-            Video.LiveTvManager = LiveTvManager;
-            Folder.UserViewManager = UserViewManager;
-            UserView.TVSeriesManager = TVSeriesManager;
-            UserView.CollectionManager = CollectionManager;
-            BaseItem.MediaSourceManager = MediaSourceManager;
-            CollectionFolder.XmlSerializer = XmlSerializer;
-            CollectionFolder.JsonSerializer = JsonSerializer;
+            BaseItem.LibraryManager = Resolve<ILibraryManager>();
+            BaseItem.ProviderManager = Resolve<IProviderManager>();
+            BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
+            BaseItem.ItemRepository = Resolve<IItemRepository>();
+            User.UserManager = Resolve<IUserManager>();
+            BaseItem.FileSystem = _fileSystemManager;
+            BaseItem.UserDataManager = Resolve<IUserDataManager>();
+            BaseItem.ChannelManager = Resolve<IChannelManager>();
+            Video.LiveTvManager = Resolve<ILiveTvManager>();
+            Folder.UserViewManager = Resolve<IUserViewManager>();
+            UserView.TVSeriesManager = Resolve<ITVSeriesManager>();
+            UserView.CollectionManager = Resolve<ICollectionManager>();
+            BaseItem.MediaSourceManager = Resolve<IMediaSourceManager>();
+            CollectionFolder.XmlSerializer = _xmlSerializer;
+            CollectionFolder.JsonSerializer = Resolve<IJsonSerializer>();
             CollectionFolder.ApplicationHost = this;
-            AuthenticatedAttribute.AuthService = AuthService;
+            AuthenticatedAttribute.AuthService = Resolve<IAuthService>();
         }
 
         /// <summary>
-        /// Finds the parts.
+        /// Finds plugin components and register them with the appropriate services.
         /// </summary>
-        public void FindParts()
+        private void FindParts()
         {
-            InstallationManager = ServiceProvider.GetService<IInstallationManager>();
-
             if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
             {
                 ServerConfigurationManager.Configuration.IsPortAuthorized = true;
@@ -1034,34 +812,34 @@ namespace Emby.Server.Implementations
                         .Where(i => i != null)
                         .ToArray();
 
-            HttpServer.Init(GetExportTypes<IService>(), GetExports<IWebSocketListener>(), GetUrlPrefixes());
+            _httpServer.Init(GetExportTypes<IService>(), GetExports<IWebSocketListener>(), GetUrlPrefixes());
 
-            LibraryManager.AddParts(
+            Resolve<ILibraryManager>().AddParts(
                 GetExports<IResolverIgnoreRule>(),
                 GetExports<IItemResolver>(),
                 GetExports<IIntroProvider>(),
                 GetExports<IBaseItemComparer>(),
                 GetExports<ILibraryPostScanTask>());
 
-            ProviderManager.AddParts(
+            Resolve<IProviderManager>().AddParts(
                 GetExports<IImageProvider>(),
                 GetExports<IMetadataService>(),
                 GetExports<IMetadataProvider>(),
                 GetExports<IMetadataSaver>(),
                 GetExports<IExternalId>());
 
-            LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
+            Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
 
-            SubtitleManager.AddParts(GetExports<ISubtitleProvider>());
+            Resolve<ISubtitleManager>().AddParts(GetExports<ISubtitleProvider>());
 
-            ChannelManager.AddParts(GetExports<IChannel>());
+            Resolve<IChannelManager>().AddParts(GetExports<IChannel>());
 
-            MediaSourceManager.AddParts(GetExports<IMediaSourceProvider>());
+            Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
 
-            NotificationManager.AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
-            UserManager.AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
+            Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
+            Resolve<IUserManager>().AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
 
-            IsoManager.AddParts(GetExports<IIsoMounter>());
+            Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>());
         }
 
         private IPlugin LoadPlugin(IPlugin plugin)
@@ -1168,16 +946,6 @@ namespace Emby.Server.Implementations
             });
         }
 
-        private CertificateInfo GetCertificateInfo(bool generateCertificate)
-        {
-            // Custom cert
-            return new CertificateInfo
-            {
-                Path = ServerConfigurationManager.Configuration.CertificatePath,
-                Password = ServerConfigurationManager.Configuration.CertificatePassword
-            };
-        }
-
         /// <summary>
         /// Called when [configuration updated].
         /// </summary>
@@ -1204,14 +972,13 @@ namespace Emby.Server.Implementations
                 }
             }
 
-            if (!HttpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase))
+            if (!_httpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase))
             {
                 requiresRestart = true;
             }
 
             var currentCertPath = CertificateInfo?.Path;
-            var newCertInfo = GetCertificateInfo(false);
-            var newCertPath = newCertInfo?.Path;
+            var newCertPath = ServerConfigurationManager.Configuration.CertificatePath;
 
             if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase))
             {
@@ -1264,7 +1031,7 @@ namespace Emby.Server.Implementations
             {
                 try
                 {
-                    await SessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false);
+                    await _sessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false);
                 }
                 catch (Exception ex)
                 {
@@ -1368,7 +1135,7 @@ namespace Emby.Server.Implementations
                 IsShuttingDown = IsShuttingDown,
                 Version = ApplicationVersionString,
                 WebSocketPortNumber = HttpPort,
-                CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
+                CompletedInstallations = Resolve<IInstallationManager>().CompletedInstallations.ToArray(),
                 Id = SystemId,
                 ProgramDataPath = ApplicationPaths.ProgramDataPath,
                 WebPath = ApplicationPaths.WebPath,
@@ -1388,15 +1155,14 @@ namespace Emby.Server.Implementations
                 ServerName = FriendlyName,
                 LocalAddress = localAddress,
                 SupportsLibraryMonitor = true,
-                EncoderLocation = MediaEncoder.EncoderLocation,
+                EncoderLocation = _mediaEncoder.EncoderLocation,
                 SystemArchitecture = RuntimeInformation.OSArchitecture,
-                SystemUpdateLevel = SystemUpdateLevel,
-                PackageName = StartupOptions.PackageName
+                PackageName = _startupOptions.PackageName
             };
         }
 
         public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
-            => NetworkManager.GetMacAddresses()
+            => _networkManager.GetMacAddresses()
                 .Select(i => new WakeOnLanInfo(i))
                 .ToList();
 
@@ -1508,7 +1274,7 @@ namespace Emby.Server.Implementations
 
             if (addresses.Count == 0)
             {
-                addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
+                addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
             }
 
             var resultList = new List<IPAddress>();
@@ -1575,7 +1341,7 @@ namespace Emby.Server.Implementations
 
             try
             {
-                using (var response = await HttpClient.SendAsync(
+                using (var response = await _httpClient.SendAsync(
                     new HttpRequestOptions
                     {
                         Url = apiUrl,
@@ -1628,7 +1394,7 @@ namespace Emby.Server.Implementations
 
             try
             {
-                await SessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false);
+                await _sessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false);
             }
             catch (Exception ex)
             {
@@ -1749,14 +1515,8 @@ namespace Emby.Server.Implementations
                         Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name);
                     }
                 }
-
-                _userRepository?.Dispose();
-                _displayPreferencesRepository?.Dispose();
             }
 
-            _userRepository = null;
-            _displayPreferencesRepository = null;
-
             _disposed = true;
         }
     }

+ 1 - 1
Emby.Server.Implementations/ConfigurationOptions.cs

@@ -18,7 +18,7 @@ namespace Emby.Server.Implementations
         {
             { HostWebClientKey, bool.TrueString },
             { HttpListenerHost.DefaultRedirectKey, "web/index.html" },
-            { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest.json" },
+            { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
             { FfmpegProbeSizeKey, "1G" },
             { FfmpegAnalyzeDurationKey, "200M" },
             { PlaylistsAllowDuplicatesKey, bool.TrueString }

+ 13 - 7
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -39,12 +39,11 @@ namespace Emby.Server.Implementations.Data
     {
         private const string ChaptersTableName = "Chapters2";
 
-        /// <summary>
-        /// The _app paths
-        /// </summary>
         private readonly IServerConfigurationManager _config;
         private readonly IServerApplicationHost _appHost;
         private readonly ILocalizationManager _localization;
+        // TODO: Remove this dependency. GetImageCacheTag() is the only method used and it can be converted to a static helper method
+        private readonly IImageProcessor _imageProcessor;
 
         private readonly TypeMapper _typeMapper;
         private readonly JsonSerializerOptions _jsonOptions;
@@ -71,7 +70,8 @@ namespace Emby.Server.Implementations.Data
             IServerConfigurationManager config,
             IServerApplicationHost appHost,
             ILogger<SqliteItemRepository> logger,
-            ILocalizationManager localization)
+            ILocalizationManager localization,
+            IImageProcessor imageProcessor)
             : base(logger)
         {
             if (config == null)
@@ -82,6 +82,7 @@ namespace Emby.Server.Implementations.Data
             _config = config;
             _appHost = appHost;
             _localization = localization;
+            _imageProcessor = imageProcessor;
 
             _typeMapper = new TypeMapper();
             _jsonOptions = JsonDefaults.GetOptions();
@@ -98,8 +99,6 @@ namespace Emby.Server.Implementations.Data
         /// <inheritdoc />
         protected override TempStoreMode TempStore => TempStoreMode.Memory;
 
-        public IImageProcessor ImageProcessor { get; set; }
-
         /// <summary>
         /// Opens the connection to the database
         /// </summary>
@@ -1991,7 +1990,14 @@ namespace Emby.Server.Implementations.Data
 
                 if (!string.IsNullOrEmpty(chapter.ImagePath))
                 {
-                    chapter.ImageTag = ImageProcessor.GetImageCacheTag(item, chapter);
+                    try
+                    {
+                        chapter.ImageTag = _imageProcessor.GetImageCacheTag(item, chapter);
+                    }
+                    catch (Exception ex)
+                    {
+                        Logger.LogError(ex, "Failed to create image cache tag.");
+                    }
                 }
             }
 

+ 3 - 3
Emby.Server.Implementations/Devices/DeviceManager.cs

@@ -38,10 +38,11 @@ namespace Emby.Server.Implementations.Devices
         private readonly IServerConfigurationManager _config;
         private readonly ILibraryManager _libraryManager;
         private readonly ILocalizationManager _localizationManager;
-
         private readonly IAuthenticationRepository _authRepo;
+        private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
 
         public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
+
         public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
 
         private readonly object _cameraUploadSyncLock = new object();
@@ -65,10 +66,9 @@ namespace Emby.Server.Implementations.Devices
             _libraryManager = libraryManager;
             _localizationManager = localizationManager;
             _authRepo = authRepo;
+            _capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
         }
 
-
-        private Dictionary<string, ClientCapabilities> _capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
         public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
         {
             var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");

+ 16 - 14
Emby.Server.Implementations/Dto/DtoService.cs

@@ -38,21 +38,23 @@ namespace Emby.Server.Implementations.Dto
         private readonly IProviderManager _providerManager;
 
         private readonly IApplicationHost _appHost;
-        private readonly Func<IMediaSourceManager> _mediaSourceManager;
-        private readonly Func<ILiveTvManager> _livetvManager;
+        private readonly IMediaSourceManager _mediaSourceManager;
+        private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
+
+        private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
 
         public DtoService(
-            ILoggerFactory loggerFactory,
+            ILogger<DtoService> logger,
             ILibraryManager libraryManager,
             IUserDataManager userDataRepository,
             IItemRepository itemRepo,
             IImageProcessor imageProcessor,
             IProviderManager providerManager,
             IApplicationHost appHost,
-            Func<IMediaSourceManager> mediaSourceManager,
-            Func<ILiveTvManager> livetvManager)
+            IMediaSourceManager mediaSourceManager,
+            Lazy<ILiveTvManager> livetvManagerFactory)
         {
-            _logger = loggerFactory.CreateLogger(nameof(DtoService));
+            _logger = logger;
             _libraryManager = libraryManager;
             _userDataRepository = userDataRepository;
             _itemRepo = itemRepo;
@@ -60,7 +62,7 @@ namespace Emby.Server.Implementations.Dto
             _providerManager = providerManager;
             _appHost = appHost;
             _mediaSourceManager = mediaSourceManager;
-            _livetvManager = livetvManager;
+            _livetvManagerFactory = livetvManagerFactory;
         }
 
         /// <summary>
@@ -125,12 +127,12 @@ namespace Emby.Server.Implementations.Dto
 
             if (programTuples.Count > 0)
             {
-                _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
+                LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
             }
 
             if (channelTuples.Count > 0)
             {
-                _livetvManager().AddChannelInfo(channelTuples, options, user);
+                LivetvManager.AddChannelInfo(channelTuples, options, user);
             }
 
             return returnItems;
@@ -142,12 +144,12 @@ namespace Emby.Server.Implementations.Dto
             if (item is LiveTvChannel tvChannel)
             {
                 var list = new List<(BaseItemDto, LiveTvChannel)>(1) { (dto, tvChannel) };
-                _livetvManager().AddChannelInfo(list, options, user);
+                LivetvManager.AddChannelInfo(list, options, user);
             }
             else if (item is LiveTvProgram)
             {
                 var list = new List<(BaseItem, BaseItemDto)>(1) { (item, dto) };
-                var task = _livetvManager().AddInfoToProgramDto(list, options.Fields, user);
+                var task = LivetvManager.AddInfoToProgramDto(list, options.Fields, user);
                 Task.WaitAll(task);
             }
 
@@ -223,7 +225,7 @@ namespace Emby.Server.Implementations.Dto
             if (item is IHasMediaSources
                 && options.ContainsField(ItemFields.MediaSources))
             {
-                dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(item, true, user).ToArray();
+                dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray();
 
                 NormalizeMediaSourceContainers(dto);
             }
@@ -254,7 +256,7 @@ namespace Emby.Server.Implementations.Dto
                 dto.Etag = item.GetEtag(user);
             }
 
-            var liveTvManager = _livetvManager();
+            var liveTvManager = LivetvManager;
             var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path);
             if (activeRecording != null)
             {
@@ -1045,7 +1047,7 @@ namespace Emby.Server.Implementations.Dto
                     }
                     else
                     {
-                        mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
+                        mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
                     }
 
                     dto.MediaStreams = mediaStreams;

+ 5 - 0
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{E383961B-9356-4D5D-8233-9A1079D03055}</ProjectGuid>
+  </PropertyGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
     <ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />

+ 31 - 14
Emby.Server.Implementations/EntryPoints/StartupWizard.cs

@@ -16,46 +16,63 @@ namespace Emby.Server.Implementations.EntryPoints
         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>
-        public StartupWizard(IServerApplicationHost appHost, IConfiguration appConfig, IServerConfigurationManager config)
+        /// <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 Task.CompletedTask;
+                return;
             }
 
-            if (!_appConfig.HostWebClient())
+            // Always launch the startup wizard if possible when it has not been completed
+            if (!_config.Configuration.IsStartupWizardCompleted && _appConfig.HostWebClient())
             {
-                BrowserLauncher.OpenSwaggerPage(_appHost);
+                BrowserLauncher.OpenWebApp(_appHost);
+                return;
+            }
+
+            // Do nothing if the web app is configured to not run automatically
+            if (!_config.Configuration.AutoRunWebApp || _startupOptions.NoAutoRunWebApp)
+            {
+                return;
             }
-            else if (!_config.Configuration.IsStartupWizardCompleted)
+
+            // Launch the swagger page if the web client is not hosted, otherwise open the web client
+            if (_appConfig.HostWebClient())
             {
                 BrowserLauncher.OpenWebApp(_appHost);
             }
-            else if (_config.Configuration.AutoRunWebApp)
+            else
             {
-                var options = ((ApplicationHost)_appHost).StartupOptions;
-
-                if (!options.NoAutoRunWebApp)
-                {
-                    BrowserLauncher.OpenWebApp(_appHost);
-                }
+                BrowserLauncher.OpenSwaggerPage(_appHost);
             }
-
-            return Task.CompletedTask;
         }
 
         /// <inheritdoc />

+ 5 - 4
Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs

@@ -6,6 +6,7 @@ using System.Linq;
 using System.Net;
 using System.Net.Http;
 using System.Threading.Tasks;
+using MediaBrowser.Common;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
@@ -24,7 +25,7 @@ namespace Emby.Server.Implementations.HttpClientManager
         private readonly ILogger _logger;
         private readonly IApplicationPaths _appPaths;
         private readonly IFileSystem _fileSystem;
-        private readonly Func<string> _defaultUserAgentFn;
+        private readonly IApplicationHost _appHost;
 
         /// <summary>
         /// Holds a dictionary of http clients by host.  Use GetHttpClient(host) to retrieve or create a client for web requests.
@@ -40,12 +41,12 @@ namespace Emby.Server.Implementations.HttpClientManager
             IApplicationPaths appPaths,
             ILogger<HttpClientManager> logger,
             IFileSystem fileSystem,
-            Func<string> defaultUserAgentFn)
+            IApplicationHost appHost)
         {
             _logger = logger ?? throw new ArgumentNullException(nameof(logger));
             _fileSystem = fileSystem;
             _appPaths = appPaths ?? throw new ArgumentNullException(nameof(appPaths));
-            _defaultUserAgentFn = defaultUserAgentFn;
+            _appHost = appHost;
         }
 
         /// <summary>
@@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.HttpClientManager
             if (options.EnableDefaultUserAgent
                 && !request.Headers.TryGetValues(HeaderNames.UserAgent, out _))
             {
-                request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn());
+                request.Headers.Add(HeaderNames.UserAgent, _appHost.ApplicationUserAgent);
             }
 
             switch (options.DecompressionMethod)

+ 58 - 49
Emby.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -14,6 +14,7 @@ using Emby.Server.Implementations.Services;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Events;
@@ -230,7 +231,8 @@ namespace Emby.Server.Implementations.HttpServer
             switch (ex)
             {
                 case ArgumentException _: return 400;
-                case SecurityException _: return 401;
+                case AuthenticationException _: return 401;
+                case SecurityException _: return 403;
                 case DirectoryNotFoundException _:
                 case FileNotFoundException _:
                 case ResourceNotFoundException _: return 404;
@@ -239,55 +241,52 @@ namespace Emby.Server.Implementations.HttpServer
             }
         }
 
-        private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace, string urlToLog)
+        private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog)
         {
-            try
-            {
-                ex = GetActualException(ex);
-
-                if (logExceptionStackTrace)
-                {
-                    _logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
-                }
-                else
-                {
-                    _logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
-                }
-
-                var httpRes = httpReq.Response;
+            bool ignoreStackTrace =
+                ex is SocketException
+                || ex is IOException
+                || ex is OperationCanceledException
+                || ex is SecurityException
+                || ex is AuthenticationException
+                || ex is FileNotFoundException;
 
-                if (httpRes.HasStarted)
-                {
-                    return;
-                }
+            if (ignoreStackTrace)
+            {
+                _logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
+            }
+            else
+            {
+                _logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
+            }
 
-                var statusCode = GetStatusCode(ex);
-                httpRes.StatusCode = statusCode;
+            var httpRes = httpReq.Response;
 
-                var errContent = NormalizeExceptionMessage(ex.Message);
-                httpRes.ContentType = "text/plain";
-                httpRes.ContentLength = errContent.Length;
-                await httpRes.WriteAsync(errContent).ConfigureAwait(false);
-            }
-            catch (Exception errorEx)
+            if (httpRes.HasStarted)
             {
-                _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response). URL: {Url}", urlToLog);
+                return;
             }
+
+            httpRes.StatusCode = statusCode;
+
+            var errContent = NormalizeExceptionMessage(ex) ?? string.Empty;
+            httpRes.ContentType = "text/plain";
+            httpRes.ContentLength = errContent.Length;
+            await httpRes.WriteAsync(errContent).ConfigureAwait(false);
         }
 
-        private string NormalizeExceptionMessage(string msg)
+        private string NormalizeExceptionMessage(Exception ex)
         {
-            if (msg == null)
+            // Do not expose the exception message for AuthenticationException
+            if (ex is AuthenticationException)
             {
-                return string.Empty;
+                return null;
             }
 
             // Strip any information we don't want to reveal
-
-            msg = msg.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase);
-            msg = msg.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
-
-            return msg;
+            return ex.Message
+                ?.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase)
+                .Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
         }
 
         /// <summary>
@@ -536,22 +535,32 @@ namespace Emby.Server.Implementations.HttpServer
                     throw new FileNotFoundException();
                 }
             }
-            catch (Exception ex)
+            catch (Exception requestEx)
             {
-                // Do not handle exceptions manually when in development mode
-                // The framework-defined development exception page will be returned instead
-                if (_hostEnvironment.IsDevelopment())
+                try
                 {
-                    throw;
+                    var requestInnerEx = GetActualException(requestEx);
+                    var statusCode = GetStatusCode(requestInnerEx);
+
+                    // Do not handle 500 server exceptions manually when in development mode
+                    // The framework-defined development exception page will be returned instead
+                    if (statusCode == 500 && _hostEnvironment.IsDevelopment())
+                    {
+                        throw;
+                    }
+
+                    await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog).ConfigureAwait(false);
                 }
+                catch (Exception handlerException)
+                {
+                    var aggregateEx = new AggregateException("Error while handling request exception", requestEx, handlerException);
+                    _logger.LogError(aggregateEx, "Error while handling exception in response to {Url}", urlToLog);
 
-                bool ignoreStackTrace =
-                    ex is SocketException
-                    || ex is IOException
-                    || ex is OperationCanceledException
-                    || ex is SecurityException
-                    || ex is FileNotFoundException;
-                await ErrorHandler(ex, httpReq, !ignoreStackTrace, urlToLog).ConfigureAwait(false);
+                    if (_hostEnvironment.IsDevelopment())
+                    {
+                        throw aggregateEx;
+                    }
+                }
             }
             finally
             {

+ 10 - 27
Emby.Server.Implementations/HttpServer/Security/AuthService.cs

@@ -2,6 +2,7 @@
 
 using System;
 using System.Linq;
+using System.Security.Authentication;
 using Emby.Server.Implementations.SocketSharp;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
@@ -68,7 +69,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
 
             if (user == null && auth.UserId != Guid.Empty)
             {
-                throw new SecurityException("User with Id " + auth.UserId + " not found");
+                throw new AuthenticationException("User with Id " + auth.UserId + " not found");
             }
 
             if (user != null)
@@ -108,18 +109,12 @@ namespace Emby.Server.Implementations.HttpServer.Security
         {
             if (user.Policy.IsDisabled)
             {
-                throw new SecurityException("User account has been disabled.")
-                {
-                    SecurityExceptionType = SecurityExceptionType.Unauthenticated
-                };
+                throw new SecurityException("User account has been disabled.");
             }
 
             if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp))
             {
-                throw new SecurityException("User account has been disabled.")
-                {
-                    SecurityExceptionType = SecurityExceptionType.Unauthenticated
-                };
+                throw new SecurityException("User account has been disabled.");
             }
 
             if (!user.Policy.IsAdministrator
@@ -128,10 +123,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
             {
                 request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
 
-                throw new SecurityException("This user account is not allowed access at this time.")
-                {
-                    SecurityExceptionType = SecurityExceptionType.ParentalControl
-                };
+                throw new SecurityException("This user account is not allowed access at this time.");
             }
         }
 
@@ -190,10 +182,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
             {
                 if (user == null || !user.Policy.IsAdministrator)
                 {
-                    throw new SecurityException("User does not have admin access.")
-                    {
-                        SecurityExceptionType = SecurityExceptionType.Unauthenticated
-                    };
+                    throw new SecurityException("User does not have admin access.");
                 }
             }
 
@@ -201,10 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
             {
                 if (user == null || !user.Policy.EnableContentDeletion)
                 {
-                    throw new SecurityException("User does not have delete access.")
-                    {
-                        SecurityExceptionType = SecurityExceptionType.Unauthenticated
-                    };
+                    throw new SecurityException("User does not have delete access.");
                 }
             }
 
@@ -212,10 +198,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
             {
                 if (user == null || !user.Policy.EnableContentDownloading)
                 {
-                    throw new SecurityException("User does not have download access.")
-                    {
-                        SecurityExceptionType = SecurityExceptionType.Unauthenticated
-                    };
+                    throw new SecurityException("User does not have download access.");
                 }
             }
         }
@@ -230,14 +213,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
         {
             if (string.IsNullOrEmpty(token))
             {
-                throw new SecurityException("Access token is required.");
+                throw new AuthenticationException("Access token is required.");
             }
 
             var info = GetTokenInfo(request);
 
             if (info == null)
             {
-                throw new SecurityException("Access token is invalid or expired.");
+                throw new AuthenticationException("Access token is invalid or expired.");
             }
 
             //if (!string.IsNullOrEmpty(info.UserId))

+ 26 - 32
Emby.Server.Implementations/IO/LibraryMonitor.cs

@@ -17,6 +17,11 @@ namespace Emby.Server.Implementations.IO
 {
     public class LibraryMonitor : ILibraryMonitor
     {
+        private readonly ILogger _logger;
+        private readonly ILibraryManager _libraryManager;
+        private readonly IServerConfigurationManager _configurationManager;
+        private readonly IFileSystem _fileSystem;
+
         /// <summary>
         /// The file system watchers.
         /// </summary>
@@ -113,34 +118,23 @@ namespace Emby.Server.Implementations.IO
                 }
                 catch (Exception ex)
                 {
-                    Logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
+                    _logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
                 }
             }
         }
 
-        /// <summary>
-        /// Gets or sets the logger.
-        /// </summary>
-        /// <value>The logger.</value>
-        private ILogger Logger { get; set; }
-
-        private ILibraryManager LibraryManager { get; set; }
-        private IServerConfigurationManager ConfigurationManager { get; set; }
-
-        private readonly IFileSystem _fileSystem;
-
         /// <summary>
         /// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
         /// </summary>
         public LibraryMonitor(
-            ILoggerFactory loggerFactory,
+            ILogger<LibraryMonitor> logger,
             ILibraryManager libraryManager,
             IServerConfigurationManager configurationManager,
             IFileSystem fileSystem)
         {
-            LibraryManager = libraryManager;
-            Logger = loggerFactory.CreateLogger(GetType().Name);
-            ConfigurationManager = configurationManager;
+            _libraryManager = libraryManager;
+            _logger = logger;
+            _configurationManager = configurationManager;
             _fileSystem = fileSystem;
         }
 
@@ -151,7 +145,7 @@ namespace Emby.Server.Implementations.IO
                 return false;
             }
 
-            var options = LibraryManager.GetLibraryOptions(item);
+            var options = _libraryManager.GetLibraryOptions(item);
 
             if (options != null)
             {
@@ -163,12 +157,12 @@ namespace Emby.Server.Implementations.IO
 
         public void Start()
         {
-            LibraryManager.ItemAdded += OnLibraryManagerItemAdded;
-            LibraryManager.ItemRemoved += OnLibraryManagerItemRemoved;
+            _libraryManager.ItemAdded += OnLibraryManagerItemAdded;
+            _libraryManager.ItemRemoved += OnLibraryManagerItemRemoved;
 
             var pathsToWatch = new List<string>();
 
-            var paths = LibraryManager
+            var paths = _libraryManager
                 .RootFolder
                 .Children
                 .Where(IsLibraryMonitorEnabled)
@@ -261,7 +255,7 @@ namespace Emby.Server.Implementations.IO
             if (!Directory.Exists(path))
             {
                 // Seeing a crash in the mono runtime due to an exception being thrown on a different thread
-                Logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path);
+                _logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path);
                 return;
             }
 
@@ -297,7 +291,7 @@ namespace Emby.Server.Implementations.IO
                     if (_fileSystemWatchers.TryAdd(path, newWatcher))
                     {
                         newWatcher.EnableRaisingEvents = true;
-                        Logger.LogInformation("Watching directory " + path);
+                        _logger.LogInformation("Watching directory " + path);
                     }
                     else
                     {
@@ -307,7 +301,7 @@ namespace Emby.Server.Implementations.IO
                 }
                 catch (Exception ex)
                 {
-                    Logger.LogError(ex, "Error watching path: {path}", path);
+                    _logger.LogError(ex, "Error watching path: {path}", path);
                 }
             });
         }
@@ -333,7 +327,7 @@ namespace Emby.Server.Implementations.IO
             {
                 using (watcher)
                 {
-                    Logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path);
+                    _logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path);
 
                     watcher.Created -= OnWatcherChanged;
                     watcher.Deleted -= OnWatcherChanged;
@@ -372,7 +366,7 @@ namespace Emby.Server.Implementations.IO
             var ex = e.GetException();
             var dw = (FileSystemWatcher)sender;
 
-            Logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path);
+            _logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path);
 
             DisposeWatcher(dw, true);
         }
@@ -390,7 +384,7 @@ namespace Emby.Server.Implementations.IO
             }
             catch (Exception ex)
             {
-                Logger.LogError(ex, "Exception in ReportFileSystemChanged. Path: {FullPath}", e.FullPath);
+                _logger.LogError(ex, "Exception in ReportFileSystemChanged. Path: {FullPath}", e.FullPath);
             }
         }
 
@@ -416,13 +410,13 @@ namespace Emby.Server.Implementations.IO
             {
                 if (_fileSystem.AreEqual(i, path))
                 {
-                    Logger.LogDebug("Ignoring change to {Path}", path);
+                    _logger.LogDebug("Ignoring change to {Path}", path);
                     return true;
                 }
 
                 if (_fileSystem.ContainsSubPath(i, path))
                 {
-                    Logger.LogDebug("Ignoring change to {Path}", path);
+                    _logger.LogDebug("Ignoring change to {Path}", path);
                     return true;
                 }
 
@@ -430,7 +424,7 @@ namespace Emby.Server.Implementations.IO
                 var parent = Path.GetDirectoryName(i);
                 if (!string.IsNullOrEmpty(parent) && _fileSystem.AreEqual(parent, path))
                 {
-                    Logger.LogDebug("Ignoring change to {Path}", path);
+                    _logger.LogDebug("Ignoring change to {Path}", path);
                     return true;
                 }
 
@@ -485,7 +479,7 @@ namespace Emby.Server.Implementations.IO
                     }
                 }
 
-                var newRefresher = new FileRefresher(path, ConfigurationManager, LibraryManager, Logger);
+                var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger);
                 newRefresher.Completed += NewRefresher_Completed;
                 _activeRefreshers.Add(newRefresher);
             }
@@ -502,8 +496,8 @@ namespace Emby.Server.Implementations.IO
         /// </summary>
         public void Stop()
         {
-            LibraryManager.ItemAdded -= OnLibraryManagerItemAdded;
-            LibraryManager.ItemRemoved -= OnLibraryManagerItemRemoved;
+            _libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
+            _libraryManager.ItemRemoved -= OnLibraryManagerItemRemoved;
 
             foreach (var watcher in _fileSystemWatchers.Values.ToList())
             {

+ 1 - 1
Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs

@@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library
         {
             if (resolvedUser == null)
             {
-                throw new ArgumentNullException(nameof(resolvedUser));
+                throw new AuthenticationException($"Specified user does not exist.");
             }
 
             bool success = false;

+ 100 - 129
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -54,9 +54,29 @@ namespace Emby.Server.Implementations.Library
     /// </summary>
     public class LibraryManager : ILibraryManager
     {
+        private readonly ILogger _logger;
+        private readonly ITaskManager _taskManager;
+        private readonly IUserManager _userManager;
+        private readonly IUserDataManager _userDataRepository;
+        private readonly IServerConfigurationManager _configurationManager;
+        private readonly Lazy<ILibraryMonitor> _libraryMonitorFactory;
+        private readonly Lazy<IProviderManager> _providerManagerFactory;
+        private readonly Lazy<IUserViewManager> _userviewManagerFactory;
+        private readonly IServerApplicationHost _appHost;
+        private readonly IMediaEncoder _mediaEncoder;
+        private readonly IFileSystem _fileSystem;
+        private readonly IItemRepository _itemRepository;
+        private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
+
         private NamingOptions _namingOptions;
         private string[] _videoFileExtensions;
 
+        private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
+
+        private IProviderManager ProviderManager => _providerManagerFactory.Value;
+
+        private IUserViewManager UserViewManager => _userviewManagerFactory.Value;
+
         /// <summary>
         /// Gets or sets the postscan tasks.
         /// </summary>
@@ -89,12 +109,6 @@ namespace Emby.Server.Implementations.Library
         /// <value>The comparers.</value>
         private IBaseItemComparer[] Comparers { get; set; }
 
-        /// <summary>
-        /// Gets or sets the active item repository
-        /// </summary>
-        /// <value>The item repository.</value>
-        public IItemRepository ItemRepository { get; set; }
-
         /// <summary>
         /// Occurs when [item added].
         /// </summary>
@@ -110,90 +124,47 @@ namespace Emby.Server.Implementations.Library
         /// </summary>
         public event EventHandler<ItemChangeEventArgs> ItemRemoved;
 
-        /// <summary>
-        /// The _logger
-        /// </summary>
-        private readonly ILogger _logger;
-
-        /// <summary>
-        /// The _task manager
-        /// </summary>
-        private readonly ITaskManager _taskManager;
-
-        /// <summary>
-        /// The _user manager
-        /// </summary>
-        private readonly IUserManager _userManager;
-
-        /// <summary>
-        /// The _user data repository
-        /// </summary>
-        private readonly IUserDataManager _userDataRepository;
-
-        /// <summary>
-        /// Gets or sets the configuration manager.
-        /// </summary>
-        /// <value>The configuration manager.</value>
-        private IServerConfigurationManager ConfigurationManager { get; set; }
-
-        private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
-        private readonly Func<IProviderManager> _providerManagerFactory;
-        private readonly Func<IUserViewManager> _userviewManager;
         public bool IsScanRunning { get; private set; }
 
-        private IServerApplicationHost _appHost;
-        private readonly IMediaEncoder _mediaEncoder;
-
-        /// <summary>
-        /// The _library items cache
-        /// </summary>
-        private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
-
-        /// <summary>
-        /// Gets the library items cache.
-        /// </summary>
-        /// <value>The library items cache.</value>
-        private ConcurrentDictionary<Guid, BaseItem> LibraryItemsCache => _libraryItemsCache;
-
-        private readonly IFileSystem _fileSystem;
-
         /// <summary>
         /// Initializes a new instance of the <see cref="LibraryManager" /> class.
         /// </summary>
         /// <param name="appHost">The application host</param>
-        /// <param name="loggerFactory">The logger factory.</param>
+        /// <param name="logger">The logger.</param>
         /// <param name="taskManager">The task manager.</param>
         /// <param name="userManager">The user manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="userDataRepository">The user data repository.</param>
         public LibraryManager(
             IServerApplicationHost appHost,
-            ILoggerFactory loggerFactory,
+            ILogger<LibraryManager> logger,
             ITaskManager taskManager,
             IUserManager userManager,
             IServerConfigurationManager configurationManager,
             IUserDataManager userDataRepository,
-            Func<ILibraryMonitor> libraryMonitorFactory,
+            Lazy<ILibraryMonitor> libraryMonitorFactory,
             IFileSystem fileSystem,
-            Func<IProviderManager> providerManagerFactory,
-            Func<IUserViewManager> userviewManager,
-            IMediaEncoder mediaEncoder)
+            Lazy<IProviderManager> providerManagerFactory,
+            Lazy<IUserViewManager> userviewManagerFactory,
+            IMediaEncoder mediaEncoder,
+            IItemRepository itemRepository)
         {
             _appHost = appHost;
-            _logger = loggerFactory.CreateLogger(nameof(LibraryManager));
+            _logger = logger;
             _taskManager = taskManager;
             _userManager = userManager;
-            ConfigurationManager = configurationManager;
+            _configurationManager = configurationManager;
             _userDataRepository = userDataRepository;
             _libraryMonitorFactory = libraryMonitorFactory;
             _fileSystem = fileSystem;
             _providerManagerFactory = providerManagerFactory;
-            _userviewManager = userviewManager;
+            _userviewManagerFactory = userviewManagerFactory;
             _mediaEncoder = mediaEncoder;
+            _itemRepository = itemRepository;
 
             _libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
 
-            ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
+            _configurationManager.ConfigurationUpdated += ConfigurationUpdated;
 
             RecordConfigurationValues(configurationManager.Configuration);
         }
@@ -272,7 +243,7 @@ namespace Emby.Server.Implementations.Library
         /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
         private void ConfigurationUpdated(object sender, EventArgs e)
         {
-            var config = ConfigurationManager.Configuration;
+            var config = _configurationManager.Configuration;
 
             var wizardChanged = config.IsStartupWizardCompleted != _wizardCompleted;
 
@@ -306,7 +277,7 @@ namespace Emby.Server.Implementations.Library
                 }
             }
 
-            LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
+            _libraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
         }
 
         public void DeleteItem(BaseItem item, DeleteOptions options)
@@ -437,10 +408,10 @@ namespace Emby.Server.Implementations.Library
 
             item.SetParent(null);
 
-            ItemRepository.DeleteItem(item.Id);
+            _itemRepository.DeleteItem(item.Id);
             foreach (var child in children)
             {
-                ItemRepository.DeleteItem(child.Id);
+                _itemRepository.DeleteItem(child.Id);
             }
 
             _libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
@@ -509,15 +480,15 @@ namespace Emby.Server.Implementations.Library
                 throw new ArgumentNullException(nameof(type));
             }
 
-            if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal))
+            if (key.StartsWith(_configurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal))
             {
                 // Try to normalize paths located underneath program-data in an attempt to make them more portable
-                key = key.Substring(ConfigurationManager.ApplicationPaths.ProgramDataPath.Length)
+                key = key.Substring(_configurationManager.ApplicationPaths.ProgramDataPath.Length)
                     .TrimStart(new[] { '/', '\\' })
                     .Replace("/", "\\");
             }
 
-            if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
+            if (forceCaseInsensitive || !_configurationManager.Configuration.EnableCaseSensitiveItemIds)
             {
                 key = key.ToLowerInvariant();
             }
@@ -550,7 +521,7 @@ namespace Emby.Server.Implementations.Library
                 collectionType = GetContentTypeOverride(fullPath, true);
             }
 
-            var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
+            var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
             {
                 Parent = parent,
                 Path = fullPath,
@@ -720,7 +691,7 @@ namespace Emby.Server.Implementations.Library
         /// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded.</exception>
         public AggregateFolder CreateRootFolder()
         {
-            var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
+            var rootFolderPath = _configurationManager.ApplicationPaths.RootFolderPath;
 
             Directory.CreateDirectory(rootFolderPath);
 
@@ -734,7 +705,7 @@ namespace Emby.Server.Implementations.Library
             }
 
             // Add in the plug-in folders
-            var path = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "playlists");
+            var path = Path.Combine(_configurationManager.ApplicationPaths.DataPath, "playlists");
 
             Directory.CreateDirectory(path);
 
@@ -786,7 +757,7 @@ namespace Emby.Server.Implementations.Library
                 {
                     if (_userRootFolder == null)
                     {
-                        var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+                        var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
 
                         _logger.LogDebug("Creating userRootPath at {path}", userRootPath);
                         Directory.CreateDirectory(userRootPath);
@@ -980,7 +951,7 @@ namespace Emby.Server.Implementations.Library
               where T : BaseItem, new()
         {
             var path = getPathFn(name);
-            var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds;
+            var forceCaseInsensitiveId = _configurationManager.Configuration.EnableNormalizedItemByNameIds;
             return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
         }
 
@@ -994,7 +965,7 @@ namespace Emby.Server.Implementations.Library
         public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
         {
             // Ensure the location is available.
-            Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
+            Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath);
 
             return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress);
         }
@@ -1031,7 +1002,7 @@ namespace Emby.Server.Implementations.Library
         public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
         {
             IsScanRunning = true;
-            _libraryMonitorFactory().Stop();
+            LibraryMonitor.Stop();
 
             try
             {
@@ -1039,7 +1010,7 @@ namespace Emby.Server.Implementations.Library
             }
             finally
             {
-                _libraryMonitorFactory().Start();
+                LibraryMonitor.Start();
                 IsScanRunning = false;
             }
         }
@@ -1148,7 +1119,7 @@ namespace Emby.Server.Implementations.Library
                 progress.Report(percent * 100);
             }
 
-            ItemRepository.UpdateInheritedValues(cancellationToken);
+            _itemRepository.UpdateInheritedValues(cancellationToken);
 
             progress.Report(100);
         }
@@ -1168,9 +1139,9 @@ namespace Emby.Server.Implementations.Library
             var topLibraryFolders = GetUserRootFolder().Children.ToList();
 
             _logger.LogDebug("Getting refreshQueue");
-            var refreshQueue = includeRefreshState ? _providerManagerFactory().GetRefreshQueue() : null;
+            var refreshQueue = includeRefreshState ? ProviderManager.GetRefreshQueue() : null;
 
-            return _fileSystem.GetDirectoryPaths(ConfigurationManager.ApplicationPaths.DefaultUserViewsPath)
+            return _fileSystem.GetDirectoryPaths(_configurationManager.ApplicationPaths.DefaultUserViewsPath)
                 .Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders, refreshQueue))
                 .ToList();
         }
@@ -1245,7 +1216,7 @@ namespace Emby.Server.Implementations.Library
                 throw new ArgumentException("Guid can't be empty", nameof(id));
             }
 
-            if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
+            if (_libraryItemsCache.TryGetValue(id, out BaseItem item))
             {
                 return item;
             }
@@ -1276,7 +1247,7 @@ namespace Emby.Server.Implementations.Library
                 AddUserToQuery(query, query.User, allowExternalContent);
             }
 
-            return ItemRepository.GetItemList(query);
+            return _itemRepository.GetItemList(query);
         }
 
         public List<BaseItem> GetItemList(InternalItemsQuery query)
@@ -1300,7 +1271,7 @@ namespace Emby.Server.Implementations.Library
                 AddUserToQuery(query, query.User);
             }
 
-            return ItemRepository.GetCount(query);
+            return _itemRepository.GetCount(query);
         }
 
         public List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
@@ -1315,7 +1286,7 @@ namespace Emby.Server.Implementations.Library
                 }
             }
 
-            return ItemRepository.GetItemList(query);
+            return _itemRepository.GetItemList(query);
         }
 
         public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
@@ -1327,12 +1298,12 @@ namespace Emby.Server.Implementations.Library
 
             if (query.EnableTotalRecordCount)
             {
-                return ItemRepository.GetItems(query);
+                return _itemRepository.GetItems(query);
             }
 
             return new QueryResult<BaseItem>
             {
-                Items = ItemRepository.GetItemList(query).ToArray()
+                Items = _itemRepository.GetItemList(query).ToArray()
             };
         }
 
@@ -1343,7 +1314,7 @@ namespace Emby.Server.Implementations.Library
                 AddUserToQuery(query, query.User);
             }
 
-            return ItemRepository.GetItemIdsList(query);
+            return _itemRepository.GetItemIdsList(query);
         }
 
         public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
@@ -1354,7 +1325,7 @@ namespace Emby.Server.Implementations.Library
             }
 
             SetTopParentOrAncestorIds(query);
-            return ItemRepository.GetStudios(query);
+            return _itemRepository.GetStudios(query);
         }
 
         public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
@@ -1365,7 +1336,7 @@ namespace Emby.Server.Implementations.Library
             }
 
             SetTopParentOrAncestorIds(query);
-            return ItemRepository.GetGenres(query);
+            return _itemRepository.GetGenres(query);
         }
 
         public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
@@ -1376,7 +1347,7 @@ namespace Emby.Server.Implementations.Library
             }
 
             SetTopParentOrAncestorIds(query);
-            return ItemRepository.GetMusicGenres(query);
+            return _itemRepository.GetMusicGenres(query);
         }
 
         public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
@@ -1387,7 +1358,7 @@ namespace Emby.Server.Implementations.Library
             }
 
             SetTopParentOrAncestorIds(query);
-            return ItemRepository.GetAllArtists(query);
+            return _itemRepository.GetAllArtists(query);
         }
 
         public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
@@ -1398,7 +1369,7 @@ namespace Emby.Server.Implementations.Library
             }
 
             SetTopParentOrAncestorIds(query);
-            return ItemRepository.GetArtists(query);
+            return _itemRepository.GetArtists(query);
         }
 
         private void SetTopParentOrAncestorIds(InternalItemsQuery query)
@@ -1439,7 +1410,7 @@ namespace Emby.Server.Implementations.Library
             }
 
             SetTopParentOrAncestorIds(query);
-            return ItemRepository.GetAlbumArtists(query);
+            return _itemRepository.GetAlbumArtists(query);
         }
 
         public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
@@ -1460,10 +1431,10 @@ namespace Emby.Server.Implementations.Library
 
             if (query.EnableTotalRecordCount)
             {
-                return ItemRepository.GetItems(query);
+                return _itemRepository.GetItems(query);
             }
 
-            var list = ItemRepository.GetItemList(query);
+            var list = _itemRepository.GetItemList(query);
 
             return new QueryResult<BaseItem>
             {
@@ -1509,7 +1480,7 @@ namespace Emby.Server.Implementations.Library
                 string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) &&
                 query.ItemIds.Length == 0)
             {
-                var userViews = _userviewManager().GetUserViews(new UserViewQuery
+                var userViews = UserViewManager.GetUserViews(new UserViewQuery
                 {
                     UserId = user.Id,
                     IncludeHidden = true,
@@ -1809,7 +1780,7 @@ namespace Emby.Server.Implementations.Library
             // Don't iterate multiple times
             var itemsList = items.ToList();
 
-            ItemRepository.SaveItems(itemsList, cancellationToken);
+            _itemRepository.SaveItems(itemsList, cancellationToken);
 
             foreach (var item in itemsList)
             {
@@ -1846,7 +1817,7 @@ namespace Emby.Server.Implementations.Library
 
         public void UpdateImages(BaseItem item)
         {
-            ItemRepository.SaveImages(item);
+            _itemRepository.SaveImages(item);
 
             RegisterItem(item);
         }
@@ -1863,7 +1834,7 @@ namespace Emby.Server.Implementations.Library
             {
                 if (item.IsFileProtocol)
                 {
-                    _providerManagerFactory().SaveMetadata(item, updateReason);
+                    ProviderManager.SaveMetadata(item, updateReason);
                 }
 
                 item.DateLastSaved = DateTime.UtcNow;
@@ -1871,7 +1842,7 @@ namespace Emby.Server.Implementations.Library
                 RegisterItem(item);
             }
 
-            ItemRepository.SaveItems(itemsList, cancellationToken);
+            _itemRepository.SaveItems(itemsList, cancellationToken);
 
             if (ItemUpdated != null)
             {
@@ -1947,7 +1918,7 @@ namespace Emby.Server.Implementations.Library
         /// <returns>BaseItem.</returns>
         public BaseItem RetrieveItem(Guid id)
         {
-            return ItemRepository.RetrieveItem(id);
+            return _itemRepository.RetrieveItem(id);
         }
 
         public List<Folder> GetCollectionFolders(BaseItem item)
@@ -2066,7 +2037,7 @@ namespace Emby.Server.Implementations.Library
 
         private string GetContentTypeOverride(string path, bool inherit)
         {
-            var nameValuePair = ConfigurationManager.Configuration.ContentTypes
+            var nameValuePair = _configurationManager.Configuration.ContentTypes
                                     .FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path)
                                                          || (inherit && !string.IsNullOrEmpty(i.Name)
                                                                      && _fileSystem.ContainsSubPath(i.Name, path)));
@@ -2115,7 +2086,7 @@ namespace Emby.Server.Implementations.Library
             string sortName)
         {
             var path = Path.Combine(
-                ConfigurationManager.ApplicationPaths.InternalMetadataPath,
+                _configurationManager.ApplicationPaths.InternalMetadataPath,
                 "views",
                 _fileSystem.GetValidFilename(viewType));
 
@@ -2147,7 +2118,7 @@ namespace Emby.Server.Implementations.Library
             if (refresh)
             {
                 item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
-                _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
+                ProviderManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
             }
 
             return item;
@@ -2165,7 +2136,7 @@ namespace Emby.Server.Implementations.Library
 
             var id = GetNewItemId(idValues, typeof(UserView));
 
-            var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
+            var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
 
             var item = GetItemById(id) as UserView;
 
@@ -2202,7 +2173,7 @@ namespace Emby.Server.Implementations.Library
 
             if (refresh)
             {
-                _providerManagerFactory().QueueRefresh(
+                ProviderManager.QueueRefresh(
                     item.Id,
                     new MetadataRefreshOptions(new DirectoryService(_fileSystem))
                     {
@@ -2269,7 +2240,7 @@ namespace Emby.Server.Implementations.Library
 
             if (refresh)
             {
-                _providerManagerFactory().QueueRefresh(
+                ProviderManager.QueueRefresh(
                     item.Id,
                     new MetadataRefreshOptions(new DirectoryService(_fileSystem))
                     {
@@ -2303,7 +2274,7 @@ namespace Emby.Server.Implementations.Library
 
             var id = GetNewItemId(idValues, typeof(UserView));
 
-            var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
+            var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
 
             var item = GetItemById(id) as UserView;
 
@@ -2346,7 +2317,7 @@ namespace Emby.Server.Implementations.Library
 
             if (refresh)
             {
-                _providerManagerFactory().QueueRefresh(
+                ProviderManager.QueueRefresh(
                     item.Id,
                     new MetadataRefreshOptions(new DirectoryService(_fileSystem))
                     {
@@ -2675,8 +2646,8 @@ namespace Emby.Server.Implementations.Library
                 }
             }
 
-            var metadataPath = ConfigurationManager.Configuration.MetadataPath;
-            var metadataNetworkPath = ConfigurationManager.Configuration.MetadataNetworkPath;
+            var metadataPath = _configurationManager.Configuration.MetadataPath;
+            var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath;
 
             if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath))
             {
@@ -2687,7 +2658,7 @@ namespace Emby.Server.Implementations.Library
                 }
             }
 
-            foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
+            foreach (var map in _configurationManager.Configuration.PathSubstitutions)
             {
                 if (!string.IsNullOrWhiteSpace(map.From))
                 {
@@ -2756,7 +2727,7 @@ namespace Emby.Server.Implementations.Library
 
         public List<PersonInfo> GetPeople(InternalPeopleQuery query)
         {
-            return ItemRepository.GetPeople(query);
+            return _itemRepository.GetPeople(query);
         }
 
         public List<PersonInfo> GetPeople(BaseItem item)
@@ -2779,7 +2750,7 @@ namespace Emby.Server.Implementations.Library
 
         public List<Person> GetPeopleItems(InternalPeopleQuery query)
         {
-            return ItemRepository.GetPeopleNames(query).Select(i =>
+            return _itemRepository.GetPeopleNames(query).Select(i =>
             {
                 try
                 {
@@ -2796,7 +2767,7 @@ namespace Emby.Server.Implementations.Library
 
         public List<string> GetPeopleNames(InternalPeopleQuery query)
         {
-            return ItemRepository.GetPeopleNames(query);
+            return _itemRepository.GetPeopleNames(query);
         }
 
         public void UpdatePeople(BaseItem item, List<PersonInfo> people)
@@ -2806,7 +2777,7 @@ namespace Emby.Server.Implementations.Library
                 return;
             }
 
-            ItemRepository.UpdatePeople(item.Id, people);
+            _itemRepository.UpdatePeople(item.Id, people);
         }
 
         public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
@@ -2817,7 +2788,7 @@ namespace Emby.Server.Implementations.Library
                 {
                     _logger.LogDebug("ConvertImageToLocal item {0} - image url: {1}", item.Id, url);
 
-                    await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
+                    await ProviderManager.SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
 
                     item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
 
@@ -2850,7 +2821,7 @@ namespace Emby.Server.Implementations.Library
 
             name = _fileSystem.GetValidFilename(name);
 
-            var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+            var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
 
             var virtualFolderPath = Path.Combine(rootFolderPath, name);
             while (Directory.Exists(virtualFolderPath))
@@ -2869,7 +2840,7 @@ namespace Emby.Server.Implementations.Library
                 }
             }
 
-            _libraryMonitorFactory().Stop();
+            LibraryMonitor.Stop();
 
             try
             {
@@ -2904,7 +2875,7 @@ namespace Emby.Server.Implementations.Library
                 {
                     // Need to add a delay here or directory watchers may still pick up the changes
                     await Task.Delay(1000).ConfigureAwait(false);
-                    _libraryMonitorFactory().Start();
+                    LibraryMonitor.Start();
                 }
             }
         }
@@ -2964,7 +2935,7 @@ namespace Emby.Server.Implementations.Library
                 throw new FileNotFoundException("The network path does not exist.");
             }
 
-            var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+            var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
             var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
 
             var shortcutFilename = Path.GetFileNameWithoutExtension(path);
@@ -3007,7 +2978,7 @@ namespace Emby.Server.Implementations.Library
                 throw new FileNotFoundException("The network path does not exist.");
             }
 
-            var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+            var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
             var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
 
             var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
@@ -3060,7 +3031,7 @@ namespace Emby.Server.Implementations.Library
                 throw new ArgumentNullException(nameof(name));
             }
 
-            var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+            var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
 
             var path = Path.Combine(rootFolderPath, name);
 
@@ -3069,7 +3040,7 @@ namespace Emby.Server.Implementations.Library
                 throw new FileNotFoundException("The media folder does not exist");
             }
 
-            _libraryMonitorFactory().Stop();
+            LibraryMonitor.Stop();
 
             try
             {
@@ -3089,7 +3060,7 @@ namespace Emby.Server.Implementations.Library
                 {
                     // Need to add a delay here or directory watchers may still pick up the changes
                     await Task.Delay(1000).ConfigureAwait(false);
-                    _libraryMonitorFactory().Start();
+                    LibraryMonitor.Start();
                 }
             }
         }
@@ -3103,7 +3074,7 @@ namespace Emby.Server.Implementations.Library
 
             var removeList = new List<NameValuePair>();
 
-            foreach (var contentType in ConfigurationManager.Configuration.ContentTypes)
+            foreach (var contentType in _configurationManager.Configuration.ContentTypes)
             {
                 if (string.IsNullOrWhiteSpace(contentType.Name))
                 {
@@ -3118,11 +3089,11 @@ namespace Emby.Server.Implementations.Library
 
             if (removeList.Count > 0)
             {
-                ConfigurationManager.Configuration.ContentTypes = ConfigurationManager.Configuration.ContentTypes
+                _configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes
                     .Except(removeList)
-                        .ToArray();
+                    .ToArray();
 
-                ConfigurationManager.SaveConfiguration();
+                _configurationManager.SaveConfiguration();
             }
         }
 
@@ -3133,7 +3104,7 @@ namespace Emby.Server.Implementations.Library
                 throw new ArgumentNullException(nameof(mediaPath));
             }
 
-            var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+            var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
             var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
 
             if (!Directory.Exists(virtualFolderPath))

+ 11 - 11
Emby.Server.Implementations/Library/MediaSourceManager.cs

@@ -33,13 +33,13 @@ namespace Emby.Server.Implementations.Library
         private readonly ILibraryManager _libraryManager;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IFileSystem _fileSystem;
-
-        private IMediaSourceProvider[] _providers;
         private readonly ILogger _logger;
         private readonly IUserDataManager _userDataManager;
-        private readonly Func<IMediaEncoder> _mediaEncoder;
-        private ILocalizationManager _localizationManager;
-        private IApplicationPaths _appPaths;
+        private readonly IMediaEncoder _mediaEncoder;
+        private readonly ILocalizationManager _localizationManager;
+        private readonly IApplicationPaths _appPaths;
+
+        private IMediaSourceProvider[] _providers;
 
         public MediaSourceManager(
             IItemRepository itemRepo,
@@ -47,16 +47,16 @@ namespace Emby.Server.Implementations.Library
             ILocalizationManager localizationManager,
             IUserManager userManager,
             ILibraryManager libraryManager,
-            ILoggerFactory loggerFactory,
+            ILogger<MediaSourceManager> logger,
             IJsonSerializer jsonSerializer,
             IFileSystem fileSystem,
             IUserDataManager userDataManager,
-            Func<IMediaEncoder> mediaEncoder)
+            IMediaEncoder mediaEncoder)
         {
             _itemRepo = itemRepo;
             _userManager = userManager;
             _libraryManager = libraryManager;
-            _logger = loggerFactory.CreateLogger(nameof(MediaSourceManager));
+            _logger = logger;
             _jsonSerializer = jsonSerializer;
             _fileSystem = fileSystem;
             _userDataManager = userDataManager;
@@ -496,7 +496,7 @@ namespace Emby.Server.Implementations.Library
                     // hack - these two values were taken from LiveTVMediaSourceProvider
                     string cacheKey = request.OpenToken;
 
-                    await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths)
+                    await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _appPaths)
                         .AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
                         .ConfigureAwait(false);
                 }
@@ -621,7 +621,7 @@ namespace Emby.Server.Implementations.Library
 
             if (liveStreamInfo is IDirectStreamProvider)
             {
-                var info = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest
+                var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
                 {
                     MediaSource = mediaSource,
                     ExtractChapters = false,
@@ -674,7 +674,7 @@ namespace Emby.Server.Implementations.Library
                     mediaSource.AnalyzeDurationMs = 3000;
                 }
 
-                mediaInfo = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest
+                mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
                 {
                     MediaSource = mediaSource,
                     MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,

+ 3 - 4
Emby.Server.Implementations/Library/SearchEngine.cs

@@ -17,16 +17,15 @@ namespace Emby.Server.Implementations.Library
 {
     public class SearchEngine : ISearchEngine
     {
+        private readonly ILogger _logger;
         private readonly ILibraryManager _libraryManager;
         private readonly IUserManager _userManager;
-        private readonly ILogger _logger;
 
-        public SearchEngine(ILoggerFactory loggerFactory, ILibraryManager libraryManager, IUserManager userManager)
+        public SearchEngine(ILogger<SearchEngine> logger, ILibraryManager libraryManager, IUserManager userManager)
         {
+            _logger = logger;
             _libraryManager = libraryManager;
             _userManager = userManager;
-
-            _logger = loggerFactory.CreateLogger("SearchEngine");
         }
 
         public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)

+ 18 - 19
Emby.Server.Implementations/Library/UserDataManager.cs

@@ -28,25 +28,24 @@ namespace Emby.Server.Implementations.Library
 
         private readonly ILogger _logger;
         private readonly IServerConfigurationManager _config;
-
-        private Func<IUserManager> _userManager;
-
-        public UserDataManager(ILoggerFactory loggerFactory, IServerConfigurationManager config, Func<IUserManager> userManager)
+        private readonly IUserManager _userManager;
+        private readonly IUserDataRepository _repository;
+
+        public UserDataManager(
+            ILogger<UserDataManager> logger,
+            IServerConfigurationManager config,
+            IUserManager userManager,
+            IUserDataRepository repository)
         {
+            _logger = logger;
             _config = config;
-            _logger = loggerFactory.CreateLogger(GetType().Name);
             _userManager = userManager;
+            _repository = repository;
         }
 
-        /// <summary>
-        /// Gets or sets the repository.
-        /// </summary>
-        /// <value>The repository.</value>
-        public IUserDataRepository Repository { get; set; }
-
         public void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
         {
-            var user = _userManager().GetUserById(userId);
+            var user = _userManager.GetUserById(userId);
 
             SaveUserData(user, item, userData, reason, cancellationToken);
         }
@@ -71,7 +70,7 @@ namespace Emby.Server.Implementations.Library
 
             foreach (var key in keys)
             {
-                Repository.SaveUserData(userId, key, userData, cancellationToken);
+                _repository.SaveUserData(userId, key, userData, cancellationToken);
             }
 
             var cacheKey = GetCacheKey(userId, item.Id);
@@ -96,9 +95,9 @@ namespace Emby.Server.Implementations.Library
         /// <returns></returns>
         public void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken)
         {
-            var user = _userManager().GetUserById(userId);
+            var user = _userManager.GetUserById(userId);
 
-            Repository.SaveAllUserData(user.InternalId, userData, cancellationToken);
+            _repository.SaveAllUserData(user.InternalId, userData, cancellationToken);
         }
 
         /// <summary>
@@ -108,14 +107,14 @@ namespace Emby.Server.Implementations.Library
         /// <returns></returns>
         public List<UserItemData> GetAllUserData(Guid userId)
         {
-            var user = _userManager().GetUserById(userId);
+            var user = _userManager.GetUserById(userId);
 
-            return Repository.GetAllUserData(user.InternalId);
+            return _repository.GetAllUserData(user.InternalId);
         }
 
         public UserItemData GetUserData(Guid userId, Guid itemId, List<string> keys)
         {
-            var user = _userManager().GetUserById(userId);
+            var user = _userManager.GetUserById(userId);
 
             return GetUserData(user, itemId, keys);
         }
@@ -131,7 +130,7 @@ namespace Emby.Server.Implementations.Library
 
         private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
         {
-            var userData = Repository.GetUserData(internalUserId, keys);
+            var userData = _repository.GetUserData(internalUserId, keys);
 
             if (userData != null)
             {

+ 14 - 23
Emby.Server.Implementations/Library/UserManager.cs

@@ -20,6 +20,7 @@ using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Providers;
@@ -44,22 +45,14 @@ namespace Emby.Server.Implementations.Library
     {
         private readonly object _policySyncLock = new object();
         private readonly object _configSyncLock = new object();
-        /// <summary>
-        /// The logger.
-        /// </summary>
-        private readonly ILogger _logger;
 
-        /// <summary>
-        /// Gets the active user repository.
-        /// </summary>
-        /// <value>The user repository.</value>
+        private readonly ILogger _logger;
         private readonly IUserRepository _userRepository;
         private readonly IXmlSerializer _xmlSerializer;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly INetworkManager _networkManager;
-
-        private readonly Func<IImageProcessor> _imageProcessorFactory;
-        private readonly Func<IDtoService> _dtoServiceFactory;
+        private readonly IImageProcessor _imageProcessor;
+        private readonly Lazy<IDtoService> _dtoServiceFactory;
         private readonly IServerApplicationHost _appHost;
         private readonly IFileSystem _fileSystem;
         private readonly ICryptoProvider _cryptoProvider;
@@ -74,13 +67,15 @@ namespace Emby.Server.Implementations.Library
         private IPasswordResetProvider[] _passwordResetProviders;
         private DefaultPasswordResetProvider _defaultPasswordResetProvider;
 
+        private IDtoService DtoService => _dtoServiceFactory.Value;
+
         public UserManager(
             ILogger<UserManager> logger,
             IUserRepository userRepository,
             IXmlSerializer xmlSerializer,
             INetworkManager networkManager,
-            Func<IImageProcessor> imageProcessorFactory,
-            Func<IDtoService> dtoServiceFactory,
+            IImageProcessor imageProcessor,
+            Lazy<IDtoService> dtoServiceFactory,
             IServerApplicationHost appHost,
             IJsonSerializer jsonSerializer,
             IFileSystem fileSystem,
@@ -90,7 +85,7 @@ namespace Emby.Server.Implementations.Library
             _userRepository = userRepository;
             _xmlSerializer = xmlSerializer;
             _networkManager = networkManager;
-            _imageProcessorFactory = imageProcessorFactory;
+            _imageProcessor = imageProcessor;
             _dtoServiceFactory = dtoServiceFactory;
             _appHost = appHost;
             _jsonSerializer = jsonSerializer;
@@ -327,23 +322,19 @@ namespace Emby.Server.Implementations.Library
             if (user.Policy.IsDisabled)
             {
                 _logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint);
-                throw new AuthenticationException(
-                    string.Format(
-                        CultureInfo.InvariantCulture,
-                        "The {0} account is currently disabled. Please consult with your administrator.",
-                        user.Name));
+                throw new SecurityException($"The {user.Name} account is currently disabled. Please consult with your administrator.");
             }
 
             if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
             {
                 _logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint);
-                throw new AuthenticationException("Forbidden.");
+                throw new SecurityException("Forbidden.");
             }
 
             if (!user.IsParentalScheduleAllowed())
             {
                 _logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint);
-                throw new AuthenticationException("User is not allowed access at this time.");
+                throw new SecurityException("User is not allowed access at this time.");
             }
 
             // Update LastActivityDate and LastLoginDate, then save
@@ -605,7 +596,7 @@ namespace Emby.Server.Implementations.Library
 
                 try
                 {
-                    _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user);
+                    DtoService.AttachPrimaryImageAspectRatio(dto, user);
                 }
                 catch (Exception ex)
                 {
@@ -630,7 +621,7 @@ namespace Emby.Server.Implementations.Library
         {
             try
             {
-                return _imageProcessorFactory().GetImageCacheTag(item, image);
+                return _imageProcessor.GetImageCacheTag(item, image);
             }
             catch (Exception ex)
             {

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

@@ -28,7 +28,6 @@ namespace Emby.Server.Implementations.LiveTv
 
         private readonly ILogger _logger;
         private readonly IImageProcessor _imageProcessor;
-
         private readonly IDtoService _dtoService;
         private readonly IApplicationHost _appHost;
         private readonly ILibraryManager _libraryManager;

+ 10 - 15
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -49,29 +49,24 @@ namespace Emby.Server.Implementations.LiveTv
         private readonly ILogger _logger;
         private readonly IItemRepository _itemRepo;
         private readonly IUserManager _userManager;
+        private readonly IDtoService _dtoService;
         private readonly IUserDataManager _userDataManager;
         private readonly ILibraryManager _libraryManager;
         private readonly ITaskManager _taskManager;
-        private readonly IJsonSerializer _jsonSerializer;
-        private readonly Func<IChannelManager> _channelManager;
-
-        private readonly IDtoService _dtoService;
         private readonly ILocalizationManager _localization;
-
+        private readonly IJsonSerializer _jsonSerializer;
+        private readonly IFileSystem _fileSystem;
+        private readonly IChannelManager _channelManager;
         private readonly LiveTvDtoService _tvDtoService;
 
         private ILiveTvService[] _services = Array.Empty<ILiveTvService>();
-
         private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
         private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
-        private readonly IFileSystem _fileSystem;
 
         public LiveTvManager(
-            IServerApplicationHost appHost,
             IServerConfigurationManager config,
-            ILoggerFactory loggerFactory,
+            ILogger<LiveTvManager> logger,
             IItemRepository itemRepo,
-            IImageProcessor imageProcessor,
             IUserDataManager userDataManager,
             IDtoService dtoService,
             IUserManager userManager,
@@ -80,10 +75,11 @@ namespace Emby.Server.Implementations.LiveTv
             ILocalizationManager localization,
             IJsonSerializer jsonSerializer,
             IFileSystem fileSystem,
-            Func<IChannelManager> channelManager)
+            IChannelManager channelManager,
+            LiveTvDtoService liveTvDtoService)
         {
             _config = config;
-            _logger = loggerFactory.CreateLogger(nameof(LiveTvManager));
+            _logger = logger;
             _itemRepo = itemRepo;
             _userManager = userManager;
             _libraryManager = libraryManager;
@@ -94,8 +90,7 @@ namespace Emby.Server.Implementations.LiveTv
             _dtoService = dtoService;
             _userDataManager = userDataManager;
             _channelManager = channelManager;
-
-            _tvDtoService = new LiveTvDtoService(dtoService, imageProcessor, loggerFactory.CreateLogger<LiveTvDtoService>(), appHost, _libraryManager);
+            _tvDtoService = liveTvDtoService;
         }
 
         public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
@@ -2496,7 +2491,7 @@ namespace Emby.Server.Implementations.LiveTv
                 .OrderBy(i => i.SortName)
                 .ToList();
 
-            folders.AddRange(_channelManager().GetChannelsInternal(new MediaBrowser.Model.Channels.ChannelQuery
+            folders.AddRange(_channelManager.GetChannelsInternal(new MediaBrowser.Model.Channels.ChannelQuery
             {
                 UserId = user.Id,
                 IsRecordingsFolder = true,

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

@@ -23,7 +23,7 @@
     "HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه",
     "HeaderFavoriteShows": "سریال‌های مورد علاقه",
     "HeaderFavoriteSongs": "آهنگ‌های مورد علاقه",
-    "HeaderLiveTV": "تلویزیون زنده",
+    "HeaderLiveTV": "پخش زنده",
     "HeaderNextUp": "قسمت بعدی",
     "HeaderRecordingGroups": "گروه‌های ضبط",
     "HomeVideos": "ویدیوهای خانگی",

+ 6 - 5
Emby.Server.Implementations/Localization/Core/ja.json

@@ -104,13 +104,14 @@
     "TasksMaintenanceCategory": "メンテナンス",
     "TaskRefreshChannelsDescription": "ネットチャンネルの情報をリフレッシュします。",
     "TaskRefreshChannels": "チャンネルのリフレッシュ",
-    "TaskCleanTranscodeDescription": "一日以上前のトランスコードを消去します。",
-    "TaskCleanTranscode": "トランスコード用のディレクトリの掃除",
+    "TaskCleanTranscodeDescription": "1日以上経過したトランスコードファイルを削除します。",
+    "TaskCleanTranscode": "トランスコードディレクトリの削除",
     "TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
     "TaskUpdatePlugins": "プラグインの更新",
-    "TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。",
-    "TaskRefreshPeople": "俳優や監督のデータのリフレッシュ",
+    "TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータを更新します。",
+    "TaskRefreshPeople": "俳優や監督のデータの更新",
     "TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
     "TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
-    "TaskRefreshChapterImages": "チャプター画像を抽出する"
+    "TaskRefreshChapterImages": "チャプター画像を抽出する",
+    "TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする"
 }

+ 3 - 3
Emby.Server.Implementations/Localization/Core/nl.json

@@ -1,11 +1,11 @@
 {
     "Albums": "Albums",
     "AppDeviceValues": "App: {0}, Apparaat: {1}",
-    "Application": "Applicatie",
+    "Application": "Programma",
     "Artists": "Artiesten",
-    "AuthenticationSucceededWithUserName": "{0} succesvol geauthenticeerd",
+    "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
     "Books": "Boeken",
-    "CameraImageUploadedFrom": "Er is een nieuwe foto toegevoegd van {0}",
+    "CameraImageUploadedFrom": "Er is een nieuwe afbeelding toegevoegd via {0}",
     "Channels": "Kanalen",
     "ChapterNameValue": "Hoofdstuk {0}",
     "Collections": "Verzamelingen",

+ 24 - 2
Emby.Server.Implementations/Localization/Core/pt-PT.json

@@ -26,7 +26,7 @@
     "HeaderLiveTV": "TV em Direto",
     "HeaderNextUp": "A Seguir",
     "HeaderRecordingGroups": "Grupos de Gravação",
-    "HomeVideos": "Home videos",
+    "HomeVideos": "Videos caseiros",
     "Inherit": "Herdar",
     "ItemAddedWithName": "{0} foi adicionado à biblioteca",
     "ItemRemovedWithName": "{0} foi removido da biblioteca",
@@ -92,5 +92,27 @@
     "UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}",
     "ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia",
     "ValueSpecialEpisodeName": "Especial - {0}",
-    "VersionNumber": "Versão {0}"
+    "VersionNumber": "Versão {0}",
+    "TaskDownloadMissingSubtitlesDescription": "Procurar na internet por legendas em falta baseado na configuração de metadados.",
+    "TaskDownloadMissingSubtitles": "Fazer download de legendas em falta",
+    "TaskRefreshChannelsDescription": "Atualizar informação sobre canais da Internet.",
+    "TaskRefreshChannels": "Atualizar Canais",
+    "TaskCleanTranscodeDescription": "Apagar ficheiros de transcode com mais de um dia.",
+    "TaskCleanTranscode": "Limpar a Diretoria de Transcode",
+    "TaskUpdatePluginsDescription": "Faz o download e instala updates para os plugins que estão configurados para atualizar automaticamente.",
+    "TaskUpdatePlugins": "Atualizar Plugins",
+    "TaskRefreshPeopleDescription": "Atualizar metadados para atores e diretores na biblioteca.",
+    "TaskRefreshPeople": "Atualizar Pessoas",
+    "TaskCleanLogsDescription": "Apagar ficheiros de log que têm mais de {0} dias.",
+    "TaskCleanLogs": "Limpar a Diretoria de Logs",
+    "TaskRefreshLibraryDescription": "Scannear a biblioteca de música para novos ficheiros e atualizar os metadados.",
+    "TaskRefreshLibrary": "Scannear Biblioteca de Música",
+    "TaskRefreshChapterImagesDescription": "Criar thumbnails para os vídeos que têm capítulos.",
+    "TaskRefreshChapterImages": "Extrair Imagens dos Capítulos",
+    "TaskCleanCacheDescription": "Apagar ficheiros em cache que já não são necessários.",
+    "TaskCleanCache": "Limpar Cache",
+    "TasksChannelsCategory": "Canais da Internet",
+    "TasksApplicationCategory": "Aplicação",
+    "TasksLibraryCategory": "Biblioteca",
+    "TasksMaintenanceCategory": "Manutenção"
 }

+ 20 - 3
Emby.Server.Implementations/Localization/Core/tr.json

@@ -50,7 +50,7 @@
     "NotificationOptionAudioPlayback": "Ses çalma başladı",
     "NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu",
     "NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi",
-    "NotificationOptionInstallationFailed": "Yükleme başarısız oldu",
+    "NotificationOptionInstallationFailed": "Kurulum hatası",
     "NotificationOptionNewLibraryContent": "Yeni içerik eklendi",
     "NotificationOptionPluginError": "Eklenti hatası",
     "NotificationOptionPluginInstalled": "Eklenti yüklendi",
@@ -95,7 +95,24 @@
     "VersionNumber": "Versiyon {0}",
     "TaskCleanCache": "Geçici dosya klasörünü temizle",
     "TasksChannelsCategory": "İnternet kanalları",
-    "TasksApplicationCategory": "Yazılım",
+    "TasksApplicationCategory": "Uygulama",
     "TasksLibraryCategory": "Kütüphane",
-    "TasksMaintenanceCategory": "Onarım"
+    "TasksMaintenanceCategory": "Onarım",
+    "TaskRefreshPeopleDescription": "Medya kütüphanenizdeki videoların oyuncu ve yönetmen bilgilerini günceller.",
+    "TaskDownloadMissingSubtitlesDescription": "Metadata ayarlarını baz alarak eksik altyazıları internette arar.",
+    "TaskDownloadMissingSubtitles": "Eksik altyazıları indir",
+    "TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.",
+    "TaskRefreshChannels": "Kanalları Yenile",
+    "TaskCleanTranscodeDescription": "Bir günü dolmuş dönüştürme bilgisi içeren dosyaları siler.",
+    "TaskCleanTranscode": "Dönüşüm Dizinini Temizle",
+    "TaskUpdatePluginsDescription": "Otomatik güncellenmeye ayarlanmış eklentilerin güncellemelerini indirir ve kurar.",
+    "TaskUpdatePlugins": "Eklentileri Güncelle",
+    "TaskRefreshPeople": "Kullanıcıları Yenile",
+    "TaskCleanLogsDescription": "{0} günden eski log dosyalarını siler.",
+    "TaskCleanLogs": "Log Dizinini Temizle",
+    "TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve bilgileri yeniler.",
+    "TaskRefreshLibrary": "Medya Kütüphanesini Tara",
+    "TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.",
+    "TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar",
+    "TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler."
 }

+ 28 - 6
Emby.Server.Implementations/Localization/Core/zh-TW.json

@@ -50,10 +50,10 @@
     "NotificationOptionCameraImageUploaded": "相機相片已上傳",
     "NotificationOptionInstallationFailed": "安裝失敗",
     "NotificationOptionNewLibraryContent": "已新增新內容",
-    "NotificationOptionPluginError": "擴充元件錯誤",
-    "NotificationOptionPluginInstalled": "擴充元件已安裝",
-    "NotificationOptionPluginUninstalled": "擴充元件已移除",
-    "NotificationOptionPluginUpdateInstalled": "已更新擴充元件",
+    "NotificationOptionPluginError": "插件安裝錯誤",
+    "NotificationOptionPluginInstalled": "件已安裝",
+    "NotificationOptionPluginUninstalled": "件已移除",
+    "NotificationOptionPluginUpdateInstalled": "插件已更新",
     "NotificationOptionServerRestartRequired": "伺服器需要重新啟動",
     "NotificationOptionTaskFailed": "排程任務失敗",
     "NotificationOptionUserLockedOut": "使用者已鎖定",
@@ -61,7 +61,7 @@
     "NotificationOptionVideoPlaybackStopped": "影片停止播放",
     "Photos": "相片",
     "Playlists": "播放清單",
-    "Plugin": "外掛",
+    "Plugin": "插件",
     "PluginInstalledWithName": "{0} 已安裝",
     "PluginUninstalledWithName": "{0} 已移除",
     "PluginUpdatedWithName": "{0} 已更新",
@@ -91,5 +91,27 @@
     "VersionNumber": "版本 {0}",
     "HeaderRecordingGroups": "錄製組",
     "Inherit": "繼承",
-    "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕"
+    "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕",
+    "TaskDownloadMissingSubtitlesDescription": "在網路上透過描述資料搜尋遺失的字幕。",
+    "TaskDownloadMissingSubtitles": "下載遺失的字幕",
+    "TaskRefreshChannels": "重新整理頻道",
+    "TaskUpdatePlugins": "更新插件",
+    "TaskRefreshPeople": "重新整理人員",
+    "TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔案。",
+    "TaskCleanLogs": "清空紀錄資料夾",
+    "TaskRefreshLibraryDescription": "掃描媒體庫內新的檔案並重新整理描述資料。",
+    "TaskRefreshLibrary": "掃描媒體庫",
+    "TaskRefreshChapterImages": "擷取章節圖片",
+    "TaskCleanCacheDescription": "刪除系統長時間不需要的快取。",
+    "TaskCleanCache": "清除快取資料夾",
+    "TasksLibraryCategory": "媒體庫",
+    "TaskRefreshChannelsDescription": "重新整理網絡頻道資料。",
+    "TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。",
+    "TaskCleanTranscode": "清除轉碼資料夾",
+    "TaskUpdatePluginsDescription": "下載並安裝配置為自動更新的插件的更新。",
+    "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的中繼資料。",
+    "TaskRefreshChapterImagesDescription": "為有章節的視頻創建縮圖。",
+    "TasksChannelsCategory": "網絡頻道",
+    "TasksApplicationCategory": "應用程式",
+    "TasksMaintenanceCategory": "維修"
 }

+ 0 - 3
Emby.Server.Implementations/Localization/LocalizationManager.cs

@@ -23,9 +23,6 @@ namespace Emby.Server.Implementations.Localization
         private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
         private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
 
-        /// <summary>
-        /// The _configuration manager.
-        /// </summary>
         private readonly IServerConfigurationManager _configurationManager;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly ILogger _logger;

+ 3 - 17
Emby.Server.Implementations/ScheduledTasks/TaskManager.cs

@@ -32,22 +32,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
         private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue =
             new ConcurrentQueue<Tuple<Type, TaskOptions>>();
 
-        /// <summary>
-        /// Gets or sets the json serializer.
-        /// </summary>
-        /// <value>The json serializer.</value>
         private readonly IJsonSerializer _jsonSerializer;
-
-        /// <summary>
-        /// Gets or sets the application paths.
-        /// </summary>
-        /// <value>The application paths.</value>
         private readonly IApplicationPaths _applicationPaths;
-
-        /// <summary>
-        /// Gets the logger.
-        /// </summary>
-        /// <value>The logger.</value>
         private readonly ILogger _logger;
         private readonly IFileSystem _fileSystem;
 
@@ -56,17 +42,17 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// </summary>
         /// <param name="applicationPaths">The application paths.</param>
         /// <param name="jsonSerializer">The json serializer.</param>
-        /// <param name="loggerFactory">The logger factory.</param>
+        /// <param name="logger">The logger.</param>
         /// <param name="fileSystem">The filesystem manager.</param>
         public TaskManager(
             IApplicationPaths applicationPaths,
             IJsonSerializer jsonSerializer,
-            ILoggerFactory loggerFactory,
+            ILogger<TaskManager> logger,
             IFileSystem fileSystem)
         {
             _applicationPaths = applicationPaths;
             _jsonSerializer = jsonSerializer;
-            _logger = loggerFactory.CreateLogger(nameof(TaskManager));
+            _logger = logger;
             _fileSystem = fileSystem;
 
             ScheduledTasks = Array.Empty<IScheduledTaskWorker>();

+ 2 - 2
Emby.Server.Implementations/Security/AuthenticationRepository.cs

@@ -15,8 +15,8 @@ namespace Emby.Server.Implementations.Security
 {
     public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository
     {
-        public AuthenticationRepository(ILoggerFactory loggerFactory, IServerConfigurationManager config)
-            : base(loggerFactory.CreateLogger(nameof(AuthenticationRepository)))
+        public AuthenticationRepository(ILogger<AuthenticationRepository> logger, IServerConfigurationManager config)
+            : base(logger)
         {
             DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db");
         }

+ 1 - 1
Emby.Server.Implementations/Session/SessionManager.cs

@@ -1414,7 +1414,7 @@ namespace Emby.Server.Implementations.Session
             if (user == null)
             {
                 AuthenticationFailed?.Invoke(this, new GenericEventArgs<AuthenticationRequest>(request));
-                throw new SecurityException("Invalid username or password entered.");
+                throw new AuthenticationException("Invalid username or password entered.");
             }
 
             if (!string.IsNullOrEmpty(request.DeviceId)

+ 35 - 42
Emby.Server.Implementations/Updates/InstallationManager.cs

@@ -26,7 +26,7 @@ using Microsoft.Extensions.Logging;
 namespace Emby.Server.Implementations.Updates
 {
     /// <summary>
-    /// Manages all install, uninstall and update operations (both plugins and system).
+    /// Manages all install, uninstall, and update operations for the system and individual plugins.
     /// </summary>
     public class InstallationManager : IInstallationManager
     {
@@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Updates
         public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
 
         /// <summary>
-        /// The _logger.
+        /// The logger.
         /// </summary>
         private readonly ILogger _logger;
         private readonly IApplicationPaths _appPaths;
@@ -112,10 +112,10 @@ namespace Emby.Server.Implementations.Updates
         public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
 
         /// <inheritdoc />
-        public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
+        public event EventHandler<GenericEventArgs<(IPlugin, VersionInfo)>> PluginUpdated;
 
         /// <inheritdoc />
-        public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
+        public event EventHandler<GenericEventArgs<VersionInfo>> PluginInstalled;
 
         /// <inheritdoc />
         public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
@@ -183,61 +183,56 @@ namespace Emby.Server.Implementations.Updates
         }
 
         /// <inheritdoc />
-        public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
-            IEnumerable<PackageVersionInfo> availableVersions,
-            Version minVersion = null,
-            PackageVersionClass classification = PackageVersionClass.Release)
+        public IEnumerable<VersionInfo> GetCompatibleVersions(
+            IEnumerable<VersionInfo> availableVersions,
+            Version minVersion = null)
         {
             var appVer = _applicationHost.ApplicationVersion;
             availableVersions = availableVersions
-                .Where(x => x.classification == classification
-                    && Version.Parse(x.requiredVersionStr) <= appVer);
+                .Where(x => Version.Parse(x.targetAbi) <= appVer);
 
             if (minVersion != null)
             {
-                availableVersions = availableVersions.Where(x => x.Version >= minVersion);
+                availableVersions = availableVersions.Where(x => x.version >= minVersion);
             }
 
-            return availableVersions.OrderByDescending(x => x.Version);
+            return availableVersions.OrderByDescending(x => x.version);
         }
 
         /// <inheritdoc />
-        public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
+        public IEnumerable<VersionInfo> GetCompatibleVersions(
             IEnumerable<PackageInfo> availablePackages,
             string name = null,
             Guid guid = default,
-            Version minVersion = null,
-            PackageVersionClass classification = PackageVersionClass.Release)
+            Version minVersion = null)
         {
             var package = FilterPackages(availablePackages, name, guid).FirstOrDefault();
 
-            // Package not found.
+            // Package not found in repository
             if (package == null)
             {
-                return Enumerable.Empty<PackageVersionInfo>();
+                return Enumerable.Empty<VersionInfo>();
             }
 
             return GetCompatibleVersions(
                 package.versions,
-                minVersion,
-                classification);
+                minVersion);
         }
 
         /// <inheritdoc />
-        public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
+        public async Task<IEnumerable<VersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
         {
             var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
             return GetAvailablePluginUpdates(catalog);
         }
 
-        private IEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
+        private IEnumerable<VersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
         {
             foreach (var plugin in _applicationHost.Plugins)
             {
-                var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version, _applicationHost.SystemUpdateLevel);
-                var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version);
-                if (version != null
-                    && !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase)))
+                var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version);
+                var version = compatibleversions.FirstOrDefault(y => y.version > plugin.Version);
+                if (version != null && !CompletedInstallations.Any(x => string.Equals(x.Guid, version.guid, StringComparison.OrdinalIgnoreCase)))
                 {
                     yield return version;
                 }
@@ -245,7 +240,7 @@ namespace Emby.Server.Implementations.Updates
         }
 
         /// <inheritdoc />
-        public async Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken)
+        public async Task InstallPackage(VersionInfo package, CancellationToken cancellationToken)
         {
             if (package == null)
             {
@@ -254,11 +249,9 @@ namespace Emby.Server.Implementations.Updates
 
             var installationInfo = new InstallationInfo
             {
-                Id = Guid.NewGuid(),
+                Guid = package.guid,
                 Name = package.name,
-                AssemblyGuid = package.guid,
-                UpdateClass = package.classification,
-                Version = package.versionStr
+                Version = package.version.ToString()
             };
 
             var innerCancellationTokenSource = new CancellationTokenSource();
@@ -276,7 +269,7 @@ namespace Emby.Server.Implementations.Updates
             var installationEventArgs = new InstallationEventArgs
             {
                 InstallationInfo = installationInfo,
-                PackageVersionInfo = package
+                VersionInfo = package
             };
 
             PackageInstalling?.Invoke(this, installationEventArgs);
@@ -301,7 +294,7 @@ namespace Emby.Server.Implementations.Updates
                     _currentInstallations.Remove(tuple);
                 }
 
-                _logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.versionStr);
+                _logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.version);
 
                 PackageInstallationCancelled?.Invoke(this, installationEventArgs);
 
@@ -337,7 +330,7 @@ namespace Emby.Server.Implementations.Updates
         /// <param name="package">The package.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns><see cref="Task" />.</returns>
-        private async Task InstallPackageInternal(PackageVersionInfo package, CancellationToken cancellationToken)
+        private async Task InstallPackageInternal(VersionInfo package, CancellationToken cancellationToken)
         {
             // Set last update time if we were installed before
             IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase))
@@ -349,26 +342,26 @@ namespace Emby.Server.Implementations.Updates
             // Do plugin-specific processing
             if (plugin == null)
             {
-                _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
+                _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.version);
 
-                PluginInstalled?.Invoke(this, new GenericEventArgs<PackageVersionInfo>(package));
+                PluginInstalled?.Invoke(this, new GenericEventArgs<VersionInfo>(package));
             }
             else
             {
-                _logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
+                _logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.version);
 
-                PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, PackageVersionInfo)>((plugin, package)));
+                PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, VersionInfo)>((plugin, package)));
             }
 
             _applicationHost.NotifyPendingRestart();
         }
 
-        private async Task PerformPackageInstallation(PackageVersionInfo package, CancellationToken cancellationToken)
+        private async Task PerformPackageInstallation(VersionInfo package, CancellationToken cancellationToken)
         {
-            var extension = Path.GetExtension(package.targetFilename);
+            var extension = Path.GetExtension(package.filename);
             if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
             {
-                _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.targetFilename);
+                _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.filename);
                 return;
             }
 
@@ -415,7 +408,7 @@ namespace Emby.Server.Implementations.Updates
         }
 
         /// <summary>
-        /// Uninstalls a plugin
+        /// Uninstalls a plugin.
         /// </summary>
         /// <param name="plugin">The plugin.</param>
         public void UninstallPlugin(IPlugin plugin)
@@ -473,7 +466,7 @@ namespace Emby.Server.Implementations.Updates
         {
             lock (_currentInstallationsLock)
             {
-                var install = _currentInstallations.Find(x => x.info.Id == id);
+                var install = _currentInstallations.Find(x => x.info.Guid == id.ToString());
                 if (install == default((InstallationInfo, CancellationTokenSource)))
                 {
                     return false;

+ 5 - 0
Jellyfin.Api/Jellyfin.Api.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{DFBEFB4C-DA19-4143-98B7-27320C7F7163}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>

+ 6 - 0
Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj

@@ -1,10 +1,16 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{154872D9-6C12-4007-96E3-8F70A58386CE}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <Nullable>enable</Nullable>
   </PropertyGroup>
 
   <ItemGroup>

+ 5 - 8
Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs

@@ -26,7 +26,7 @@ namespace Jellyfin.Drawing.Skia
             {
                 paint.Color = SKColor.Parse("#CC00A4DC");
                 paint.Style = SKPaintStyle.Fill;
-                canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint);
+                canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint);
             }
 
             using (var paint = new SKPaint())
@@ -39,16 +39,13 @@ namespace Jellyfin.Drawing.Skia
 
                 // or:
                 // var emojiChar = 0x1F680;
-                var text = "✔️";
-                var emojiChar = StringUtilities.GetUnicodeCharacterCode(text, SKTextEncoding.Utf32);
+                const string Text = "✔️";
+                var emojiChar = StringUtilities.GetUnicodeCharacterCode(Text, SKTextEncoding.Utf32);
 
                 // ask the font manager for a font with that character
-                var fontManager = SKFontManager.Default;
-                var emojiTypeface = fontManager.MatchCharacter(emojiChar);
+                paint.Typeface = SKFontManager.Default.MatchCharacter(emojiChar);
 
-                paint.Typeface = emojiTypeface;
-
-                canvas.DrawText(text, (float)x - 20, OffsetFromTopRightCorner + 12, paint);
+                canvas.DrawText(Text, (float)x - 20, OffsetFromTopRightCorner + 12, paint);
             }
         }
     }

+ 28 - 21
Jellyfin.Drawing.Skia/SkiaEncoder.cs

@@ -78,12 +78,21 @@ namespace Jellyfin.Drawing.Skia
             => new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
 
         /// <summary>
-        /// Test to determine if the native lib is available.
+        /// Check if the native lib is available.
         /// </summary>
-        public static void TestSkia()
+        /// <returns>True if the native lib is available, otherwise false.</returns>
+        public static bool IsNativeLibAvailable()
         {
-            // test an operation that requires the native library
-            SKPMColor.PreMultiply(SKColors.Black);
+            try
+            {
+                // test an operation that requires the native library
+                SKPMColor.PreMultiply(SKColors.Black);
+                return true;
+            }
+            catch (Exception)
+            {
+                return false;
+            }
         }
 
         private static bool IsTransparent(SKColor color)
@@ -205,11 +214,6 @@ namespace Jellyfin.Drawing.Skia
         /// <exception cref="SkiaCodecException">The file at the specified path could not be used to generate a codec.</exception>
         public ImageDimensions GetImageSize(string path)
         {
-            if (path == null)
-            {
-                throw new ArgumentNullException(nameof(path));
-            }
-
             if (!File.Exists(path))
             {
                 throw new FileNotFoundException("File not found", path);
@@ -297,7 +301,7 @@ namespace Jellyfin.Drawing.Skia
         /// <param name="orientation">The orientation of the image.</param>
         /// <param name="origin">The detected origin of the image.</param>
         /// <returns>The resulting bitmap of the image.</returns>
-        internal SKBitmap Decode(string path, bool forceCleanBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
+        internal SKBitmap? Decode(string path, bool forceCleanBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
         {
             if (!File.Exists(path))
             {
@@ -348,12 +352,17 @@ namespace Jellyfin.Drawing.Skia
             return resultBitmap;
         }
 
-        private SKBitmap GetBitmap(string path, bool cropWhitespace, bool forceAnalyzeBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
+        private SKBitmap? GetBitmap(string path, bool cropWhitespace, bool forceAnalyzeBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
         {
             if (cropWhitespace)
             {
                 using (var bitmap = Decode(path, forceAnalyzeBitmap, orientation, out origin))
                 {
+                    if (bitmap == null)
+                    {
+                        return null;
+                    }
+
                     return CropWhiteSpace(bitmap);
                 }
             }
@@ -361,13 +370,11 @@ namespace Jellyfin.Drawing.Skia
             return Decode(path, forceAnalyzeBitmap, orientation, out origin);
         }
 
-        private SKBitmap GetBitmap(string path, bool cropWhitespace, bool autoOrient, ImageOrientation? orientation)
+        private SKBitmap? GetBitmap(string path, bool cropWhitespace, bool autoOrient, ImageOrientation? orientation)
         {
-            SKEncodedOrigin origin;
-
             if (autoOrient)
             {
-                var bitmap = GetBitmap(path, cropWhitespace, true, orientation, out origin);
+                var bitmap = GetBitmap(path, cropWhitespace, true, orientation, out var origin);
 
                 if (bitmap != null && origin != SKEncodedOrigin.TopLeft)
                 {
@@ -380,7 +387,7 @@ namespace Jellyfin.Drawing.Skia
                 return bitmap;
             }
 
-            return GetBitmap(path, cropWhitespace, false, orientation, out origin);
+            return GetBitmap(path, cropWhitespace, false, orientation, out _);
         }
 
         private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
@@ -517,14 +524,14 @@ namespace Jellyfin.Drawing.Skia
         /// <inheritdoc/>
         public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
         {
-            if (string.IsNullOrWhiteSpace(inputPath))
+            if (inputPath.Length == 0)
             {
-                throw new ArgumentNullException(nameof(inputPath));
+                throw new ArgumentException("String can't be empty.", nameof(inputPath));
             }
 
-            if (string.IsNullOrWhiteSpace(inputPath))
+            if (outputPath.Length == 0)
             {
-                throw new ArgumentNullException(nameof(outputPath));
+                throw new ArgumentException("String can't be empty.", nameof(outputPath));
             }
 
             var skiaOutputFormat = GetImageFormat(selectedOutputFormat);
@@ -538,7 +545,7 @@ namespace Jellyfin.Drawing.Skia
             {
                 if (bitmap == null)
                 {
-                    throw new ArgumentOutOfRangeException($"Skia unable to read image {inputPath}");
+                    throw new InvalidDataException($"Skia unable to read image {inputPath}");
                 }
 
                 var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height);

+ 5 - 5
Jellyfin.Drawing.Skia/StripCollageBuilder.cs

@@ -120,13 +120,13 @@ namespace Jellyfin.Drawing.Skia
                         }
 
                         // resize to the same aspect as the original
-                        int iWidth = (int)Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height);
+                        int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height);
                         using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType))
                         {
                             currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High);
 
                             // crop image
-                            int ix = (int)Math.Abs((iWidth - iSlice) / 2);
+                            int ix = Math.Abs((iWidth - iSlice) / 2);
                             using (var image = SKImage.FromBitmap(resizeBitmap))
                             using (var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)))
                             {
@@ -141,10 +141,10 @@ namespace Jellyfin.Drawing.Skia
             return bitmap;
         }
 
-        private SKBitmap GetNextValidImage(string[] paths, int currentIndex, out int newIndex)
+        private SKBitmap? GetNextValidImage(string[] paths, int currentIndex, out int newIndex)
         {
             var imagesTested = new Dictionary<int, int>();
-            SKBitmap bitmap = null;
+            SKBitmap? bitmap = null;
 
             while (imagesTested.Count < paths.Length)
             {
@@ -153,7 +153,7 @@ namespace Jellyfin.Drawing.Skia
                     currentIndex = 0;
                 }
 
-                bitmap = _skiaEncoder.Decode(paths[currentIndex], false, null, out var origin);
+                bitmap = _skiaEncoder.Decode(paths[currentIndex], false, null, out _);
 
                 imagesTested[currentIndex] = 0;
 

+ 2 - 2
Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs

@@ -32,7 +32,7 @@ namespace Jellyfin.Drawing.Skia
             {
                 paint.Color = SKColor.Parse("#CC00A4DC");
                 paint.Style = SKPaintStyle.Fill;
-                canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint);
+                canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint);
             }
 
             using (var paint = new SKPaint())
@@ -61,7 +61,7 @@ namespace Jellyfin.Drawing.Skia
                     paint.TextSize = 18;
                 }
 
-                canvas.DrawText(text, (float)x, y, paint);
+                canvas.DrawText(text, x, y, paint);
             }
         }
     }

+ 22 - 5
Jellyfin.Server/CoreAppHost.cs

@@ -1,9 +1,13 @@
+using System;
 using System.Collections.Generic;
 using System.Reflection;
+using Emby.Drawing;
 using Emby.Server.Implementations;
+using Jellyfin.Drawing.Skia;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Model.IO;
+using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 
 namespace Jellyfin.Server
@@ -20,27 +24,40 @@ namespace Jellyfin.Server
         /// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
         /// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
         /// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
-        /// <param name="imageEncoder">The <see cref="IImageEncoder" /> to be used by the <see cref="CoreAppHost" />.</param>
         /// <param name="networkManager">The <see cref="INetworkManager" /> to be used by the <see cref="CoreAppHost" />.</param>
         public CoreAppHost(
             ServerApplicationPaths applicationPaths,
             ILoggerFactory loggerFactory,
             StartupOptions options,
             IFileSystem fileSystem,
-            IImageEncoder imageEncoder,
             INetworkManager networkManager)
             : base(
                 applicationPaths,
                 loggerFactory,
                 options,
                 fileSystem,
-                imageEncoder,
                 networkManager)
         {
         }
 
-        /// <inheritdoc />
-        public override bool CanSelfRestart => StartupOptions.RestartPath != null;
+        /// <inheritdoc/>
+        protected override void RegisterServices(IServiceCollection serviceCollection)
+        {
+            // Register an image encoder
+            bool useSkiaEncoder = SkiaEncoder.IsNativeLibAvailable();
+            Type imageEncoderType = useSkiaEncoder
+                ? typeof(SkiaEncoder)
+                : typeof(NullImageEncoder);
+            serviceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType);
+
+            // Log a warning if the Skia encoder could not be used
+            if (!useSkiaEncoder)
+            {
+                Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
+            }
+
+            base.RegisterServices(serviceCollection);
+        }
 
         /// <inheritdoc />
         protected override void RestartInternal() => Program.Restart();

+ 5 - 0
Jellyfin.Server/Jellyfin.Server.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{07E39F42-A2C6-4B32-AF8C-725F957A73FF}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <AssemblyName>jellyfin</AssemblyName>
     <OutputType>Exe</OutputType>

+ 2 - 23
Jellyfin.Server/Program.cs

@@ -168,7 +168,6 @@ namespace Jellyfin.Server
                 _loggerFactory,
                 options,
                 new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
-                GetImageEncoder(appPaths),
                 new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()));
 
             try
@@ -188,14 +187,13 @@ namespace Jellyfin.Server
                 }
 
                 ServiceCollection serviceCollection = new ServiceCollection();
-                await appHost.InitAsync(serviceCollection, startupConfig).ConfigureAwait(false);
+                appHost.Init(serviceCollection);
 
                 var webHost = new WebHostBuilder().ConfigureWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
 
                 // Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection.
                 appHost.ServiceProvider = webHost.Services;
-                appHost.InitializeServices();
-                appHost.FindParts();
+                await appHost.InitializeServices().ConfigureAwait(false);
                 Migrations.MigrationRunner.Run(appHost, _loggerFactory);
 
                 try
@@ -598,25 +596,6 @@ namespace Jellyfin.Server
             }
         }
 
-        private static IImageEncoder GetImageEncoder(IApplicationPaths appPaths)
-        {
-            try
-            {
-                // Test if the native lib is available
-                SkiaEncoder.TestSkia();
-
-                return new SkiaEncoder(
-                    _loggerFactory.CreateLogger<SkiaEncoder>(),
-                    appPaths);
-            }
-            catch (Exception ex)
-            {
-                _logger.LogWarning(ex, $"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
-            }
-
-            return new NullImageEncoder();
-        }
-
         private static void StartNewInstance(StartupOptions options)
         {
             _logger.LogInformation("Starting new instance");

+ 8 - 1
MediaBrowser.Api/Images/ImageService.cs

@@ -332,7 +332,8 @@ namespace MediaBrowser.Api.Images
                     var fileInfo = _fileSystem.GetFileInfo(info.Path);
                     length = fileInfo.Length;
 
-                    ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true);
+                    ImageDimensions size = _imageProcessor.GetImageDimensions(item, info);
+                    _libraryManager.UpdateImages(item);
                     width = size.Width;
                     height = size.Height;
 
@@ -606,6 +607,12 @@ namespace MediaBrowser.Api.Images
             IDictionary<string, string> headers,
             bool isHeadRequest)
         {
+            if (!image.IsLocalFile)
+            {
+                item ??= _libraryManager.GetItemById(itemId);
+                image = await _libraryManager.ConvertImageToLocal(item, image, request.Index ?? 0).ConfigureAwait(false);
+            }
+
             var options = new ImageProcessingOptions
             {
                 CropWhiteSpace = cropwhitespace,

+ 5 - 0
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{4FD51AC5-2C16-4308-A993-C3A84F3B4582}</ProjectGuid>
+  </PropertyGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />

+ 1 - 43
MediaBrowser.Api/PackageService.cs

@@ -42,23 +42,6 @@ namespace MediaBrowser.Api
     [Authenticated]
     public class GetPackages : IReturn<PackageInfo[]>
     {
-        /// <summary>
-        /// Gets or sets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        [ApiMember(Name = "PackageType", Description = "Optional package type filter (System/UserInstalled)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string PackageType { get; set; }
-
-        [ApiMember(Name = "TargetSystems", Description = "Optional. Filter by target system type. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string TargetSystems { get; set; }
-
-        [ApiMember(Name = "IsPremium", Description = "Optional. Filter by premium status", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
-        public bool? IsPremium { get; set; }
-
-        [ApiMember(Name = "IsAdult", Description = "Optional. Filter by package that contain adult content.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
-        public bool? IsAdult { get; set; }
-
-        public bool? IsAppStoreEnabled { get; set; }
     }
 
     /// <summary>
@@ -88,13 +71,6 @@ namespace MediaBrowser.Api
         /// <value>The version.</value>
         [ApiMember(Name = "Version", Description = "Optional version. Defaults to latest version.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         public string Version { get; set; }
-
-        /// <summary>
-        /// Gets or sets the update class.
-        /// </summary>
-        /// <value>The update class.</value>
-        [ApiMember(Name = "UpdateClass", Description = "Optional update class (Dev, Beta, Release). Defaults to Release.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public PackageVersionClass UpdateClass { get; set; }
     }
 
     /// <summary>
@@ -154,23 +130,6 @@ namespace MediaBrowser.Api
         {
             IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
 
-            if (!string.IsNullOrEmpty(request.TargetSystems))
-            {
-                var apps = request.TargetSystems.Split(',').Select(i => (PackageTargetSystem)Enum.Parse(typeof(PackageTargetSystem), i, true));
-
-                packages = packages.Where(p => apps.Contains(p.targetSystem));
-            }
-
-            if (request.IsAdult.HasValue)
-            {
-                packages = packages.Where(p => p.adult == request.IsAdult.Value);
-            }
-
-            if (request.IsAppStoreEnabled.HasValue)
-            {
-                packages = packages.Where(p => p.enableInAppStore == request.IsAppStoreEnabled.Value);
-            }
-
             return ToOptimizedResult(packages.ToArray());
         }
 
@@ -186,8 +145,7 @@ namespace MediaBrowser.Api
                     packages,
                     request.Name,
                     string.IsNullOrEmpty(request.AssemblyGuid) ? Guid.Empty : Guid.Parse(request.AssemblyGuid),
-                    string.IsNullOrEmpty(request.Version) ? null : Version.Parse(request.Version),
-                    request.UpdateClass).FirstOrDefault();
+                    string.IsNullOrEmpty(request.Version) ? null : Version.Parse(request.Version)).FirstOrDefault();
 
             if (package == null)
             {

+ 1 - 1
MediaBrowser.Api/UserService.cs

@@ -426,7 +426,7 @@ namespace MediaBrowser.Api
             catch (SecurityException e)
             {
                 // rethrow adding IP address to message
-                throw new SecurityException($"[{Request.RemoteIp}] {e.Message}");
+                throw new SecurityException($"[{Request.RemoteIp}] {e.Message}", e);
             }
         }
 

+ 2 - 0
MediaBrowser.Common/Extensions/ShuffleExtensions.cs

@@ -1,3 +1,5 @@
+#nullable enable
+
 using System;
 using System.Collections.Generic;
 

+ 1 - 11
MediaBrowser.Common/IApplicationHost.cs

@@ -2,8 +2,6 @@ using System;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Plugins;
-using MediaBrowser.Model.Updates;
-using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 
 namespace MediaBrowser.Common
@@ -48,12 +46,6 @@ namespace MediaBrowser.Common
         /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
         bool CanSelfRestart { get; }
 
-        /// <summary>
-        /// Gets the version class of the system.
-        /// </summary>
-        /// <value><see cref="PackageVersionClass.Release" /> or <see cref="PackageVersionClass.Beta" />.</value>
-        PackageVersionClass SystemUpdateLevel { get; }
-
         /// <summary>
         /// Gets the application version.
         /// </summary>
@@ -125,9 +117,7 @@ namespace MediaBrowser.Common
         /// Initializes this instance.
         /// </summary>
         /// <param name="serviceCollection">The service collection.</param>
-        /// <param name="startupConfig">The configuration to use for initialization.</param>
-        /// <returns>A task.</returns>
-        Task InitAsync(IServiceCollection serviceCollection, IConfiguration startupConfig);
+        void Init(IServiceCollection serviceCollection);
 
         /// <summary>
         /// Creates the instance.

+ 5 - 0
MediaBrowser.Common/MediaBrowser.Common.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <Authors>Jellyfin Contributors</Authors>
     <PackageId>Jellyfin.Common</PackageId>

+ 4 - 4
MediaBrowser.Common/Plugins/BasePlugin.cs

@@ -67,7 +67,7 @@ namespace MediaBrowser.Common.Plugins
         }
 
         /// <summary>
-        /// Called when just before the plugin is uninstalled from the server.
+        /// Called just before the plugin is uninstalled from the server.
         /// </summary>
         public virtual void OnUninstalling()
         {
@@ -101,7 +101,7 @@ namespace MediaBrowser.Common.Plugins
         private readonly object _configurationSyncLock = new object();
 
         /// <summary>
-        /// The save lock.
+        /// The configuration save lock.
         /// </summary>
         private readonly object _configurationSaveLock = new object();
 
@@ -148,7 +148,7 @@ namespace MediaBrowser.Common.Plugins
         protected string AssemblyFileName => Path.GetFileName(AssemblyFilePath);
 
         /// <summary>
-        /// Gets or sets the plugin's configuration.
+        /// Gets or sets the plugin configuration.
         /// </summary>
         /// <value>The configuration.</value>
         public TConfigurationType Configuration
@@ -186,7 +186,7 @@ namespace MediaBrowser.Common.Plugins
         public string ConfigurationFilePath => Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName);
 
         /// <summary>
-        /// Gets the plugin's configuration.
+        /// Gets the plugin configuration.
         /// </summary>
         /// <value>The configuration.</value>
         BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration;

+ 9 - 13
MediaBrowser.Common/Updates/IInstallationManager.cs

@@ -28,12 +28,12 @@ namespace MediaBrowser.Common.Updates
         /// <summary>
         /// Occurs when a plugin is updated.
         /// </summary>
-        event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
+        event EventHandler<GenericEventArgs<(IPlugin, VersionInfo)>> PluginUpdated;
 
         /// <summary>
         /// Occurs when a plugin is installed.
         /// </summary>
-        event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
+        event EventHandler<GenericEventArgs<VersionInfo>> PluginInstalled;
 
         /// <summary>
         /// Gets the completed installations.
@@ -64,12 +64,10 @@ namespace MediaBrowser.Common.Updates
         /// </summary>
         /// <param name="availableVersions">The available version of the plugin.</param>
         /// <param name="minVersion">The minimum required version of the plugin.</param>
-        /// <param name="classification">The classification of updates.</param>
         /// <returns>All compatible versions ordered from newest to oldest.</returns>
-        IEnumerable<PackageVersionInfo> GetCompatibleVersions(
-            IEnumerable<PackageVersionInfo> availableVersions,
-            Version minVersion = null,
-            PackageVersionClass classification = PackageVersionClass.Release);
+        IEnumerable<VersionInfo> GetCompatibleVersions(
+            IEnumerable<VersionInfo> availableVersions,
+            Version minVersion = null);
 
         /// <summary>
         /// Returns all compatible versions ordered from newest to oldest.
@@ -78,21 +76,19 @@ namespace MediaBrowser.Common.Updates
         /// <param name="name">The name.</param>
         /// <param name="guid">The guid of the plugin.</param>
         /// <param name="minVersion">The minimum required version of the plugin.</param>
-        /// <param name="classification">The classification.</param>
         /// <returns>All compatible versions ordered from newest to oldest.</returns>
-        IEnumerable<PackageVersionInfo> GetCompatibleVersions(
+        IEnumerable<VersionInfo> GetCompatibleVersions(
             IEnumerable<PackageInfo> availablePackages,
             string name = null,
             Guid guid = default,
-            Version minVersion = null,
-            PackageVersionClass classification = PackageVersionClass.Release);
+            Version minVersion = null);
 
         /// <summary>
         /// Returns the available plugin updates.
         /// </summary>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>The available plugin updates.</returns>
-        Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
+        Task<IEnumerable<VersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
 
         /// <summary>
         /// Installs the package.
@@ -100,7 +96,7 @@ namespace MediaBrowser.Common.Updates
         /// <param name="package">The package.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns><see cref="Task" />.</returns>
-        Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken = default);
+        Task InstallPackage(VersionInfo package, CancellationToken cancellationToken = default);
 
         /// <summary>
         /// Uninstalls a plugin.

+ 1 - 1
MediaBrowser.Common/Updates/InstallationEventArgs.cs

@@ -8,6 +8,6 @@ namespace MediaBrowser.Common.Updates
     {
         public InstallationInfo InstallationInfo { get; set; }
 
-        public PackageVersionInfo PackageVersionInfo { get; set; }
+        public VersionInfo VersionInfo { get; set; }
     }
 }

+ 12 - 6
MediaBrowser.Controller/Authentication/AuthenticationException.cs

@@ -7,23 +7,29 @@ namespace MediaBrowser.Controller.Authentication
     /// </summary>
     public class AuthenticationException : Exception
     {
-        /// <inheritdoc />
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationException"/> class.
+        /// </summary>
         public AuthenticationException() : base()
         {
-
         }
 
-        /// <inheritdoc />
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationException"/> class.
+        /// </summary>
+        /// <param name="message">The message that describes the error.</param>
         public AuthenticationException(string message) : base(message)
         {
-
         }
 
-        /// <inheritdoc />
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationException"/> class.
+        /// </summary>
+        /// <param name="message">The message that describes the error.</param>
+        /// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
         public AuthenticationException(string message, Exception innerException)
             : base(message, innerException)
         {
-
         }
     }
 }

+ 0 - 9
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -40,15 +40,6 @@ namespace MediaBrowser.Controller.Drawing
         /// <returns>ImageDimensions</returns>
         ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info);
 
-        /// <summary>
-        /// Gets the dimensions of the image.
-        /// </summary>
-        /// <param name="item">The base item.</param>
-        /// <param name="info">The information.</param>
-        /// <param name="updateItem">Whether or not the item info should be updated.</param>
-        /// <returns>ImageDimensions</returns>
-        ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem);
-
         /// <summary>
         /// Gets the image cache tag.
         /// </summary>

+ 5 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <Authors>Jellyfin Contributors</Authors>
     <PackageId>Jellyfin.Controller</PackageId>

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

@@ -459,7 +459,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             {
                 var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions);
                 var outputVideoCodec = GetVideoEncoder(state, encodingOptions);
-				
+
                 var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
 
                 if (!hasTextSubs)

+ 24 - 8
MediaBrowser.Controller/Net/SecurityException.cs

@@ -2,20 +2,36 @@ using System;
 
 namespace MediaBrowser.Controller.Net
 {
+    /// <summary>
+    /// The exception that is thrown when a user is authenticated, but not authorized to access a requested resource.
+    /// </summary>
     public class SecurityException : Exception
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SecurityException"/> class.
+        /// </summary>
+        public SecurityException()
+            : base()
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SecurityException"/> class.
+        /// </summary>
+        /// <param name="message">The message that describes the error.</param>
         public SecurityException(string message)
             : base(message)
         {
-
         }
 
-        public SecurityExceptionType SecurityExceptionType { get; set; }
-    }
-
-    public enum SecurityExceptionType
-    {
-        Unauthenticated = 0,
-        ParentalControl = 1
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SecurityException"/> class.
+        /// </summary>
+        /// <param name="message">The message that describes the error</param>
+        /// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
+        public SecurityException(string message, Exception innerException)
+            : base(message, innerException)
+        {
+        }
     }
 }

+ 5 - 0
MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}</ProjectGuid>
+  </PropertyGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />

+ 4 - 13
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -19,7 +19,6 @@ using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.System;
-using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using System.Diagnostics;
 
@@ -39,8 +38,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         private readonly IServerConfigurationManager _configurationManager;
         private readonly IFileSystem _fileSystem;
         private readonly ILocalizationManager _localization;
-        private readonly Func<ISubtitleEncoder> _subtitleEncoder;
-        private readonly IConfiguration _configuration;
+        private readonly Lazy<EncodingHelper> _encodingHelperFactory;
         private readonly string _startupOptionFFmpegPath;
 
         private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
@@ -48,8 +46,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
         private readonly object _runningProcessesLock = new object();
         private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
 
-        private EncodingHelper _encodingHelper;
-
         private string _ffmpegPath;
         private string _ffprobePath;
 
@@ -58,23 +54,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
             IServerConfigurationManager configurationManager,
             IFileSystem fileSystem,
             ILocalizationManager localization,
-            Func<ISubtitleEncoder> subtitleEncoder,
-            IConfiguration configuration,
+            Lazy<EncodingHelper> encodingHelperFactory,
             string startupOptionsFFmpegPath)
         {
             _logger = logger;
             _configurationManager = configurationManager;
             _fileSystem = fileSystem;
             _localization = localization;
+            _encodingHelperFactory = encodingHelperFactory;
             _startupOptionFFmpegPath = startupOptionsFFmpegPath;
-            _subtitleEncoder = subtitleEncoder;
-            _configuration = configuration;
         }
 
-        private EncodingHelper EncodingHelper
-            => LazyInitializer.EnsureInitialized(
-                ref _encodingHelper,
-                () => new EncodingHelper(this, _fileSystem, _subtitleEncoder(), _configuration));
+        private EncodingHelper EncodingHelper => _encodingHelperFactory.Value;
 
         /// <inheritdoc />
         public string EncoderPath => _ffmpegPath;

+ 5 - 0
MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{960295EE-4AF4-4440-A525-B4C295B01A61}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>

+ 5 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <Authors>Jellyfin Contributors</Authors>
     <PackageId>Jellyfin.Model</PackageId>

+ 5 - 5
MediaBrowser.Model/Services/IHasRequestFilter.cs

@@ -8,17 +8,17 @@ namespace MediaBrowser.Model.Services
     {
         /// <summary>
         /// Order in which Request Filters are executed.
-        /// &lt;0 Executed before global request filters
-        /// &gt;0 Executed after global request filters
+        /// &lt;0 Executed before global request filters.
+        /// &gt;0 Executed after global request filters.
         /// </summary>
         int Priority { get; }
 
         /// <summary>
         /// The request filter is executed before the service.
         /// </summary>
-        /// <param name="req">The http request wrapper</param>
-        /// <param name="res">The http response wrapper</param>
-        /// <param name="requestDto">The request DTO</param>
+        /// <param name="req">The http request wrapper.</param>
+        /// <param name="res">The http response wrapper.</param>
+        /// <param name="requestDto">The request DTO.</param>
         void RequestFilter(IRequest req, HttpResponse res, object requestDto);
     }
 }

+ 0 - 2
MediaBrowser.Model/System/SystemInfo.cs

@@ -26,8 +26,6 @@ namespace MediaBrowser.Model.System
     /// </summary>
     public class SystemInfo : PublicSystemInfo
     {
-        public PackageVersionClass SystemUpdateLevel { get; set; }
-
         /// <summary>
         /// Gets or sets the display name of the operating system.
         /// </summary>

+ 0 - 29
MediaBrowser.Model/Updates/CheckForUpdateResult.cs

@@ -1,29 +0,0 @@
-namespace MediaBrowser.Model.Updates
-{
-    /// <summary>
-    /// Class CheckForUpdateResult.
-    /// </summary>
-    public class CheckForUpdateResult
-    {
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance is update available.
-        /// </summary>
-        /// <value><c>true</c> if this instance is update available; otherwise, <c>false</c>.</value>
-        public bool IsUpdateAvailable { get; set; }
-
-        /// <summary>
-        /// Gets or sets the available version.
-        /// </summary>
-        /// <value>The available version.</value>
-        public string AvailableVersion
-        {
-            get => Package != null ? Package.versionStr : "0.0.0.1";
-            set { } // need this for the serializer
-        }
-
-        /// <summary>
-        /// Get or sets package information for an available update
-        /// </summary>
-        public PackageVersionInfo Package { get; set; }
-    }
-}

+ 3 - 15
MediaBrowser.Model/Updates/InstallationInfo.cs

@@ -8,10 +8,10 @@ namespace MediaBrowser.Model.Updates
     public class InstallationInfo
     {
         /// <summary>
-        /// Gets or sets the id.
+        /// Gets or sets the guid.
         /// </summary>
-        /// <value>The id.</value>
-        public Guid Id { get; set; }
+        /// <value>The guid.</value>
+        public string Guid { get; set; }
 
         /// <summary>
         /// Gets or sets the name.
@@ -19,22 +19,10 @@ namespace MediaBrowser.Model.Updates
         /// <value>The name.</value>
         public string Name { get; set; }
 
-        /// <summary>
-        /// Gets or sets the assembly guid.
-        /// </summary>
-        /// <value>The guid of the assembly.</value>
-        public string AssemblyGuid { get; set; }
-
         /// <summary>
         /// Gets or sets the version.
         /// </summary>
         /// <value>The version.</value>
         public string Version { get; set; }
-
-        /// <summary>
-        /// Gets or sets the update class.
-        /// </summary>
-        /// <value>The update class.</value>
-        public PackageVersionClass UpdateClass { get; set; }
     }
 }

+ 7 - 121
MediaBrowser.Model/Updates/PackageInfo.cs

@@ -8,12 +8,6 @@ namespace MediaBrowser.Model.Updates
     /// </summary>
     public class PackageInfo
     {
-        /// <summary>
-        /// The internal id of this package.
-        /// </summary>
-        /// <value>The id.</value>
-        public string id { get; set; }
-
         /// <summary>
         /// Gets or sets the name.
         /// </summary>
@@ -21,59 +15,17 @@ namespace MediaBrowser.Model.Updates
         public string name { get; set; }
 
         /// <summary>
-        /// Gets or sets the short description.
+        /// Gets or sets a long description of the plugin containing features or helpful explanations.
         /// </summary>
-        /// <value>The short description.</value>
-        public string shortDescription { get; set; }
+        /// <value>The description.</value>
+        public string description { get; set; }
 
         /// <summary>
-        /// Gets or sets the overview.
+        /// Gets or sets a short overview of what the plugin does.
         /// </summary>
         /// <value>The overview.</value>
         public string overview { get; set; }
 
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance is premium.
-        /// </summary>
-        /// <value><c>true</c> if this instance is premium; otherwise, <c>false</c>.</value>
-        public bool isPremium { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance is adult only content.
-        /// </summary>
-        /// <value><c>true</c> if this instance is adult; otherwise, <c>false</c>.</value>
-        public bool adult { get; set; }
-
-        /// <summary>
-        /// Gets or sets the rich desc URL.
-        /// </summary>
-        /// <value>The rich desc URL.</value>
-        public string richDescUrl { get; set; }
-
-        /// <summary>
-        /// Gets or sets the thumb image.
-        /// </summary>
-        /// <value>The thumb image.</value>
-        public string thumbImage { get; set; }
-
-        /// <summary>
-        /// Gets or sets the preview image.
-        /// </summary>
-        /// <value>The preview image.</value>
-        public string previewImage { get; set; }
-
-        /// <summary>
-        /// Gets or sets the type.
-        /// </summary>
-        /// <value>The type.</value>
-        public string type { get; set; }
-
-        /// <summary>
-        /// Gets or sets the target filename.
-        /// </summary>
-        /// <value>The target filename.</value>
-        public string targetFilename { get; set; }
-
         /// <summary>
         /// Gets or sets the owner.
         /// </summary>
@@ -87,90 +39,24 @@ namespace MediaBrowser.Model.Updates
         public string category { get; set; }
 
         /// <summary>
-        /// Gets or sets the catalog tile color.
-        /// </summary>
-        /// <value>The owner.</value>
-        public string tileColor { get; set; }
-
-        /// <summary>
-        /// Gets or sets the feature id of this package (if premium).
-        /// </summary>
-        /// <value>The feature id.</value>
-        public string featureId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the registration info for this package (if premium).
-        /// </summary>
-        /// <value>The registration info.</value>
-        public string regInfo { get; set; }
-
-        /// <summary>
-        /// Gets or sets the price for this package (if premium).
-        /// </summary>
-        /// <value>The price.</value>
-        public float price { get; set; }
-
-        /// <summary>
-        /// Gets or sets the target system for this plug-in (Server, MBTheater, MBClassic).
-        /// </summary>
-        /// <value>The target system.</value>
-        public PackageTargetSystem targetSystem { get; set; }
-
-        /// <summary>
-        /// The guid of the assembly associated with this package (if a plug-in).
+        /// The guid of the assembly associated with this plugin.
         /// This is used to identify the proper item for automatic updates.
         /// </summary>
         /// <value>The name.</value>
         public string guid { get; set; }
 
-        /// <summary>
-        /// Gets or sets the total number of ratings for this package.
-        /// </summary>
-        /// <value>The total ratings.</value>
-        public int? totalRatings { get; set; }
-
-        /// <summary>
-        /// Gets or sets the average rating for this package .
-        /// </summary>
-        /// <value>The rating.</value>
-        public float avgRating { get; set; }
-
-        /// <summary>
-        /// Gets or sets whether or not this package is registered.
-        /// </summary>
-        /// <value>True if registered.</value>
-        public bool isRegistered { get; set; }
-
-        /// <summary>
-        /// Gets or sets the expiration date for this package.
-        /// </summary>
-        /// <value>Expiration Date.</value>
-        public DateTime expDate { get; set; }
-
         /// <summary>
         /// Gets or sets the versions.
         /// </summary>
         /// <value>The versions.</value>
-        public IReadOnlyList<PackageVersionInfo> versions { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether [enable in application store].
-        /// </summary>
-        /// <value><c>true</c> if [enable in application store]; otherwise, <c>false</c>.</value>
-        public bool enableInAppStore { get; set; }
-
-        /// <summary>
-        /// Gets or sets the installs.
-        /// </summary>
-        /// <value>The installs.</value>
-        public int installs { get; set; }
+        public IReadOnlyList<VersionInfo> versions { get; set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="PackageInfo"/> class.
         /// </summary>
         public PackageInfo()
         {
-            versions = Array.Empty<PackageVersionInfo>();
+            versions = Array.Empty<VersionInfo>();
         }
     }
 }

+ 0 - 23
MediaBrowser.Model/Updates/PackageTargetSystem.cs

@@ -1,23 +0,0 @@
-namespace MediaBrowser.Model.Updates
-{
-    /// <summary>
-    /// Enum PackageType.
-    /// </summary>
-    public enum PackageTargetSystem
-    {
-        /// <summary>
-        /// Server.
-        /// </summary>
-        Server,
-
-        /// <summary>
-        /// MB Theater.
-        /// </summary>
-        MBTheater,
-
-        /// <summary>
-        /// MB Classic.
-        /// </summary>
-        MBClassic
-    }
-}

+ 0 - 23
MediaBrowser.Model/Updates/PackageVersionClass.cs

@@ -1,23 +0,0 @@
-namespace MediaBrowser.Model.Updates
-{
-    /// <summary>
-    /// Enum PackageVersionClass.
-    /// </summary>
-    public enum PackageVersionClass
-    {
-        /// <summary>
-        /// The release.
-        /// </summary>
-        Release = 0,
-
-        /// <summary>
-        /// The beta.
-        /// </summary>
-        Beta = 1,
-
-        /// <summary>
-        /// The dev.
-        /// </summary>
-        Dev = 2
-    }
-}

+ 0 - 96
MediaBrowser.Model/Updates/PackageVersionInfo.cs

@@ -1,96 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Text.Json.Serialization;
-
-namespace MediaBrowser.Model.Updates
-{
-    /// <summary>
-    /// Class PackageVersionInfo.
-    /// </summary>
-    public class PackageVersionInfo
-    {
-        /// <summary>
-        /// Gets or sets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        public string name { get; set; }
-
-        /// <summary>
-        /// Gets or sets the guid.
-        /// </summary>
-        /// <value>The guid.</value>
-        public string guid { get; set; }
-
-        /// <summary>
-        /// Gets or sets the version STR.
-        /// </summary>
-        /// <value>The version STR.</value>
-        public string versionStr { get; set; }
-
-        /// <summary>
-        /// The _version
-        /// </summary>
-        private Version _version;
-
-        /// <summary>
-        /// Gets or sets the version.
-        /// Had to make this an interpreted property since Protobuf can't handle Version
-        /// </summary>
-        /// <value>The version.</value>
-        [JsonIgnore]
-        public Version Version
-        {
-            get
-            {
-                if (_version == null)
-                {
-                    var ver = versionStr;
-                    _version = new Version(string.IsNullOrEmpty(ver) ? "0.0.0.1" : ver);
-                }
-
-                return _version;
-            }
-        }
-
-        /// <summary>
-        /// Gets or sets the classification.
-        /// </summary>
-        /// <value>The classification.</value>
-        public PackageVersionClass classification { get; set; }
-
-        /// <summary>
-        /// Gets or sets the description.
-        /// </summary>
-        /// <value>The description.</value>
-        public string description { get; set; }
-
-        /// <summary>
-        /// Gets or sets the required version STR.
-        /// </summary>
-        /// <value>The required version STR.</value>
-        public string requiredVersionStr { get; set; }
-
-        /// <summary>
-        /// Gets or sets the source URL.
-        /// </summary>
-        /// <value>The source URL.</value>
-        public string sourceUrl { get; set; }
-
-        /// <summary>
-        /// Gets or sets the source URL.
-        /// </summary>
-        /// <value>The source URL.</value>
-        public string checksum { get; set; }
-
-        /// <summary>
-        /// Gets or sets the target filename.
-        /// </summary>
-        /// <value>The target filename.</value>
-        public string targetFilename { get; set; }
-
-        public string infoUrl { get; set; }
-
-        public string runtimes { get; set; }
-    }
-}

+ 58 - 0
MediaBrowser.Model/Updates/VersionInfo.cs

@@ -0,0 +1,58 @@
+using System;
+
+namespace MediaBrowser.Model.Updates
+{
+    /// <summary>
+    /// Class PackageVersionInfo.
+    /// </summary>
+    public class VersionInfo
+    {
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the guid.
+        /// </summary>
+        /// <value>The guid.</value>
+        public string guid { get; set; }
+
+        /// <summary>
+        /// Gets or sets the version.
+        /// </summary>
+        /// <value>The version.</value>
+        public Version version { get; set; }
+
+        /// <summary>
+        /// Gets or sets the changelog for this version.
+        /// </summary>
+        /// <value>The changelog.</value>
+        public string changelog { get; set; }
+
+        /// <summary>
+        /// Gets or sets the ABI that this version was built against.
+        /// </summary>
+        /// <value>The target ABI version.</value>
+        public string targetAbi { get; set; }
+
+        /// <summary>
+        /// Gets or sets the source URL.
+        /// </summary>
+        /// <value>The source URL.</value>
+        public string sourceUrl { get; set; }
+
+        /// <summary>
+        /// Gets or sets a checksum for the binary.
+        /// </summary>
+        /// <value>The checksum.</value>
+        public string checksum { get; set; }
+
+        /// <summary>
+        /// Gets or sets the target filename for the downloaded binary.
+        /// </summary>
+        /// <value>The target filename.</value>
+        public string filename { get; set; }
+    }
+}

+ 32 - 41
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -36,60 +36,51 @@ namespace MediaBrowser.Providers.Manager
     /// </summary>
     public class ProviderManager : IProviderManager, IDisposable
     {
-        /// <summary>
-        /// The _logger
-        /// </summary>
         private readonly ILogger _logger;
-
-        /// <summary>
-        /// The _HTTP client
-        /// </summary>
         private readonly IHttpClient _httpClient;
-
-        /// <summary>
-        /// The _directory watchers
-        /// </summary>
         private readonly ILibraryMonitor _libraryMonitor;
-
-        /// <summary>
-        /// Gets or sets the configuration manager.
-        /// </summary>
-        /// <value>The configuration manager.</value>
-        private IServerConfigurationManager ConfigurationManager { get; set; }
+        private readonly IFileSystem _fileSystem;
+        private readonly IServerApplicationPaths _appPaths;
+        private readonly IJsonSerializer _json;
+        private readonly ILibraryManager _libraryManager;
+        private readonly ISubtitleManager _subtitleManager;
+        private readonly IServerConfigurationManager _configurationManager;
 
         private IImageProvider[] ImageProviders { get; set; }
 
-        private readonly IFileSystem _fileSystem;
-
         private IMetadataService[] _metadataServices = { };
         private IMetadataProvider[] _metadataProviders = { };
         private IEnumerable<IMetadataSaver> _savers;
-        private readonly IServerApplicationPaths _appPaths;
-        private readonly IJsonSerializer _json;
 
         private IExternalId[] _externalIds;
 
-        private readonly Func<ILibraryManager> _libraryManagerFactory;
         private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
 
         public event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
         public event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
         public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
 
-        private ISubtitleManager _subtitleManager;
-
         /// <summary>
         /// Initializes a new instance of the <see cref="ProviderManager" /> class.
         /// </summary>
-        public ProviderManager(IHttpClient httpClient, ISubtitleManager subtitleManager, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILoggerFactory loggerFactory, IFileSystem fileSystem, IServerApplicationPaths appPaths, Func<ILibraryManager> libraryManagerFactory, IJsonSerializer json)
+        public ProviderManager(
+            IHttpClient httpClient,
+            ISubtitleManager subtitleManager,
+            IServerConfigurationManager configurationManager,
+            ILibraryMonitor libraryMonitor,
+            ILogger<ProviderManager> logger,
+            IFileSystem fileSystem,
+            IServerApplicationPaths appPaths,
+            ILibraryManager libraryManager,
+            IJsonSerializer json)
         {
-            _logger = loggerFactory.CreateLogger("ProviderManager");
+            _logger = logger;
             _httpClient = httpClient;
-            ConfigurationManager = configurationManager;
+            _configurationManager = configurationManager;
             _libraryMonitor = libraryMonitor;
             _fileSystem = fileSystem;
             _appPaths = appPaths;
-            _libraryManagerFactory = libraryManagerFactory;
+            _libraryManager = libraryManager;
             _json = json;
             _subtitleManager = subtitleManager;
         }
@@ -176,7 +167,7 @@ namespace MediaBrowser.Providers.Manager
 
         public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
         {
-            return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
+            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
         }
 
         public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken)
@@ -188,7 +179,7 @@ namespace MediaBrowser.Providers.Manager
 
             var fileStream = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, true);
 
-            return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
+            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
         }
 
         public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken)
@@ -273,7 +264,7 @@ namespace MediaBrowser.Providers.Manager
 
         public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
         {
-            return GetImageProviders(item, _libraryManagerFactory().GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false);
+            return GetImageProviders(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false);
         }
 
         private IEnumerable<IImageProvider> GetImageProviders(BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled)
@@ -328,7 +319,7 @@ namespace MediaBrowser.Providers.Manager
         private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(BaseItem item, bool includeDisabled)
         {
             var options = GetMetadataOptions(item);
-            var libraryOptions = _libraryManagerFactory().GetLibraryOptions(item);
+            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 
             return GetImageProviders(item, libraryOptions, options,
                     new ImageRefreshOptions(
@@ -593,7 +584,7 @@ namespace MediaBrowser.Providers.Manager
         {
             var type = item.GetType().Name;
 
-            return ConfigurationManager.Configuration.MetadataOptions
+            return _configurationManager.Configuration.MetadataOptions
                 .FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ??
                 new MetadataOptions();
         }
@@ -623,7 +614,7 @@ namespace MediaBrowser.Providers.Manager
         /// <returns>Task.</returns>
         private void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers)
         {
-            var libraryOptions = _libraryManagerFactory().GetLibraryOptions(item);
+            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 
             foreach (var saver in savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)))
             {
@@ -743,7 +734,7 @@ namespace MediaBrowser.Providers.Manager
 
             if (!searchInfo.ItemId.Equals(Guid.Empty))
             {
-                referenceItem = _libraryManagerFactory().GetItemById(searchInfo.ItemId);
+                referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
             }
 
             return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
@@ -771,7 +762,7 @@ namespace MediaBrowser.Providers.Manager
             }
             else
             {
-                libraryOptions = _libraryManagerFactory().GetLibraryOptions(referenceItem);
+                libraryOptions = _libraryManager.GetLibraryOptions(referenceItem);
             }
 
             var options = GetMetadataOptions(referenceItem);
@@ -786,11 +777,11 @@ namespace MediaBrowser.Providers.Manager
 
             if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataLanguage))
             {
-                searchInfo.SearchInfo.MetadataLanguage = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+                searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
             }
             if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
             {
-                searchInfo.SearchInfo.MetadataCountryCode = ConfigurationManager.Configuration.MetadataCountryCode;
+                searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
             }
 
             var resultList = new List<RemoteSearchResult>();
@@ -967,7 +958,7 @@ namespace MediaBrowser.Providers.Manager
         public void OnRefreshProgress(BaseItem item, double progress)
         {
             var id = item.Id;
-            _logger.LogInformation("OnRefreshProgress {0} {1}", id.ToString("N", CultureInfo.InvariantCulture), progress);
+            _logger.LogDebug("OnRefreshProgress {0} {1}", id.ToString("N", CultureInfo.InvariantCulture), progress);
 
             // TODO: Need to hunt down the conditions for this happening
             _activeRefreshes.AddOrUpdate(
@@ -1010,7 +1001,7 @@ namespace MediaBrowser.Providers.Manager
 
         private async Task StartProcessingRefreshQueue()
         {
-            var libraryManager = _libraryManagerFactory();
+            var libraryManager = _libraryManager;
 
             if (_disposed)
             {
@@ -1088,7 +1079,7 @@ namespace MediaBrowser.Providers.Manager
 
         private async Task RefreshArtist(MusicArtist item, MetadataRefreshOptions options, CancellationToken cancellationToken)
         {
-            var albums = _libraryManagerFactory()
+            var albums = _libraryManager
                 .GetItemList(new InternalItemsQuery
                 {
                     IncludeItemTypes = new[] { nameof(MusicAlbum) },

+ 5 - 0
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{442B5058-DCAF-4263-BB6A-F21E31120A1B}</ProjectGuid>
+  </PropertyGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />

+ 4 - 4
MediaBrowser.Providers/Subtitles/SubtitleManager.cs

@@ -25,22 +25,22 @@ namespace MediaBrowser.Providers.Subtitles
 {
     public class SubtitleManager : ISubtitleManager
     {
-        private ISubtitleProvider[] _subtitleProviders;
         private readonly ILogger _logger;
         private readonly IFileSystem _fileSystem;
         private readonly ILibraryMonitor _monitor;
         private readonly IMediaSourceManager _mediaSourceManager;
+        private readonly ILocalizationManager _localization;
 
-        private ILocalizationManager _localization;
+        private ISubtitleProvider[] _subtitleProviders;
 
         public SubtitleManager(
-            ILoggerFactory loggerFactory,
+            ILogger<SubtitleManager> logger,
             IFileSystem fileSystem,
             ILibraryMonitor monitor,
             IMediaSourceManager mediaSourceManager,
             ILocalizationManager localizationManager)
         {
-            _logger = loggerFactory.CreateLogger(nameof(SubtitleManager));
+            _logger = logger;
             _fileSystem = fileSystem;
             _monitor = monitor;
             _mediaSourceManager = mediaSourceManager;

+ 5 - 0
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{5624B7B5-B5A7-41D8-9F10-CC5611109619}</ProjectGuid>
+  </PropertyGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />

+ 5 - 0
MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{23499896-B135-4527-8574-C26E926EA99E}</ProjectGuid>
+  </PropertyGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />

+ 5 - 0
RSSDP/RSSDP.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{21002819-C39A-4D3E-BE83-2A276A77FB1F}</ProjectGuid>
+  </PropertyGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />

+ 5 - 0
tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <TargetFramework>netcoreapp3.1</TargetFramework>
     <IsPackable>false</IsPackable>

+ 5 - 0
tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{DF194677-DFD3-42AF-9F75-D44D5A416478}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <TargetFramework>netcoreapp3.1</TargetFramework>
     <IsPackable>false</IsPackable>

+ 5 - 0
tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{462584F7-5023-4019-9EAC-B98CA458C0A0}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <TargetFramework>netcoreapp3.1</TargetFramework>
     <IsPackable>false</IsPackable>

+ 5 - 0
tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{28464062-0939-4AA7-9F7B-24DDDA61A7C0}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <TargetFramework>netcoreapp3.1</TargetFramework>
     <IsPackable>false</IsPackable>

+ 5 - 0
tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{3998657B-1CCC-49DD-A19F-275DC8495F57}</ProjectGuid>
+  </PropertyGroup>
+
   <PropertyGroup>
     <TargetFramework>netcoreapp3.1</TargetFramework>
     <IsPackable>false</IsPackable>

+ 5 - 0
tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj

@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+  <PropertyGroup>
+    <ProjectGuid>{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}</ProjectGuid>
+  </PropertyGroup>
+
     <PropertyGroup>
       <TargetFramework>netcoreapp3.1</TargetFramework>
       <IsPackable>false</IsPackable>

+ 2 - 4
tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs

@@ -82,11 +82,10 @@ namespace MediaBrowser.Api.Tests
                 loggerFactory,
                 commandLineOpts,
                 new ManagedFileSystem(loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
-                new SkiaEncoder(loggerFactory.CreateLogger<SkiaEncoder>(), appPaths),
                 new NetworkManager(loggerFactory.CreateLogger<NetworkManager>()));
             _appHosts.Add(appHost);
             var serviceCollection = new ServiceCollection();
-            appHost.InitAsync(serviceCollection, startupConfig).Wait();
+            appHost.Init(serviceCollection);
 
             // Configure the web host builder
             Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths);
@@ -101,8 +100,7 @@ namespace MediaBrowser.Api.Tests
             // Finish initializing the app host
             var appHost = (CoreAppHost)testServer.Services.GetRequiredService<IApplicationHost>();
             appHost.ServiceProvider = testServer.Services;
-            appHost.InitializeServices();
-            appHost.FindParts();
+            appHost.InitializeServices().Wait();
             appHost.RunStartupTasksAsync().Wait();
 
             return testServer;