Ver código fonte

Merge remote-tracking branch 'jellyfin/master'

artiume 5 anos atrás
pai
commit
918df5e352
100 arquivos alterados com 703 adições e 866 exclusões
  1. 96 0
      .ci/azure-pipelines-compat.yml
  2. 101 0
      .ci/azure-pipelines-main.yml
  3. 65 0
      .ci/azure-pipelines-test.yml
  4. 82 0
      .ci/azure-pipelines-windows.yml
  5. 25 299
      .ci/azure-pipelines.yml
  6. 0 46
      .ci/publish-nightly.yml
  7. 0 48
      .ci/publish-release.yml
  8. 4 2
      .github/ISSUE_TEMPLATE/media_playback.md
  9. 1 0
      CONTRIBUTORS.md
  10. 3 1
      Dockerfile
  11. 2 5
      Dockerfile.arm
  12. 2 5
      Dockerfile.arm64
  13. 2 2
      DvdLib/Ifo/Dvd.cs
  14. 8 8
      Emby.Dlna/Api/DlnaServerService.cs
  15. 5 2
      Emby.Dlna/ConnectionManager/ConnectionManager.cs
  16. 5 2
      Emby.Dlna/ContentDirectory/ContentDirectory.cs
  17. 4 4
      Emby.Dlna/ContentDirectory/ControlHandler.cs
  18. 1 1
      Emby.Dlna/DlnaManager.cs
  19. 3 1
      Emby.Dlna/IUpnpService.cs
  20. 5 2
      Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs
  21. 39 55
      Emby.Dlna/Service/BaseControlHandler.cs
  22. 2 2
      Emby.Dlna/Service/ControlErrorHandler.cs
  23. 1 2
      Emby.Drawing/ImageProcessor.cs
  24. 22 22
      Emby.Naming/Common/NamingOptions.cs
  25. 1 1
      Emby.Naming/Emby.Naming.csproj
  26. 1 2
      Emby.Naming/Subtitles/SubtitleParser.cs
  27. 1 1
      Emby.Naming/TV/EpisodePathParser.cs
  28. 15 56
      Emby.Naming/Video/CleanDateTimeParser.cs
  29. 18 11
      Emby.Naming/Video/CleanDateTimeResult.cs
  30. 18 25
      Emby.Naming/Video/CleanStringParser.cs
  31. 0 20
      Emby.Naming/Video/CleanStringResult.cs
  32. 1 1
      Emby.Naming/Video/ExtraResolver.cs
  33. 3 1
      Emby.Naming/Video/ExtraResult.cs
  34. 3 2
      Emby.Naming/Video/ExtraRule.cs
  35. 2 2
      Emby.Naming/Video/StubResolver.cs
  36. 4 2
      Emby.Naming/Video/VideoFileInfo.cs
  37. 5 4
      Emby.Naming/Video/VideoListResolver.cs
  38. 6 5
      Emby.Naming/Video/VideoResolver.cs
  39. 4 3
      Emby.Photos/Emby.Photos.csproj
  40. 1 0
      Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
  41. 1 0
      Emby.Server.Implementations/Activity/ActivityManager.cs
  42. 1 0
      Emby.Server.Implementations/Activity/ActivityRepository.cs
  43. 6 9
      Emby.Server.Implementations/ApplicationHost.cs
  44. 1 0
      Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs
  45. 1 0
      Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
  46. 1 0
      Emby.Server.Implementations/Channels/ChannelImageProvider.cs
  47. 1 0
      Emby.Server.Implementations/Channels/ChannelManager.cs
  48. 1 0
      Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
  49. 1 0
      Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
  50. 1 0
      Emby.Server.Implementations/Collections/CollectionImageProvider.cs
  51. 1 0
      Emby.Server.Implementations/Collections/CollectionManager.cs
  52. 1 1
      Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
  53. 1 0
      Emby.Server.Implementations/Data/BaseSqliteRepository.cs
  54. 1 0
      Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
  55. 1 0
      Emby.Server.Implementations/Data/ManagedConnection.cs
  56. 1 0
      Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
  57. 1 0
      Emby.Server.Implementations/Data/SqliteExtensions.cs
  58. 1 0
      Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
  59. 1 0
      Emby.Server.Implementations/Data/SqliteUserRepository.cs
  60. 1 0
      Emby.Server.Implementations/Devices/DeviceId.cs
  61. 2 1
      Emby.Server.Implementations/Devices/DeviceManager.cs
  62. 1 0
      Emby.Server.Implementations/Diagnostics/CommonProcess.cs
  63. 1 0
      Emby.Server.Implementations/Diagnostics/ProcessFactory.cs
  64. 1 0
      Emby.Server.Implementations/Dto/DtoService.cs
  65. 8 8
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  66. 1 0
      Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs
  67. 1 0
      Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
  68. 1 0
      Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
  69. 1 0
      Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
  70. 21 37
      Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
  71. 1 0
      Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
  72. 2 2
      Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
  73. 8 7
      Emby.Server.Implementations/HttpServer/FileWriter.cs
  74. 1 1
      Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
  75. 5 4
      Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
  76. 1 0
      Emby.Server.Implementations/HttpServer/IHttpListener.cs
  77. 1 0
      Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
  78. 1 0
      Emby.Server.Implementations/HttpServer/Security/AuthService.cs
  79. 1 0
      Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
  80. 1 0
      Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
  81. 1 0
      Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs
  82. 1 0
      Emby.Server.Implementations/IO/FileRefresher.cs
  83. 1 0
      Emby.Server.Implementations/IO/LibraryMonitor.cs
  84. 20 101
      Emby.Server.Implementations/IO/ManagedFileSystem.cs
  85. 1 0
      Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs
  86. 1 0
      Emby.Server.Implementations/IO/StreamHelper.cs
  87. 1 0
      Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
  88. 7 6
      Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
  89. 1 0
      Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
  90. 11 42
      Emby.Server.Implementations/Library/LibraryManager.cs
  91. 1 0
      Emby.Server.Implementations/Library/LiveStreamHelper.cs
  92. 3 2
      Emby.Server.Implementations/Library/MediaSourceManager.cs
  93. 1 0
      Emby.Server.Implementations/Library/MediaStreamSelector.cs
  94. 1 0
      Emby.Server.Implementations/Library/MusicManager.cs
  95. 1 0
      Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
  96. 1 0
      Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
  97. 1 0
      Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
  98. 1 0
      Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
  99. 1 0
      Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
  100. 1 0
      Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs

+ 96 - 0
.ci/azure-pipelines-compat.yml

@@ -0,0 +1,96 @@
+parameters:
+  - name: Packages
+    type: object
+    default: {}
+  - name: LinuxImage
+    type: string
+    default: "ubuntu-latest"
+  - name: DotNetSdkVersion
+    type: string
+    default: 3.1.100
+
+jobs:
+  - job: CompatibilityCheck
+    displayName: Compatibility Check
+    pool:
+      vmImage: "${{ parameters.LinuxImage }}"
+    # only execute for pull requests
+    condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
+    strategy:
+      matrix:
+        ${{ each Package in parameters.Packages }}:
+          ${{ Package.key }}:
+            NugetPackageName: ${{ Package.value.NugetPackageName }}
+            AssemblyFileName: ${{ Package.value.AssemblyFileName }}
+      maxParallel: 2
+    dependsOn: MainBuild
+    steps:
+      - checkout: none
+
+      - task: UseDotNet@2
+        displayName: "Update DotNet"
+        inputs:
+          packageType: sdk
+          version: ${{ parameters.DotNetSdkVersion }}
+
+      - task: DownloadPipelineArtifact@2
+        displayName: "Download New Assembly Build Artifact"
+        inputs:
+          source: "current"
+          artifact: "$(NugetPackageName)"
+          path: "$(System.ArtifactsDirectory)/new-artifacts"
+          runVersion: "latest"
+
+      - task: CopyFiles@2
+        displayName: "Copy New Assembly Build Artifact"
+        inputs:
+          sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
+          contents: "**/*.dll"
+          targetFolder: $(System.ArtifactsDirectory)/new-release
+          cleanTargetFolder: true
+          overWrite: true
+          flattenFolders: true
+
+      - task: DownloadPipelineArtifact@2
+        displayName: "Download Reference Assembly Build Artifact"
+        inputs:
+          source: "specific"
+          artifact: "$(NugetPackageName)"
+          path: "$(System.ArtifactsDirectory)/current-artifacts"
+          project: "$(System.TeamProjectId)"
+          pipeline: "$(System.DefinitionId)"
+          runVersion: "latestFromBranch"
+          runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
+
+      - task: CopyFiles@2
+        displayName: "Copy Reference Assembly Build Artifact"
+        inputs:
+          sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
+          contents: "**/*.dll"
+          targetFolder: $(System.ArtifactsDirectory)/current-release
+          cleanTargetFolder: true
+          overWrite: true
+          flattenFolders: true
+
+      - task: DownloadGitHubRelease@0
+        displayName: "Download ABI Compatibility Check Tool"
+        inputs:
+          connection: Jellyfin Release Download
+          userRepository: EraYaN/dotnet-compatibility
+          defaultVersionType: "latest"
+          itemPattern: "**-ci.zip"
+          downloadPath: "$(System.ArtifactsDirectory)"
+
+      - task: ExtractFiles@1
+        displayName: "Extract ABI Compatibility Check Tool"
+        inputs:
+          archiveFilePatterns: "$(System.ArtifactsDirectory)/*-ci.zip"
+          destinationFolder: $(System.ArtifactsDirectory)/tools
+          cleanDestinationFolder: true
+
+      # The `--warnings-only` switch will swallow the return code and not emit any errors.
+      - task: CmdLine@2
+        displayName: "Execute ABI Compatibility Check Tool"
+        inputs:
+          script: "dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only"
+          workingDirectory: $(System.ArtifactsDirectory)

+ 101 - 0
.ci/azure-pipelines-main.yml

@@ -0,0 +1,101 @@
+parameters:
+  LinuxImage: "ubuntu-latest"
+  RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj"
+  DotNetSdkVersion: 3.1.100
+
+jobs:
+  - job: MainBuild
+    displayName: Main Build
+    strategy:
+      matrix:
+        Release:
+          BuildConfiguration: Release
+        Debug:
+          BuildConfiguration: Debug
+      maxParallel: 2
+    pool:
+      vmImage: "${{ parameters.LinuxImage }}"
+    steps:
+      - checkout: self
+        clean: true
+        submodules: true
+        persistCredentials: true
+
+      - task: CmdLine@2
+        displayName: "Clone Web Client (Master, Release, or Tag)"
+        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'))
+        inputs:
+          script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
+
+      - task: CmdLine@2
+        displayName: "Clone Web Client (PR)"
+        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'))
+        inputs:
+          script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
+
+      - 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'))
+        inputs:
+          versionSpec: "10.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
+
+      - task: UseDotNet@2
+        displayName: "Update DotNet"
+        inputs:
+          packageType: sdk
+          version: ${{ parameters.DotNetSdkVersion }}
+
+      - task: DotNetCoreCLI@2
+        displayName: "Publish Server"
+        inputs:
+          command: publish
+          publishWebProjects: false
+          projects: "${{ parameters.RestoreBuildProjects }}"
+          arguments: "--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)"
+          zipAfterPublish: false
+
+      - task: PublishPipelineArtifact@0
+        displayName: "Publish Artifact Naming"
+        condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
+        inputs:
+          targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll"
+          artifactName: "Jellyfin.Naming"
+
+      - task: PublishPipelineArtifact@0
+        displayName: "Publish Artifact Controller"
+        condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
+        inputs:
+          targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
+          artifactName: "Jellyfin.Controller"
+
+      - task: PublishPipelineArtifact@0
+        displayName: "Publish Artifact Model"
+        condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
+        inputs:
+          targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
+          artifactName: "Jellyfin.Model"
+
+      - task: PublishPipelineArtifact@0
+        displayName: "Publish Artifact Common"
+        condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
+        inputs:
+          targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
+          artifactName: "Jellyfin.Common"

+ 65 - 0
.ci/azure-pipelines-test.yml

@@ -0,0 +1,65 @@
+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
+
+jobs:
+  - job: MainTest
+    displayName: Main Test
+    strategy:
+      matrix:
+        ${{ each imageName in parameters.ImageNames }}:
+          ${{ imageName.key }}:
+            ImageName: ${{ imageName.value }}
+      maxParallel: 3
+    pool:
+      vmImage: "$(ImageName)"
+    steps:
+      - checkout: self
+        clean: true
+        submodules: true
+        persistCredentials: false
+
+      - task: UseDotNet@2
+        displayName: "Update DotNet"
+        inputs:
+          packageType: sdk
+          version: ${{ parameters.DotNetSdkVersion }}
+
+      - task: DotNetCoreCLI@2
+        displayName: Run .NET Core CLI tests
+        inputs:
+          command: "test"
+          projects: ${{ parameters.TestProjects }}
+          arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal "-p:GenerateDocumentationFile=False"'
+          publishTestResults: true
+          testRunTitle: $(Agent.JobName)
+          workingDirectory: "$(Build.SourcesDirectory)"
+
+      - 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)
+        inputs:
+          reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
+          targetdir: "$(Agent.TempDirectory)/merged/"
+          reporttypes: "Cobertura"
+
+      ## 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
+        inputs:
+          codeCoverageTool: "cobertura"
+          #summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
+          summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
+          pathToSources: $(Build.SourcesDirectory)
+          failIfCoverageEmpty: true

+ 82 - 0
.ci/azure-pipelines-windows.yml

@@ -0,0 +1,82 @@
+parameters:
+  WindowsImage: "windows-latest"
+  TestProjects: "tests/**/*Tests.csproj"
+  DotNetSdkVersion: 3.1.100
+
+jobs:
+  - job: PublishWindows
+    displayName: Publish Windows
+    pool:
+      vmImage: ${{ parameters.WindowsImage }}
+    steps:
+      - checkout: self
+        clean: true
+        submodules: true
+        persistCredentials: true
+
+      - task: CmdLine@2
+        displayName: "Clone Web Client (Master, Release, or Tag)"
+        condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')), 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"
+
+      - task: CmdLine@2
+        displayName: "Clone Web Client (PR)"
+        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), in(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"
+
+      - 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')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+        inputs:
+          versionSpec: "10.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')), 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')), 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
+
+      - task: CmdLine@2
+        displayName: "Clone UX Repository"
+        inputs:
+          script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
+
+      - task: PowerShell@2
+        displayName: "Build NSIS Installer"
+        inputs:
+          targetType: "filePath"
+          filePath: ./deployment/windows/build-jellyfin.ps1
+          arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
+          errorActionPreference: "stop"
+          workingDirectory: $(Build.SourcesDirectory)
+
+      - task: CopyFiles@2
+        displayName: "Copy NSIS Installer"
+        inputs:
+          sourceFolder: $(Build.SourcesDirectory)/deployment/windows/
+          contents: "jellyfin*.exe"
+          targetFolder: $(System.ArtifactsDirectory)/setup
+          cleanTargetFolder: true
+          overWrite: true
+          flattenFolders: true
+
+      - task: PublishPipelineArtifact@0
+        displayName: "Publish Artifact Setup"
+        condition: succeeded()
+        inputs:
+          targetPath: "$(build.artifactstagingdirectory)/setup"
+          artifactName: "Jellyfin Server Setup"

+ 25 - 299
.ci/azure-pipelines.yml

@@ -2,9 +2,11 @@ name: $(Date:yyyyMMdd)$(Rev:.r)
 
 variables:
   - name: TestProjects
-    value: 'tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj'
+    value: "tests/**/*Tests.csproj"
   - name: RestoreBuildProjects
-    value: 'Jellyfin.Server/Jellyfin.Server.csproj'
+    value: "Jellyfin.Server/Jellyfin.Server.csproj"
+  - name: DotNetSdkVersion
+    value: 3.1.100
 
 pr:
   autoCancel: true
@@ -13,232 +15,26 @@ trigger:
   batch: true
 
 jobs:
-  - job: main_build
-    displayName: Main Build
-    pool:
-      vmImage: ubuntu-latest
-    strategy:
-      matrix:
-        Release:
-          BuildConfiguration: Release
-        Debug:
-          BuildConfiguration: Debug
-      maxParallel: 2
-    steps:
-    - checkout: self
-      clean: true
-      submodules: true
-      persistCredentials: true
-
-    - task: CmdLine@2
-      displayName: "Clone Web Client (Master, Release, or Tag)"
-      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'))
-      inputs:
-        script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
-
-    - task: CmdLine@2
-      displayName: "Clone Web Client (PR)"
-      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'))
-      inputs:
-        script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
-
-    - 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'))
-      inputs:
-        versionSpec: '10.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 # Optional
-        contents: '**'
-        targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
-        cleanTargetFolder: true # Optional
-        overWrite: true # Optional
-        flattenFolders: false # Optional
-
-    - task: UseDotNet@2
-      displayName: 'Update DotNet'
-      inputs:
-        packageType: sdk
-        version: 3.1.100
-
-    - task: DotNetCoreCLI@2
-      displayName: 'Publish Server'
-      inputs:
-        command: publish
-        publishWebProjects: false
-        projects: '$(RestoreBuildProjects)'
-        arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'
-        zipAfterPublish: false
-
-    - task: PublishPipelineArtifact@0
-      displayName: 'Publish Artifact Naming'
-      condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
-      inputs:
-        targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
-        artifactName: 'Jellyfin.Naming'
-
-    - task: PublishPipelineArtifact@0
-      displayName: 'Publish Artifact Controller'
-      condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
-      inputs:
-        targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
-        artifactName: 'Jellyfin.Controller'
-
-    - task: PublishPipelineArtifact@0
-      displayName: 'Publish Artifact Model'
-      condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
-      inputs:
-        targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
-        artifactName: 'Jellyfin.Model'
-
-    - task: PublishPipelineArtifact@0
-      displayName: 'Publish Artifact Common'
-      condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
-      inputs:
-        targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
-        artifactName: 'Jellyfin.Common'
-
-  - job: main_test
-    displayName: Main Test
-    pool:
-      vmImage: windows-latest
-    steps:
-    - checkout: self
-      clean: true
-      submodules: true
-      persistCredentials: false
-
-    - task: DotNetCoreCLI@2
-      displayName: Build
-      inputs:
-        command: build
-        publishWebProjects: false
-        projects: '$(TestProjects)'
-        arguments: '--configuration $(BuildConfiguration)'
-        zipAfterPublish: false
-
-    - task: VisualStudioTestPlatformInstaller@1
-      inputs:
-        packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare
-        versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion
-
-    - task: VSTest@2
-      inputs:
-        testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun
-        testAssemblyVer2: | # Required when testSelector == TestAssemblies
-          **\bin\$(BuildConfiguration)\**\*test*.dll
-          !**\obj\**
-          !**\xunit.runner.visualstudio.testadapter.dll
-          !**\xunit.runner.visualstudio.dotnetcore.testadapter.dll
-        searchFolder: '$(System.DefaultWorkingDirectory)'
-        runInParallel: True # Optional
-        runTestsInIsolation: True # Optional
-        codeCoverageEnabled: True # Optional
-        configuration: 'Debug' # Optional
-        publishRunAttachments: true # Optional
-
-  - job: main_build_win
-    displayName: Publish Windows
-    pool:
-      vmImage: windows-latest
-    strategy:
-      matrix:
-        Release:
-          BuildConfiguration: Release
-      maxParallel: 2
-    steps:
-    - checkout: self
-      clean: true
-      submodules: true
-      persistCredentials: true
-
-    - task: CmdLine@2
-      displayName: "Clone Web Client (Master, Release, or Tag)"
-      condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')) ,eq(variables['BuildConfiguration'], 'Release'), 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'
-
-    - task: CmdLine@2
-      displayName: "Clone Web Client (PR)"
-      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'))
-      inputs:
-        script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
-
-    - 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'))
-      inputs:
-        versionSpec: '10.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 # Optional
-        contents: '**'
-        targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
-        cleanTargetFolder: true # Optional
-        overWrite: true # Optional
-        flattenFolders: false # Optional
-
-    - task: CmdLine@2
-      displayName: 'Clone UX Repository'
-      inputs:
-        script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
-
-    - task: PowerShell@2
-      displayName: 'Build NSIS Installer'
-      inputs:
-        targetType: 'filePath' # Optional. Options: filePath, inline
-        filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
-        arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
-        errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
-        workingDirectory: $(Build.SourcesDirectory) # Optional
-
-    - task: CopyFiles@2
-      displayName: 'Copy NSIS Installer'
-      inputs:
-        sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional
-        contents: 'jellyfin*.exe'
-        targetFolder: $(System.ArtifactsDirectory)/setup
-        cleanTargetFolder: true # Optional
-        overWrite: true # Optional
-        flattenFolders: true # Optional
-
-    - task: PublishPipelineArtifact@0
-      displayName: 'Publish Artifact Setup'
-      condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
-      inputs:
-        targetPath: '$(build.artifactstagingdirectory)/setup'
-        artifactName: 'Jellyfin Server Setup'
-
-  - job: dotnet_compat
-    displayName: Compatibility Check
-    pool:
-      vmImage: ubuntu-latest
-    dependsOn: main_build
-    # only execute for pull requests
-    condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
-    strategy:
-      matrix:
+  - template: azure-pipelines-main.yml
+    parameters:
+      LinuxImage: "ubuntu-latest"
+      RestoreBuildProjects: $(RestoreBuildProjects)
+
+  - template: azure-pipelines-test.yml
+    parameters:
+      ImageNames:
+        Linux: "ubuntu-latest"
+        Windows: "windows-latest"
+        macOS: "macos-latest"
+
+  - template: azure-pipelines-windows.yml
+    parameters:
+      WindowsImage: "windows-latest"
+      TestProjects: $(TestProjects)
+
+  - template: azure-pipelines-compat.yml
+    parameters:
+      Packages:
         Naming:
           NugetPackageName: Jellyfin.Naming
           AssemblyFileName: Emby.Naming.dll
@@ -251,74 +47,4 @@ jobs:
         Common:
           NugetPackageName: Jellyfin.Common
           AssemblyFileName: MediaBrowser.Common.dll
-      maxParallel: 2
-    steps:
-    - checkout: none
-    
-    - task: UseDotNet@2
-      displayName: 'Update DotNet'
-      inputs:
-        packageType: sdk
-        version: 3.1.100
-
-    - task: DownloadPipelineArtifact@2
-      displayName: 'Download New Assembly Build Artifact'
-      inputs:
-        source: 'current' # Options: current, specific
-        artifact: '$(NugetPackageName)' # Optional
-        path: '$(System.ArtifactsDirectory)/new-artifacts'
-        runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific
-
-    - task: CopyFiles@2
-      displayName: 'Copy New Assembly Build Artifact'
-      inputs:
-        sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
-        contents: '**/*.dll'
-        targetFolder: $(System.ArtifactsDirectory)/new-release
-        cleanTargetFolder: true # Optional
-        overWrite: true # Optional
-        flattenFolders: true # Optional
-
-    - task: DownloadPipelineArtifact@2
-      displayName: 'Download Reference Assembly Build Artifact'
-      inputs:
-        source: 'specific' # Options: current, specific
-        artifact: '$(NugetPackageName)' # Optional
-        path: '$(System.ArtifactsDirectory)/current-artifacts'
-        project: '$(System.TeamProjectId)' # Required when source == Specific
-        pipeline: '$(System.DefinitionId)' # Required when source == Specific
-        runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
-        runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch
-
-    - task: CopyFiles@2
-      displayName: 'Copy Reference Assembly Build Artifact'
-      inputs:
-        sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
-        contents: '**/*.dll'
-        targetFolder: $(System.ArtifactsDirectory)/current-release
-        cleanTargetFolder: true # Optional
-        overWrite: true # Optional
-        flattenFolders: true # Optional
-
-    - task: DownloadGitHubRelease@0
-      displayName: 'Download ABI Compatibility Check Tool'
-      inputs:
-        connection: Jellyfin Release Download
-        userRepository: EraYaN/dotnet-compatibility
-        defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
-        itemPattern: '**-ci.zip' # Optional
-        downloadPath: '$(System.ArtifactsDirectory)'
-
-    - task: ExtractFiles@1
-      displayName: 'Extract ABI Compatibility Check Tool'
-      inputs:
-        archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip'
-        destinationFolder: $(System.ArtifactsDirectory)/tools
-        cleanDestinationFolder: true
-
-    # The `--warnings-only` switch will swallow the return code and not emit any errors.
-    - task: CmdLine@2
-      displayName: 'Execute ABI Compatibility Check Tool'
-      inputs:
-        script: 'dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
-        workingDirectory: $(System.ArtifactsDirectory) # Optional
+      LinuxImage: "ubuntu-latest"

+ 0 - 46
.ci/publish-nightly.yml

@@ -1,46 +0,0 @@
-name: Nightly-$(date:yyyyMMdd).$(rev:r)
-
-variables:
-  - name: Version
-    value: '1.0.0'
-
-trigger: none
-pr: none
-
-jobs:
-  - job: publish_artifacts_nightly
-    displayName: Publish Artifacts Nightly
-    pool:
-      vmImage: ubuntu-latest
-    steps:
-    - checkout: none
-    - task: DownloadPipelineArtifact@2
-      displayName: Download the Windows Setup Artifact
-      inputs:
-        source: 'specific' # Options: current, specific
-        artifact: 'Jellyfin Server Setup' # Optional
-        path: '$(System.ArtifactsDirectory)/win-installer'
-        project: '$(System.TeamProjectId)' # Required when source == Specific
-        pipelineId: 1 # Required when source == Specific
-        runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
-        runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
-
-    - task: SSH@0
-      displayName: 'Create Drop directory'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_nightly_azure_upload'
-
-    - task: CopyFilesOverSSH@0
-      displayName: 'Copy the Windows Setup to the Repo'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
-        contents: 'jellyfin_*.exe'
-        targetFolder: '/srv/incoming/jellyfin_nightly_azure_upload/win-installer'
-
-    - task: SSH@0
-      displayName: 'Clean up SCP symlink'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        commands: 'rm -f /srv/incoming/jellyfin_nightly_azure_upload'

+ 0 - 48
.ci/publish-release.yml

@@ -1,48 +0,0 @@
-name: Release-$(Version)-$(date:yyyyMMdd).$(rev:r)
-
-variables:
-  - name: Version
-    value: '1.0.0'
-  - name: UsedRunId
-    value: 0
-
-trigger: none
-pr: none
-
-jobs:
-  - job: publish_artifacts_release
-    displayName: Publish Artifacts Release
-    pool:
-      vmImage: ubuntu-latest
-    steps:
-    - checkout: none
-    - task: DownloadPipelineArtifact@2
-      displayName: Download the Windows Setup Artifact
-      inputs:
-        source: 'specific' # Options: current, specific
-        artifact: 'Jellyfin Server Setup' # Optional
-        path: '$(System.ArtifactsDirectory)/win-installer'
-        project: '$(System.TeamProjectId)' # Required when source == Specific
-        pipelineId: 1 # Required when source == Specific
-        runVersion: 'specific' # Required when source == Specific. Options: latest, latestFromBranch, specific
-        runId: $(UsedRunId)
-
-    - task: SSH@0
-      displayName: 'Create Drop directory'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_release_azure_upload'
-
-    - task: CopyFilesOverSSH@0
-      displayName: 'Copy the Windows Setup to the Repo'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
-        contents: 'jellyfin_*.exe'
-        targetFolder: '/srv/incoming/jellyfin_release_azure_upload/win-installer'
-
-    - task: SSH@0
-      displayName: 'Clean up SCP symlink'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        commands: 'rm -f /srv/incoming/jellyfin_release_azure_upload'

+ 4 - 2
.github/ISSUE_TEMPLATE/media_playback.md

@@ -11,7 +11,10 @@ assignees: ''
 <!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. -->
 
 **Logs**
-<!-- Please paste any log message from during the playback issue, for example the ffmpeg command line can be very useful. -->
+<!-- Please paste any log messages from during the playback issue. -->
+
+**FFmpeg Logs**
+<!-- Please paste any FFmpeg logs if remuxing or transcoding appears to be part of the issue. -->
 
 **Stats for Nerds Screenshots**
 <!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
@@ -29,4 +32,3 @@ assignees: ''
  - Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron]
  - Browser (if Web client): [e.g. Firefox, Chrome, Safari]
  - Client and Browser Version: [e.g. 10.3.4 and 68.0]
-

+ 1 - 0
CONTRIBUTORS.md

@@ -32,6 +32,7 @@
  - [nevado](https://github.com/nevado)
  - [mark-monteiro](https://github.com/mark-monteiro)
  - [ullmie02](https://github.com/ullmie02)
+ - [pR0Ps](https://github.com/pR0Ps)
 
 # Emby Contributors
 

+ 3 - 1
Dockerfile

@@ -14,7 +14,9 @@ FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder
 WORKDIR /repo
 COPY . .
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+# because of changes in docker and systemd we need to not build in parallel at the moment
+# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
 
 FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
 FROM debian:buster-slim

+ 2 - 5
Dockerfile.arm

@@ -1,5 +1,3 @@
-# Requires binfm_misc registration
-# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
 ARG DOTNET_VERSION=3.1
 
 
@@ -23,11 +21,10 @@ RUN find . -type d -name obj | xargs -r rm -r
 RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
 
 
-FROM multiarch/qemu-user-static:x86_64-arm as qemu
-FROM debian:stretch-slim-arm32v7
-COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
+FROM debian:buster-slim
 RUN apt-get update \
  && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
+ libssl-dev \
  && rm -rf /var/lib/apt/lists/* \
  && mkdir -p /cache /config /media \
  && chmod 777 /cache /config /media

+ 2 - 5
Dockerfile.arm64

@@ -1,5 +1,3 @@
-# Requires binfm_misc registration
-# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
 ARG DOTNET_VERSION=3.1
 
 
@@ -23,11 +21,10 @@ RUN find . -type d -name obj | xargs -r rm -r
 RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
 
 
-FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
-FROM debian:stretch-slim-arm64v8
-COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
+FROM debian:buster-slim
 RUN apt-get update \
  && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
+ libssl-dev \
  && rm -rf /var/lib/apt/lists/* \
  && mkdir -p /cache /config /media \
  && chmod 777 /cache /config /media

+ 2 - 2
DvdLib/Ifo/Dvd.cs

@@ -42,7 +42,7 @@ namespace DvdLib.Ifo
             }
             else
             {
-                using (var vmgFs = _fileSystem.GetFileStream(vmgPath.FullName, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+                using (var vmgFs = new FileStream(vmgPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
                 {
                     using (var vmgRead = new BigEndianBinaryReader(vmgFs))
                     {
@@ -95,7 +95,7 @@ namespace DvdLib.Ifo
         {
             VTSPaths[vtsNum] = vtsPath;
 
-            using (var vtsFs = _fileSystem.GetFileStream(vtsPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+            using (var vtsFs = new FileStream(vtsPath, FileMode.Open, FileAccess.Read, FileShare.Read))
             {
                 using (var vtsRead = new BigEndianBinaryReader(vtsFs))
                 {

+ 8 - 8
Emby.Dlna/Api/DlnaServerService.cs

@@ -170,32 +170,32 @@ namespace Emby.Dlna.Api
             return _resultFactory.GetResult(Request, xml, XMLContentType);
         }
 
-        public object Post(ProcessMediaReceiverRegistrarControlRequest request)
+        public async Task<object> Post(ProcessMediaReceiverRegistrarControlRequest request)
         {
-            var response = PostAsync(request.RequestStream, MediaReceiverRegistrar);
+            var response = await PostAsync(request.RequestStream, MediaReceiverRegistrar).ConfigureAwait(false);
 
             return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
         }
 
-        public object Post(ProcessContentDirectoryControlRequest request)
+        public async Task<object> Post(ProcessContentDirectoryControlRequest request)
         {
-            var response = PostAsync(request.RequestStream, ContentDirectory);
+            var response = await PostAsync(request.RequestStream, ContentDirectory).ConfigureAwait(false);
 
             return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
         }
 
-        public object Post(ProcessConnectionManagerControlRequest request)
+        public async Task<object> Post(ProcessConnectionManagerControlRequest request)
         {
-            var response = PostAsync(request.RequestStream, ConnectionManager);
+            var response = await PostAsync(request.RequestStream, ConnectionManager).ConfigureAwait(false);
 
             return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
         }
 
-        private ControlResponse PostAsync(Stream requestStream, IUpnpService service)
+        private Task<ControlResponse> PostAsync(Stream requestStream, IUpnpService service)
         {
             var id = GetPathValue(2).ToString();
 
-            return service.ProcessControlRequest(new ControlRequest
+            return service.ProcessControlRequestAsync(new ControlRequest
             {
                 Headers = Request.Headers,
                 InputXml = requestStream,

+ 5 - 2
Emby.Dlna/ConnectionManager/ConnectionManager.cs

@@ -1,3 +1,4 @@
+using System.Threading.Tasks;
 using Emby.Dlna.Service;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
@@ -20,17 +21,19 @@ namespace Emby.Dlna.ConnectionManager
             _logger = logger;
         }
 
+        /// <inheritdoc />
         public string GetServiceXml()
         {
             return new ConnectionManagerXmlBuilder().GetXml();
         }
 
-        public ControlResponse ProcessControlRequest(ControlRequest request)
+        /// <inheritdoc />
+        public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
         {
             var profile = _dlna.GetProfile(request.Headers) ??
                          _dlna.GetDefaultProfile();
 
-            return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request);
+            return new ControlHandler(_config, _logger, profile).ProcessControlRequestAsync(request);
         }
     }
 }

+ 5 - 2
Emby.Dlna/ContentDirectory/ContentDirectory.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Threading.Tasks;
 using Emby.Dlna.Service;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
@@ -66,12 +67,14 @@ namespace Emby.Dlna.ContentDirectory
             }
         }
 
+        /// <inheritdoc />
         public string GetServiceXml()
         {
             return new ContentDirectoryXmlBuilder().GetXml();
         }
 
-        public ControlResponse ProcessControlRequest(ControlRequest request)
+        /// <inheritdoc />
+        public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
         {
             var profile = _dlna.GetProfile(request.Headers) ??
                           _dlna.GetDefaultProfile();
@@ -96,7 +99,7 @@ namespace Emby.Dlna.ContentDirectory
                 _userViewManager,
                 _mediaEncoder,
                 _tvSeriesManager)
-                .ProcessControlRequest(request);
+                .ProcessControlRequestAsync(request);
         }
 
         private User GetUser(DeviceProfile profile)

+ 4 - 4
Emby.Dlna/ContentDirectory/ControlHandler.cs

@@ -76,7 +76,7 @@ namespace Emby.Dlna.ContentDirectory
             _profile = profile;
             _config = config;
 
-            _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, mediaEncoder);
+            _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, mediaEncoder);
         }
 
         protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
@@ -771,11 +771,11 @@ namespace Emby.Dlna.ContentDirectory
                 })
                 .ToArray();
 
-            return new QueryResult<ServerItem>
+            return ApplyPaging(new QueryResult<ServerItem>
             {
                 Items = folders,
                 TotalRecordCount = folders.Length
-            };
+            }, startIndex, limit);
         }
 
         private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
@@ -1336,7 +1336,7 @@ namespace Emby.Dlna.ContentDirectory
                 };
             }
 
-            _logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
+            Logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
 
             return new ServerItem(_libraryManager.GetUserRootFolder());
         }

+ 1 - 1
Emby.Dlna/DlnaManager.cs

@@ -385,7 +385,7 @@ namespace Emby.Dlna
                     {
                         Directory.CreateDirectory(systemProfilesPath);
 
-                        using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+                        using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
                         {
                             await stream.CopyToAsync(fileStream);
                         }

+ 3 - 1
Emby.Dlna/IUpnpService.cs

@@ -1,3 +1,5 @@
+using System.Threading.Tasks;
+
 namespace Emby.Dlna
 {
     public interface IUpnpService
@@ -13,6 +15,6 @@ namespace Emby.Dlna
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>ControlResponse.</returns>
-        ControlResponse ProcessControlRequest(ControlRequest request);
+        Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request);
     }
 }

+ 5 - 2
Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs

@@ -1,3 +1,4 @@
+using System.Threading.Tasks;
 using Emby.Dlna.Service;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
@@ -15,17 +16,19 @@ namespace Emby.Dlna.MediaReceiverRegistrar
             _config = config;
         }
 
+        /// <inheritdoc />
         public string GetServiceXml()
         {
             return new MediaReceiverRegistrarXmlBuilder().GetXml();
         }
 
-        public ControlResponse ProcessControlRequest(ControlRequest request)
+        /// <inheritdoc />
+        public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
         {
             return new ControlHandler(
                 _config,
                 Logger)
-                .ProcessControlRequest(request);
+                .ProcessControlRequestAsync(request);
         }
     }
 }

+ 39 - 55
Emby.Dlna/Service/BaseControlHandler.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Text;
+using System.Threading.Tasks;
 using System.Xml;
 using Emby.Dlna.Didl;
 using MediaBrowser.Controller.Configuration;
@@ -15,44 +16,34 @@ namespace Emby.Dlna.Service
     {
         private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
 
-        protected readonly IServerConfigurationManager Config;
-        protected readonly ILogger _logger;
+        protected IServerConfigurationManager Config { get; }
+        protected ILogger Logger { get; }
 
         protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
         {
             Config = config;
-            _logger = logger;
+            Logger = logger;
         }
 
-        public ControlResponse ProcessControlRequest(ControlRequest request)
+        public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
         {
             try
             {
-                var enableDebugLogging = Config.GetDlnaConfiguration().EnableDebugLog;
-
-                if (enableDebugLogging)
-                {
-                    LogRequest(request);
-                }
-
-                var response = ProcessControlRequestInternal(request);
-
-                if (enableDebugLogging)
-                {
-                    LogResponse(response);
-                }
+                LogRequest(request);
 
+                var response = await ProcessControlRequestInternalAsync(request).ConfigureAwait(false);
+                LogResponse(response);
                 return response;
             }
             catch (Exception ex)
             {
-                _logger.LogError(ex, "Error processing control request");
+                Logger.LogError(ex, "Error processing control request");
 
-                return new ControlErrorHandler().GetResponse(ex);
+                return ControlErrorHandler.GetResponse(ex);
             }
         }
 
-        private ControlResponse ProcessControlRequestInternal(ControlRequest request)
+        private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
         {
             ControlRequestInfo requestInfo = null;
 
@@ -63,16 +54,17 @@ namespace Emby.Dlna.Service
                     ValidationType = ValidationType.None,
                     CheckCharacters = false,
                     IgnoreProcessingInstructions = true,
-                    IgnoreComments = true
+                    IgnoreComments = true,
+                    Async = true
                 };
 
                 using (var reader = XmlReader.Create(streamReader, readerSettings))
                 {
-                    requestInfo = ParseRequest(reader);
+                    requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false);
                 }
             }
 
-            _logger.LogDebug("Received control request {0}", requestInfo.LocalName);
+            Logger.LogDebug("Received control request {0}", requestInfo.LocalName);
 
             var result = GetResult(requestInfo.LocalName, requestInfo.Headers);
 
@@ -114,17 +106,15 @@ namespace Emby.Dlna.Service
                 IsSuccessful = true
             };
 
-            //logger.LogDebug(xml);
-
             controlResponse.Headers.Add("EXT", string.Empty);
 
             return controlResponse;
         }
 
-        private ControlRequestInfo ParseRequest(XmlReader reader)
+        private async Task<ControlRequestInfo> ParseRequestAsync(XmlReader reader)
         {
-            reader.MoveToContent();
-            reader.Read();
+            await reader.MoveToContentAsync().ConfigureAwait(false);
+            await reader.ReadAsync().ConfigureAwait(false);
 
             // Loop through each element
             while (!reader.EOF && reader.ReadState == ReadState.Interactive)
@@ -139,37 +129,38 @@ namespace Emby.Dlna.Service
                                 {
                                     using (var subReader = reader.ReadSubtree())
                                     {
-                                        return ParseBodyTag(subReader);
+                                        return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
                                     }
                                 }
                                 else
                                 {
-                                    reader.Read();
+                                    await reader.ReadAsync().ConfigureAwait(false);
                                 }
+
                                 break;
                             }
                         default:
                             {
-                                reader.Skip();
+                                await reader.SkipAsync().ConfigureAwait(false);
                                 break;
                             }
                     }
                 }
                 else
                 {
-                    reader.Read();
+                    await reader.ReadAsync().ConfigureAwait(false);
                 }
             }
 
             return new ControlRequestInfo();
         }
 
-        private ControlRequestInfo ParseBodyTag(XmlReader reader)
+        private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
         {
             var result = new ControlRequestInfo();
 
-            reader.MoveToContent();
-            reader.Read();
+            await reader.MoveToContentAsync().ConfigureAwait(false);
+            await reader.ReadAsync().ConfigureAwait(false);
 
             // Loop through each element
             while (!reader.EOF && reader.ReadState == ReadState.Interactive)
@@ -183,28 +174,28 @@ namespace Emby.Dlna.Service
                     {
                         using (var subReader = reader.ReadSubtree())
                         {
-                            ParseFirstBodyChild(subReader, result.Headers);
+                            await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
                             return result;
                         }
                     }
                     else
                     {
-                        reader.Read();
+                        await reader.ReadAsync().ConfigureAwait(false);
                     }
                 }
                 else
                 {
-                    reader.Read();
+                    await reader.ReadAsync().ConfigureAwait(false);
                 }
             }
 
             return result;
         }
 
-        private void ParseFirstBodyChild(XmlReader reader, IDictionary<string, string> headers)
+        private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers)
         {
-            reader.MoveToContent();
-            reader.Read();
+            await reader.MoveToContentAsync().ConfigureAwait(false);
+            await reader.ReadAsync().ConfigureAwait(false);
 
             // Loop through each element
             while (!reader.EOF && reader.ReadState == ReadState.Interactive)
@@ -212,20 +203,20 @@ namespace Emby.Dlna.Service
                 if (reader.NodeType == XmlNodeType.Element)
                 {
                     // TODO: Should we be doing this here, or should it be handled earlier when decoding the request?
-                    headers[reader.LocalName.RemoveDiacritics()] = reader.ReadElementContentAsString();
+                    headers[reader.LocalName.RemoveDiacritics()] = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false);
                 }
                 else
                 {
-                    reader.Read();
+                    await reader.ReadAsync().ConfigureAwait(false);
                 }
             }
         }
 
         private class ControlRequestInfo
         {
-            public string LocalName;
-            public string NamespaceURI;
-            public IDictionary<string, string> Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+            public string LocalName { get; set; }
+            public string NamespaceURI { get; set; }
+            public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
         }
 
         protected abstract IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams);
@@ -237,10 +228,7 @@ namespace Emby.Dlna.Service
                 return;
             }
 
-            var originalHeaders = request.Headers;
-            var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
-
-            _logger.LogDebug("Control request. Headers: {0}", headers);
+            Logger.LogDebug("Control request. Headers: {@Headers}", request.Headers);
         }
 
         private void LogResponse(ControlResponse response)
@@ -250,11 +238,7 @@ namespace Emby.Dlna.Service
                 return;
             }
 
-            var originalHeaders = response.Headers;
-            var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
-            //builder.Append(response.Xml);
-
-            _logger.LogDebug("Control response. Headers: {0}", headers);
+            Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
         }
     }
 }

+ 2 - 2
Emby.Dlna/Service/ControlErrorHandler.cs

@@ -6,11 +6,11 @@ using Emby.Dlna.Didl;
 
 namespace Emby.Dlna.Service
 {
-    public class ControlErrorHandler
+    public static class ControlErrorHandler
     {
         private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
 
-        public ControlResponse GetResponse(Exception ex)
+        public static ControlResponse GetResponse(Exception ex)
         {
             var settings = new XmlWriterSettings
             {

+ 1 - 2
Emby.Drawing/ImageProcessor.cs

@@ -14,7 +14,6 @@ using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Net;
 using Microsoft.Extensions.Logging;
@@ -129,7 +128,7 @@ namespace Emby.Drawing
         {
             var file = await ProcessImage(options).ConfigureAwait(false);
 
-            using (var fileStream = _fileSystem.GetFileStream(file.Item1, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true))
+            using (var fileStream = new FileStream(file.Item1, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true))
             {
                 await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
             }

+ 22 - 22
Emby.Naming/Common/NamingOptions.cs

@@ -5,6 +5,7 @@ using System;
 using System.Linq;
 using System.Text.RegularExpressions;
 using Emby.Naming.Video;
+using MediaBrowser.Model.Entities;
 
 namespace Emby.Naming.Common
 {
@@ -176,13 +177,12 @@ namespace Emby.Naming.Common
 
             CleanDateTimes = new[]
             {
-                @"(.+[^ _\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9][0-9]|20[0-1][0-9])([ _\,\.\(\)\[\]\-][^0-9]|$)"
+                @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
             };
 
             CleanStrings = new[]
             {
-                @"[ _\,\.\(\)\[\]\-](ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
-                @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
+                @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
                 @"(\[.*\])"
             };
 
@@ -339,7 +339,7 @@ namespace Emby.Naming.Common
 
                 // *** End Kodi Standard Naming
 
-                // [bar] Foo - 1 [baz]
+                // [bar] Foo - 1 [baz]
                 new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>\d+).*$")
                 {
                     IsNamed = true
@@ -423,126 +423,126 @@ namespace Emby.Naming.Common
             {
                 new ExtraRule
                 {
-                    ExtraType = "trailer",
+                    ExtraType = ExtraType.Trailer,
                     RuleType = ExtraRuleType.Filename,
                     Token = "trailer",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "trailer",
+                    ExtraType = ExtraType.Trailer,
                     RuleType = ExtraRuleType.Suffix,
                     Token = "-trailer",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "trailer",
+                    ExtraType = ExtraType.Trailer,
                     RuleType = ExtraRuleType.Suffix,
                     Token = ".trailer",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "trailer",
+                    ExtraType = ExtraType.Trailer,
                     RuleType = ExtraRuleType.Suffix,
                     Token = "_trailer",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "trailer",
+                    ExtraType = ExtraType.Trailer,
                     RuleType = ExtraRuleType.Suffix,
                     Token = " trailer",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "sample",
+                    ExtraType = ExtraType.Sample,
                     RuleType = ExtraRuleType.Filename,
                     Token = "sample",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "sample",
+                    ExtraType = ExtraType.Sample,
                     RuleType = ExtraRuleType.Suffix,
                     Token = "-sample",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "sample",
+                    ExtraType = ExtraType.Sample,
                     RuleType = ExtraRuleType.Suffix,
                     Token = ".sample",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "sample",
+                    ExtraType = ExtraType.Sample,
                     RuleType = ExtraRuleType.Suffix,
                     Token = "_sample",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "sample",
+                    ExtraType = ExtraType.Sample,
                     RuleType = ExtraRuleType.Suffix,
                     Token = " sample",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "themesong",
+                    ExtraType = ExtraType.ThemeSong,
                     RuleType = ExtraRuleType.Filename,
                     Token = "theme",
                     MediaType = MediaType.Audio
                 },
                 new ExtraRule
                 {
-                    ExtraType = "scene",
+                    ExtraType = ExtraType.Scene,
                     RuleType = ExtraRuleType.Suffix,
                     Token = "-scene",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "clip",
+                    ExtraType = ExtraType.Clip,
                     RuleType = ExtraRuleType.Suffix,
                     Token = "-clip",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "interview",
+                    ExtraType = ExtraType.Interview,
                     RuleType = ExtraRuleType.Suffix,
                     Token = "-interview",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "behindthescenes",
+                    ExtraType = ExtraType.BehindTheScenes,
                     RuleType = ExtraRuleType.Suffix,
                     Token = "-behindthescenes",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "deletedscene",
+                    ExtraType = ExtraType.DeletedScene,
                     RuleType = ExtraRuleType.Suffix,
                     Token = "-deleted",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "featurette",
+                    ExtraType = ExtraType.Clip,
                     RuleType = ExtraRuleType.Suffix,
                     Token = "-featurette",
                     MediaType = MediaType.Video
                 },
                 new ExtraRule
                 {
-                    ExtraType = "short",
+                    ExtraType = ExtraType.Clip,
                     RuleType = ExtraRuleType.Suffix,
                     Token = "-short",
                     MediaType = MediaType.Video

+ 1 - 1
Emby.Naming/Emby.Naming.csproj

@@ -27,7 +27,7 @@
 
   <!-- Code Analyzers-->
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
-    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" />
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
     <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
     <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
     <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />

+ 1 - 2
Emby.Naming/Subtitles/SubtitleParser.cs

@@ -31,7 +31,6 @@ namespace Emby.Naming.Subtitles
             }
 
             var flags = GetFlags(path);
-
             var info = new SubtitleInfo
             {
                 Path = path,
@@ -45,7 +44,7 @@ namespace Emby.Naming.Subtitles
             // Should have a name, language and file extension
             if (parts.Count >= 3)
             {
-                info.Language = parts[parts.Count - 2];
+                info.Language = parts[^2];
             }
 
             return info;

+ 1 - 1
Emby.Naming/TV/EpisodePathParser.cs

@@ -131,7 +131,7 @@ namespace Emby.Naming.TV
                     var endingNumberGroup = match.Groups["endingepnumber"];
                     if (endingNumberGroup.Success)
                     {
-                        // Will only set EndingEpsiodeNumber if the captured number is not followed by additional numbers
+                        // Will only set EndingEpisodeNumber if the captured number is not followed by additional numbers
                         // or a 'p' or 'i' as what you would get with a pixel resolution specification.
                         // It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
                         int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;

+ 15 - 56
Emby.Naming/Video/CleanDateTimeParser.cs

@@ -1,89 +1,48 @@
 #pragma warning disable CS1591
 #pragma warning disable SA1600
+#nullable enable
 
-using System;
+using System.Collections.Generic;
 using System.Globalization;
-using System.IO;
-using System.Linq;
 using System.Text.RegularExpressions;
-using Emby.Naming.Common;
 
 namespace Emby.Naming.Video
 {
     /// <summary>
     /// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
     /// </summary>
-    public class CleanDateTimeParser
+    public static class CleanDateTimeParser
     {
-        private readonly NamingOptions _options;
-
-        public CleanDateTimeParser(NamingOptions options)
+        public static CleanDateTimeResult Clean(string name, IReadOnlyList<Regex> cleanDateTimeRegexes)
         {
-            _options = options;
-        }
-
-        public CleanDateTimeResult Clean(string name)
-        {
-            var originalName = name;
-
-            try
+            CleanDateTimeResult result = new CleanDateTimeResult(name);
+            var len = cleanDateTimeRegexes.Count;
+            for (int i = 0; i < len; i++)
             {
-                var extension = Path.GetExtension(name) ?? string.Empty;
-                // Check supported extensions
-                if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)
-                    && !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
+                if (TryClean(name, cleanDateTimeRegexes[i], ref result))
                 {
-                    // Dummy up a file extension because the expressions will fail without one
-                    // This is tricky because we can't just check Path.GetExtension for empty
-                    // If the input is "St. Vincent (2014)", it will produce ". Vincent (2014)" as the extension
-                    name += ".mkv";
+                    return result;
                 }
             }
-            catch (ArgumentException)
-            {
-            }
-
-            var result = _options.CleanDateTimeRegexes.Select(i => Clean(name, i))
-                .FirstOrDefault(i => i.HasChanged) ??
-                new CleanDateTimeResult { Name = originalName };
-
-            if (result.HasChanged)
-            {
-                return result;
-            }
-
-            // Make a second pass, running clean string first
-            var cleanStringResult = new CleanStringParser().Clean(name, _options.CleanStringRegexes);
 
-            if (!cleanStringResult.HasChanged)
-            {
-                return result;
-            }
-
-            return _options.CleanDateTimeRegexes.Select(i => Clean(cleanStringResult.Name, i))
-                .FirstOrDefault(i => i.HasChanged) ??
-                result;
+            return result;
         }
 
-        private static CleanDateTimeResult Clean(string name, Regex expression)
+        private static bool TryClean(string name, Regex expression, ref CleanDateTimeResult result)
         {
-            var result = new CleanDateTimeResult();
-
             var match = expression.Match(name);
 
             if (match.Success
-                && match.Groups.Count == 4
+                && match.Groups.Count == 5
                 && match.Groups[1].Success
                 && match.Groups[2].Success
                 && int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
             {
-                name = match.Groups[1].Value;
-                result.Year = year;
-                result.HasChanged = true;
+                result = new CleanDateTimeResult(match.Groups[1].Value.TrimEnd(), year);
+                return true;
             }
 
-            result.Name = name;
-            return result;
+            return false;
         }
     }
 }

+ 18 - 11
Emby.Naming/Video/CleanDateTimeResult.cs

@@ -1,26 +1,33 @@
 #pragma warning disable CS1591
 #pragma warning disable SA1600
+#nullable enable
 
 namespace Emby.Naming.Video
 {
-    public class CleanDateTimeResult
+    public readonly struct CleanDateTimeResult
     {
+        public CleanDateTimeResult(string name, int? year)
+        {
+            Name = name;
+            Year = year;
+        }
+
+        public CleanDateTimeResult(string name)
+        {
+            Name = name;
+            Year = null;
+        }
+
         /// <summary>
-        /// Gets or sets the name.
+        /// Gets the name.
         /// </summary>
         /// <value>The name.</value>
-        public string Name { get; set; }
+        public string Name { get; }
 
         /// <summary>
-        /// Gets or sets the year.
+        /// Gets the year.
         /// </summary>
         /// <value>The year.</value>
-        public int? Year { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance has changed.
-        /// </summary>
-        /// <value><c>true</c> if this instance has changed; otherwise, <c>false</c>.</value>
-        public bool HasChanged { get; set; }
+        public int? Year { get; }
     }
 }

+ 18 - 25
Emby.Naming/Video/CleanStringParser.cs

@@ -1,52 +1,45 @@
 #pragma warning disable CS1591
 #pragma warning disable SA1600
+#nullable enable
 
+using System;
 using System.Collections.Generic;
 using System.Text.RegularExpressions;
 
 namespace Emby.Naming.Video
 {
     /// <summary>
-    /// http://kodi.wiki/view/Advancedsettings.xml#video
+    /// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
     /// </summary>
-    public class CleanStringParser
+    public static class CleanStringParser
     {
-        public CleanStringResult Clean(string name, IEnumerable<Regex> expressions)
+        public static bool TryClean(string name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName)
         {
-            var hasChanged = false;
-
-            foreach (var exp in expressions)
+            var len = expressions.Count;
+            for (int i = 0; i < len; i++)
             {
-                var result = Clean(name, exp);
-
-                if (!string.IsNullOrEmpty(result.Name))
+                if (TryClean(name, expressions[i], out newName))
                 {
-                    name = result.Name;
-                    hasChanged = hasChanged || result.HasChanged;
+                    return true;
                 }
             }
 
-            return new CleanStringResult
-            {
-                Name = name,
-                HasChanged = hasChanged
-            };
+            newName = ReadOnlySpan<char>.Empty;
+            return false;
         }
 
-        private static CleanStringResult Clean(string name, Regex expression)
+        private static bool TryClean(string name, Regex expression, out ReadOnlySpan<char> newName)
         {
-            var result = new CleanStringResult();
-
             var match = expression.Match(name);
-
-            if (match.Success)
+            int index = match.Index;
+            if (match.Success && index != 0)
             {
-                result.HasChanged = true;
-                name = name.Substring(0, match.Index);
+                newName = name.AsSpan().Slice(0, match.Index);
+                return true;
             }
 
-            result.Name = name;
-            return result;
+            newName = string.Empty;
+            return false;
         }
     }
 }

+ 0 - 20
Emby.Naming/Video/CleanStringResult.cs

@@ -1,20 +0,0 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
-namespace Emby.Naming.Video
-{
-    public class CleanStringResult
-    {
-        /// <summary>
-        /// Gets or sets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        public string Name { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance has changed.
-        /// </summary>
-        /// <value><c>true</c> if this instance has changed; otherwise, <c>false</c>.</value>
-        public bool HasChanged { get; set; }
-    }
-}

+ 1 - 1
Emby.Naming/Video/ExtraResolver.cs

@@ -23,7 +23,7 @@ namespace Emby.Naming.Video
         {
             return _options.VideoExtraRules
                 .Select(i => GetExtraInfo(path, i))
-                .FirstOrDefault(i => !string.IsNullOrEmpty(i.ExtraType)) ?? new ExtraResult();
+                .FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult();
         }
 
         private ExtraResult GetExtraInfo(string path, ExtraRule rule)

+ 3 - 1
Emby.Naming/Video/ExtraResult.cs

@@ -1,6 +1,8 @@
 #pragma warning disable CS1591
 #pragma warning disable SA1600
 
+using MediaBrowser.Model.Entities;
+
 namespace Emby.Naming.Video
 {
     public class ExtraResult
@@ -9,7 +11,7 @@ namespace Emby.Naming.Video
         /// Gets or sets the type of the extra.
         /// </summary>
         /// <value>The type of the extra.</value>
-        public string ExtraType { get; set; }
+        public ExtraType? ExtraType { get; set; }
 
         /// <summary>
         /// Gets or sets the rule.

+ 3 - 2
Emby.Naming/Video/ExtraRule.cs

@@ -1,7 +1,8 @@
 #pragma warning disable CS1591
 #pragma warning disable SA1600
 
-using Emby.Naming.Common;
+using MediaBrowser.Model.Entities;
+using MediaType = Emby.Naming.Common.MediaType;
 
 namespace Emby.Naming.Video
 {
@@ -17,7 +18,7 @@ namespace Emby.Naming.Video
         /// Gets or sets the type of the extra.
         /// </summary>
         /// <value>The type of the extra.</value>
-        public string ExtraType { get; set; }
+        public ExtraType ExtraType { get; set; }
 
         /// <summary>
         /// Gets or sets the type of the rule.

+ 2 - 2
Emby.Naming/Video/StubResolver.cs

@@ -14,14 +14,14 @@ namespace Emby.Naming.Video
         {
             if (path == null)
             {
-                return default(StubResult);
+                return default;
             }
 
             var extension = Path.GetExtension(path);
 
             if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
             {
-                return default(StubResult);
+                return default;
             }
 
             var result = new StubResult()

+ 4 - 2
Emby.Naming/Video/VideoFileInfo.cs

@@ -1,3 +1,5 @@
+using MediaBrowser.Model.Entities;
+
 namespace Emby.Naming.Video
 {
     /// <summary>
@@ -30,10 +32,10 @@ namespace Emby.Naming.Video
         public int? Year { get; set; }
 
         /// <summary>
-        /// Gets or sets the type of the extra, e.g. trailer, theme song, behing the scenes, etc.
+        /// Gets or sets the type of the extra, e.g. trailer, theme song, behind the scenes, etc.
         /// </summary>
         /// <value>The type of the extra.</value>
-        public string ExtraType { get; set; }
+        public ExtraType? ExtraType { get; set; }
 
         /// <summary>
         /// Gets or sets the extra rule.

+ 5 - 4
Emby.Naming/Video/VideoListResolver.cs

@@ -7,6 +7,7 @@ using System.IO;
 using System.Linq;
 using System.Text.RegularExpressions;
 using Emby.Naming.Common;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 
 namespace Emby.Naming.Video
@@ -32,7 +33,7 @@ namespace Emby.Naming.Video
             // Filter out all extras, otherwise they could cause stacks to not be resolved
             // See the unit test TestStackedWithTrailer
             var nonExtras = videoInfos
-                .Where(i => string.IsNullOrEmpty(i.ExtraType))
+                .Where(i => i.ExtraType == null)
                 .Select(i => new FileSystemMetadata
                 {
                     FullName = i.Path,
@@ -79,7 +80,7 @@ namespace Emby.Naming.Video
             }
 
             var standaloneMedia = remainingFiles
-                .Where(i => string.IsNullOrEmpty(i.ExtraType))
+                .Where(i => i.ExtraType == null)
                 .ToList();
 
             foreach (var media in standaloneMedia)
@@ -148,7 +149,7 @@ namespace Emby.Naming.Video
             if (list.Count == 1)
             {
                 var trailers = remainingFiles
-                    .Where(i => string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase))
+                    .Where(i => i.ExtraType == ExtraType.Trailer)
                     .ToList();
 
                 list[0].Extras.AddRange(trailers);
@@ -229,7 +230,7 @@ namespace Emby.Naming.Video
             }
 
             return remainingFiles
-                .Where(i => !string.IsNullOrEmpty(i.ExtraType))
+                .Where(i => i.ExtraType == null)
                 .Where(i => baseNames.Any(b => i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
                 .ToList();
         }

+ 6 - 5
Emby.Naming/Video/VideoResolver.cs

@@ -94,9 +94,10 @@ namespace Emby.Naming.Video
             {
                 var cleanDateTimeResult = CleanDateTime(name);
 
-                if (string.IsNullOrEmpty(extraResult.ExtraType))
+                if (extraResult.ExtraType == null
+                    && TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan<char> newName))
                 {
-                    name = CleanString(cleanDateTimeResult.Name).Name;
+                    name = newName.ToString();
                 }
 
                 year = cleanDateTimeResult.Year;
@@ -130,14 +131,14 @@ namespace Emby.Naming.Video
             return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
         }
 
-        public CleanStringResult CleanString(string name)
+        public bool TryCleanString(string name, out ReadOnlySpan<char> newName)
         {
-            return new CleanStringParser().Clean(name, _options.CleanStringRegexes);
+            return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
         }
 
         public CleanDateTimeResult CleanDateTime(string name)
         {
-            return new CleanDateTimeParser(_options).Clean(name);
+            return CleanDateTimeParser.Clean(name, _options.CleanDateTimeRegexes);
         }
     }
 }

+ 4 - 3
Emby.Photos/Emby.Photos.csproj

@@ -26,9 +26,10 @@
 
   <!-- Code Analyzers-->
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
-    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
-    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
-    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
   </ItemGroup>
 
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

+ 1 - 0
Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Activity/ActivityManager.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Linq;

+ 1 - 0
Emby.Server.Implementations/Activity/ActivityRepository.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 6 - 9
Emby.Server.Implementations/ApplicationHost.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Concurrent;
@@ -177,11 +178,7 @@ namespace Emby.Server.Implementations
         /// Gets the plugins.
         /// </summary>
         /// <value>The plugins.</value>
-        public IPlugin[] Plugins
-        {
-            get => _plugins;
-            protected set => _plugins = value;
-        }
+        public IReadOnlyList<IPlugin> Plugins => _plugins;
 
         /// <summary>
         /// Gets or sets the logger factory.
@@ -602,7 +599,7 @@ namespace Emby.Server.Implementations
                 HttpsPort = ServerConfiguration.DefaultHttpsPort;
             }
 
-            JsonSerializer = new JsonSerializer(FileSystemManager);
+            JsonSerializer = new JsonSerializer();
 
             if (Plugins != null)
             {
@@ -1056,7 +1053,7 @@ namespace Emby.Server.Implementations
             }
 
             ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
-            Plugins = GetExports<IPlugin>()
+            _plugins = GetExports<IPlugin>()
                         .Select(LoadPlugin)
                         .Where(i => i != null)
                         .ToArray();
@@ -1705,9 +1702,9 @@ namespace Emby.Server.Implementations
         /// <param name="plugin">The plugin.</param>
         public void RemovePlugin(IPlugin plugin)
         {
-            var list = Plugins.ToList();
+            var list = _plugins.ToList();
             list.Remove(plugin);
-            Plugins = list.ToArray();
+            _plugins = list.ToArray();
         }
 
         /// <summary>

+ 1 - 0
Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System.Collections.Generic;
 using MediaBrowser.Common.Configuration;

+ 1 - 0
Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Channels/ChannelImageProvider.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System.Collections.Generic;
 using System.Linq;

+ 1 - 0
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Concurrent;

+ 1 - 0
Emby.Server.Implementations/Channels/ChannelPostScanTask.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Linq;

+ 1 - 0
Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Collections/CollectionImageProvider.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Collections/CollectionManager.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 1
Emby.Server.Implementations/Cryptography/CryptographyProvider.cs

@@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Cryptography
         /// <summary>
         /// Releases unmanaged and - optionally - managed resources.
         /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
         protected virtual void Dispose(bool disposing)
         {
             if (_disposed)

+ 1 - 0
Emby.Server.Implementations/Data/BaseSqliteRepository.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Threading;

+ 1 - 0
Emby.Server.Implementations/Data/ManagedConnection.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Data/SqliteExtensions.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Data/SqliteUserDataRepository.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Data/SqliteUserRepository.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Devices/DeviceId.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Globalization;

+ 2 - 1
Emby.Server.Implementations/Devices/DeviceManager.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;
@@ -242,7 +243,7 @@ namespace Emby.Server.Implementations.Devices
 
             try
             {
-                using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+                using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
                 {
                     await stream.CopyToAsync(fs).ConfigureAwait(false);
                 }

+ 1 - 0
Emby.Server.Implementations/Diagnostics/CommonProcess.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Diagnostics;

+ 1 - 0
Emby.Server.Implementations/Diagnostics/ProcessFactory.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using MediaBrowser.Model.Diagnostics;
 

+ 1 - 0
Emby.Server.Implementations/Dto/DtoService.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 8 - 8
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -29,13 +29,13 @@
     <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
-    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
-    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
+    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.0" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.0" />
     <PackageReference Include="Mono.Nat" Version="2.0.0" />
     <PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" />
     <PackageReference Include="sharpcompress" Version="0.24.0" />
-    <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
+    <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
     <PackageReference Include="System.Interactive.Async" Version="4.0.0" />
   </ItemGroup>
 
@@ -51,10 +51,10 @@
 
   <!-- Code Analyzers-->
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
-    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
-    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
-    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
-    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
   </ItemGroup>
 
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

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

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Linq;

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

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

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

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

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

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Linq;

+ 21 - 37
Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Threading;
 using System.Threading.Tasks;
 using Emby.Server.Implementations.Udp;
 using MediaBrowser.Controller;
@@ -12,7 +13,7 @@ namespace Emby.Server.Implementations.EntryPoints
     /// <summary>
     /// Class UdpServerEntryPoint.
     /// </summary>
-    public class UdpServerEntryPoint : IServerEntryPoint
+    public sealed class UdpServerEntryPoint : IServerEntryPoint
     {
         /// <summary>
         /// The port of the UDP server.
@@ -31,61 +32,44 @@ namespace Emby.Server.Implementations.EntryPoints
         /// The UDP server.
         /// </summary>
         private UdpServer _udpServer;
+        private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
+        private bool _disposed = false;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="UdpServerEntryPoint" /> class.
         /// </summary>
         public UdpServerEntryPoint(
-            ILogger logger,
-            IServerApplicationHost appHost,
-            IJsonSerializer json,
-            ISocketFactory socketFactory)
+            ILogger<UdpServerEntryPoint> logger,
+            IServerApplicationHost appHost)
         {
             _logger = logger;
             _appHost = appHost;
-            _json = json;
-            _socketFactory = socketFactory;
-        }
 
-        /// <inheritdoc />
-        public Task RunAsync()
-        {
-            var udpServer = new UdpServer(_logger, _appHost, _json, _socketFactory);
-
-            try
-            {
-                udpServer.Start(PortNumber);
-
-                _udpServer = udpServer;
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Failed to start UDP Server");
-            }
 
-            return Task.CompletedTask;
         }
 
         /// <inheritdoc />
-        public void Dispose()
+        public async Task RunAsync()
         {
-            Dispose(true);
-            GC.SuppressFinalize(this);
+            _udpServer = new UdpServer(_logger, _appHost);
+            _udpServer.Start(PortNumber, _cancellationTokenSource.Token);
         }
 
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool dispose)
+        /// <inheritdoc />
+        public void Dispose()
         {
-            if (dispose)
+            if (_disposed)
             {
-                if (_udpServer != null)
-                {
-                    _udpServer.Dispose();
-                }
+                return;
             }
+
+            _cancellationTokenSource.Cancel();
+            _udpServer.Dispose();
+
+            _cancellationTokenSource = null;
+            _udpServer = null;
+
+            _disposed = true;
         }
     }
 }

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

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 2 - 2
Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs

@@ -197,7 +197,7 @@ namespace Emby.Server.Implementations.HttpClientManager
             if (File.Exists(responseCachePath)
                 && _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
             {
-                var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true);
+                var stream = new FileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true);
 
                 return new HttpResponseInfo
                 {
@@ -220,7 +220,7 @@ namespace Emby.Server.Implementations.HttpClientManager
                 FileMode.Create,
                 FileAccess.Write,
                 FileShare.None,
-                StreamDefaults.DefaultFileStreamBufferSize,
+                IODefaults.FileStreamBufferSize,
                 true))
             {
                 await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);

+ 8 - 7
Emby.Server.Implementations/HttpServer/FileWriter.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;
@@ -71,7 +72,7 @@ namespace Emby.Server.Implementations.HttpServer
                 SetRangeValues();
             }
 
-            FileShare = FileShareMode.Read;
+            FileShare = FileShare.Read;
             Cookies = new List<Cookie>();
         }
 
@@ -93,7 +94,7 @@ namespace Emby.Server.Implementations.HttpServer
 
         public List<Cookie> Cookies { get; private set; }
 
-        public FileShareMode FileShare { get; set; }
+        public FileShare FileShare { get; set; }
 
         /// <summary>
         /// Gets the options.
@@ -221,17 +222,17 @@ namespace Emby.Server.Implementations.HttpServer
             }
         }
 
-        public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
+        public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShare fileShare, CancellationToken cancellationToken)
         {
-            var fileOpenOptions = FileOpenOptions.SequentialScan;
+            var fileOptions = FileOptions.SequentialScan;
 
             // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
             if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
             {
-                fileOpenOptions |= FileOpenOptions.Asynchronous;
+                fileOptions |= FileOptions.Asynchronous;
             }
 
-            using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
+            using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, fileOptions))
             {
                 if (offset > 0)
                 {
@@ -244,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer
                 }
                 else
                 {
-                    await fs.CopyToAsync(stream, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
+                    await fs.CopyToAsync(stream, IODefaults.CopyToBufferSize, cancellationToken).ConfigureAwait(false);
                 }
             }
         }

+ 1 - 1
Emby.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;
@@ -218,7 +219,6 @@ namespace Emby.Server.Implementations.HttpServer
                 case FileNotFoundException _:
                 case ResourceNotFoundException _: return 404;
                 case MethodNotAllowedException _: return 405;
-                case RemoteServiceUnavailableException _: return 502;
                 default: return 500;
             }
         }

+ 5 - 4
Emby.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;
@@ -439,7 +440,7 @@ namespace Emby.Server.Implementations.HttpServer
 
         public Task<object> GetStaticFileResult(IRequest requestContext,
             string path,
-            FileShareMode fileShare = FileShareMode.Read)
+            FileShare fileShare = FileShare.Read)
         {
             if (string.IsNullOrEmpty(path))
             {
@@ -463,7 +464,7 @@ namespace Emby.Server.Implementations.HttpServer
                 throw new ArgumentException("Path can't be empty.", nameof(options));
             }
 
-            if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite)
+            if (fileShare != FileShare.Read && fileShare != FileShare.ReadWrite)
             {
                 throw new ArgumentException("FileShare must be either Read or ReadWrite");
             }
@@ -491,9 +492,9 @@ namespace Emby.Server.Implementations.HttpServer
         /// <param name="path">The path.</param>
         /// <param name="fileShare">The file share.</param>
         /// <returns>Stream.</returns>
-        private Stream GetFileStream(string path, FileShareMode fileShare)
+        private Stream GetFileStream(string path, FileShare fileShare)
         {
-            return _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShare);
+            return new FileStream(path, FileMode.Open, FileAccess.Read, fileShare);
         }
 
         public Task<object> GetStaticResult(IRequest requestContext,

+ 1 - 0
Emby.Server.Implementations/HttpServer/IHttpListener.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Threading;

+ 1 - 0
Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/HttpServer/Security/AuthService.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Linq;

+ 1 - 0
Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/HttpServer/Security/SessionContext.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using MediaBrowser.Controller.Entities;

+ 1 - 0
Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 namespace Emby.Server.Implementations.IO
 {

+ 1 - 0
Emby.Server.Implementations/IO/FileRefresher.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/IO/LibraryMonitor.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Concurrent;

+ 20 - 101
Emby.Server.Implementations/IO/ManagedFileSystem.cs

@@ -1,9 +1,10 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;
-using System.Globalization;
 using System.Diagnostics;
+using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Text;
@@ -16,7 +17,7 @@ using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
 namespace Emby.Server.Implementations.IO
 {
     /// <summary>
-    /// Class ManagedFileSystem
+    /// Class ManagedFileSystem.
     /// </summary>
     public class ManagedFileSystem : IFileSystem
     {
@@ -79,20 +80,20 @@ namespace Emby.Server.Implementations.IO
 
         public virtual string MakeAbsolutePath(string folderPath, string filePath)
         {
-            if (string.IsNullOrWhiteSpace(filePath)
-                // stream
-                || filePath.Contains("://"))
+            // path is actually a stream
+            if (string.IsNullOrWhiteSpace(filePath) || filePath.Contains("://", StringComparison.Ordinal))
             {
                 return filePath;
             }
 
             if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/')
             {
-                return filePath; // absolute local path
+                // absolute local path
+                return filePath;
             }
 
             // unc path
-            if (filePath.StartsWith("\\\\"))
+            if (filePath.StartsWith("\\\\", StringComparison.Ordinal))
             {
                 return filePath;
             }
@@ -100,13 +101,16 @@ namespace Emby.Server.Implementations.IO
             var firstChar = filePath[0];
             if (firstChar == '/')
             {
-                // For this we don't really know.
+                // for this we don't really know
                 return filePath;
             }
-            if (firstChar == '\\') //relative path
+
+            // relative path
+            if (firstChar == '\\')
             {
                 filePath = filePath.Substring(1);
             }
+
             try
             {
                 return Path.GetFullPath(Path.Combine(folderPath, filePath));
@@ -130,11 +134,7 @@ namespace Emby.Server.Implementations.IO
         /// </summary>
         /// <param name="shortcutPath">The shortcut path.</param>
         /// <param name="target">The target.</param>
-        /// <exception cref="ArgumentNullException">
-        /// shortcutPath
-        /// or
-        /// target
-        /// </exception>
+        /// <exception cref="ArgumentNullException">The shortcutPath or target is null.</exception>
         public virtual void CreateShortcut(string shortcutPath, string target)
         {
             if (string.IsNullOrEmpty(shortcutPath))
@@ -280,11 +280,11 @@ namespace Emby.Server.Implementations.IO
         }
 
         /// <summary>
-        /// Takes a filename and removes invalid characters
+        /// Takes a filename and removes invalid characters.
         /// </summary>
         /// <param name="filename">The filename.</param>
         /// <returns>System.String.</returns>
-        /// <exception cref="ArgumentNullException">filename</exception>
+        /// <exception cref="ArgumentNullException">The filename is null.</exception>
         public virtual string GetValidFilename(string filename)
         {
             var builder = new StringBuilder(filename);
@@ -365,90 +365,9 @@ namespace Emby.Server.Implementations.IO
             return GetLastWriteTimeUtc(GetFileSystemInfo(path));
         }
 
-        /// <summary>
-        /// Gets the file stream.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="mode">The mode.</param>
-        /// <param name="access">The access.</param>
-        /// <param name="share">The share.</param>
-        /// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
-        /// <returns>FileStream.</returns>
-        public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false)
-        {
-            if (isAsync)
-            {
-                return GetFileStream(path, mode, access, share, FileOpenOptions.Asynchronous);
-            }
-
-            return GetFileStream(path, mode, access, share, FileOpenOptions.None);
-        }
-
-        public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions)
-            => new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 4096, GetFileOptions(fileOpenOptions));
-
-        private static FileOptions GetFileOptions(FileOpenOptions mode)
-        {
-            var val = (int)mode;
-            return (FileOptions)val;
-        }
-
-        private static FileMode GetFileMode(FileOpenMode mode)
-        {
-            switch (mode)
-            {
-                //case FileOpenMode.Append:
-                //    return FileMode.Append;
-                case FileOpenMode.Create:
-                    return FileMode.Create;
-                case FileOpenMode.CreateNew:
-                    return FileMode.CreateNew;
-                case FileOpenMode.Open:
-                    return FileMode.Open;
-                case FileOpenMode.OpenOrCreate:
-                    return FileMode.OpenOrCreate;
-                //case FileOpenMode.Truncate:
-                //    return FileMode.Truncate;
-                default:
-                    throw new Exception("Unrecognized FileOpenMode");
-            }
-        }
-
-        private static FileAccess GetFileAccess(FileAccessMode mode)
-        {
-            switch (mode)
-            {
-                //case FileAccessMode.ReadWrite:
-                //    return FileAccess.ReadWrite;
-                case FileAccessMode.Write:
-                    return FileAccess.Write;
-                case FileAccessMode.Read:
-                    return FileAccess.Read;
-                default:
-                    throw new Exception("Unrecognized FileAccessMode");
-            }
-        }
-
-        private static FileShare GetFileShare(FileShareMode mode)
-        {
-            switch (mode)
-            {
-                case FileShareMode.ReadWrite:
-                    return FileShare.ReadWrite;
-                case FileShareMode.Write:
-                    return FileShare.Write;
-                case FileShareMode.Read:
-                    return FileShare.Read;
-                case FileShareMode.None:
-                    return FileShare.None;
-                default:
-                    throw new Exception("Unrecognized FileShareMode");
-            }
-        }
-
         public virtual void SetHidden(string path, bool isHidden)
         {
-            if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
+            if (OperatingSystem.Id != OperatingSystemId.Windows)
             {
                 return;
             }
@@ -472,7 +391,7 @@ namespace Emby.Server.Implementations.IO
 
         public virtual void SetReadOnly(string path, bool isReadOnly)
         {
-            if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
+            if (OperatingSystem.Id != OperatingSystemId.Windows)
             {
                 return;
             }
@@ -496,7 +415,7 @@ namespace Emby.Server.Implementations.IO
 
         public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
         {
-            if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
+            if (OperatingSystem.Id != OperatingSystemId.Windows)
             {
                 return;
             }
@@ -779,7 +698,7 @@ namespace Emby.Server.Implementations.IO
 
         public virtual void SetExecutable(string path)
         {
-            if (OperatingSystem.Id == MediaBrowser.Model.System.OperatingSystemId.Darwin)
+            if (OperatingSystem.Id == OperatingSystemId.Darwin)
             {
                 RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path));
             }

+ 1 - 0
Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.IO;

+ 1 - 0
Emby.Server.Implementations/IO/StreamHelper.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Buffers;

+ 1 - 0
Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 7 - 6
Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs

@@ -70,9 +70,9 @@ namespace Emby.Server.Implementations.Library
                 byte[] calculatedHash = _cryptographyProvider.ComputeHash(
                     readyHash.Id,
                     passwordbytes,
-                    readyHash.Salt);
+                    readyHash.Salt.ToArray());
 
-                if (calculatedHash.SequenceEqual(readyHash.Hash))
+                if (readyHash.Hash.SequenceEqual(calculatedHash))
                 {
                     success = true;
                 }
@@ -148,17 +148,18 @@ namespace Emby.Server.Implementations.Library
 
             // TODO: make use of iterations parameter?
             PasswordHash passwordHash = PasswordHash.Parse(user.Password);
+            var salt = passwordHash.Salt.ToArray();
             return new PasswordHash(
                 passwordHash.Id,
                 _cryptographyProvider.ComputeHash(
                     passwordHash.Id,
                     Encoding.UTF8.GetBytes(str),
-                    passwordHash.Salt),
-                passwordHash.Salt,
+                    salt),
+                salt,
                 passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString();
         }
 
-        public byte[] GetHashed(User user, string str)
+        public ReadOnlySpan<byte> GetHashed(User user, string str)
         {
             if (string.IsNullOrEmpty(user.Password))
             {
@@ -170,7 +171,7 @@ namespace Emby.Server.Implementations.Library
             return _cryptographyProvider.ComputeHash(
                     passwordHash.Id,
                     Encoding.UTF8.GetBytes(str),
-                    passwordHash.Salt);
+                    passwordHash.Salt.ToArray());
         }
     }
 }

+ 1 - 0
Emby.Server.Implementations/Library/ExclusiveLiveStream.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Globalization;

+ 11 - 42
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Concurrent;
@@ -35,7 +36,6 @@ using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Library;
 using MediaBrowser.Model.Net;
@@ -53,6 +53,9 @@ namespace Emby.Server.Implementations.Library
     /// </summary>
     public class LibraryManager : ILibraryManager
     {
+        private NamingOptions _namingOptions;
+        private string[] _videoFileExtensions;
+
         /// <summary>
         /// Gets or sets the postscan tasks.
         /// </summary>
@@ -2507,21 +2510,11 @@ namespace Emby.Server.Implementations.Library
         }
 
         public NamingOptions GetNamingOptions()
-        {
-            return GetNamingOptionsInternal();
-        }
-
-        private NamingOptions _namingOptions;
-        private string[] _videoFileExtensions;
-
-        private NamingOptions GetNamingOptionsInternal()
         {
             if (_namingOptions == null)
             {
-                var options = new NamingOptions();
-
-                _namingOptions = options;
-                _videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray();
+                _namingOptions = new NamingOptions();
+                _videoFileExtensions = _namingOptions.VideoFileExtensions;
             }
 
             return _namingOptions;
@@ -2532,11 +2525,10 @@ namespace Emby.Server.Implementations.Library
             var resolver = new VideoResolver(GetNamingOptions());
 
             var result = resolver.CleanDateTime(name);
-            var cleanName = resolver.CleanString(result.Name);
 
             return new ItemLookupInfo
             {
-                Name = cleanName.Name,
+                Name = resolver.TryCleanString(result.Name, out var newName) ? newName.ToString() : result.Name,
                 Year = result.Year
             };
         }
@@ -2558,7 +2550,7 @@ namespace Emby.Server.Implementations.Library
 
             if (currentVideo != null)
             {
-                files.AddRange(currentVideo.Extras.Where(i => string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)).Select(i => _fileSystem.GetFileInfo(i.Path)));
+                files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
             }
 
             var resolvers = new IItemResolver[]
@@ -2608,7 +2600,7 @@ namespace Emby.Server.Implementations.Library
 
             if (currentVideo != null)
             {
-                files.AddRange(currentVideo.Extras.Where(i => !string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)).Select(i => _fileSystem.GetFileInfo(i.Path)));
+                files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
             }
 
             return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
@@ -2712,7 +2704,7 @@ namespace Emby.Server.Implementations.Library
 
             if (!string.Equals(newPath, path, StringComparison.Ordinal))
             {
-                if (to.IndexOf('/') != -1)
+                if (to.IndexOf('/', StringComparison.Ordinal) != -1)
                 {
                     newPath = newPath.Replace('\\', '/');
                 }
@@ -2733,30 +2725,7 @@ namespace Emby.Server.Implementations.Library
 
             var result = resolver.GetExtraInfo(item.Path);
 
-            if (string.Equals(result.ExtraType, "deletedscene", StringComparison.OrdinalIgnoreCase))
-            {
-                item.ExtraType = ExtraType.DeletedScene;
-            }
-            else if (string.Equals(result.ExtraType, "behindthescenes", StringComparison.OrdinalIgnoreCase))
-            {
-                item.ExtraType = ExtraType.BehindTheScenes;
-            }
-            else if (string.Equals(result.ExtraType, "interview", StringComparison.OrdinalIgnoreCase))
-            {
-                item.ExtraType = ExtraType.Interview;
-            }
-            else if (string.Equals(result.ExtraType, "scene", StringComparison.OrdinalIgnoreCase))
-            {
-                item.ExtraType = ExtraType.Scene;
-            }
-            else if (string.Equals(result.ExtraType, "sample", StringComparison.OrdinalIgnoreCase))
-            {
-                item.ExtraType = ExtraType.Sample;
-            }
-            else
-            {
-                item.ExtraType = ExtraType.Clip;
-            }
+            item.ExtraType = result.ExtraType;
         }
 
         public List<PersonInfo> GetPeople(InternalPeopleQuery query)

+ 1 - 0
Emby.Server.Implementations/Library/LiveStreamHelper.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 3 - 2
Emby.Server.Implementations/Library/MediaSourceManager.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;
@@ -145,7 +146,7 @@ namespace Emby.Server.Implementations.Library
             });
         }
 
-        public async Task<List<MediaSourceInfo>> GetPlayackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
+        public async Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
         {
             var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
 
@@ -307,7 +308,7 @@ namespace Emby.Server.Implementations.Library
                 return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);
             }
 
-            var sources = await GetPlayackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false);
+            var sources = await GetPlaybackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false);
 
             return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
         }

+ 1 - 0
Emby.Server.Implementations/Library/MediaStreamSelector.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Library/MusicManager.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.IO;

+ 1 - 0
Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.IO;

+ 1 - 0
Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.Collections.Generic;

+ 1 - 0
Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.IO;

+ 1 - 0
Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs

@@ -1,4 +1,5 @@
 #pragma warning disable CS1591
+#pragma warning disable SA1600
 
 using System;
 using System.IO;

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff