Ver código fonte

Merge branch 'master' into custom-repos

dkanada 5 anos atrás
pai
commit
f5d82441a4
100 arquivos alterados com 673 adições e 1897 exclusões
  1. 12 19
      .ci/azure-pipelines-abi.yml
  2. 22 22
      .ci/azure-pipelines-main.yml
  3. 5 1
      .ci/azure-pipelines-test.yml
  4. 8 8
      .ci/azure-pipelines.yml
  5. 9 0
      .github/dependabot.yml
  6. 2 0
      CONTRIBUTORS.md
  7. 1 0
      DvdLib/Ifo/Cell.cs
  8. 2 0
      DvdLib/Ifo/Chapter.cs
  9. 4 0
      DvdLib/Ifo/ProgramChain.cs
  10. 4 0
      DvdLib/Ifo/Title.cs
  11. 5 3
      Emby.Dlna/ContentDirectory/ContentDirectory.cs
  12. 24 16
      Emby.Dlna/ContentDirectory/ControlHandler.cs
  13. 21 15
      Emby.Dlna/Didl/DidlBuilder.cs
  14. 1 2
      Emby.Dlna/Didl/Filter.cs
  15. 10 8
      Emby.Dlna/DlnaManager.cs
  16. 18 10
      Emby.Dlna/Eventing/EventManager.cs
  17. 3 0
      Emby.Dlna/Eventing/EventSubscription.cs
  18. 9 5
      Emby.Dlna/Main/DlnaEntryPoint.cs
  19. 4 5
      Emby.Dlna/PlayTo/Device.cs
  20. 14 1
      Emby.Dlna/PlayTo/PlayToController.cs
  21. 4 4
      Emby.Dlna/PlayTo/PlayToManager.cs
  22. 1 0
      Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
  23. 0 1
      Emby.Dlna/PlayTo/SsdpHttpClient.cs
  24. 2 0
      Emby.Dlna/PlayTo/uBaseObject.cs
  25. 5 0
      Emby.Dlna/Server/DescriptionXmlBuilder.cs
  26. 4 0
      Emby.Dlna/Service/BaseControlHandler.cs
  27. 1 1
      Emby.Dlna/Service/BaseService.cs
  28. 1 0
      Emby.Dlna/Service/ServiceXmlBuilder.cs
  29. 1 1
      Emby.Dlna/Ssdp/DeviceDiscovery.cs
  30. 11 2
      Emby.Drawing/ImageProcessor.cs
  31. 1 0
      Emby.Naming/AudioBook/AudioBookFilePathParser.cs
  32. 3 3
      Emby.Naming/Common/MediaType.cs
  33. 6 4
      Emby.Notifications/Api/NotificationsService.cs
  34. 1 1
      Emby.Notifications/NotificationEntryPoint.cs
  35. 6 4
      Emby.Notifications/NotificationManager.cs
  36. 2 2
      Emby.Photos/PhotoProvider.cs
  37. 22 34
      Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
  38. 2 2
      Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
  39. 3 11
      Emby.Server.Implementations/ApplicationHost.cs
  40. 1 1
      Emby.Server.Implementations/Browser/BrowserLauncher.cs
  41. 10 5
      Emby.Server.Implementations/Channels/ChannelManager.cs
  42. 1 1
      Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
  43. 4 3
      Emby.Server.Implementations/Collections/CollectionManager.cs
  44. 4 5
      Emby.Server.Implementations/Data/BaseSqliteRepository.cs
  45. 1 2
      Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
  46. 4 4
      Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
  47. 126 18
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  48. 8 4
      Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
  49. 0 240
      Emby.Server.Implementations/Data/SqliteUserRepository.cs
  50. 2 2
      Emby.Server.Implementations/Devices/DeviceId.cs
  51. 11 21
      Emby.Server.Implementations/Devices/DeviceManager.cs
  52. 36 21
      Emby.Server.Implementations/Dto/DtoService.cs
  53. 7 7
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  54. 1 1
      Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
  55. 2 2
      Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
  56. 3 2
      Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
  57. 0 77
      Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
  58. 5 25
      Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs
  59. 1 2
      Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
  60. 2 2
      Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
  61. 2 2
      Emby.Server.Implementations/HttpServer/FileWriter.cs
  62. 7 4
      Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
  63. 3 4
      Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
  64. 11 4
      Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
  65. 0 1
      Emby.Server.Implementations/HttpServer/ResponseFilter.cs
  66. 16 13
      Emby.Server.Implementations/HttpServer/Security/AuthService.cs
  67. 4 3
      Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
  68. 1 1
      Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
  69. 7 5
      Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
  70. 1 3
      Emby.Server.Implementations/IO/LibraryMonitor.cs
  71. 4 2
      Emby.Server.Implementations/IO/ManagedFileSystem.cs
  72. 0 1
      Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
  73. 0 1
      Emby.Server.Implementations/Images/DynamicImageProvider.cs
  74. 1 1
      Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
  75. 2 0
      Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
  76. 3 3
      Emby.Server.Implementations/Library/IgnorePatterns.cs
  77. 28 19
      Emby.Server.Implementations/Library/LibraryManager.cs
  78. 2 5
      Emby.Server.Implementations/Library/LiveStreamHelper.cs
  79. 31 32
      Emby.Server.Implementations/Library/MediaSourceManager.cs
  80. 1 1
      Emby.Server.Implementations/Library/MediaStreamSelector.cs
  81. 7 11
      Emby.Server.Implementations/Library/MusicManager.cs
  82. 1 1
      Emby.Server.Implementations/Library/ResolverHelper.cs
  83. 2 2
      Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
  84. 2 2
      Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
  85. 2 2
      Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
  86. 1 1
      Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
  87. 1 1
      Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
  88. 2 2
      Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  89. 3 2
      Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
  90. 1 0
      Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
  91. 1 2
      Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
  92. 4 4
      Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
  93. 5 2
      Emby.Server.Implementations/Library/SearchEngine.cs
  94. 6 4
      Emby.Server.Implementations/Library/UserDataManager.cs
  95. 0 1107
      Emby.Server.Implementations/Library/UserManager.cs
  96. 20 10
      Emby.Server.Implementations/Library/UserViewManager.cs
  97. 2 2
      Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs
  98. 2 3
      Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
  99. 2 2
      Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs
  100. 2 2
      Emby.Server.Implementations/Library/Validators/GenresValidator.cs

+ 12 - 19
.ci/azure-pipelines-compat.yml → .ci/azure-pipelines-abi.yml

@@ -33,6 +33,13 @@ jobs:
           packageType: sdk
           version: ${{ parameters.DotNetSdkVersion }}
 
+      - task: DotNetCoreCLI@2
+        displayName: 'Install ABI CompatibilityChecker tool'
+        inputs:
+          command: custom
+          custom: tool
+          arguments: 'update compatibilitychecker -g'
+
       - task: DownloadPipelineArtifact@2
         displayName: "Download New Assembly Build Artifact"
         inputs:
@@ -72,25 +79,11 @@ jobs:
           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"
+      - task: DotNetCoreCLI@2
+        displayName: 'Execute ABI Compatibility Check Tool'
         inputs:
-          script: "dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only"
+          command: custom
+          custom: compat
+          arguments: 'current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
           workingDirectory: $(System.ArtifactsDirectory)

+ 22 - 22
.ci/azure-pipelines-main.yml

@@ -1,6 +1,6 @@
 parameters:
-  LinuxImage: "ubuntu-latest"
-  RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj"
+  LinuxImage: 'ubuntu-latest'
+  RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
   DotNetSdkVersion: 3.1.100
 
 jobs:
@@ -13,7 +13,7 @@ jobs:
         Debug:
           BuildConfiguration: Debug
     pool:
-      vmImage: "${{ parameters.LinuxImage }}"
+      vmImage: '${{ parameters.LinuxImage }}'
     steps:
       - checkout: self
         clean: true
@@ -21,7 +21,7 @@ jobs:
         persistCredentials: true
 
       - task: DownloadPipelineArtifact@2
-        displayName: "Download Web Branch"
+        displayName: 'Download Web Branch'
         condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
         inputs:
           path: '$(Agent.TempDirectory)'
@@ -32,7 +32,7 @@ jobs:
           runBranch: variables['Build.SourceBranch']
 
       - task: DownloadPipelineArtifact@2
-        displayName: "Download Web Target"
+        displayName: 'Download Web Target'
         condition: eq(variables['Build.Reason'], 'PullRequest')
         inputs:
           path: '$(Agent.TempDirectory)'
@@ -43,51 +43,51 @@ jobs:
           runBranch: variables['System.PullRequest.TargetBranch']
 
       - task: ExtractFiles@1
-        displayName: "Extract Web Client"
+        displayName: 'Extract Web Client'
         inputs:
           archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
           destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
           cleanDestinationFolder: false
 
       - task: UseDotNet@2
-        displayName: "Update DotNet"
+        displayName: 'Update DotNet'
         inputs:
           packageType: sdk
           version: ${{ parameters.DotNetSdkVersion }}
 
       - task: DotNetCoreCLI@2
-        displayName: "Publish Server"
+        displayName: 'Publish Server'
         inputs:
           command: publish
           publishWebProjects: false
-          projects: "${{ parameters.RestoreBuildProjects }}"
-          arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)"
+          projects: '${{ parameters.RestoreBuildProjects }}'
+          arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
           zipAfterPublish: false
 
       - task: PublishPipelineArtifact@0
-        displayName: "Publish Artifact Naming"
+        displayName: 'Publish Artifact Naming'
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         inputs:
-          targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll"
-          artifactName: "Jellyfin.Naming"
+          targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
+          artifactName: 'Jellyfin.Naming'
 
       - task: PublishPipelineArtifact@0
-        displayName: "Publish Artifact Controller"
+        displayName: 'Publish Artifact Controller'
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         inputs:
-          targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
-          artifactName: "Jellyfin.Controller"
+          targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
+          artifactName: 'Jellyfin.Controller'
 
       - task: PublishPipelineArtifact@0
-        displayName: "Publish Artifact Model"
+        displayName: 'Publish Artifact Model'
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         inputs:
-          targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
-          artifactName: "Jellyfin.Model"
+          targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
+          artifactName: 'Jellyfin.Model'
 
       - task: PublishPipelineArtifact@0
-        displayName: "Publish Artifact Common"
+        displayName: 'Publish Artifact Common'
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         inputs:
-          targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
-          artifactName: "Jellyfin.Common"
+          targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
+          artifactName: 'Jellyfin.Common'

+ 5 - 1
.ci/azure-pipelines-test.yml

@@ -45,6 +45,7 @@ jobs:
       - task: SonarCloudPrepare@1
         displayName: 'Prepare analysis on SonarCloud'
         condition: eq(variables['ImageName'], 'ubuntu-latest')
+        enabled: false
         inputs:
           SonarCloud: 'Sonarcloud for Jellyfin'
           organization: 'jellyfin'
@@ -63,14 +64,17 @@ jobs:
       - task: SonarCloudAnalyze@1
         displayName: 'Run Code Analysis'
         condition: eq(variables['ImageName'], 'ubuntu-latest')
+        enabled: false
 
       - task: SonarCloudPublish@1
         displayName: 'Publish Quality Gate Result'
         condition: eq(variables['ImageName'], 'ubuntu-latest')
+        enabled: false
 
       - 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: 'Run ReportGenerator'
+        enabled: false
         inputs:
           reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
           targetdir: "$(Agent.TempDirectory)/merged/"
@@ -80,10 +84,10 @@ jobs:
       - 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'
+        enabled: false
         inputs:
           codeCoverageTool: "cobertura"
           #summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
           summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
           pathToSources: $(Build.SourcesDirectory)
           failIfCoverageEmpty: true
-

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

@@ -2,9 +2,9 @@ name: $(Date:yyyyMMdd)$(Rev:.r)
 
 variables:
 - name: TestProjects
-  value: "tests/**/*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
 
@@ -17,17 +17,17 @@ trigger:
 jobs:
   - template: azure-pipelines-main.yml
     parameters:
-      LinuxImage: "ubuntu-latest"
+      LinuxImage: 'ubuntu-latest'
       RestoreBuildProjects: $(RestoreBuildProjects)
 
   - template: azure-pipelines-test.yml
     parameters:
       ImageNames:
-        Linux: "ubuntu-latest"
-        Windows: "windows-latest"
-        macOS: "macos-latest"
+        Linux: 'ubuntu-latest'
+        Windows: 'windows-latest'
+        macOS: 'macos-latest'
 
-  - template: azure-pipelines-compat.yml
+  - template: azure-pipelines-abi.yml
     parameters:
       Packages:
         Naming:
@@ -42,4 +42,4 @@ jobs:
         Common:
           NugetPackageName: Jellyfin.Common
           AssemblyFileName: MediaBrowser.Common.dll
-      LinuxImage: "ubuntu-latest"
+      LinuxImage: 'ubuntu-latest'

+ 9 - 0
.github/dependabot.yml

@@ -0,0 +1,9 @@
+version: 2
+updates:
+- package-ecosystem: nuget
+  directory: "/"
+  schedule:
+    interval: weekly
+    time: '12:00'
+  open-pull-requests-limit: 10
+  

+ 2 - 0
CONTRIBUTORS.md

@@ -7,6 +7,7 @@
  - [anthonylavado](https://github.com/anthonylavado)
  - [Artiume](https://github.com/Artiume)
  - [AThomsen](https://github.com/AThomsen)
+ - [barronpm](https://github.com/barronpm)
  - [bilde2910](https://github.com/bilde2910)
  - [bfayers](https://github.com/bfayers)
  - [BnMcG](https://github.com/BnMcG)
@@ -130,6 +131,7 @@
  - [XVicarious](https://github.com/XVicarious)
  - [YouKnowBlom](https://github.com/YouKnowBlom)
  - [KristupasSavickas](https://github.com/KristupasSavickas)
+ - [Pusta](https://github.com/pusta)
 
 # Emby Contributors
 

+ 1 - 0
DvdLib/Ifo/Cell.cs

@@ -7,6 +7,7 @@ namespace DvdLib.Ifo
     public class Cell
     {
         public CellPlaybackInfo PlaybackInfo { get; private set; }
+
         public CellPositionInfo PositionInfo { get; private set; }
 
         internal void ParsePlayback(BinaryReader br)

+ 2 - 0
DvdLib/Ifo/Chapter.cs

@@ -5,7 +5,9 @@ namespace DvdLib.Ifo
     public class Chapter
     {
         public ushort ProgramChainNumber { get; private set; }
+
         public ushort ProgramNumber { get; private set; }
+
         public uint ChapterNumber { get; private set; }
 
         public Chapter(ushort pgcNum, ushort programNum, uint chapterNum)

+ 4 - 0
DvdLib/Ifo/ProgramChain.cs

@@ -22,7 +22,9 @@ namespace DvdLib.Ifo
         public readonly List<Cell> Cells;
 
         public DvdTime PlaybackTime { get; private set; }
+
         public UserOperation ProhibitedUserOperations { get; private set; }
+
         public byte[] AudioStreamControl { get; private set; } // 8*2 entries
         public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
 
@@ -33,9 +35,11 @@ namespace DvdLib.Ifo
         private ushort _goupProgramNumber;
 
         public ProgramPlaybackMode PlaybackMode { get; private set; }
+
         public uint ProgramCount { get; private set; }
 
         public byte StillTime { get; private set; }
+
         public byte[] Palette { get; private set; } // 16*4 entries
 
         private ushort _commandTableOffset;

+ 4 - 0
DvdLib/Ifo/Title.cs

@@ -8,8 +8,11 @@ namespace DvdLib.Ifo
     public class Title
     {
         public uint TitleNumber { get; private set; }
+
         public uint AngleCount { get; private set; }
+
         public ushort ChapterCount { get; private set; }
+
         public byte VideoTitleSetNumber { get; private set; }
 
         private ushort _parentalManagementMask;
@@ -17,6 +20,7 @@ namespace DvdLib.Ifo
         private uint _vtsStartSector; // relative to start of entire disk
 
         public ProgramChain EntryProgramChain { get; private set; }
+
         public readonly List<ProgramChain> ProgramChains;
 
         public readonly List<Chapter> Chapters;

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

@@ -4,11 +4,12 @@ using System;
 using System.Linq;
 using System.Threading.Tasks;
 using Emby.Dlna.Service;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.TV;
@@ -32,7 +33,8 @@ namespace Emby.Dlna.ContentDirectory
         private readonly IMediaEncoder _mediaEncoder;
         private readonly ITVSeriesManager _tvSeriesManager;
 
-        public ContentDirectory(IDlnaManager dlna,
+        public ContentDirectory(
+            IDlnaManager dlna,
             IUserDataManager userDataManager,
             IImageProcessor imageProcessor,
             ILibraryManager libraryManager,
@@ -131,7 +133,7 @@ namespace Emby.Dlna.ContentDirectory
 
             foreach (var user in _userManager.Users)
             {
-                if (user.Policy.IsAdministrator)
+                if (user.HasPermission(PermissionKind.IsAdministrator))
                 {
                     return user;
                 }

+ 24 - 16
Emby.Dlna/ContentDirectory/ControlHandler.cs

@@ -10,6 +10,7 @@ using System.Threading;
 using System.Xml;
 using Emby.Dlna.Didl;
 using Emby.Dlna.Service;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Drawing;
@@ -17,7 +18,6 @@ using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
@@ -28,6 +28,12 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Querying;
 using Microsoft.Extensions.Logging;
+using Book = MediaBrowser.Controller.Entities.Book;
+using Episode = MediaBrowser.Controller.Entities.TV.Episode;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
+using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
+using Series = MediaBrowser.Controller.Entities.TV.Series;
 
 namespace Emby.Dlna.ContentDirectory
 {
@@ -460,12 +466,12 @@ namespace Emby.Dlna.ContentDirectory
             }
             else if (search.SearchType == SearchType.Playlist)
             {
-                //items = items.OfType<Playlist>();
+                // items = items.OfType<Playlist>();
                 isFolder = true;
             }
             else if (search.SearchType == SearchType.MusicAlbum)
             {
-                //items = items.OfType<MusicAlbum>();
+                // items = items.OfType<MusicAlbum>();
                 isFolder = true;
             }
 
@@ -731,7 +737,7 @@ namespace Emby.Dlna.ContentDirectory
                 return GetGenres(item, user, query);
             }
 
-            var array = new ServerItem[]
+            var array = new[]
             {
                 new ServerItem(item)
                 {
@@ -920,7 +926,7 @@ namespace Emby.Dlna.ContentDirectory
         private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            //query.Parent = parent;
+            // query.Parent = parent;
             query.SetUser(user);
 
             query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
@@ -1115,7 +1121,7 @@ namespace Emby.Dlna.ContentDirectory
         private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query)
         {
             query.Parent = null;
-            query.IncludeItemTypes = new[] { typeof(Playlist).Name };
+            query.IncludeItemTypes = new[] { nameof(Playlist) };
             query.SetUser(user);
             query.Recursive = true;
 
@@ -1132,10 +1138,9 @@ namespace Emby.Dlna.ContentDirectory
             {
                 UserId = user.Id,
                 Limit = 50,
-                IncludeItemTypes = new[] { typeof(Audio).Name },
-                ParentId = parent == null ? Guid.Empty : parent.Id,
+                IncludeItemTypes = new[] { nameof(Audio) },
+                ParentId = parent?.Id ?? Guid.Empty,
                 GroupItems = true
-
             }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
 
             return ToResult(items);
@@ -1150,7 +1155,6 @@ namespace Emby.Dlna.ContentDirectory
                 Limit = query.Limit,
                 StartIndex = query.StartIndex,
                 UserId = query.User.Id
-
             }, new[] { parent }, query.DtoOptions);
 
             return ToResult(result);
@@ -1167,7 +1171,6 @@ namespace Emby.Dlna.ContentDirectory
                 IncludeItemTypes = new[] { typeof(Episode).Name },
                 ParentId = parent == null ? Guid.Empty : parent.Id,
                 GroupItems = false
-
             }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
 
             return ToResult(items);
@@ -1177,14 +1180,14 @@ namespace Emby.Dlna.ContentDirectory
         {
             query.OrderBy = Array.Empty<(string, SortOrder)>();
 
-            var items = _userViewManager.GetLatestItems(new LatestItemsQuery
+            var items = _userViewManager.GetLatestItems(
+                new LatestItemsQuery
             {
                 UserId = user.Id,
                 Limit = 50,
-                IncludeItemTypes = new[] { typeof(Movie).Name },
-                ParentId = parent == null ? Guid.Empty : parent.Id,
+                IncludeItemTypes = new[] { nameof(Movie) },
+                ParentId = parent?.Id ?? Guid.Empty,
                 GroupItems = true
-
             }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
 
             return ToResult(items);
@@ -1217,7 +1220,11 @@ namespace Emby.Dlna.ContentDirectory
                 Recursive = true,
                 ParentId = parentId,
                 GenreIds = new[] { item.Id },
-                IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name },
+                IncludeItemTypes = new[]
+                {
+                    nameof(Movie),
+                    nameof(Series)
+                },
                 Limit = limit,
                 StartIndex = startIndex,
                 DtoOptions = GetDtoOptions()
@@ -1350,6 +1357,7 @@ namespace Emby.Dlna.ContentDirectory
     internal class ServerItem
     {
         public BaseItem Item { get; set; }
+
         public StubType? StubType { get; set; }
 
         public ServerItem(BaseItem item)

+ 21 - 15
Emby.Dlna/Didl/DidlBuilder.cs

@@ -6,14 +6,13 @@ using System.IO;
 using System.Linq;
 using System.Text;
 using System.Xml;
-using Emby.Dlna.Configuration;
 using Emby.Dlna.ContentDirectory;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Playlists;
@@ -23,6 +22,13 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Net;
 using Microsoft.Extensions.Logging;
+using Episode = MediaBrowser.Controller.Entities.TV.Episode;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
+using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
+using Season = MediaBrowser.Controller.Entities.TV.Season;
+using Series = MediaBrowser.Controller.Entities.TV.Series;
+using XmlAttribute = MediaBrowser.Model.Dlna.XmlAttribute;
 
 namespace Emby.Dlna.Didl
 {
@@ -92,21 +98,21 @@ namespace Emby.Dlna.Didl
             {
                 using (var writer = XmlWriter.Create(builder, settings))
                 {
-                    //writer.WriteStartDocument();
+                    // writer.WriteStartDocument();
 
                     writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
 
                     writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
                     writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
                     writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
-                    //didl.SetAttribute("xmlns:sec", NS_SEC);
+                    // didl.SetAttribute("xmlns:sec", NS_SEC);
 
                     WriteXmlRootAttributes(_profile, writer);
 
                     WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
 
                     writer.WriteFullEndElement();
-                    //writer.WriteEndDocument();
+                    // writer.WriteEndDocument();
                 }
 
                 return builder.ToString();
@@ -421,7 +427,6 @@ namespace Emby.Dlna.Didl
                     case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
                     case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
                     case StubType.Series: return _localization.GetLocalizedString("Shows");
-                    default: break;
                 }
             }
 
@@ -670,7 +675,7 @@ namespace Emby.Dlna.Didl
                 return;
             }
 
-            MediaBrowser.Model.Dlna.XmlAttribute secAttribute = null;
+            XmlAttribute secAttribute = null;
             foreach (var attribute in _profile.XmlRootAttributes)
             {
                 if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase))
@@ -700,13 +705,13 @@ namespace Emby.Dlna.Didl
         }
 
         /// <summary>
-        /// Adds fields used by both items and folders
+        /// Adds fields used by both items and folders.
         /// </summary>
         private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
         {
             // Don't filter on dc:title because not all devices will include it in the filter
             // MediaMonkey for example won't display content without a title
-            //if (filter.Contains("dc:title"))
+            // if (filter.Contains("dc:title"))
             {
                 AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
             }
@@ -745,7 +750,7 @@ namespace Emby.Dlna.Didl
                         AddValue(writer, "dc", "description", desc, NS_DC);
                     }
                 }
-                //if (filter.Contains("upnp:longDescription"))
+                // if (filter.Contains("upnp:longDescription"))
                 //{
                 //    if (!string.IsNullOrWhiteSpace(item.Overview))
                 //    {
@@ -760,6 +765,7 @@ namespace Emby.Dlna.Didl
                 {
                     AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
                 }
+
                 if (filter.Contains("upnp:rating"))
                 {
                     AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
@@ -995,7 +1001,6 @@ namespace Emby.Dlna.Didl
             }
 
             AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
-
         }
 
         private void AddImageResElement(
@@ -1048,10 +1053,12 @@ namespace Emby.Dlna.Didl
             {
                 return GetImageInfo(item, ImageType.Primary);
             }
+
             if (item.HasImage(ImageType.Thumb))
             {
                 return GetImageInfo(item, ImageType.Thumb);
             }
+
             if (item.HasImage(ImageType.Backdrop))
             {
                 if (item is Channel)
@@ -1131,25 +1138,24 @@ namespace Emby.Dlna.Didl
 
             if (width == 0 || height == 0)
             {
-                //_imageProcessor.GetImageSize(item, imageInfo);
+                // _imageProcessor.GetImageSize(item, imageInfo);
                 width = null;
                 height = null;
             }
-
             else if (width == -1 || height == -1)
             {
                 width = null;
                 height = null;
             }
 
-            //try
+            // try
             //{
             //    var size = _imageProcessor.GetImageSize(imageInfo);
 
             //    width = size.Width;
             //    height = size.Height;
             //}
-            //catch
+            // catch
             //{
 
             //}

+ 1 - 2
Emby.Dlna/Didl/Filter.cs

@@ -12,7 +12,6 @@ namespace Emby.Dlna.Didl
         public Filter()
             : this("*")
         {
-
         }
 
         public Filter(string filter)
@@ -26,7 +25,7 @@ namespace Emby.Dlna.Didl
         {
             // Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back.
             return true;
-            //return _all || ListHelper.ContainsIgnoreCase(_fields, field);
+            // return _all || ListHelper.ContainsIgnoreCase(_fields, field);
         }
     }
 }

+ 10 - 8
Emby.Dlna/DlnaManager.cs

@@ -31,7 +31,7 @@ namespace Emby.Dlna
         private readonly IApplicationPaths _appPaths;
         private readonly IXmlSerializer _xmlSerializer;
         private readonly IFileSystem _fileSystem;
-        private readonly ILogger _logger;
+        private readonly ILogger<DlnaManager> _logger;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IServerApplicationHost _appHost;
         private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
@@ -49,7 +49,7 @@ namespace Emby.Dlna
             _xmlSerializer = xmlSerializer;
             _fileSystem = fileSystem;
             _appPaths = appPaths;
-            _logger = loggerFactory.CreateLogger("Dlna");
+            _logger = loggerFactory.CreateLogger<DlnaManager>();
             _jsonSerializer = jsonSerializer;
             _appHost = appHost;
         }
@@ -88,7 +88,6 @@ namespace Emby.Dlna
                     .Select(i => i.Item2)
                     .ToList();
             }
-
         }
 
         public DeviceProfile GetDefaultProfile()
@@ -251,7 +250,7 @@ namespace Emby.Dlna
                         return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
                     case HeaderMatchType.Substring:
                         var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
-                        //_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
+                        // _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
                         return isMatch;
                     case HeaderMatchType.Regex:
                         return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
@@ -439,6 +438,7 @@ namespace Emby.Dlna
             {
                 throw new ArgumentException("Profile is missing Id");
             }
+
             if (string.IsNullOrEmpty(profile.Name))
             {
                 throw new ArgumentException("Profile is missing Name");
@@ -464,6 +464,7 @@ namespace Emby.Dlna
             {
                 _profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
             }
+
             SerializeToXml(profile, path);
         }
 
@@ -474,7 +475,7 @@ namespace Emby.Dlna
 
         /// <summary>
         /// Recreates the object using serialization, to ensure it's not a subclass.
-        /// If it's a subclass it may not serlialize properly to xml (different root element tag name)
+        /// If it's a subclass it may not serlialize properly to xml (different root element tag name).
         /// </summary>
         /// <param name="profile"></param>
         /// <returns></returns>
@@ -493,6 +494,7 @@ namespace Emby.Dlna
         class InternalProfileInfo
         {
             internal DeviceProfileInfo Info { get; set; }
+
             internal string Path { get; set; }
         }
 
@@ -566,9 +568,9 @@ namespace Emby.Dlna
                 new Foobar2000Profile(),
                 new SharpSmartTvProfile(),
                 new MediaMonkeyProfile(),
-                //new Windows81Profile(),
-                //new WindowsMediaCenterProfile(),
-                //new WindowsPhoneProfile(),
+                // new Windows81Profile(),
+                // new WindowsMediaCenterProfile(),
+                // new WindowsPhoneProfile(),
                 new DirectTvProfile(),
                 new DishHopperJoeyProfile(),
                 new DefaultProfile(),

+ 18 - 10
Emby.Dlna/Eventing/EventManager.cs

@@ -31,18 +31,26 @@ namespace Emby.Dlna.Eventing
         public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
         {
             var subscription = GetSubscription(subscriptionId, false);
+            if (subscription != null)
+            {
+                subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
+                int timeoutSeconds = subscription.TimeoutSeconds;
+                subscription.SubscriptionTime = DateTime.UtcNow;
 
-            subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
-            int timeoutSeconds = subscription.TimeoutSeconds;
-            subscription.SubscriptionTime = DateTime.UtcNow;
+                _logger.LogDebug(
+                    "Renewing event subscription for {0} with timeout of {1} to {2}",
+                    subscription.NotificationType,
+                    timeoutSeconds,
+                    subscription.CallbackUrl);
 
-            _logger.LogDebug(
-                "Renewing event subscription for {0} with timeout of {1} to {2}",
-                subscription.NotificationType,
-                timeoutSeconds,
-                subscription.CallbackUrl);
+                return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
+            }
 
-            return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
+            return new EventSubscriptionResponse
+            {
+                Content = string.Empty,
+                ContentType = "text/plain"
+            };
         }
 
         public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
@@ -150,6 +158,7 @@ namespace Emby.Dlna.Eventing
                 builder.Append("</" + key + ">");
                 builder.Append("</e:property>");
             }
+
             builder.Append("</e:propertyset>");
 
             var options = new HttpRequestOptions
@@ -169,7 +178,6 @@ namespace Emby.Dlna.Eventing
             {
                 using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
                 {
-
                 }
             }
             catch (OperationCanceledException)

+ 3 - 0
Emby.Dlna/Eventing/EventSubscription.cs

@@ -7,10 +7,13 @@ namespace Emby.Dlna.Eventing
     public class EventSubscription
     {
         public string Id { get; set; }
+
         public string CallbackUrl { get; set; }
+
         public string NotificationType { get; set; }
 
         public DateTime SubscriptionTime { get; set; }
+
         public int TimeoutSeconds { get; set; }
 
         public long TriggerCount { get; set; }

+ 9 - 5
Emby.Dlna/Main/DlnaEntryPoint.cs

@@ -33,7 +33,7 @@ namespace Emby.Dlna.Main
     public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
     {
         private readonly IServerConfigurationManager _config;
-        private readonly ILogger _logger;
+        private readonly ILogger<DlnaEntryPoint> _logger;
         private readonly IServerApplicationHost _appHost;
 
         private PlayToManager _manager;
@@ -65,7 +65,8 @@ namespace Emby.Dlna.Main
 
         public static DlnaEntryPoint Current;
 
-        public DlnaEntryPoint(IServerConfigurationManager config,
+        public DlnaEntryPoint(
+            IServerConfigurationManager config,
             ILoggerFactory loggerFactory,
             IServerApplicationHost appHost,
             ISessionManager sessionManager,
@@ -99,7 +100,7 @@ namespace Emby.Dlna.Main
             _mediaEncoder = mediaEncoder;
             _socketFactory = socketFactory;
             _networkManager = networkManager;
-            _logger = loggerFactory.CreateLogger("Dlna");
+            _logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
 
             ContentDirectory = new ContentDirectory.ContentDirectory(
                 dlnaManager,
@@ -275,7 +276,7 @@ namespace Emby.Dlna.Main
 
                 var device = new SsdpRootDevice
                 {
-                    CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
+                    CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
                     Location = uri, // Must point to the URL that serves your devices UPnP description document.
                     Address = address,
                     SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
@@ -319,6 +320,7 @@ namespace Emby.Dlna.Main
             {
                 guid = text.GetMD5();
             }
+
             return guid.ToString("N", CultureInfo.InvariantCulture);
         }
 
@@ -347,7 +349,8 @@ namespace Emby.Dlna.Main
 
                 try
                 {
-                    _manager = new PlayToManager(_logger,
+                    _manager = new PlayToManager(
+                        _logger,
                         _sessionManager,
                         _libraryManager,
                         _userManager,
@@ -386,6 +389,7 @@ namespace Emby.Dlna.Main
                     {
                         _logger.LogError(ex, "Error disposing PlayTo manager");
                     }
+
                     _manager = null;
                 }
             }

+ 4 - 5
Emby.Dlna/PlayTo/Device.cs

@@ -37,6 +37,7 @@ namespace Emby.Dlna.PlayTo
                 RefreshVolumeIfNeeded().GetAwaiter().GetResult();
                 return _volume;
             }
+
             set => _volume = value;
         }
 
@@ -232,7 +233,7 @@ namespace Emby.Dlna.PlayTo
         }
 
         /// <summary>
-        /// Sets volume on a scale of 0-100
+        /// Sets volume on a scale of 0-100.
         /// </summary>
         public async Task SetVolume(int value, CancellationToken cancellationToken)
         {
@@ -494,6 +495,7 @@ namespace Emby.Dlna.PlayTo
                         return;
                     }
                 }
+
                 RestartTimerInactive();
             }
         }
@@ -750,7 +752,7 @@ namespace Emby.Dlna.PlayTo
 
             if (track == null)
             {
-                //If track is null, some vendors do this, use GetMediaInfo instead
+                // If track is null, some vendors do this, use GetMediaInfo instead
                 return (true, null);
             }
 
@@ -794,7 +796,6 @@ namespace Emby.Dlna.PlayTo
             }
             catch (XmlException)
             {
-
             }
 
             // first try to add a root node with a dlna namesapce
@@ -806,7 +807,6 @@ namespace Emby.Dlna.PlayTo
             }
             catch (XmlException)
             {
-
             }
 
             // some devices send back invalid xml
@@ -816,7 +816,6 @@ namespace Emby.Dlna.PlayTo
             }
             catch (XmlException)
             {
-
             }
 
             return null;

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

@@ -7,6 +7,7 @@ using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using Emby.Dlna.Didl;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
@@ -22,6 +23,7 @@ using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Session;
 using Microsoft.AspNetCore.WebUtilities;
 using Microsoft.Extensions.Logging;
+using Photo = MediaBrowser.Controller.Entities.Photo;
 
 namespace Emby.Dlna.PlayTo
 {
@@ -423,6 +425,7 @@ namespace Emby.Dlna.PlayTo
                     await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
                     return;
                 }
+
                 await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
             }
         }
@@ -446,7 +449,13 @@ namespace Emby.Dlna.PlayTo
             }
         }
 
-        private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
+        private PlaylistItem CreatePlaylistItem(
+            BaseItem item,
+            User user,
+            long startPostionTicks,
+            string mediaSourceId,
+            int? audioStreamIndex,
+            int? subtitleStreamIndex)
         {
             var deviceInfo = _device.Properties;
 
@@ -705,6 +714,7 @@ namespace Emby.Dlna.PlayTo
 
                             throw new ArgumentException("Volume argument cannot be null");
                         }
+
                     default:
                         return Task.CompletedTask;
                 }
@@ -790,12 +800,15 @@ namespace Emby.Dlna.PlayTo
             public int? SubtitleStreamIndex { get; set; }
 
             public string DeviceProfileId { get; set; }
+
             public string DeviceId { get; set; }
 
             public string MediaSourceId { get; set; }
+
             public string LiveStreamId { get; set; }
 
             public BaseItem Item { get; set; }
+
             private MediaSourceInfo MediaSource;
 
             private IMediaSourceManager _mediaSourceManager;

+ 4 - 4
Emby.Dlna/PlayTo/PlayToManager.cs

@@ -88,7 +88,7 @@ namespace Emby.Dlna.PlayTo
             if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
                      nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1)
             {
-                //_logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location);
+                // _logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location);
                 return;
             }
 
@@ -112,7 +112,6 @@ namespace Emby.Dlna.PlayTo
             }
             catch (OperationCanceledException)
             {
-
             }
             catch (Exception ex)
             {
@@ -133,6 +132,7 @@ namespace Emby.Dlna.PlayTo
                 usn = usn.Substring(index);
                 found = true;
             }
+
             index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase);
             if (index != -1)
             {
@@ -184,7 +184,8 @@ namespace Emby.Dlna.PlayTo
                     serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
                 }
 
-                controller = new PlayToController(sessionInfo,
+                controller = new PlayToController(
+                    sessionInfo,
                    _sessionManager,
                    _libraryManager,
                    _logger,
@@ -242,7 +243,6 @@ namespace Emby.Dlna.PlayTo
             }
             catch
             {
-
             }
 
             _sessionLock.Dispose();

+ 1 - 0
Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs

@@ -12,6 +12,7 @@ namespace Emby.Dlna.PlayTo
     public class MediaChangedEventArgs : EventArgs
     {
         public uBaseObject OldMediaInfo { get; set; }
+
         public uBaseObject NewMediaInfo { get; set; }
     }
 }

+ 0 - 1
Emby.Dlna/PlayTo/SsdpHttpClient.cs

@@ -91,7 +91,6 @@ namespace Emby.Dlna.PlayTo
 
             using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
             {
-
             }
         }
 

+ 2 - 0
Emby.Dlna/PlayTo/uBaseObject.cs

@@ -44,10 +44,12 @@ namespace Emby.Dlna.PlayTo
                 {
                     return MediaBrowser.Model.Entities.MediaType.Audio;
                 }
+
                 if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1)
                 {
                     return MediaBrowser.Model.Entities.MediaType.Video;
                 }
+
                 if (classType.IndexOf("image", StringComparison.Ordinal) != -1)
                 {
                     return MediaBrowser.Model.Entities.MediaType.Photo;

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

@@ -134,6 +134,7 @@ namespace Emby.Dlna.Server
                     return result;
                 }
             }
+
             return c.ToString(CultureInfo.InvariantCulture);
         }
 
@@ -157,18 +158,22 @@ namespace Emby.Dlna.Server
                 {
                     break;
                 }
+
                 if (stringBuilder == null)
                 {
                     stringBuilder = new StringBuilder();
                 }
+
                 stringBuilder.Append(str, num, num2 - num);
                 stringBuilder.Append(GetEscapeSequence(str[num2]));
                 num = num2 + 1;
             }
+
             if (stringBuilder == null)
             {
                 return str;
             }
+
             stringBuilder.Append(str, num, length - num);
             return stringBuilder.ToString();
         }

+ 4 - 0
Emby.Dlna/Service/BaseControlHandler.cs

@@ -18,6 +18,7 @@ namespace Emby.Dlna.Service
         private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
 
         protected IServerConfigurationManager Config { get; }
+
         protected ILogger Logger { get; }
 
         protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
@@ -135,6 +136,7 @@ namespace Emby.Dlna.Service
 
                                 break;
                             }
+
                         default:
                             {
                                 await reader.SkipAsync().ConfigureAwait(false);
@@ -211,7 +213,9 @@ namespace Emby.Dlna.Service
         private class ControlRequestInfo
         {
             public string LocalName { get; set; }
+
             public string NamespaceURI { get; set; }
+
             public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
         }
 

+ 1 - 1
Emby.Dlna/Service/BaseService.cs

@@ -17,7 +17,7 @@ namespace Emby.Dlna.Service
             Logger = logger;
             HttpClient = httpClient;
 
-            EventManager = new EventManager(Logger, HttpClient);
+            EventManager = new EventManager(logger, HttpClient);
         }
 
         public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)

+ 1 - 0
Emby.Dlna/Service/ServiceXmlBuilder.cs

@@ -80,6 +80,7 @@ namespace Emby.Dlna.Service
                     {
                         builder.Append("<allowedValue>" + DescriptionXmlBuilder.Escape(allowedValue) + "</allowedValue>");
                     }
+
                     builder.Append("</allowedValueList>");
                 }
 

+ 1 - 1
Emby.Dlna/Ssdp/DeviceDiscovery.cs

@@ -77,7 +77,7 @@ namespace Emby.Dlna.Ssdp
                     // (Optional) Set the filter so we only see notifications for devices we care about
                     // (can be any search target value i.e device type, uuid value etc - any value that appears in the
                     // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method).
-                    //_DeviceLocator.NotificationFilter = "upnp:rootdevice";
+                    // _DeviceLocator.NotificationFilter = "upnp:rootdevice";
 
                     // Connect our event handler so we process devices as they are found
                     _deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable;

+ 11 - 2
Emby.Drawing/ImageProcessor.cs

@@ -4,6 +4,7 @@ using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Drawing;
@@ -14,6 +15,7 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Net;
 using Microsoft.Extensions.Logging;
+using Photo = MediaBrowser.Controller.Entities.Photo;
 
 namespace Emby.Drawing
 {
@@ -28,7 +30,7 @@ namespace Emby.Drawing
         private static readonly HashSet<string> _transparentImageTypes
             = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
 
-        private readonly ILogger _logger;
+        private readonly ILogger<ImageProcessor> _logger;
         private readonly IFileSystem _fileSystem;
         private readonly IServerApplicationPaths _appPaths;
         private readonly IImageEncoder _imageEncoder;
@@ -300,7 +302,7 @@ namespace Emby.Drawing
             }
 
             string path = info.Path;
-            _logger.LogInformation("Getting image size for item {ItemType} {Path}", item.GetType().Name, path);
+            _logger.LogDebug("Getting image size for item {ItemType} {Path}", item.GetType().Name, path);
 
             ImageDimensions size = GetImageDimensions(path);
             info.Width = size.Width;
@@ -349,6 +351,13 @@ namespace Emby.Drawing
             });
         }
 
+        /// <inheritdoc />
+        public string GetImageCacheTag(User user)
+        {
+            return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5()
+                .ToString("N", CultureInfo.InvariantCulture);
+        }
+
         private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
         {
             var inputFormat = Path.GetExtension(originalImagePath)

+ 1 - 0
Emby.Naming/AudioBook/AudioBookFilePathParser.cs

@@ -64,6 +64,7 @@ namespace Emby.Naming.AudioBook
                 {
                     result.ChapterNumber = int.Parse(matches[0].Groups[0].Value);
                 }
+
                 if (matches.Count > 1)
                 {
                     result.PartNumber = int.Parse(matches[matches.Count - 1].Groups[0].Value);

+ 3 - 3
Emby.Naming/Common/MediaType.cs

@@ -5,17 +5,17 @@ namespace Emby.Naming.Common
     public enum MediaType
     {
         /// <summary>
-        /// The audio
+        /// The audio.
         /// </summary>
         Audio = 0,
 
         /// <summary>
-        /// The photo
+        /// The photo.
         /// </summary>
         Photo = 1,
 
         /// <summary>
-        /// The video
+        /// The video.
         /// </summary>
         Video = 2
     }

+ 6 - 4
Emby.Notifications/Api/NotificationsService.cs

@@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Notifications;
@@ -149,9 +150,7 @@ namespace Emby.Notifications.Api
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
         public object Get(GetNotificationsSummary request)
         {
-            return new NotificationsSummary
-            {
-            };
+            return new NotificationsSummary();
         }
 
         public Task Post(AddAdminNotification request)
@@ -164,7 +163,10 @@ namespace Emby.Notifications.Api
                 Level = request.Level,
                 Name = request.Name,
                 Url = request.Url,
-                UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray()
+                UserIds = _userManager.Users
+                    .Where(user => user.HasPermission(PermissionKind.IsAdministrator))
+                    .Select(user => user.Id)
+                    .ToArray()
             };
 
             return _notificationManager.SendNotification(notification, CancellationToken.None);

+ 1 - 1
Emby.Notifications/NotificationEntryPoint.cs

@@ -25,7 +25,7 @@ namespace Emby.Notifications
     /// </summary>
     public class NotificationEntryPoint : IServerEntryPoint
     {
-        private readonly ILogger _logger;
+        private readonly ILogger<NotificationEntryPoint> _logger;
         private readonly IActivityManager _activityManager;
         private readonly ILocalizationManager _localization;
         private readonly INotificationManager _notificationManager;

+ 6 - 4
Emby.Notifications/NotificationManager.cs

@@ -4,6 +4,8 @@ using System.Globalization;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
@@ -21,7 +23,7 @@ namespace Emby.Notifications
     /// </summary>
     public class NotificationManager : INotificationManager
     {
-        private readonly ILogger _logger;
+        private readonly ILogger<NotificationManager> _logger;
         private readonly IUserManager _userManager;
         private readonly IServerConfigurationManager _config;
 
@@ -101,7 +103,7 @@ namespace Emby.Notifications
                 switch (request.SendToUserMode.Value)
                 {
                     case SendToUserType.Admins:
-                        return _userManager.Users.Where(i => i.Policy.IsAdministrator)
+                        return _userManager.Users.Where(i => i.HasPermission(PermissionKind.IsAdministrator))
                                 .Select(i => i.Id);
                     case SendToUserType.All:
                         return _userManager.UsersIds;
@@ -117,7 +119,7 @@ namespace Emby.Notifications
                 var config = GetConfiguration();
 
                 return _userManager.Users
-                    .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i.Policy))
+                    .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i))
                     .Select(i => i.Id);
             }
 
@@ -142,7 +144,7 @@ namespace Emby.Notifications
                 User = user
             };
 
-            _logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Name);
+            _logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Username);
 
             try
             {

+ 2 - 2
Emby.Photos/PhotoProvider.cs

@@ -22,7 +22,7 @@ namespace Emby.Photos
     /// </summary>
     public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
     {
-        private readonly ILogger _logger;
+        private readonly ILogger<PhotoProvider> _logger;
         private readonly IImageProcessor _imageProcessor;
 
         // These are causing taglib to hang
@@ -104,7 +104,7 @@ namespace Emby.Photos
                             item.Overview = image.ImageTag.Comment;
 
                             if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)
-                                && !item.LockedFields.Contains(MetadataFields.Name))
+                                && !item.LockedFields.Contains(MetadataField.Name))
                             {
                                 item.Name = image.ImageTag.Title;
                             }

+ 22 - 34
Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs

@@ -88,25 +88,26 @@ namespace Emby.Server.Implementations.Activity
 
             _subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
 
-            _userManager.UserCreated += OnUserCreated;
-            _userManager.UserPasswordChanged += OnUserPasswordChanged;
-            _userManager.UserDeleted += OnUserDeleted;
-            _userManager.UserPolicyUpdated += OnUserPolicyUpdated;
-            _userManager.UserLockedOut += OnUserLockedOut;
+            _userManager.OnUserCreated += OnUserCreated;
+            _userManager.OnUserPasswordChanged += OnUserPasswordChanged;
+            _userManager.OnUserDeleted += OnUserDeleted;
+            _userManager.OnUserLockedOut += OnUserLockedOut;
 
             return Task.CompletedTask;
         }
 
-        private async void OnUserLockedOut(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
+        private async void OnUserLockedOut(object sender, GenericEventArgs<User> e)
         {
             await CreateLogEntry(new ActivityLog(
                     string.Format(
                         CultureInfo.InvariantCulture,
                         _localization.GetLocalizedString("UserLockedOutWithName"),
-                        e.Argument.Name),
+                        e.Argument.Username),
                     NotificationType.UserLockedOut.ToString(),
-                    e.Argument.Id))
-                .ConfigureAwait(false);
+                    e.Argument.Id)
+            {
+                LogSeverity = LogLevel.Error
+            }).ConfigureAwait(false);
         }
 
         private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
@@ -152,7 +153,7 @@ namespace Emby.Server.Implementations.Activity
                 string.Format(
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
-                    user.Name,
+                    user.Username,
                     GetItemName(item),
                     e.DeviceName),
                 GetPlaybackStoppedNotificationType(item.MediaType),
@@ -187,7 +188,7 @@ namespace Emby.Server.Implementations.Activity
                 string.Format(
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
-                    user.Name,
+                    user.Username,
                     GetItemName(item),
                     e.DeviceName),
                 GetPlaybackNotificationType(item.MediaType),
@@ -304,49 +305,37 @@ namespace Emby.Server.Implementations.Activity
             }).ConfigureAwait(false);
         }
 
-        private async void OnUserPolicyUpdated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
-        {
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("UserPolicyUpdatedWithName"),
-                    e.Argument.Name),
-                "UserPolicyUpdated",
-                e.Argument.Id))
-                .ConfigureAwait(false);
-        }
-
-        private async void OnUserDeleted(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
+        private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
         {
             await CreateLogEntry(new ActivityLog(
                 string.Format(
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserDeletedWithName"),
-                    e.Argument.Name),
+                    e.Argument.Username),
                 "UserDeleted",
                 Guid.Empty))
                 .ConfigureAwait(false);
         }
 
-        private async void OnUserPasswordChanged(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
+        private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
         {
             await CreateLogEntry(new ActivityLog(
                 string.Format(
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserPasswordChangedWithName"),
-                    e.Argument.Name),
+                    e.Argument.Username),
                 "UserPasswordChanged",
                 e.Argument.Id))
                 .ConfigureAwait(false);
         }
 
-        private async void OnUserCreated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
+        private async void OnUserCreated(object sender, GenericEventArgs<User> e)
         {
             await CreateLogEntry(new ActivityLog(
                 string.Format(
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserCreatedWithName"),
-                    e.Argument.Name),
+                    e.Argument.Username),
                 "UserCreated",
                 e.Argument.Id))
                 .ConfigureAwait(false);
@@ -510,11 +499,10 @@ namespace Emby.Server.Implementations.Activity
 
             _subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
 
-            _userManager.UserCreated -= OnUserCreated;
-            _userManager.UserPasswordChanged -= OnUserPasswordChanged;
-            _userManager.UserDeleted -= OnUserDeleted;
-            _userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
-            _userManager.UserLockedOut -= OnUserLockedOut;
+            _userManager.OnUserCreated -= OnUserCreated;
+            _userManager.OnUserPasswordChanged -= OnUserPasswordChanged;
+            _userManager.OnUserDeleted -= OnUserDeleted;
+            _userManager.OnUserLockedOut -= OnUserLockedOut;
         }
 
         /// <summary>

+ 2 - 2
Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs

@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.AppBase
             CommonApplicationPaths = applicationPaths;
             XmlSerializer = xmlSerializer;
             _fileSystem = fileSystem;
-            Logger = loggerFactory.CreateLogger(GetType().Name);
+            Logger = loggerFactory.CreateLogger<BaseConfigurationManager>();
 
             UpdateCachePath();
         }
@@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.AppBase
         /// Gets the logger.
         /// </summary>
         /// <value>The logger.</value>
-        protected ILogger Logger { get; private set; }
+        protected ILogger<BaseConfigurationManager> Logger { get; private set; }
 
         /// <summary>
         /// Gets the XML serializer.

+ 3 - 11
Emby.Server.Implementations/ApplicationHost.cs

@@ -173,7 +173,7 @@ namespace Emby.Server.Implementations
         /// <summary>
         /// Gets the logger.
         /// </summary>
-        protected ILogger Logger { get; }
+        protected ILogger<ApplicationHost> Logger { get; }
 
         private IPlugin[] _plugins;
 
@@ -562,11 +562,8 @@ namespace Emby.Server.Implementations
 
             serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
 
-            serviceCollection.AddSingleton<IUserRepository, SqliteUserRepository>();
-
             // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
             serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
-            serviceCollection.AddSingleton<IUserManager, UserManager>();
 
             // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
             // TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
@@ -659,15 +656,11 @@ namespace Emby.Server.Implementations
 
             ((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
             ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
-            ((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
 
             SetStaticProperties();
 
-            var userManager = (UserManager)Resolve<IUserManager>();
-            userManager.Initialize();
-
             var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
-            ((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, userManager);
+            ((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, Resolve<IUserManager>());
 
             FindParts();
         }
@@ -750,7 +743,6 @@ namespace Emby.Server.Implementations
             BaseItem.ProviderManager = Resolve<IProviderManager>();
             BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
             BaseItem.ItemRepository = Resolve<IItemRepository>();
-            User.UserManager = Resolve<IUserManager>();
             BaseItem.FileSystem = _fileSystemManager;
             BaseItem.UserDataManager = Resolve<IUserDataManager>();
             BaseItem.ChannelManager = Resolve<IChannelManager>();
@@ -964,7 +956,7 @@ namespace Emby.Server.Implementations
         }
 
         /// <summary>
-        /// Notifies that the kernel that a change has been made that requires a restart
+        /// Notifies that the kernel that a change has been made that requires a restart.
         /// </summary>
         public void NotifyPendingRestart()
         {

+ 1 - 1
Emby.Server.Implementations/Browser/BrowserLauncher.cs

@@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.Browser
             }
             catch (Exception ex)
             {
-                var logger = appHost.Resolve<ILogger>();
+                var logger = appHost.Resolve<ILogger<IServerApplicationHost>>();
                 logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
             }
         }

+ 10 - 5
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -6,6 +6,7 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller.Channels;
@@ -13,8 +14,6 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Channels;
@@ -24,6 +23,11 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Serialization;
 using Microsoft.Extensions.Logging;
+using Episode = MediaBrowser.Controller.Entities.TV.Episode;
+using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
+using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
+using Season = MediaBrowser.Controller.Entities.TV.Season;
+using Series = MediaBrowser.Controller.Entities.TV.Series;
 
 namespace Emby.Server.Implementations.Channels
 {
@@ -36,7 +40,7 @@ namespace Emby.Server.Implementations.Channels
         private readonly IUserDataManager _userDataManager;
         private readonly IDtoService _dtoService;
         private readonly ILibraryManager _libraryManager;
-        private readonly ILogger _logger;
+        private readonly ILogger<ChannelManager> _logger;
         private readonly IServerConfigurationManager _config;
         private readonly IFileSystem _fileSystem;
         private readonly IJsonSerializer _jsonSerializer;
@@ -791,7 +795,8 @@ namespace Emby.Server.Implementations.Channels
             return result;
         }
 
-        private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
+        private async Task<ChannelItemResult> GetChannelItems(
+            IChannel channel,
             User user,
             string externalFolderId,
             ChannelItemSortField? sortField,
@@ -1067,7 +1072,7 @@ namespace Emby.Server.Implementations.Channels
             }
 
             // was used for status
-            //if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
+            // if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
             //{
             //    item.ExternalEtag = info.Etag;
             //    forceUpdate = true;

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

@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Channels
     public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
     {
         private readonly IChannelManager _channelManager;
-        private readonly ILogger _logger;
+        private readonly ILogger<RefreshChannelsScheduledTask> _logger;
         private readonly ILibraryManager _libraryManager;
         private readonly ILocalizationManager _localization;
 

+ 4 - 3
Emby.Server.Implementations/Collections/CollectionManager.cs

@@ -5,6 +5,7 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Collections;
 using MediaBrowser.Controller.Configuration;
@@ -29,7 +30,7 @@ namespace Emby.Server.Implementations.Collections
         private readonly ILibraryManager _libraryManager;
         private readonly IFileSystem _fileSystem;
         private readonly ILibraryMonitor _iLibraryMonitor;
-        private readonly ILogger _logger;
+        private readonly ILogger<CollectionManager> _logger;
         private readonly IProviderManager _providerManager;
         private readonly ILocalizationManager _localizationManager;
         private readonly IApplicationPaths _appPaths;
@@ -56,7 +57,7 @@ namespace Emby.Server.Implementations.Collections
             _libraryManager = libraryManager;
             _fileSystem = fileSystem;
             _iLibraryMonitor = iLibraryMonitor;
-            _logger = loggerFactory.CreateLogger(nameof(CollectionManager));
+            _logger = loggerFactory.CreateLogger<CollectionManager>();
             _providerManager = providerManager;
             _localizationManager = localizationManager;
             _appPaths = appPaths;
@@ -370,7 +371,7 @@ namespace Emby.Server.Implementations.Collections
     {
         private readonly CollectionManager _collectionManager;
         private readonly IServerConfigurationManager _config;
-        private readonly ILogger _logger;
+        private readonly ILogger<CollectionManagerEntryPoint> _logger;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class.

+ 4 - 5
Emby.Server.Implementations/Data/BaseSqliteRepository.cs

@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Data
         /// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class.
         /// </summary>
         /// <param name="logger">The logger.</param>
-        protected BaseSqliteRepository(ILogger logger)
+        protected BaseSqliteRepository(ILogger<BaseSqliteRepository> logger)
         {
             Logger = logger;
         }
@@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Data
         /// Gets the logger.
         /// </summary>
         /// <value>The logger.</value>
-        protected ILogger Logger { get; }
+        protected ILogger<BaseSqliteRepository> Logger { get; }
 
         /// <summary>
         /// Gets the default connection flags.
@@ -162,7 +162,6 @@ namespace Emby.Server.Implementations.Data
                 }
 
                 return false;
-
             }, ReadTransactionMode);
         }
 
@@ -248,12 +247,12 @@ namespace Emby.Server.Implementations.Data
     public enum SynchronousMode
     {
         /// <summary>
-        /// SQLite continues without syncing as soon as it has handed data off to the operating system
+        /// SQLite continues without syncing as soon as it has handed data off to the operating system.
         /// </summary>
         Off = 0,
 
         /// <summary>
-        /// SQLite database engine will still sync at the most critical moments
+        /// SQLite database engine will still sync at the most critical moments.
         /// </summary>
         Normal = 1,
 

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

@@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Data
     public class CleanDatabaseScheduledTask : ILibraryPostScanTask
     {
         private readonly ILibraryManager _libraryManager;
-        private readonly ILogger _logger;
+        private readonly ILogger<CleanDatabaseScheduledTask> _logger;
 
         public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger<CleanDatabaseScheduledTask> logger)
         {
@@ -51,7 +51,6 @@ namespace Emby.Server.Implementations.Data
                     _libraryManager.DeleteItem(item, new DeleteOptions
                     {
                         DeleteFileLocation = false
-
                     });
                 }
 

+ 4 - 4
Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs

@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
 
 using System;
 using System.Collections.Generic;
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Data
         }
 
         /// <summary>
-        /// Opens the connection to the database
+        /// Opens the connection to the database.
         /// </summary>
         /// <returns>Task.</returns>
         private void InitializeInternal()
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Data
         }
 
         /// <summary>
-        /// Save the display preferences associated with an item in the repo
+        /// Save the display preferences associated with an item in the repo.
         /// </summary>
         /// <param name="displayPreferences">The display preferences.</param>
         /// <param name="userId">The user id.</param>
@@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Data
         }
 
         /// <summary>
-        /// Save all display preferences associated with a user in the repo
+        /// Save all display preferences associated with a user in the repo.
         /// </summary>
         /// <param name="displayPreferences">The display preferences.</param>
         /// <param name="userId">The user id.</param>

+ 126 - 18
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Data
         protected override TempStoreMode TempStore => TempStoreMode.Memory;
 
         /// <summary>
-        /// Opens the connection to the database
+        /// Opens the connection to the database.
         /// </summary>
         public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager)
         {
@@ -321,7 +321,6 @@ namespace Emby.Server.Implementations.Data
                     AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames);
                     AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames);
                     AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames);
-
                 }, TransactionMode);
 
                 connection.RunQueries(postQueries);
@@ -549,7 +548,7 @@ namespace Emby.Server.Implementations.Data
         }
 
         /// <summary>
-        /// Save a standard item in the repo
+        /// Save a standard item in the repo.
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
@@ -794,6 +793,7 @@ namespace Emby.Server.Implementations.Data
             {
                 saveItemStatement.TryBindNull("@Width");
             }
+
             if (item.Height > 0)
             {
                 saveItemStatement.TryBind("@Height", item.Height);
@@ -933,6 +933,7 @@ namespace Emby.Server.Implementations.Data
             {
                 saveItemStatement.TryBindNull("@SeriesName");
             }
+
             if (string.IsNullOrWhiteSpace(userDataKey))
             {
                 saveItemStatement.TryBindNull("@UserDataKey");
@@ -1008,6 +1009,7 @@ namespace Emby.Server.Implementations.Data
             {
                 artists = string.Join("|", hasArtists.Artists);
             }
+
             saveItemStatement.TryBind("@Artists", artists);
 
             string albumArtists = null;
@@ -1107,6 +1109,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     continue;
                 }
+
                 str.Append(ToValueString(i) + "|");
             }
 
@@ -1205,7 +1208,7 @@ namespace Emby.Server.Implementations.Data
         }
 
         /// <summary>
-        /// Internal retrieve from items or users table
+        /// Internal retrieve from items or users table.
         /// </summary>
         /// <param name="id">The id.</param>
         /// <returns>BaseItem.</returns>
@@ -1367,6 +1370,7 @@ namespace Emby.Server.Implementations.Data
                         hasStartDate.StartDate = reader[index].ReadDateTime();
                     }
                 }
+
                 index++;
             }
 
@@ -1374,12 +1378,14 @@ namespace Emby.Server.Implementations.Data
             {
                 item.EndDate = reader[index].TryReadDateTime();
             }
+
             index++;
 
             if (!reader.IsDBNull(index))
             {
                 item.ChannelId = new Guid(reader.GetString(index));
             }
+
             index++;
 
             if (enableProgramAttributes)
@@ -1390,24 +1396,28 @@ namespace Emby.Server.Implementations.Data
                     {
                         hasProgramAttributes.IsMovie = reader.GetBoolean(index);
                     }
+
                     index++;
 
                     if (!reader.IsDBNull(index))
                     {
                         hasProgramAttributes.IsSeries = reader.GetBoolean(index);
                     }
+
                     index++;
 
                     if (!reader.IsDBNull(index))
                     {
                         hasProgramAttributes.EpisodeTitle = reader.GetString(index);
                     }
+
                     index++;
 
                     if (!reader.IsDBNull(index))
                     {
                         hasProgramAttributes.IsRepeat = reader.GetBoolean(index);
                     }
+
                     index++;
                 }
                 else
@@ -1420,6 +1430,7 @@ namespace Emby.Server.Implementations.Data
             {
                 item.CommunityRating = reader.GetFloat(index);
             }
+
             index++;
 
             if (HasField(query, ItemFields.CustomRating))
@@ -1428,6 +1439,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.CustomRating = reader.GetString(index);
                 }
+
                 index++;
             }
 
@@ -1435,6 +1447,7 @@ namespace Emby.Server.Implementations.Data
             {
                 item.IndexNumber = reader.GetInt32(index);
             }
+
             index++;
 
             if (HasField(query, ItemFields.Settings))
@@ -1443,18 +1456,21 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.IsLocked = reader.GetBoolean(index);
                 }
+
                 index++;
 
                 if (!reader.IsDBNull(index))
                 {
                     item.PreferredMetadataLanguage = reader.GetString(index);
                 }
+
                 index++;
 
                 if (!reader.IsDBNull(index))
                 {
                     item.PreferredMetadataCountryCode = reader.GetString(index);
                 }
+
                 index++;
             }
 
@@ -1464,6 +1480,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.Width = reader.GetInt32(index);
                 }
+
                 index++;
             }
 
@@ -1473,6 +1490,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.Height = reader.GetInt32(index);
                 }
+
                 index++;
             }
 
@@ -1482,6 +1500,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.DateLastRefreshed = reader[index].ReadDateTime();
                 }
+
                 index++;
             }
 
@@ -1489,18 +1508,21 @@ namespace Emby.Server.Implementations.Data
             {
                 item.Name = reader.GetString(index);
             }
+
             index++;
 
             if (!reader.IsDBNull(index))
             {
                 item.Path = RestorePath(reader.GetString(index));
             }
+
             index++;
 
             if (!reader.IsDBNull(index))
             {
                 item.PremiereDate = reader[index].TryReadDateTime();
             }
+
             index++;
 
             if (HasField(query, ItemFields.Overview))
@@ -1509,6 +1531,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.Overview = reader.GetString(index);
                 }
+
                 index++;
             }
 
@@ -1516,18 +1539,21 @@ namespace Emby.Server.Implementations.Data
             {
                 item.ParentIndexNumber = reader.GetInt32(index);
             }
+
             index++;
 
             if (!reader.IsDBNull(index))
             {
                 item.ProductionYear = reader.GetInt32(index);
             }
+
             index++;
 
             if (!reader.IsDBNull(index))
             {
                 item.OfficialRating = reader.GetString(index);
             }
+
             index++;
 
             if (HasField(query, ItemFields.SortName))
@@ -1536,6 +1562,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.ForcedSortName = reader.GetString(index);
                 }
+
                 index++;
             }
 
@@ -1543,12 +1570,14 @@ namespace Emby.Server.Implementations.Data
             {
                 item.RunTimeTicks = reader.GetInt64(index);
             }
+
             index++;
 
             if (!reader.IsDBNull(index))
             {
                 item.Size = reader.GetInt64(index);
             }
+
             index++;
 
             if (HasField(query, ItemFields.DateCreated))
@@ -1557,6 +1586,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.DateCreated = reader[index].ReadDateTime();
                 }
+
                 index++;
             }
 
@@ -1564,6 +1594,7 @@ namespace Emby.Server.Implementations.Data
             {
                 item.DateModified = reader[index].ReadDateTime();
             }
+
             index++;
 
             item.Id = reader.GetGuid(index);
@@ -1575,6 +1606,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.Genres = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                 }
+
                 index++;
             }
 
@@ -1582,6 +1614,7 @@ namespace Emby.Server.Implementations.Data
             {
                 item.ParentId = reader.GetGuid(index);
             }
+
             index++;
 
             if (!reader.IsDBNull(index))
@@ -1591,6 +1624,7 @@ namespace Emby.Server.Implementations.Data
                     item.Audio = audio;
                 }
             }
+
             index++;
 
             // TODO: Even if not needed by apps, the server needs it internally
@@ -1604,6 +1638,7 @@ namespace Emby.Server.Implementations.Data
                         liveTvChannel.ServiceName = reader.GetString(index);
                     }
                 }
+
                 index++;
             }
 
@@ -1611,6 +1646,7 @@ namespace Emby.Server.Implementations.Data
             {
                 item.IsInMixedFolder = reader.GetBoolean(index);
             }
+
             index++;
 
             if (HasField(query, ItemFields.DateLastSaved))
@@ -1619,6 +1655,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.DateLastSaved = reader[index].ReadDateTime();
                 }
+
                 index++;
             }
 
@@ -1626,18 +1663,20 @@ namespace Emby.Server.Implementations.Data
             {
                 if (!reader.IsDBNull(index))
                 {
-                    IEnumerable<MetadataFields> GetLockedFields(string s)
+                    IEnumerable<MetadataField> GetLockedFields(string s)
                     {
                         foreach (var i in s.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
                         {
-                            if (Enum.TryParse(i, true, out MetadataFields parsedValue))
+                            if (Enum.TryParse(i, true, out MetadataField parsedValue))
                             {
                                 yield return parsedValue;
                             }
                         }
                     }
+
                     item.LockedFields = GetLockedFields(reader.GetString(index)).ToArray();
                 }
+
                 index++;
             }
 
@@ -1647,6 +1686,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.Studios = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                 }
+
                 index++;
             }
 
@@ -1656,6 +1696,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.Tags = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                 }
+
                 index++;
             }
 
@@ -1675,9 +1716,11 @@ namespace Emby.Server.Implementations.Data
                                 }
                             }
                         }
+
                         trailer.TrailerTypes = GetTrailerTypes(reader.GetString(index)).ToArray();
                     }
                 }
+
                 index++;
             }
 
@@ -1687,6 +1730,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.OriginalTitle = reader.GetString(index);
                 }
+
                 index++;
             }
 
@@ -1697,6 +1741,7 @@ namespace Emby.Server.Implementations.Data
                     video.PrimaryVersionId = reader.GetString(index);
                 }
             }
+
             index++;
 
             if (HasField(query, ItemFields.DateLastMediaAdded))
@@ -1705,6 +1750,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     folder.DateLastMediaAdded = reader[index].TryReadDateTime();
                 }
+
                 index++;
             }
 
@@ -1712,18 +1758,21 @@ namespace Emby.Server.Implementations.Data
             {
                 item.Album = reader.GetString(index);
             }
+
             index++;
 
             if (!reader.IsDBNull(index))
             {
                 item.CriticRating = reader.GetFloat(index);
             }
+
             index++;
 
             if (!reader.IsDBNull(index))
             {
                 item.IsVirtualItem = reader.GetBoolean(index);
             }
+
             index++;
 
             if (item is IHasSeries hasSeriesName)
@@ -1733,6 +1782,7 @@ namespace Emby.Server.Implementations.Data
                     hasSeriesName.SeriesName = reader.GetString(index);
                 }
             }
+
             index++;
 
             if (hasEpisodeAttributes)
@@ -1743,6 +1793,7 @@ namespace Emby.Server.Implementations.Data
                     {
                         episode.SeasonName = reader.GetString(index);
                     }
+
                     index++;
                     if (!reader.IsDBNull(index))
                     {
@@ -1753,6 +1804,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     index++;
                 }
+
                 index++;
             }
 
@@ -1766,6 +1818,7 @@ namespace Emby.Server.Implementations.Data
                         hasSeries.SeriesId = reader.GetGuid(index);
                     }
                 }
+
                 index++;
             }
 
@@ -1775,6 +1828,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.PresentationUniqueKey = reader.GetString(index);
                 }
+
                 index++;
             }
 
@@ -1784,6 +1838,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.InheritedParentalRatingValue = reader.GetInt32(index);
                 }
+
                 index++;
             }
 
@@ -1793,6 +1848,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.ExternalSeriesId = reader.GetString(index);
                 }
+
                 index++;
             }
 
@@ -1802,6 +1858,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.Tagline = reader.GetString(index);
                 }
+
                 index++;
             }
 
@@ -1809,6 +1866,7 @@ namespace Emby.Server.Implementations.Data
             {
                 DeserializeProviderIds(reader.GetString(index), item);
             }
+
             index++;
 
             if (query.DtoOptions.EnableImages)
@@ -1817,6 +1875,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     DeserializeImages(reader.GetString(index), item);
                 }
+
                 index++;
             }
 
@@ -1826,6 +1885,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.ProductionLocations = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
                 }
+
                 index++;
             }
 
@@ -1835,6 +1895,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     item.ExtraIds = SplitToGuids(reader.GetString(index));
                 }
+
                 index++;
             }
 
@@ -1842,6 +1903,7 @@ namespace Emby.Server.Implementations.Data
             {
                 item.TotalBitrate = reader.GetInt32(index);
             }
+
             index++;
 
             if (!reader.IsDBNull(index))
@@ -1851,6 +1913,7 @@ namespace Emby.Server.Implementations.Data
                     item.ExtraType = extraType;
                 }
             }
+
             index++;
 
             if (hasArtistFields)
@@ -1859,12 +1922,14 @@ namespace Emby.Server.Implementations.Data
                 {
                     hasArtists.Artists = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                 }
+
                 index++;
 
                 if (item is IHasAlbumArtist hasAlbumArtists && !reader.IsDBNull(index))
                 {
                     hasAlbumArtists.AlbumArtists = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                 }
+
                 index++;
             }
 
@@ -1872,6 +1937,7 @@ namespace Emby.Server.Implementations.Data
             {
                 item.ExternalId = reader.GetString(index);
             }
+
             index++;
 
             if (HasField(query, ItemFields.SeriesPresentationUniqueKey))
@@ -1883,6 +1949,7 @@ namespace Emby.Server.Implementations.Data
                         hasSeries.SeriesPresentationUniqueKey = reader.GetString(index);
                     }
                 }
+
                 index++;
             }
 
@@ -1892,6 +1959,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     program.ShowId = reader.GetString(index);
                 }
+
                 index++;
             }
 
@@ -1899,6 +1967,7 @@ namespace Emby.Server.Implementations.Data
             {
                 item.OwnerId = reader.GetGuid(index);
             }
+
             index++;
 
             return item;
@@ -1919,7 +1988,7 @@ namespace Emby.Server.Implementations.Data
         }
 
         /// <summary>
-        /// Gets chapters for an item
+        /// Gets chapters for an item.
         /// </summary>
         /// <param name="item">The item.</param>
         /// <returns>IEnumerable{ChapterInfo}.</returns>
@@ -1947,7 +2016,7 @@ namespace Emby.Server.Implementations.Data
         }
 
         /// <summary>
-        /// Gets a single chapter for an item
+        /// Gets a single chapter for an item.
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="index">The index.</param>
@@ -2044,7 +2113,6 @@ namespace Emby.Server.Implementations.Data
                     db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob);
 
                     InsertChapters(idBlob, chapters, db);
-
                 }, TransactionMode);
             }
         }
@@ -2475,6 +2543,7 @@ namespace Emby.Server.Implementations.Data
             {
                 statement.TryBind("@SearchTermStartsWith", searchTerm + "%");
             }
+
             if (commandText.IndexOf("@SearchTermContains", StringComparison.OrdinalIgnoreCase) != -1)
             {
                 statement.TryBind("@SearchTermContains", "%" + searchTerm + "%");
@@ -2734,7 +2803,7 @@ namespace Emby.Server.Implementations.Data
 
                 foreach (var providerId in newItem.ProviderIds)
                 {
-                    if (providerId.Key == MetadataProviders.TmdbCollection.ToString())
+                    if (providerId.Key == MetadataProvider.TmdbCollection.ToString())
                     {
                         continue;
                     }
@@ -2745,6 +2814,7 @@ namespace Emby.Server.Implementations.Data
                         {
                             items[i] = newItem;
                         }
+
                         return;
                     }
                 }
@@ -2837,6 +2907,7 @@ namespace Emby.Server.Implementations.Data
             {
                 statementTexts.Add(commandText);
             }
+
             if (query.EnableTotalRecordCount)
             {
                 commandText = string.Empty;
@@ -3241,6 +3312,7 @@ namespace Emby.Server.Implementations.Data
             {
                 statementTexts.Add(commandText);
             }
+
             if (query.EnableTotalRecordCount)
             {
                 commandText = string.Empty;
@@ -3594,11 +3666,13 @@ namespace Emby.Server.Implementations.Data
                 whereClauses.Add("IndexNumber=@IndexNumber");
                 statement?.TryBind("@IndexNumber", query.IndexNumber.Value);
             }
+
             if (query.ParentIndexNumber.HasValue)
             {
                 whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
                 statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
             }
+
             if (query.ParentIndexNumberNotEquals.HasValue)
             {
                 whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
@@ -3884,6 +3958,7 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind(paramName, artistId.ToByteArray());
                     }
+
                     index++;
                 }
 
@@ -3904,6 +3979,7 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind(paramName, artistId.ToByteArray());
                     }
+
                     index++;
                 }
 
@@ -3924,8 +4000,10 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind(paramName, artistId.ToByteArray());
                     }
+
                     index++;
                 }
+
                 var clause = "(" + string.Join(" OR ", clauses) + ")";
                 whereClauses.Add(clause);
             }
@@ -3943,8 +4021,10 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind(paramName, albumId.ToByteArray());
                     }
+
                     index++;
                 }
+
                 var clause = "(" + string.Join(" OR ", clauses) + ")";
                 whereClauses.Add(clause);
             }
@@ -3962,8 +4042,10 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind(paramName, artistId.ToByteArray());
                     }
+
                     index++;
                 }
+
                 var clause = "(" + string.Join(" OR ", clauses) + ")";
                 whereClauses.Add(clause);
             }
@@ -3981,8 +4063,10 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind(paramName, genreId.ToByteArray());
                     }
+
                     index++;
                 }
+
                 var clause = "(" + string.Join(" OR ", clauses) + ")";
                 whereClauses.Add(clause);
             }
@@ -3998,8 +4082,10 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind("@Genre" + index, GetCleanValue(item));
                     }
+
                     index++;
                 }
+
                 var clause = "(" + string.Join(" OR ", clauses) + ")";
                 whereClauses.Add(clause);
             }
@@ -4015,8 +4101,10 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind("@Tag" + index, GetCleanValue(item));
                     }
+
                     index++;
                 }
+
                 var clause = "(" + string.Join(" OR ", clauses) + ")";
                 whereClauses.Add(clause);
             }
@@ -4032,8 +4120,10 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind("@ExcludeTag" + index, GetCleanValue(item));
                     }
+
                     index++;
                 }
+
                 var clause = "(" + string.Join(" OR ", clauses) + ")";
                 whereClauses.Add(clause);
             }
@@ -4052,8 +4142,10 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind(paramName, studioId.ToByteArray());
                     }
+
                     index++;
                 }
+
                 var clause = "(" + string.Join(" OR ", clauses) + ")";
                 whereClauses.Add(clause);
             }
@@ -4069,8 +4161,10 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind("@OfficialRating" + index, item);
                     }
+
                     index++;
                 }
+
                 var clause = "(" + string.Join(" OR ", clauses) + ")";
                 whereClauses.Add(clause);
             }
@@ -4245,6 +4339,7 @@ namespace Emby.Server.Implementations.Data
                     statement.TryBind("@IsVirtualItem", isVirtualItem.Value);
                 }
             }
+
             if (query.IsSpecialSeason.HasValue)
             {
                 if (query.IsSpecialSeason.Value)
@@ -4256,6 +4351,7 @@ namespace Emby.Server.Implementations.Data
                     whereClauses.Add("IndexNumber <> 0");
                 }
             }
+
             if (query.IsUnaired.HasValue)
             {
                 if (query.IsUnaired.Value)
@@ -4267,6 +4363,7 @@ namespace Emby.Server.Implementations.Data
                     whereClauses.Add("PremiereDate < DATETIME('now')");
                 }
             }
+
             var queryMediaTypes = query.MediaTypes.Where(IsValidMediaType).ToArray();
             if (queryMediaTypes.Length == 1)
             {
@@ -4282,6 +4379,7 @@ namespace Emby.Server.Implementations.Data
 
                 whereClauses.Add("MediaType in (" + val + ")");
             }
+
             if (query.ItemIds.Length > 0)
             {
                 var includeIds = new List<string>();
@@ -4294,11 +4392,13 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind("@IncludeId" + index, id);
                     }
+
                     index++;
                 }
 
                 whereClauses.Add("(" + string.Join(" OR ", includeIds) + ")");
             }
+
             if (query.ExcludeItemIds.Length > 0)
             {
                 var excludeIds = new List<string>();
@@ -4311,6 +4411,7 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind("@ExcludeId" + index, id);
                     }
+
                     index++;
                 }
 
@@ -4324,7 +4425,7 @@ namespace Emby.Server.Implementations.Data
                 var index = 0;
                 foreach (var pair in query.ExcludeProviderIds)
                 {
-                    if (string.Equals(pair.Key, MetadataProviders.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
+                    if (string.Equals(pair.Key, MetadataProvider.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
                     {
                         continue;
                     }
@@ -4335,6 +4436,7 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
                     }
+
                     index++;
 
                     break;
@@ -4353,14 +4455,14 @@ namespace Emby.Server.Implementations.Data
                 var index = 0;
                 foreach (var pair in query.HasAnyProviderId)
                 {
-                    if (string.Equals(pair.Key, MetadataProviders.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
+                    if (string.Equals(pair.Key, MetadataProvider.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
                     {
                         continue;
                     }
 
                     // TODO this seems to be an idea for a better schema where ProviderIds are their own table
                     //      buut this is not implemented
-                    //hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
+                    // hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
 
                     // TODO this is a really BAD way to do it since the pair:
                     //      Tmdb, 1234 matches Tmdb=1234 but also Tmdb=1234567
@@ -4377,6 +4479,7 @@ namespace Emby.Server.Implementations.Data
                     {
                         statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
                     }
+
                     index++;
 
                     break;
@@ -4427,6 +4530,7 @@ namespace Emby.Server.Implementations.Data
                 {
                     whereClauses.Add("(TopParentId=@TopParentId)");
                 }
+
                 if (statement != null)
                 {
                     statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture));
@@ -4464,11 +4568,13 @@ namespace Emby.Server.Implementations.Data
                     statement.TryBind("@AncestorId", query.AncestorIds[0]);
                 }
             }
+
             if (query.AncestorIds.Length > 1)
             {
                 var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
                 whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
             }
+
             if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
             {
                 var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
@@ -4497,6 +4603,7 @@ namespace Emby.Server.Implementations.Data
                     statement.TryBind("@UnratedType", query.BlockUnratedItems[0].ToString());
                 }
             }
+
             if (query.BlockUnratedItems.Length > 1)
             {
                 var inClause = string.Join(",", query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'"));
@@ -4789,7 +4896,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                 connection.RunInTransaction(db =>
                 {
                     connection.ExecuteAll(sql);
-
                 }, TransactionMode);
             }
         }
@@ -4972,6 +5078,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                     statement.TryBind("@ItemId", query.ItemId.ToByteArray());
                 }
             }
+
             if (!query.AppearsInItemId.Equals(Guid.Empty))
             {
                 whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)");
@@ -4980,6 +5087,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                     statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray());
                 }
             }
+
             var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
 
             if (queryPersonTypes.Count == 1)
@@ -4996,6 +5104,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
 
                 whereClauses.Add("PersonType in (" + val + ")");
             }
+
             var queryExcludePersonTypes = query.ExcludePersonTypes.Where(IsValidPersonType).ToList();
 
             if (queryExcludePersonTypes.Count == 1)
@@ -5012,6 +5121,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
 
                 whereClauses.Add("PersonType not in (" + val + ")");
             }
+
             if (query.MaxListOrder.HasValue)
             {
                 whereClauses.Add("ListOrder<=@MaxListOrder");
@@ -5020,6 +5130,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                     statement.TryBind("@MaxListOrder", query.MaxListOrder.Value);
                 }
             }
+
             if (!string.IsNullOrWhiteSpace(query.NameContains))
             {
                 whereClauses.Add("Name like @NameContains");
@@ -5159,6 +5270,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                 var typeString = string.Join(",", withItemTypes.Select(i => "'" + i + "'"));
                 commandText += " AND ItemId In (select guid from typedbaseitems where type in (" + typeString + "))";
             }
+
             if (excludeItemTypes.Count > 0)
             {
                 var typeString = string.Join(",", excludeItemTypes.Select(i => "'" + i + "'"));
@@ -5180,7 +5292,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                         }
                     }
                 }
-
             }
 
             LogQueryTime("GetItemValueNames", commandText, now);
@@ -5631,7 +5742,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                     db.Execute("delete from People where ItemId=@ItemId", itemIdBlob);
 
                     InsertPeople(itemIdBlob, people, db);
-
                 }, TransactionMode);
             }
         }
@@ -5788,7 +5898,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                     db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
 
                     InsertMediaStreams(itemIdBlob, streams, db);
-
                 }, TransactionMode);
             }
         }
@@ -6134,7 +6243,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                     db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob);
 
                     InsertMediaAttachments(itemIdBlob, attachments, db, cancellationToken);
-
                 }, TransactionMode);
             }
         }

+ 8 - 4
Emby.Server.Implementations/Data/SqliteUserDataRepository.cs

@@ -4,6 +4,7 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Threading;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
@@ -134,10 +135,12 @@ namespace Emby.Server.Implementations.Data
             {
                 throw new ArgumentNullException(nameof(userData));
             }
+
             if (internalUserId <= 0)
             {
                 throw new ArgumentNullException(nameof(internalUserId));
             }
+
             if (string.IsNullOrEmpty(key))
             {
                 throw new ArgumentNullException(nameof(key));
@@ -152,6 +155,7 @@ namespace Emby.Server.Implementations.Data
             {
                 throw new ArgumentNullException(nameof(userData));
             }
+
             if (internalUserId <= 0)
             {
                 throw new ArgumentNullException(nameof(internalUserId));
@@ -234,7 +238,7 @@ namespace Emby.Server.Implementations.Data
         }
 
         /// <summary>
-        /// Persist all user data for the specified user
+        /// Persist all user data for the specified user.
         /// </summary>
         private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken)
         {
@@ -308,7 +312,7 @@ namespace Emby.Server.Implementations.Data
         }
 
         /// <summary>
-        /// Return all user-data associated with the given user
+        /// Return all user-data associated with the given user.
         /// </summary>
         /// <param name="internalUserId"></param>
         /// <returns></returns>
@@ -338,7 +342,7 @@ namespace Emby.Server.Implementations.Data
         }
 
         /// <summary>
-        /// Read a row from the specified reader into the provided userData object
+        /// Read a row from the specified reader into the provided userData object.
         /// </summary>
         /// <param name="reader"></param>
         private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader)
@@ -346,7 +350,7 @@ namespace Emby.Server.Implementations.Data
             var userData = new UserItemData();
 
             userData.Key = reader[0].ToString();
-            //userData.UserId = reader[1].ReadGuidFromBlob();
+            // userData.UserId = reader[1].ReadGuidFromBlob();
 
             if (reader[2].SQLiteType != SQLiteType.Null)
             {

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

@@ -1,240 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text.Json;
-using MediaBrowser.Common.Json;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Persistence;
-using Microsoft.Extensions.Logging;
-using SQLitePCL.pretty;
-
-namespace Emby.Server.Implementations.Data
-{
-    /// <summary>
-    /// Class SQLiteUserRepository
-    /// </summary>
-    public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
-    {
-        private readonly JsonSerializerOptions _jsonOptions;
-
-        public SqliteUserRepository(
-            ILogger<SqliteUserRepository> logger,
-            IServerApplicationPaths appPaths)
-            : base(logger)
-        {
-            _jsonOptions = JsonDefaults.GetOptions();
-
-            DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
-        }
-
-        /// <summary>
-        /// Gets the name of the repository
-        /// </summary>
-        /// <value>The name.</value>
-        public string Name => "SQLite";
-
-        /// <summary>
-        /// Opens the connection to the database.
-        /// </summary>
-        public void Initialize()
-        {
-            using (var connection = GetConnection())
-            {
-                var localUsersTableExists = TableExists(connection, "LocalUsersv2");
-
-                connection.RunQueries(new[] {
-                    "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)",
-                    "drop index if exists idx_users"
-                });
-
-                if (!localUsersTableExists && TableExists(connection, "Users"))
-                {
-                    TryMigrateToLocalUsersTable(connection);
-                }
-
-                RemoveEmptyPasswordHashes(connection);
-            }
-        }
-
-        private void TryMigrateToLocalUsersTable(ManagedConnection connection)
-        {
-            try
-            {
-                connection.RunQueries(new[]
-                {
-                    "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users"
-                });
-            }
-            catch (Exception ex)
-            {
-                Logger.LogError(ex, "Error migrating users database");
-            }
-        }
-
-        private void RemoveEmptyPasswordHashes(ManagedConnection connection)
-        {
-            foreach (var user in RetrieveAllUsers(connection))
-            {
-                // If the user password is the sha1 hash of the empty string, remove it
-                if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
-                    && !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
-                {
-                    continue;
-                }
-
-                user.Password = null;
-                var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
-
-                connection.RunInTransaction(db =>
-                {
-                    using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
-                    {
-                        statement.TryBind("@InternalId", user.InternalId);
-                        statement.TryBind("@data", serialized);
-                        statement.MoveNext();
-                    }
-                }, TransactionMode);
-            }
-        }
-
-        /// <summary>
-        /// Save a user in the repo
-        /// </summary>
-        public void CreateUser(User user)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException(nameof(user));
-            }
-
-            var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
-
-            using (var connection = GetConnection())
-            {
-                connection.RunInTransaction(db =>
-                {
-                    using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
-                    {
-                        statement.TryBind("@guid", user.Id.ToByteArray());
-                        statement.TryBind("@data", serialized);
-
-                        statement.MoveNext();
-                    }
-
-                    var createdUser = GetUser(user.Id, connection);
-
-                    if (createdUser == null)
-                    {
-                        throw new ApplicationException("created user should never be null");
-                    }
-
-                    user.InternalId = createdUser.InternalId;
-
-                }, TransactionMode);
-            }
-        }
-
-        public void UpdateUser(User user)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException(nameof(user));
-            }
-
-            var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
-
-            using (var connection = GetConnection())
-            {
-                connection.RunInTransaction(db =>
-                {
-                    using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
-                    {
-                        statement.TryBind("@InternalId", user.InternalId);
-                        statement.TryBind("@data", serialized);
-                        statement.MoveNext();
-                    }
-
-                }, TransactionMode);
-            }
-        }
-
-        private User GetUser(Guid guid, ManagedConnection connection)
-        {
-            using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid"))
-            {
-                statement.TryBind("@guid", guid);
-
-                foreach (var row in statement.ExecuteQuery())
-                {
-                    return GetUser(row);
-                }
-            }
-
-            return null;
-        }
-
-        private User GetUser(IReadOnlyList<IResultSetValue> row)
-        {
-            var id = row[0].ToInt64();
-            var guid = row[1].ReadGuidFromBlob();
-
-            var user = JsonSerializer.Deserialize<User>(row[2].ToBlob(), _jsonOptions);
-            user.InternalId = id;
-            user.Id = guid;
-            return user;
-        }
-
-        /// <summary>
-        /// Retrieve all users from the database
-        /// </summary>
-        /// <returns>IEnumerable{User}.</returns>
-        public List<User> RetrieveAllUsers()
-        {
-            using (var connection = GetConnection(true))
-            {
-                return new List<User>(RetrieveAllUsers(connection));
-            }
-        }
-
-        /// <summary>
-        /// Retrieve all users from the database
-        /// </summary>
-        /// <returns>IEnumerable{User}.</returns>
-        private IEnumerable<User> RetrieveAllUsers(ManagedConnection connection)
-        {
-            foreach (var row in connection.Query("select id,guid,data from LocalUsersv2"))
-            {
-                yield return GetUser(row);
-            }
-        }
-
-        /// <summary>
-        /// Deletes the user.
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="ArgumentNullException">user</exception>
-        public void DeleteUser(User user)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException(nameof(user));
-            }
-
-            using (var connection = GetConnection())
-            {
-                connection.RunInTransaction(db =>
-                {
-                    using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id"))
-                    {
-                        statement.TryBind("@id", user.InternalId);
-                        statement.MoveNext();
-                    }
-                }, TransactionMode);
-            }
-        }
-    }
-}

+ 2 - 2
Emby.Server.Implementations/Devices/DeviceId.cs

@@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Devices
     public class DeviceId
     {
         private readonly IApplicationPaths _appPaths;
-        private readonly ILogger _logger;
+        private readonly ILogger<DeviceId> _logger;
 
         private readonly object _syncLock = new object();
 
@@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.Devices
         public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
         {
             _appPaths = appPaths;
-            _logger = loggerFactory.CreateLogger("SystemId");
+            _logger = loggerFactory.CreateLogger<DeviceId>();
         }
 
         public string Value => _id ?? (_id = GetDeviceId());

+ 11 - 21
Emby.Server.Implementations/Devices/DeviceManager.cs

@@ -5,10 +5,11 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Linq;
+using Jellyfin.Data.Enums;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Model.Devices;
@@ -16,7 +17,6 @@ using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Session;
-using MediaBrowser.Model.Users;
 
 namespace Emby.Server.Implementations.Devices
 {
@@ -27,11 +27,10 @@ namespace Emby.Server.Implementations.Devices
         private readonly IServerConfigurationManager _config;
         private readonly IAuthenticationRepository _authRepo;
         private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
+        private readonly object _capabilitiesSyncLock = new object();
 
         public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
 
-        private readonly object _capabilitiesSyncLock = new object();
-
         public DeviceManager(
             IAuthenticationRepository authRepo,
             IJsonSerializer json,
@@ -113,7 +112,7 @@ namespace Emby.Server.Implementations.Devices
         {
             IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
             {
-                //UserId = query.UserId
+                // UserId = query.UserId
                 HasUser = true
             }).Items;
 
@@ -170,12 +169,18 @@ namespace Emby.Server.Implementations.Devices
             {
                 throw new ArgumentException("user not found");
             }
+
             if (string.IsNullOrEmpty(deviceId))
             {
                 throw new ArgumentNullException(nameof(deviceId));
             }
 
-            if (!CanAccessDevice(user.Policy, deviceId))
+            if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator))
+            {
+                return true;
+            }
+
+            if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase))
             {
                 var capabilities = GetCapabilities(deviceId);
 
@@ -187,20 +192,5 @@ namespace Emby.Server.Implementations.Devices
 
             return true;
         }
-
-        private static bool CanAccessDevice(UserPolicy policy, string id)
-        {
-            if (policy.EnableAllDevices)
-            {
-                return true;
-            }
-
-            if (policy.IsAdministrator)
-            {
-                return true;
-            }
-
-            return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase);
-        }
     }
 }

+ 36 - 21
Emby.Server.Implementations/Dto/DtoService.cs

@@ -6,14 +6,14 @@ using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
 using MediaBrowser.Common;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Persistence;
@@ -24,12 +24,20 @@ using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using Microsoft.Extensions.Logging;
+using Book = MediaBrowser.Controller.Entities.Book;
+using Episode = MediaBrowser.Controller.Entities.TV.Episode;
+using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
+using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
+using Person = MediaBrowser.Controller.Entities.Person;
+using Photo = MediaBrowser.Controller.Entities.Photo;
+using Season = MediaBrowser.Controller.Entities.TV.Season;
+using Series = MediaBrowser.Controller.Entities.TV.Series;
 
 namespace Emby.Server.Implementations.Dto
 {
     public class DtoService : IDtoService
     {
-        private readonly ILogger _logger;
+        private readonly ILogger<DtoService> _logger;
         private readonly ILibraryManager _libraryManager;
         private readonly IUserDataManager _userDataRepository;
         private readonly IItemRepository _itemRepo;
@@ -66,7 +74,7 @@ namespace Emby.Server.Implementations.Dto
         }
 
         /// <summary>
-        /// Converts a BaseItem to a DTOBaseItem
+        /// Converts a BaseItem to a DTOBaseItem.
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="fields">The fields.</param>
@@ -269,6 +277,7 @@ namespace Emby.Server.Implementations.Dto
                     dto.EpisodeTitle = dto.Name;
                     dto.Name = dto.SeriesName;
                 }
+
                 liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
             }
 
@@ -284,6 +293,7 @@ namespace Emby.Server.Implementations.Dto
                 {
                     continue;
                 }
+
                 var containers = container.Split(new[] { ',' });
                 if (containers.Length < 2)
                 {
@@ -384,7 +394,7 @@ namespace Emby.Server.Implementations.Dto
 
                     if (options.ContainsField(ItemFields.ChildCount))
                     {
-                        dto.ChildCount = dto.ChildCount ?? GetChildCount(folder, user);
+                        dto.ChildCount ??= GetChildCount(folder, user);
                     }
                 }
 
@@ -398,7 +408,6 @@ namespace Emby.Server.Implementations.Dto
                     dto.DateLastMediaAdded = folder.DateLastMediaAdded;
                 }
             }
-
             else
             {
                 if (options.EnableUserData)
@@ -414,7 +423,7 @@ namespace Emby.Server.Implementations.Dto
 
             if (options.ContainsField(ItemFields.BasicSyncInfo))
             {
-                var userCanSync = user != null && user.Policy.EnableContentDownloading;
+                var userCanSync = user != null && user.HasPermission(PermissionKind.EnableContentDownloading);
                 if (userCanSync && item.SupportsExternalTransfer)
                 {
                     dto.SupportsSync = true;
@@ -435,7 +444,7 @@ namespace Emby.Server.Implementations.Dto
         }
 
         /// <summary>
-        /// Gets client-side Id of a server-side BaseItem
+        /// Gets client-side Id of a server-side BaseItem.
         /// </summary>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
@@ -449,6 +458,7 @@ namespace Emby.Server.Implementations.Dto
         {
             dto.SeriesName = item.SeriesName;
         }
+
         private static void SetPhotoProperties(BaseItemDto dto, Photo item)
         {
             dto.CameraMake = item.CameraMake;
@@ -530,7 +540,7 @@ namespace Emby.Server.Implementations.Dto
         }
 
         /// <summary>
-        /// Attaches People DTO's to a DTOBaseItem
+        /// Attaches People DTO's to a DTOBaseItem.
         /// </summary>
         /// <param name="dto">The dto.</param>
         /// <param name="item">The item.</param>
@@ -547,22 +557,27 @@ namespace Emby.Server.Implementations.Dto
                     {
                         return 0;
                     }
+
                     if (i.IsType(PersonType.GuestStar))
                     {
                         return 1;
                     }
+
                     if (i.IsType(PersonType.Director))
                     {
                         return 2;
                     }
+
                     if (i.IsType(PersonType.Writer))
                     {
                         return 3;
                     }
+
                     if (i.IsType(PersonType.Producer))
                     {
                         return 4;
                     }
+
                     if (i.IsType(PersonType.Composer))
                     {
                         return 4;
@@ -586,7 +601,6 @@ namespace Emby.Server.Implementations.Dto
                         _logger.LogError(ex, "Error getting person {Name}", c);
                         return null;
                     }
-
                 }).Where(i => i != null)
                 .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
                 .Select(x => x.First())
@@ -719,7 +733,7 @@ namespace Emby.Server.Implementations.Dto
         }
 
         /// <summary>
-        /// Sets simple property values on a DTOBaseItem
+        /// Sets simple property values on a DTOBaseItem.
         /// </summary>
         /// <param name="dto">The dto.</param>
         /// <param name="item">The item.</param>
@@ -939,7 +953,7 @@ namespace Emby.Server.Implementations.Dto
                     dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
                 }
 
-                //if (options.ContainsField(ItemFields.MediaSourceCount))
+                // if (options.ContainsField(ItemFields.MediaSourceCount))
                 //{
                 // Songs always have one
                 //}
@@ -949,13 +963,13 @@ namespace Emby.Server.Implementations.Dto
             {
                 dto.Artists = hasArtist.Artists;
 
-                //var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
+                // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
                 //{
                 //    EnableTotalRecordCount = false,
                 //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
                 //});
 
-                //dto.ArtistItems = artistItems.Items
+                // dto.ArtistItems = artistItems.Items
                 //    .Select(i =>
                 //    {
                 //        var artist = i.Item1;
@@ -968,7 +982,7 @@ namespace Emby.Server.Implementations.Dto
                 //    .ToList();
 
                 // Include artists that are not in the database yet, e.g., just added via metadata editor
-                //var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
+                // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
                 dto.ArtistItems = hasArtist.Artists
                     //.Except(foundArtists, new DistinctNameComparer())
                     .Select(i =>
@@ -993,7 +1007,6 @@ namespace Emby.Server.Implementations.Dto
                         }
 
                         return null;
-
                     }).Where(i => i != null).ToArray();
             }
 
@@ -1002,13 +1015,13 @@ namespace Emby.Server.Implementations.Dto
             {
                 dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
 
-                //var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
+                // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
                 //{
                 //    EnableTotalRecordCount = false,
                 //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
                 //});
 
-                //dto.AlbumArtists = artistItems.Items
+                // dto.AlbumArtists = artistItems.Items
                 //    .Select(i =>
                 //    {
                 //        var artist = i.Item1;
@@ -1044,7 +1057,6 @@ namespace Emby.Server.Implementations.Dto
                         }
 
                         return null;
-
                     }).Where(i => i != null).ToArray();
             }
 
@@ -1158,7 +1170,7 @@ namespace Emby.Server.Implementations.Dto
 
                 // this block will add the series poster for episodes without a poster
                 // TODO maybe remove the if statement entirely
-                //if (options.ContainsField(ItemFields.SeriesPrimaryImage))
+                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
                 {
                     episodeSeries = episodeSeries ?? episode.Series;
                     if (episodeSeries != null)
@@ -1204,7 +1216,7 @@ namespace Emby.Server.Implementations.Dto
 
                 // this block will add the series poster for seasons without a poster
                 // TODO maybe remove the if statement entirely
-                //if (options.ContainsField(ItemFields.SeriesPrimaryImage))
+                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
                 {
                     series = series ?? season.Series;
                     if (series != null)
@@ -1342,6 +1354,7 @@ namespace Emby.Server.Implementations.Dto
                         dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
                     }
                 }
+
                 if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null)
                 {
                     var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
@@ -1352,6 +1365,7 @@ namespace Emby.Server.Implementations.Dto
                         dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
                     }
                 }
+
                 if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView))
                 {
                     var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
@@ -1362,6 +1376,7 @@ namespace Emby.Server.Implementations.Dto
                         dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
                     }
                 }
+
                 if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0)))
                 {
                     var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();

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

@@ -24,7 +24,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="IPNetwork2" Version="2.4.0.126" />
+    <PackageReference Include="IPNetwork2" Version="2.5.211" />
     <PackageReference Include="Jellyfin.XmlTv" Version="10.4.3" />
     <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
     <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
@@ -34,14 +34,14 @@
     <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.1.4" />
-    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.4" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.4" />
-    <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.4" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
+    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.5" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
+    <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
     <PackageReference Include="Mono.Nat" Version="2.0.1" />
     <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
-    <PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
-    <PackageReference Include="sharpcompress" Version="0.25.0" />
+    <PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" />
+    <PackageReference Include="sharpcompress" Version="0.25.1" />
     <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
     <PackageReference Include="DotNet.Glob" Version="3.0.9" />
   </ItemGroup>

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

@@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.EntryPoints
     public class ExternalPortForwarding : IServerEntryPoint
     {
         private readonly IServerApplicationHost _appHost;
-        private readonly ILogger _logger;
+        private readonly ILogger<ExternalPortForwarding> _logger;
         private readonly IServerConfigurationManager _config;
         private readonly IDeviceDiscovery _deviceDiscovery;
 

+ 2 - 2
Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs

@@ -6,6 +6,7 @@ using System.Globalization;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -28,7 +29,7 @@ namespace Emby.Server.Implementations.EntryPoints
 
         private readonly ISessionManager _sessionManager;
         private readonly IUserManager _userManager;
-        private readonly ILogger _logger;
+        private readonly ILogger<LibraryChangedNotifier> _logger;
 
         /// <summary>
         /// The library changed sync lock.
@@ -131,7 +132,6 @@ namespace Emby.Server.Implementations.EntryPoints
                 }
                 catch
                 {
-
                 }
             }
         }

+ 3 - 2
Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs

@@ -4,6 +4,7 @@ using System;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Plugins;
@@ -17,7 +18,7 @@ namespace Emby.Server.Implementations.EntryPoints
         private readonly ILiveTvManager _liveTvManager;
         private readonly ISessionManager _sessionManager;
         private readonly IUserManager _userManager;
-        private readonly ILogger _logger;
+        private readonly ILogger<RecordingNotifier> _logger;
 
         public RecordingNotifier(
             ISessionManager sessionManager,
@@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.EntryPoints
 
         private async Task SendMessage(string name, TimerEventInfo info)
         {
-            var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList();
+            var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList();
 
             try
             {

+ 0 - 77
Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs

@@ -1,77 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Tasks;
-
-namespace Emby.Server.Implementations.EntryPoints
-{
-    /// <summary>
-    /// Class RefreshUsersMetadata.
-    /// </summary>
-    public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask
-    {
-        /// <summary>
-        /// The user manager.
-        /// </summary>
-        private readonly IUserManager _userManager;
-        private readonly IFileSystem _fileSystem;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class.
-        /// </summary>
-        public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem)
-        {
-            _userManager = userManager;
-            _fileSystem = fileSystem;
-        }
-
-        /// <inheritdoc />
-        public string Name => "Refresh Users";
-
-        /// <inheritdoc />
-        public string Key => "RefreshUsers";
-
-        /// <inheritdoc />
-        public string Description => "Refresh user infos";
-
-        /// <inheritdoc />
-        public string Category => "Library";
-
-        /// <inheritdoc />
-        public bool IsHidden => true;
-
-        /// <inheritdoc />
-        public bool IsEnabled => true;
-
-        /// <inheritdoc />
-        public bool IsLogged => true;
-
-        /// <inheritdoc />
-        public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
-        {
-            foreach (var user in _userManager.Users)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
-            }
-        }
-
-        /// <inheritdoc />
-        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
-        {
-            return new[]
-            {
-                new TaskTriggerInfo
-                {
-                    IntervalTicks = TimeSpan.FromDays(1).Ticks,
-                    Type = TaskTriggerInfo.TriggerInterval
-                }
-            };
-        }
-    }
-}

+ 5 - 25
Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs

@@ -3,10 +3,10 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.Updates;
 using MediaBrowser.Controller;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Session;
@@ -68,10 +68,8 @@ namespace Emby.Server.Implementations.EntryPoints
         /// <inheritdoc />
         public Task RunAsync()
         {
-            _userManager.UserDeleted += OnUserDeleted;
-            _userManager.UserUpdated += OnUserUpdated;
-            _userManager.UserPolicyUpdated += OnUserPolicyUpdated;
-            _userManager.UserConfigurationUpdated += OnUserConfigurationUpdated;
+            _userManager.OnUserDeleted += OnUserDeleted;
+            _userManager.OnUserUpdated += OnUserUpdated;
 
             _appHost.HasPendingRestartChanged += OnHasPendingRestartChanged;
 
@@ -153,20 +151,6 @@ namespace Emby.Server.Implementations.EntryPoints
             await SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)).ConfigureAwait(false);
         }
 
-        private async void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
-        {
-            var dto = _userManager.GetUserDto(e.Argument);
-
-            await SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto).ConfigureAwait(false);
-        }
-
-        private async void OnUserConfigurationUpdated(object sender, GenericEventArgs<User> e)
-        {
-            var dto = _userManager.GetUserDto(e.Argument);
-
-            await SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto).ConfigureAwait(false);
-        }
-
         private async Task SendMessageToAdminSessions<T>(string name, T data)
         {
             try
@@ -175,7 +159,6 @@ namespace Emby.Server.Implementations.EntryPoints
             }
             catch (Exception)
             {
-
             }
         }
 
@@ -191,7 +174,6 @@ namespace Emby.Server.Implementations.EntryPoints
             }
             catch (Exception)
             {
-
             }
         }
 
@@ -210,10 +192,8 @@ namespace Emby.Server.Implementations.EntryPoints
         {
             if (dispose)
             {
-                _userManager.UserDeleted -= OnUserDeleted;
-                _userManager.UserUpdated -= OnUserUpdated;
-                _userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
-                _userManager.UserConfigurationUpdated -= OnUserConfigurationUpdated;
+                _userManager.OnUserDeleted -= OnUserDeleted;
+                _userManager.OnUserUpdated -= OnUserUpdated;
 
                 _installationManager.PluginUninstalled -= OnPluginUninstalled;
                 _installationManager.PackageInstalling -= OnPackageInstalling;

+ 1 - 2
Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs

@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints
         /// <summary>
         /// The logger.
         /// </summary>
-        private readonly ILogger _logger;
+        private readonly ILogger<UdpServerEntryPoint> _logger;
         private readonly IServerApplicationHost _appHost;
         private readonly IConfiguration _config;
 
@@ -43,7 +43,6 @@ namespace Emby.Server.Implementations.EntryPoints
             _logger = logger;
             _appHost = appHost;
             _config = configuration;
-
         }
 
         /// <inheritdoc />

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

@@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.HttpClientManager
     /// </summary>
     public class HttpClientManager : IHttpClient
     {
-        private readonly ILogger _logger;
+        private readonly ILogger<HttpClientManager> _logger;
         private readonly IApplicationPaths _appPaths;
         private readonly IFileSystem _fileSystem;
         private readonly IApplicationHost _appHost;
@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.HttpClientManager
             => SendAsync(options, HttpMethod.Get);
 
         /// <summary>
-        /// Performs a GET request and returns the resulting stream
+        /// Performs a GET request and returns the resulting stream.
         /// </summary>
         /// <param name="options">The options.</param>
         /// <returns>Task{Stream}.</returns>

+ 2 - 2
Emby.Server.Implementations/HttpServer/FileWriter.cs

@@ -32,12 +32,12 @@ namespace Emby.Server.Implementations.HttpServer
         private readonly IFileSystem _fileSystem;
 
         /// <summary>
-        /// The _options
+        /// The _options.
         /// </summary>
         private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
 
         /// <summary>
-        /// The _requested ranges
+        /// The _requested ranges.
         /// </summary>
         private List<KeyValuePair<long, long?>> _requestedRanges;
 

+ 7 - 4
Emby.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.HttpServer
         /// </summary>
         public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath";
 
-        private readonly ILogger _logger;
+        private readonly ILogger<HttpListenerHost> _logger;
         private readonly ILoggerFactory _loggerFactory;
         private readonly IServerConfigurationManager _config;
         private readonly INetworkManager _networkManager;
@@ -230,7 +230,9 @@ namespace Emby.Server.Implementations.HttpServer
 
             httpRes.StatusCode = statusCode;
 
-            var errContent = NormalizeExceptionMessage(ex) ?? string.Empty;
+            var errContent = _hostEnvironment.IsDevelopment()
+                    ? (NormalizeExceptionMessage(ex) ?? string.Empty)
+                    : "Error processing request.";
             httpRes.ContentType = "text/plain";
             httpRes.ContentLength = errContent.Length;
             await httpRes.WriteAsync(errContent).ConfigureAwait(false);
@@ -397,7 +399,7 @@ namespace Emby.Server.Implementations.HttpServer
             var response = context.Response;
             var localPath = context.Request.Path.ToString();
 
-            var req = new WebSocketSharpRequest(request, response, request.Path, _logger);
+            var req = new WebSocketSharpRequest(request, response, request.Path);
             return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted);
         }
 
@@ -451,6 +453,7 @@ namespace Emby.Server.Implementations.HttpServer
                     {
                         httpRes.Headers.Add(key, value);
                     }
+
                     httpRes.ContentType = "text/plain";
                     await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
                     return;
@@ -589,7 +592,7 @@ namespace Emby.Server.Implementations.HttpServer
         }
 
         /// <summary>
-        /// Get the default CORS headers
+        /// Get the default CORS headers.
         /// </summary>
         /// <param name="req"></param>
         /// <returns></returns>

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

@@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.HttpServer
         /// <summary>
         /// The logger.
         /// </summary>
-        private readonly ILogger _logger;
+        private readonly ILogger<HttpResultFactory> _logger;
         private readonly IFileSystem _fileSystem;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IStreamHelper _streamHelper;
@@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.HttpServer
             _fileSystem = fileSystem;
             _jsonSerializer = jsonSerializer;
             _streamHelper = streamHelper;
-            _logger = loggerfactory.CreateLogger("HttpResultFactory");
+            _logger = loggerfactory.CreateLogger<HttpResultFactory>();
         }
 
         /// <summary>
@@ -580,7 +580,6 @@ namespace Emby.Server.Implementations.HttpServer
                 }
                 catch (NotSupportedException)
                 {
-
                 }
             }
 
@@ -693,7 +692,7 @@ namespace Emby.Server.Implementations.HttpServer
 
 
         /// <summary>
-        /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
+        /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that.
         /// </summary>
         /// <param name="date">The date.</param>
         /// <returns>DateTime.</returns>

+ 11 - 4
Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs

@@ -20,31 +20,37 @@ namespace Emby.Server.Implementations.HttpServer
         /// </summary>
         /// <value>The source stream.</value>
         private Stream SourceStream { get; set; }
+
         private string RangeHeader { get; set; }
+
         private bool IsHeadRequest { get; set; }
 
         private long RangeStart { get; set; }
+
         private long RangeEnd { get; set; }
+
         private long RangeLength { get; set; }
+
         private long TotalContentLength { get; set; }
 
         public Action OnComplete { get; set; }
+
         private readonly ILogger _logger;
 
         private const int BufferSize = 81920;
 
         /// <summary>
-        /// The _options
+        /// The _options.
         /// </summary>
         private readonly Dictionary<string, string> _options = new Dictionary<string, string>();
 
         /// <summary>
-        /// The us culture
+        /// The us culture.
         /// </summary>
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
         /// <summary>
-        /// Additional HTTP Headers
+        /// Additional HTTP Headers.
         /// </summary>
         /// <value>The headers.</value>
         public IDictionary<string, string> Headers => _options;
@@ -110,7 +116,7 @@ namespace Emby.Server.Implementations.HttpServer
         }
 
         /// <summary>
-        /// The _requested ranges
+        /// The _requested ranges.
         /// </summary>
         private List<KeyValuePair<long, long?>> _requestedRanges;
         /// <summary>
@@ -139,6 +145,7 @@ namespace Emby.Server.Implementations.HttpServer
                         {
                             start = long.Parse(vals[0], UsCulture);
                         }
+
                         if (!string.IsNullOrEmpty(vals[1]))
                         {
                             end = long.Parse(vals[1], UsCulture);

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

@@ -1,5 +1,4 @@
 using System;
-using System.Collections.Generic;
 using System.Globalization;
 using System.Text;
 using MediaBrowser.Controller.Net;

+ 16 - 13
Emby.Server.Implementations/HttpServer/Security/AuthService.cs

@@ -2,11 +2,12 @@
 
 using System;
 using System.Linq;
-using System.Security.Authentication;
 using Emby.Server.Implementations.SocketSharp;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
@@ -45,7 +46,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
 
         public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
         {
-            var req = new WebSocketSharpRequest(request, null, request.Path, _logger);
+            var req = new WebSocketSharpRequest(request, null, request.Path);
             var user = ValidateUser(req, authAttributes);
             return user;
         }
@@ -90,7 +91,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
                 !string.IsNullOrEmpty(auth.Client) &&
                 !string.IsNullOrEmpty(auth.Device))
             {
-                _sessionManager.LogSessionActivity(auth.Client,
+                _sessionManager.LogSessionActivity(
+                    auth.Client,
                     auth.Version,
                     auth.DeviceId,
                     auth.Device,
@@ -104,21 +106,21 @@ namespace Emby.Server.Implementations.HttpServer.Security
         private void ValidateUserAccess(
             User user,
             IRequest request,
-            IAuthenticationAttributes authAttribtues,
+            IAuthenticationAttributes authAttributes,
             AuthorizationInfo auth)
         {
-            if (user.Policy.IsDisabled)
+            if (user.HasPermission(PermissionKind.IsDisabled))
             {
                 throw new SecurityException("User account has been disabled.");
             }
 
-            if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp))
+            if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !_networkManager.IsInLocalNetwork(request.RemoteIp))
             {
                 throw new SecurityException("User account has been disabled.");
             }
 
-            if (!user.Policy.IsAdministrator
-                && !authAttribtues.EscapeParentalControl
+            if (!user.HasPermission(PermissionKind.IsAdministrator)
+                && !authAttributes.EscapeParentalControl
                 && !user.IsParentalScheduleAllowed())
             {
                 request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
@@ -138,6 +140,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
             {
                 return true;
             }
+
             if (authAttribtues.AllowLocalOnly && request.IsLocal)
             {
                 return true;
@@ -180,7 +183,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
         {
             if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
             {
-                if (user == null || !user.Policy.IsAdministrator)
+                if (user == null || !user.HasPermission(PermissionKind.IsAdministrator))
                 {
                     throw new SecurityException("User does not have admin access.");
                 }
@@ -188,7 +191,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
 
             if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
             {
-                if (user == null || !user.Policy.EnableContentDeletion)
+                if (user == null || !user.HasPermission(PermissionKind.EnableContentDeletion))
                 {
                     throw new SecurityException("User does not have delete access.");
                 }
@@ -196,7 +199,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
 
             if (roles.Contains("download", StringComparer.OrdinalIgnoreCase))
             {
-                if (user == null || !user.Policy.EnableContentDownloading)
+                if (user == null || !user.HasPermission(PermissionKind.EnableContentDownloading))
                 {
                     throw new SecurityException("User does not have download access.");
                 }
@@ -223,7 +226,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
                 throw new AuthenticationException("Access token is invalid or expired.");
             }
 
-            //if (!string.IsNullOrEmpty(info.UserId))
+            // if (!string.IsNullOrEmpty(info.UserId))
             //{
             //    var user = _userManager.GetUserById(info.UserId);
 

+ 4 - 3
Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs

@@ -71,6 +71,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
             {
                 token = httpReq.Headers["X-MediaBrowser-Token"];
             }
+
             if (string.IsNullOrEmpty(token))
             {
                 token = httpReq.QueryString["api_key"];
@@ -116,7 +117,6 @@ namespace Emby.Server.Implementations.HttpServer.Security
                     {
                         info.Device = tokenInfo.DeviceName;
                     }
-
                     else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
                     {
                         if (allowTokenInfoUpdate)
@@ -149,9 +149,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
                     {
                         info.User = _userManager.GetUserById(tokenInfo.UserId);
 
-                        if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase))
+                        if (info.User != null && !string.Equals(info.User.Username, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase))
                         {
-                            tokenInfo.UserName = info.User.Name;
+                            tokenInfo.UserName = info.User.Username;
                             updateToken = true;
                         }
                     }
@@ -161,6 +161,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
                         _authRepo.Update(tokenInfo);
                     }
                 }
+
                 httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo;
             }
 

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

@@ -1,7 +1,7 @@
 #pragma warning disable CS1591
 
 using System;
-using MediaBrowser.Controller.Entities;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Security;

+ 7 - 5
Emby.Server.Implementations/HttpServer/WebSocketConnection.cs

@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.HttpServer
         /// <summary>
         /// The logger.
         /// </summary>
-        private readonly ILogger _logger;
+        private readonly ILogger<WebSocketConnection> _logger;
 
         /// <summary>
         /// The json serializer options.
@@ -234,10 +234,12 @@ namespace Emby.Server.Implementations.HttpServer
         private Task SendKeepAliveResponse()
         {
             LastKeepAliveDate = DateTime.UtcNow;
-            return SendAsync(new WebSocketMessage<string>
-            {
-                MessageType = "KeepAlive"
-            }, CancellationToken.None);
+            return SendAsync(
+                new WebSocketMessage<string>
+                {
+                    MessageId = Guid.NewGuid(),
+                    MessageType = "KeepAlive"
+                }, CancellationToken.None);
         }
 
         /// <inheritdoc />

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

@@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.IO
 {
     public class LibraryMonitor : ILibraryMonitor
     {
-        private readonly ILogger _logger;
+        private readonly ILogger<LibraryMonitor> _logger;
         private readonly ILibraryManager _libraryManager;
         private readonly IServerConfigurationManager _configurationManager;
         private readonly IFileSystem _fileSystem;
@@ -266,7 +266,6 @@ namespace Emby.Server.Implementations.IO
                     {
                         DisposeWatcher(newWatcher, false);
                     }
-
                 }
                 catch (Exception ex)
                 {
@@ -393,7 +392,6 @@ namespace Emby.Server.Implementations.IO
                 }
 
                 return false;
-
             }))
             {
                 monitorPath = false;

+ 4 - 2
Emby.Server.Implementations/IO/ManagedFileSystem.cs

@@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.IO
     /// </summary>
     public class ManagedFileSystem : IFileSystem
     {
-        protected ILogger Logger;
+        protected ILogger<ManagedFileSystem> Logger;
 
         private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
         private readonly string _tempPath;
@@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.IO
             {
                 result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
 
-                //if (!result.IsDirectory)
+                // if (!result.IsDirectory)
                 //{
                 //    result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
                 //}
@@ -628,6 +628,7 @@ namespace Emby.Server.Implementations.IO
                     {
                         return false;
                     }
+
                     return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
                 });
             }
@@ -682,6 +683,7 @@ namespace Emby.Server.Implementations.IO
                     {
                         return false;
                     }
+
                     return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
                 });
             }

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

@@ -71,7 +71,6 @@ namespace Emby.Server.Implementations.Images
                     new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
                 },
                 IncludeItemTypes = includeItemTypes
-
             });
         }
 

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

@@ -78,7 +78,6 @@ namespace Emby.Server.Implementations.Images
                 }
 
                 return i;
-
             }).GroupBy(x => x.Id)
             .Select(x => x.First());
 

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

@@ -8,7 +8,7 @@ using MediaBrowser.Model.IO;
 namespace Emby.Server.Implementations.Library
 {
     /// <summary>
-    /// Provides the core resolver ignore rules
+    /// Provides the core resolver ignore rules.
     /// </summary>
     public class CoreResolutionIgnoreRule : IResolverIgnoreRule
     {

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

@@ -12,11 +12,13 @@ namespace Emby.Server.Implementations.Library
     public class ExclusiveLiveStream : ILiveStream
     {
         public int ConsumerCount { get; set; }
+
         public string OriginalStreamId { get; set; }
 
         public string TunerHostId => null;
 
         public bool EnableStreamSharing { get; set; }
+
         public MediaSourceInfo MediaSource { get; set; }
 
         public string UniqueId { get; private set; }

+ 3 - 3
Emby.Server.Implementations/Library/IgnorePatterns.cs

@@ -4,12 +4,12 @@ using DotNet.Globbing;
 namespace Emby.Server.Implementations.Library
 {
     /// <summary>
-    /// Glob patterns for files to ignore
+    /// Glob patterns for files to ignore.
     /// </summary>
     public static class IgnorePatterns
     {
         /// <summary>
-        /// Files matching these glob patterns will be ignored
+        /// Files matching these glob patterns will be ignored.
         /// </summary>
         public static readonly string[] Patterns = new string[]
         {
@@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library
         private static readonly Glob[] _globs = Patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray();
 
         /// <summary>
-        /// Returns true if the supplied path should be ignored
+        /// Returns true if the supplied path should be ignored.
         /// </summary>
         public static bool ShouldIgnore(string path)
         {

+ 28 - 19
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -17,6 +17,8 @@ using Emby.Server.Implementations.Library.Resolvers;
 using Emby.Server.Implementations.Library.Validators;
 using Emby.Server.Implementations.Playlists;
 using Emby.Server.Implementations.ScheduledTasks;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller;
@@ -25,7 +27,6 @@ using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
@@ -46,6 +47,9 @@ using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Providers.MediaInfo;
 using Microsoft.Extensions.Logging;
+using Episode = MediaBrowser.Controller.Entities.TV.Episode;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Person = MediaBrowser.Controller.Entities.Person;
 using SortOrder = MediaBrowser.Model.Entities.SortOrder;
 using VideoResolver = Emby.Naming.Video.VideoResolver;
 
@@ -56,7 +60,7 @@ namespace Emby.Server.Implementations.Library
     /// </summary>
     public class LibraryManager : ILibraryManager
     {
-        private readonly ILogger _logger;
+        private readonly ILogger<LibraryManager> _logger;
         private readonly ITaskManager _taskManager;
         private readonly IUserManager _userManager;
         private readonly IUserDataManager _userDataRepository;
@@ -93,13 +97,13 @@ namespace Emby.Server.Implementations.Library
         private IIntroProvider[] IntroProviders { get; set; }
 
         /// <summary>
-        /// Gets or sets the list of entity resolution ignore rules
+        /// Gets or sets the list of entity resolution ignore rules.
         /// </summary>
         /// <value>The entity resolution ignore rules.</value>
         private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
 
         /// <summary>
-        /// Gets or sets the list of currently registered entity resolvers
+        /// Gets or sets the list of currently registered entity resolvers.
         /// </summary>
         /// <value>The entity resolvers enumerable.</value>
         private IItemResolver[] EntityResolvers { get; set; }
@@ -205,12 +209,12 @@ namespace Emby.Server.Implementations.Library
         }
 
         /// <summary>
-        /// The _root folder
+        /// The _root folder.
         /// </summary>
         private volatile AggregateFolder _rootFolder;
 
         /// <summary>
-        /// The _root folder sync lock
+        /// The _root folder sync lock.
         /// </summary>
         private readonly object _rootFolderSyncLock = new object();
 
@@ -510,8 +514,8 @@ namespace Emby.Server.Implementations.Library
             return key.GetMD5();
         }
 
-        public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
-            => ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
+        public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, bool allowIgnorePath = true)
+            => ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent, allowIgnorePath: allowIgnorePath);
 
         private BaseItem ResolvePath(
             FileSystemMetadata fileInfo,
@@ -519,7 +523,8 @@ namespace Emby.Server.Implementations.Library
             IItemResolver[] resolvers,
             Folder parent = null,
             string collectionType = null,
-            LibraryOptions libraryOptions = null)
+            LibraryOptions libraryOptions = null,
+            bool allowIgnorePath = true)
         {
             if (fileInfo == null)
             {
@@ -543,7 +548,7 @@ namespace Emby.Server.Implementations.Library
             };
 
             // Return null if ignore rules deem that we should do so
-            if (IgnoreFile(args.FileInfo, args.Parent))
+            if (allowIgnorePath && IgnoreFile(args.FileInfo, args.Parent))
             {
                 return null;
             }
@@ -622,7 +627,7 @@ namespace Emby.Server.Implementations.Library
         }
 
         /// <summary>
-        /// Determines whether a path should be ignored based on its contents - called after the contents have been read
+        /// Determines whether a path should be ignored based on its contents - called after the contents have been read.
         /// </summary>
         /// <param name="args">The args.</param>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
@@ -707,7 +712,9 @@ namespace Emby.Server.Implementations.Library
 
             Directory.CreateDirectory(rootFolderPath);
 
-            var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))).DeepCopy<Folder, AggregateFolder>();
+            var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ??
+                             ((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath), allowIgnorePath: false))
+                             .DeepCopy<Folder, AggregateFolder>();
 
             // In case program data folder was moved
             if (!string.Equals(rootFolder.Path, rootFolderPath, StringComparison.Ordinal))
@@ -788,7 +795,7 @@ namespace Emby.Server.Implementations.Library
                         if (tmpItem == null)
                         {
                             _logger.LogDebug("Creating new userRootFolder with DeepCopy");
-                            tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath))).DeepCopy<Folder, UserRootFolder>();
+                            tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath), allowIgnorePath: false)).DeepCopy<Folder, UserRootFolder>();
                         }
 
                         // In case program data folder was moved
@@ -902,7 +909,7 @@ namespace Emby.Server.Implementations.Library
         }
 
         /// <summary>
-        /// Gets a Genre
+        /// Gets a Genre.
         /// </summary>
         /// <param name="name">The name.</param>
         /// <returns>Task{Genre}.</returns>
@@ -983,7 +990,7 @@ namespace Emby.Server.Implementations.Library
         }
 
         /// <summary>
-        /// Reloads the root media folder
+        /// Reloads the root media folder.
         /// </summary>
         /// <param name="progress">The progress.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
@@ -1536,7 +1543,8 @@ namespace Emby.Server.Implementations.Library
                 }
 
                 // Handle grouping
-                if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0)
+                if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType)
+                    && user.GetPreference(PreferenceKind.GroupedFolders).Length > 0)
                 {
                     return GetUserRootFolder()
                         .GetChildren(user, true)
@@ -2587,7 +2595,7 @@ namespace Emby.Server.Implementations.Library
                     Anime series don't generally have a season in their file name, however,
                     tvdb needs a season to correctly get the metadata.
                     Hence, a null season needs to be filled with something. */
-                    //FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
+                    // FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
                     episode.ParentIndexNumber = 1;
                 }
 
@@ -2776,10 +2784,12 @@ namespace Emby.Server.Implementations.Library
             {
                 throw new ArgumentNullException(nameof(path));
             }
+
             if (string.IsNullOrWhiteSpace(from))
             {
                 throw new ArgumentNullException(nameof(from));
             }
+
             if (string.IsNullOrWhiteSpace(to))
             {
                 throw new ArgumentNullException(nameof(to));
@@ -2853,7 +2863,6 @@ namespace Emby.Server.Implementations.Library
                     _logger.LogError(ex, "Error getting person");
                     return null;
                 }
-
             }).Where(i => i != null).ToList();
         }
 
@@ -2983,7 +2992,7 @@ namespace Emby.Server.Implementations.Library
 
         private static bool ValidateNetworkPath(string path)
         {
-            //if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+            // if (Environment.OSVersion.Platform == PlatformID.Win32NT)
             //{
             //    // We can't validate protocol-based paths, so just allow them
             //    if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)

+ 2 - 5
Emby.Server.Implementations/Library/LiveStreamHelper.cs

@@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library
                 {
                     mediaInfo = _json.DeserializeFromFile<MediaInfo>(cacheFilePath);
 
-                    //_logger.LogDebug("Found cached media info");
+                    // _logger.LogDebug("Found cached media info");
                 }
                 catch
                 {
@@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.Library
                     Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
                     _json.SerializeToFile(mediaInfo, cacheFilePath);
 
-                    //_logger.LogDebug("Saved media info to {0}", cacheFilePath);
+                    // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
                 }
             }
 
@@ -148,17 +148,14 @@ namespace Emby.Server.Implementations.Library
                     {
                         videoStream.BitRate = 30000000;
                     }
-
                     else if (width >= 1900)
                     {
                         videoStream.BitRate = 20000000;
                     }
-
                     else if (width >= 1200)
                     {
                         videoStream.BitRate = 8000000;
                     }
-
                     else if (width >= 700)
                     {
                         videoStream.BitRate = 2000000;

+ 31 - 32
Emby.Server.Implementations/Library/MediaSourceManager.cs

@@ -7,6 +7,8 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Entities;
@@ -14,7 +16,6 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
@@ -33,7 +34,7 @@ namespace Emby.Server.Implementations.Library
         private readonly ILibraryManager _libraryManager;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IFileSystem _fileSystem;
-        private readonly ILogger _logger;
+        private readonly ILogger<MediaSourceManager> _logger;
         private readonly IUserDataManager _userDataManager;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly ILocalizationManager _localizationManager;
@@ -190,10 +191,7 @@ namespace Emby.Server.Implementations.Library
                 {
                     if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
                     {
-                        if (!user.Policy.EnableAudioPlaybackTranscoding)
-                        {
-                            source.SupportsTranscoding = false;
-                        }
+                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
                     }
                 }
             }
@@ -207,22 +205,27 @@ namespace Emby.Server.Implementations.Library
             {
                 return MediaProtocol.Rtsp;
             }
+
             if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
             {
                 return MediaProtocol.Rtmp;
             }
+
             if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
             {
                 return MediaProtocol.Http;
             }
+
             if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
             {
                 return MediaProtocol.Rtp;
             }
+
             if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase))
             {
                 return MediaProtocol.Ftp;
             }
+
             if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
             {
                 return MediaProtocol.Udp;
@@ -352,7 +355,9 @@ namespace Emby.Server.Implementations.Library
 
         private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
         {
-            if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
+            if (userData.SubtitleStreamIndex.HasValue
+                && user.RememberSubtitleSelections
+                && user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
             {
                 var index = userData.SubtitleStreamIndex.Value;
                 // Make sure the saved index is still valid
@@ -363,26 +368,27 @@ namespace Emby.Server.Implementations.Library
                 }
             }
 
-            var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
-                ? Array.Empty<string>() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference);
+
+            var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
+                ? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference);
 
             var defaultAudioIndex = source.DefaultAudioStreamIndex;
             var audioLangage = defaultAudioIndex == null
                 ? null
                 : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();
 
-            source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams,
+            source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(
+                source.MediaStreams,
                 preferredSubs,
-                user.Configuration.SubtitleMode,
+                user.SubtitleMode,
                 audioLangage);
 
-            MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs,
-                user.Configuration.SubtitleMode, audioLangage);
+            MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage);
         }
 
         private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
         {
-            if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection)
+            if (userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection)
             {
                 var index = userData.AudioStreamIndex.Value;
                 // Make sure the saved index is still valid
@@ -393,11 +399,11 @@ namespace Emby.Server.Implementations.Library
                 }
             }
 
-            var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
+            var preferredAudio = string.IsNullOrEmpty(user.AudioLanguagePreference)
                 ? Array.Empty<string>()
-                : NormalizeLanguage(user.Configuration.AudioLanguagePreference);
+                : NormalizeLanguage(user.AudioLanguagePreference);
 
-            source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
+            source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack);
         }
 
         public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user)
@@ -435,7 +441,6 @@ namespace Emby.Server.Implementations.Library
                 }
 
                 return 1;
-
             }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
             .ThenByDescending(i =>
             {
@@ -534,7 +539,7 @@ namespace Emby.Server.Implementations.Library
                 mediaSource.RunTimeTicks = null;
             }
 
-            var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio);
+            var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
 
             if (audioStream == null || audioStream.Index == -1)
             {
@@ -545,7 +550,7 @@ namespace Emby.Server.Implementations.Library
                 mediaSource.DefaultAudioStreamIndex = audioStream.Index;
             }
 
-            var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video);
+            var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
             if (videoStream != null)
             {
                 if (!videoStream.BitRate.HasValue)
@@ -556,17 +561,14 @@ namespace Emby.Server.Implementations.Library
                     {
                         videoStream.BitRate = 30000000;
                     }
-
                     else if (width >= 1900)
                     {
                         videoStream.BitRate = 20000000;
                     }
-
                     else if (width >= 1200)
                     {
                         videoStream.BitRate = 8000000;
                     }
-
                     else if (width >= 700)
                     {
                         videoStream.BitRate = 2000000;
@@ -622,7 +624,6 @@ namespace Emby.Server.Implementations.Library
                     MediaSource = mediaSource,
                     ExtractChapters = false,
                     MediaType = DlnaProfileType.Video
-
                 }, cancellationToken).ConfigureAwait(false);
 
                 mediaSource.MediaStreams = info.MediaStreams;
@@ -648,7 +649,7 @@ namespace Emby.Server.Implementations.Library
                 {
                     mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath);
 
-                    //_logger.LogDebug("Found cached media info");
+                    // _logger.LogDebug("Found cached media info");
                 }
                 catch (Exception ex)
                 {
@@ -670,20 +671,21 @@ namespace Emby.Server.Implementations.Library
                     mediaSource.AnalyzeDurationMs = 3000;
                 }
 
-                mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
+                mediaInfo = await _mediaEncoder.GetMediaInfo(
+                    new MediaInfoRequest
                 {
                     MediaSource = mediaSource,
                     MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
                     ExtractChapters = false
-
-                }, cancellationToken).ConfigureAwait(false);
+                },
+                    cancellationToken).ConfigureAwait(false);
 
                 if (cacheFilePath != null)
                 {
                     Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
                     _jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath);
 
-                    //_logger.LogDebug("Saved media info to {0}", cacheFilePath);
+                    // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
                 }
             }
 
@@ -749,17 +751,14 @@ namespace Emby.Server.Implementations.Library
                     {
                         videoStream.BitRate = 30000000;
                     }
-
                     else if (width >= 1900)
                     {
                         videoStream.BitRate = 20000000;
                     }
-
                     else if (width >= 1200)
                     {
                         videoStream.BitRate = 8000000;
                     }
-
                     else if (width >= 700)
                     {
                         videoStream.BitRate = 2000000;

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

@@ -3,7 +3,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using MediaBrowser.Model.Configuration;
+using Jellyfin.Data.Enums;
 using MediaBrowser.Model.Entities;
 
 namespace Emby.Server.Implementations.Library

+ 7 - 11
Emby.Server.Implementations/Library/MusicManager.cs

@@ -3,6 +3,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -10,6 +11,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
+using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
 
 namespace Emby.Server.Implementations.Library
 {
@@ -75,7 +77,6 @@ namespace Emby.Server.Implementations.Library
                 {
                     return Guid.Empty;
                 }
-
             }).Where(i => !i.Equals(Guid.Empty)).ToArray();
 
             return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
@@ -105,32 +106,27 @@ namespace Emby.Server.Implementations.Library
                 return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
             }
 
-            var playlist = item as Playlist;
-            if (playlist != null)
+            if (item is Playlist playlist)
             {
                 return GetInstantMixFromPlaylist(playlist, user, dtoOptions);
             }
 
-            var album = item as MusicAlbum;
-            if (album != null)
+            if (item is MusicAlbum album)
             {
                 return GetInstantMixFromAlbum(album, user, dtoOptions);
             }
 
-            var artist = item as MusicArtist;
-            if (artist != null)
+            if (item is MusicArtist artist)
             {
                 return GetInstantMixFromArtist(artist, user, dtoOptions);
             }
 
-            var song = item as Audio;
-            if (song != null)
+            if (item is Audio song)
             {
                 return GetInstantMixFromSong(song, user, dtoOptions);
             }
 
-            var folder = item as Folder;
-            if (folder != null)
+            if (item is Folder folder)
             {
                 return GetInstantMixFromFolder(folder, user, dtoOptions);
             }

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

@@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Library
         }
 
         /// <summary>
-        /// Ensures DateCreated and DateModified have values
+        /// Ensures DateCreated and DateModified have values.
         /// </summary>
         /// <param name="fileSystem">The file system.</param>
         /// <param name="item">The item.</param>

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

@@ -209,8 +209,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
                     Name = parseName ?
                         resolvedItem.Name :
                         Path.GetFileNameWithoutExtension(firstMedia.Path),
-                    //AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(),
-                    //LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray()
+                    // AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(),
+                    // LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray()
                 };
 
                 result.Items.Add(libraryItem);

+ 2 - 2
Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs

@@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
             // Args points to an album if parent is an Artist folder or it directly contains music
             if (args.IsDirectory)
             {
-                // if (args.Parent is MusicArtist) return true;  //saves us from testing children twice
+                // if (args.Parent is MusicArtist) return true;  // saves us from testing children twice
                 if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager))
                 {
                     return true;
@@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
             IEnumerable<FileSystemMetadata> list,
             bool allowSubfolders,
             IDirectoryService directoryService,
-            ILogger logger,
+            ILogger<MusicAlbumResolver> logger,
             IFileSystem fileSystem,
             ILibraryManager libraryManager)
         {

+ 2 - 2
Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs

@@ -292,7 +292,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
             }
 
             return true;
-            //var blurayExtensions = new[]
+            // var blurayExtensions = new[]
             //{
             //    ".mts",
             //    ".m2ts",
@@ -300,7 +300,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
             //    ".mpls"
             //};
 
-            //return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
+            // return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
         }
     }
 }

+ 1 - 1
Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs

@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
         public virtual ResolverPriority Priority => ResolverPriority.First;
 
         /// <summary>
-        /// Sets initial values on the newly resolved item
+        /// Sets initial values on the newly resolved item.
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="args">The args.</param>

+ 1 - 1
Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs

@@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
 
             if (!string.IsNullOrEmpty(id))
             {
-                item.SetProviderId(MetadataProviders.Tmdb, id);
+                item.SetProviderId(MetadataProvider.Tmdb, id);
             }
         }
     }

+ 2 - 2
Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

@@ -350,7 +350,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
 
                     if (!string.IsNullOrWhiteSpace(tmdbid))
                     {
-                        item.SetProviderId(MetadataProviders.Tmdb, tmdbid);
+                        item.SetProviderId(MetadataProvider.Tmdb, tmdbid);
                     }
                 }
 
@@ -361,7 +361,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
 
                     if (!string.IsNullOrWhiteSpace(imdbid))
                     {
-                        item.SetProviderId(MetadataProviders.Imdb, imdbid);
+                        item.SetProviderId(MetadataProvider.Imdb, imdbid);
                     }
                 }
             }

+ 3 - 2
Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs

@@ -41,10 +41,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
                 {
                     return new AggregateFolder();
                 }
+
                 if (string.Equals(args.Path, _appPaths.DefaultUserViewsPath, StringComparison.OrdinalIgnoreCase))
                 {
-                    return new UserRootFolder();  //if we got here and still a root - must be user root
+                    return new UserRootFolder();  // if we got here and still a root - must be user root
                 }
+
                 if (args.IsVf)
                 {
                     return new CollectionFolder
@@ -73,7 +75,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
                     {
                         return false;
                     }
-
                 })
                 .Select(i => _fileSystem.GetFileNameWithoutExtension(i))
                 .FirstOrDefault();

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

@@ -55,6 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
                         episode.SeriesId = series.Id;
                         episode.SeriesName = series.Name;
                     }
+
                     if (season != null)
                     {
                         episode.SeasonId = season.Id;

+ 1 - 2
Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs

@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
         private readonly IServerConfigurationManager _config;
         private readonly ILibraryManager _libraryManager;
         private readonly ILocalizationManager _localization;
-        private readonly ILogger _logger;
+        private readonly ILogger<SeasonResolver> _logger;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="SeasonResolver"/> class.
@@ -94,7 +94,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
                             _localization.GetLocalizedString("NameSeasonNumber"),
                             seasonNumber,
                             args.GetLibraryOptions().PreferredMetadataLanguage);
-
                 }
 
                 return season;

+ 4 - 4
Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs

@@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
     public class SeriesResolver : FolderResolver<Series>
     {
         private readonly IFileSystem _fileSystem;
-        private readonly ILogger _logger;
+        private readonly ILogger<SeriesResolver> _logger;
         private readonly ILibraryManager _libraryManager;
 
         /// <summary>
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
                 var collectionType = args.GetCollectionType();
                 if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
                 {
-                    //if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
+                    // if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
                     //{
                     //    return new Series
                     //    {
@@ -119,7 +119,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
             IEnumerable<FileSystemMetadata> fileSystemChildren,
             IDirectoryService directoryService,
             IFileSystem fileSystem,
-            ILogger logger,
+            ILogger<SeriesResolver> logger,
             ILibraryManager libraryManager,
             bool isTvContentType)
         {
@@ -217,7 +217,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
 
             if (!string.IsNullOrEmpty(id))
             {
-                item.SetProviderId(MetadataProviders.Tvdb, id);
+                item.SetProviderId(MetadataProvider.Tvdb, id);
             }
         }
     }

+ 5 - 2
Emby.Server.Implementations/Library/SearchEngine.cs

@@ -3,6 +3,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -12,12 +13,14 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Search;
 using Microsoft.Extensions.Logging;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Person = MediaBrowser.Controller.Entities.Person;
 
 namespace Emby.Server.Implementations.Library
 {
     public class SearchEngine : ISearchEngine
     {
-        private readonly ILogger _logger;
+        private readonly ILogger<SearchEngine> _logger;
         private readonly ILibraryManager _libraryManager;
         private readonly IUserManager _userManager;
 
@@ -191,6 +194,7 @@ namespace Emby.Server.Implementations.Library
                 {
                     searchQuery.AncestorIds = new[] { searchQuery.ParentId };
                 }
+
                 searchQuery.ParentId = Guid.Empty;
                 searchQuery.IncludeItemsByName = true;
                 searchQuery.IncludeItemTypes = Array.Empty<string>();
@@ -204,7 +208,6 @@ namespace Emby.Server.Implementations.Library
             return mediaItems.Select(i => new SearchHintInfo
             {
                 Item = i
-
             }).ToList();
         }
     }

+ 6 - 4
Emby.Server.Implementations/Library/UserDataManager.cs

@@ -5,6 +5,7 @@ using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Threading;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
@@ -13,6 +14,7 @@ using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using Microsoft.Extensions.Logging;
+using Book = MediaBrowser.Controller.Entities.Book;
 
 namespace Emby.Server.Implementations.Library
 {
@@ -26,7 +28,7 @@ namespace Emby.Server.Implementations.Library
         private readonly ConcurrentDictionary<string, UserItemData> _userData =
             new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
 
-        private readonly ILogger _logger;
+        private readonly ILogger<UserDataManager> _logger;
         private readonly IServerConfigurationManager _config;
         private readonly IUserManager _userManager;
         private readonly IUserDataRepository _repository;
@@ -101,7 +103,7 @@ namespace Emby.Server.Implementations.Library
         }
 
         /// <summary>
-        /// Retrieve all user data for the given user
+        /// Retrieve all user data for the given user.
         /// </summary>
         /// <param name="userId"></param>
         /// <returns></returns>
@@ -186,7 +188,7 @@ namespace Emby.Server.Implementations.Library
         }
 
         /// <summary>
-        /// Converts a UserItemData to a DTOUserItemData
+        /// Converts a UserItemData to a DTOUserItemData.
         /// </summary>
         /// <param name="data">The data.</param>
         /// <returns>DtoUserItemData.</returns>
@@ -240,7 +242,7 @@ namespace Emby.Server.Implementations.Library
                 {
                     // Enforce MinResumeDuration
                     var durationSeconds = TimeSpan.FromTicks(runtimeTicks).TotalSeconds;
-                    if (durationSeconds < _config.Configuration.MinResumeDurationSeconds)
+                    if (durationSeconds < _config.Configuration.MinResumeDurationSeconds && !(item is Book))
                     {
                         positionTicks = 0;
                         data.Played = playedToCompletion = true;

+ 0 - 1107
Emby.Server.Implementations/Library/UserManager.cs

@@ -1,1107 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Cryptography;
-using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Cryptography;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Users;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.Library
-{
-    /// <summary>
-    /// Class UserManager.
-    /// </summary>
-    public class UserManager : IUserManager
-    {
-        private readonly object _policySyncLock = new object();
-        private readonly object _configSyncLock = new object();
-
-        private readonly ILogger _logger;
-        private readonly IUserRepository _userRepository;
-        private readonly IXmlSerializer _xmlSerializer;
-        private readonly IJsonSerializer _jsonSerializer;
-        private readonly INetworkManager _networkManager;
-        private readonly IImageProcessor _imageProcessor;
-        private readonly Lazy<IDtoService> _dtoServiceFactory;
-        private readonly IServerApplicationHost _appHost;
-        private readonly IFileSystem _fileSystem;
-        private readonly ICryptoProvider _cryptoProvider;
-
-        private ConcurrentDictionary<Guid, User> _users;
-
-        private IAuthenticationProvider[] _authenticationProviders;
-        private DefaultAuthenticationProvider _defaultAuthenticationProvider;
-
-        private InvalidAuthProvider _invalidAuthProvider;
-
-        private IPasswordResetProvider[] _passwordResetProviders;
-        private DefaultPasswordResetProvider _defaultPasswordResetProvider;
-
-        private IDtoService DtoService => _dtoServiceFactory.Value;
-
-        public UserManager(
-            ILogger<UserManager> logger,
-            IUserRepository userRepository,
-            IXmlSerializer xmlSerializer,
-            INetworkManager networkManager,
-            IImageProcessor imageProcessor,
-            Lazy<IDtoService> dtoServiceFactory,
-            IServerApplicationHost appHost,
-            IJsonSerializer jsonSerializer,
-            IFileSystem fileSystem,
-            ICryptoProvider cryptoProvider)
-        {
-            _logger = logger;
-            _userRepository = userRepository;
-            _xmlSerializer = xmlSerializer;
-            _networkManager = networkManager;
-            _imageProcessor = imageProcessor;
-            _dtoServiceFactory = dtoServiceFactory;
-            _appHost = appHost;
-            _jsonSerializer = jsonSerializer;
-            _fileSystem = fileSystem;
-            _cryptoProvider = cryptoProvider;
-            _users = null;
-        }
-
-        public event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
-
-        /// <summary>
-        /// Occurs when [user updated].
-        /// </summary>
-        public event EventHandler<GenericEventArgs<User>> UserUpdated;
-
-        public event EventHandler<GenericEventArgs<User>> UserPolicyUpdated;
-
-        public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
-
-        public event EventHandler<GenericEventArgs<User>> UserLockedOut;
-
-        public event EventHandler<GenericEventArgs<User>> UserCreated;
-
-        /// <summary>
-        /// Occurs when [user deleted].
-        /// </summary>
-        public event EventHandler<GenericEventArgs<User>> UserDeleted;
-
-        /// <inheritdoc />
-        public IEnumerable<User> Users => _users.Values;
-
-        /// <inheritdoc />
-        public IEnumerable<Guid> UsersIds => _users.Keys;
-
-        /// <summary>
-        /// Called when [user updated].
-        /// </summary>
-        /// <param name="user">The user.</param>
-        private void OnUserUpdated(User user)
-        {
-            UserUpdated?.Invoke(this, new GenericEventArgs<User>(user));
-        }
-
-        /// <summary>
-        /// Called when [user deleted].
-        /// </summary>
-        /// <param name="user">The user.</param>
-        private void OnUserDeleted(User user)
-        {
-            UserDeleted?.Invoke(this, new GenericEventArgs<User>(user));
-        }
-
-        public NameIdPair[] GetAuthenticationProviders()
-        {
-            return _authenticationProviders
-                .Where(i => i.IsEnabled)
-                .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1)
-                .ThenBy(i => i.Name)
-                .Select(i => new NameIdPair
-                {
-                    Name = i.Name,
-                    Id = GetAuthenticationProviderId(i)
-                })
-                .ToArray();
-        }
-
-        public NameIdPair[] GetPasswordResetProviders()
-        {
-            return _passwordResetProviders
-                .Where(i => i.IsEnabled)
-                .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1)
-                .ThenBy(i => i.Name)
-                .Select(i => new NameIdPair
-                {
-                    Name = i.Name,
-                    Id = GetPasswordResetProviderId(i)
-                })
-                .ToArray();
-        }
-
-        public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders)
-        {
-            _authenticationProviders = authenticationProviders.ToArray();
-
-            _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
-
-            _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
-
-            _passwordResetProviders = passwordResetProviders.ToArray();
-
-            _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
-        }
-
-        /// <inheritdoc />
-        public User GetUserById(Guid id)
-        {
-            if (id == Guid.Empty)
-            {
-                throw new ArgumentException("Guid can't be empty", nameof(id));
-            }
-
-            _users.TryGetValue(id, out User user);
-            return user;
-        }
-
-        public User GetUserByName(string name)
-        {
-            if (string.IsNullOrWhiteSpace(name))
-            {
-                throw new ArgumentException("Invalid username", nameof(name));
-            }
-
-            return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase));
-        }
-
-        public void Initialize()
-        {
-            LoadUsers();
-
-            var users = Users;
-
-            // If there are no local users with admin rights, make them all admins
-            if (!users.Any(i => i.Policy.IsAdministrator))
-            {
-                foreach (var user in users)
-                {
-                    user.Policy.IsAdministrator = true;
-                    UpdateUserPolicy(user, user.Policy, false);
-                }
-            }
-        }
-
-        public static bool IsValidUsername(string username)
-        {
-            // This is some regex that matches only on unicode "word" characters, as well as -, _ and @
-            // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
-            // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.)
-            return Regex.IsMatch(username, @"^[\w\-'._@]*$");
-        }
-
-        private static bool IsValidUsernameCharacter(char i)
-            => IsValidUsername(i.ToString(CultureInfo.InvariantCulture));
-
-        public string MakeValidUsername(string username)
-        {
-            if (IsValidUsername(username))
-            {
-                return username;
-            }
-
-            // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)
-            var builder = new StringBuilder();
-
-            foreach (var c in username)
-            {
-                if (IsValidUsernameCharacter(c))
-                {
-                    builder.Append(c);
-                }
-            }
-
-            return builder.ToString();
-        }
-
-        public async Task<User> AuthenticateUser(
-            string username,
-            string password,
-            string hashedPassword,
-            string remoteEndPoint,
-            bool isUserSession)
-        {
-            if (string.IsNullOrWhiteSpace(username))
-            {
-                _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint);
-                throw new ArgumentNullException(nameof(username));
-            }
-
-            var user = Users.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
-
-            var success = false;
-            IAuthenticationProvider authenticationProvider = null;
-
-            if (user != null)
-            {
-                var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
-                authenticationProvider = authResult.authenticationProvider;
-                success = authResult.success;
-            }
-            else
-            {
-                // user is null
-                var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false);
-                authenticationProvider = authResult.authenticationProvider;
-                string updatedUsername = authResult.username;
-                success = authResult.success;
-
-                if (success
-                    && authenticationProvider != null
-                    && !(authenticationProvider is DefaultAuthenticationProvider))
-                {
-                    // Trust the username returned by the authentication provider
-                    username = updatedUsername;
-
-                    // Search the database for the user again
-                    // the authentication provider might have created it
-                    user = Users
-                        .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
-
-                    if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy)
-                    {
-                        var policy = hasNewUserPolicy.GetNewUserPolicy();
-                        UpdateUserPolicy(user, policy, true);
-                    }
-                }
-            }
-
-            if (success && user != null && authenticationProvider != null)
-            {
-                var providerId = GetAuthenticationProviderId(authenticationProvider);
-
-                if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase))
-                {
-                    user.Policy.AuthenticationProviderId = providerId;
-                    UpdateUserPolicy(user, user.Policy, true);
-                }
-            }
-
-            if (user == null)
-            {
-                _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", username, remoteEndPoint);
-                throw new AuthenticationException("Invalid username or password entered.");
-            }
-
-            if (user.Policy.IsDisabled)
-            {
-                _logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint);
-                throw new SecurityException($"The {user.Name} account is currently disabled. Please consult with your administrator.");
-            }
-
-            if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
-            {
-                _logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint);
-                throw new SecurityException("Forbidden.");
-            }
-
-            if (!user.IsParentalScheduleAllowed())
-            {
-                _logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint);
-                throw new SecurityException("User is not allowed access at this time.");
-            }
-
-            // Update LastActivityDate and LastLoginDate, then save
-            if (success)
-            {
-                if (isUserSession)
-                {
-                    user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
-                    UpdateUser(user);
-                }
-
-                ResetInvalidLoginAttemptCount(user);
-                _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Name);
-            }
-            else
-            {
-                IncrementInvalidLoginAttemptCount(user);
-                _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", user.Name, remoteEndPoint);
-            }
-
-            return success ? user : null;
-        }
-
-#nullable enable
-
-        private static string GetAuthenticationProviderId(IAuthenticationProvider provider)
-        {
-            return provider.GetType().FullName;
-        }
-
-        private static string GetPasswordResetProviderId(IPasswordResetProvider provider)
-        {
-            return provider.GetType().FullName;
-        }
-
-        private IAuthenticationProvider GetAuthenticationProvider(User user)
-        {
-            return GetAuthenticationProviders(user)[0];
-        }
-
-        private IPasswordResetProvider GetPasswordResetProvider(User user)
-        {
-            return GetPasswordResetProviders(user)[0];
-        }
-
-        private IAuthenticationProvider[] GetAuthenticationProviders(User? user)
-        {
-            var authenticationProviderId = user?.Policy.AuthenticationProviderId;
-
-            var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray();
-
-            if (!string.IsNullOrEmpty(authenticationProviderId))
-            {
-                providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray();
-            }
-
-            if (providers.Length == 0)
-            {
-                // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found
-                _logger.LogWarning("User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", user?.Name, user?.Policy.AuthenticationProviderId);
-                providers = new IAuthenticationProvider[] { _invalidAuthProvider };
-            }
-
-            return providers;
-        }
-
-        private IPasswordResetProvider[] GetPasswordResetProviders(User? user)
-        {
-            var passwordResetProviderId = user?.Policy.PasswordResetProviderId;
-
-            var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray();
-
-            if (!string.IsNullOrEmpty(passwordResetProviderId))
-            {
-                providers = providers.Where(i => string.Equals(passwordResetProviderId, GetPasswordResetProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray();
-            }
-
-            if (providers.Length == 0)
-            {
-                providers = new IPasswordResetProvider[] { _defaultPasswordResetProvider };
-            }
-
-            return providers;
-        }
-
-        private async Task<(string username, bool success)> AuthenticateWithProvider(
-            IAuthenticationProvider provider,
-            string username,
-            string password,
-            User? resolvedUser)
-        {
-            try
-            {
-                var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
-                    ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false)
-                    : await provider.Authenticate(username, password).ConfigureAwait(false);
-
-                if (authenticationResult.Username != username)
-                {
-                    _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username);
-                    username = authenticationResult.Username;
-                }
-
-                return (username, true);
-            }
-            catch (AuthenticationException ex)
-            {
-                _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name);
-
-                return (username, false);
-            }
-        }
-
-        private async Task<(IAuthenticationProvider? authenticationProvider, string username, bool success)> AuthenticateLocalUser(
-            string username,
-            string password,
-            string hashedPassword,
-            User? user,
-            string remoteEndPoint)
-        {
-            bool success = false;
-            IAuthenticationProvider? authenticationProvider = null;
-
-            foreach (var provider in GetAuthenticationProviders(user))
-            {
-                var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
-                var updatedUsername = providerAuthResult.username;
-                success = providerAuthResult.success;
-
-                if (success)
-                {
-                    authenticationProvider = provider;
-                    username = updatedUsername;
-                    break;
-                }
-            }
-
-            if (!success
-                && _networkManager.IsInLocalNetwork(remoteEndPoint)
-                && user?.Configuration.EnableLocalPassword == true
-                && !string.IsNullOrEmpty(user.EasyPassword))
-            {
-                // Check easy password
-                var passwordHash = PasswordHash.Parse(user.EasyPassword);
-                var hash = _cryptoProvider.ComputeHash(
-                    passwordHash.Id,
-                    Encoding.UTF8.GetBytes(password),
-                    passwordHash.Salt.ToArray());
-                success = passwordHash.Hash.SequenceEqual(hash);
-            }
-
-            return (authenticationProvider, username, success);
-        }
-
-        private void ResetInvalidLoginAttemptCount(User user)
-        {
-            user.Policy.InvalidLoginAttemptCount = 0;
-            UpdateUserPolicy(user, user.Policy, false);
-        }
-
-        private void IncrementInvalidLoginAttemptCount(User user)
-        {
-            int invalidLogins = ++user.Policy.InvalidLoginAttemptCount;
-            int maxInvalidLogins = user.Policy.LoginAttemptsBeforeLockout;
-            if (maxInvalidLogins > 0
-                && invalidLogins >= maxInvalidLogins)
-            {
-                user.Policy.IsDisabled = true;
-                UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
-                _logger.LogWarning(
-                    "Disabling user {UserName} due to {Attempts} unsuccessful login attempts.",
-                    user.Name,
-                    invalidLogins);
-            }
-
-            UpdateUserPolicy(user, user.Policy, false);
-        }
-
-        /// <summary>
-        /// Loads the users from the repository.
-        /// </summary>
-        private void LoadUsers()
-        {
-            var users = _userRepository.RetrieveAllUsers();
-
-            // There always has to be at least one user.
-            if (users.Count != 0)
-            {
-                _users = new ConcurrentDictionary<Guid, User>(
-                    users.Select(x => new KeyValuePair<Guid, User>(x.Id, x)));
-                return;
-            }
-
-            var defaultName = Environment.UserName;
-            if (string.IsNullOrWhiteSpace(defaultName))
-            {
-                defaultName = "MyJellyfinUser";
-            }
-
-            _logger.LogWarning("No users, creating one with username {UserName}", defaultName);
-
-            var name = MakeValidUsername(defaultName);
-
-            var user = InstantiateNewUser(name);
-
-            user.DateLastSaved = DateTime.UtcNow;
-
-            _userRepository.CreateUser(user);
-
-            user.Policy.IsAdministrator = true;
-            user.Policy.EnableContentDeletion = true;
-            user.Policy.EnableRemoteControlOfOtherUsers = true;
-            UpdateUserPolicy(user, user.Policy, false);
-
-            _users = new ConcurrentDictionary<Guid, User>();
-            _users[user.Id] = user;
-        }
-
-#nullable restore
-
-        public UserDto GetUserDto(User user, string remoteEndPoint = null)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException(nameof(user));
-            }
-
-            bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user);
-            bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user));
-
-            bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
-                hasConfiguredEasyPassword :
-                hasConfiguredPassword;
-
-            UserDto dto = new UserDto
-            {
-                Id = user.Id,
-                Name = user.Name,
-                HasPassword = hasPassword,
-                HasConfiguredPassword = hasConfiguredPassword,
-                HasConfiguredEasyPassword = hasConfiguredEasyPassword,
-                LastActivityDate = user.LastActivityDate,
-                LastLoginDate = user.LastLoginDate,
-                Configuration = user.Configuration,
-                ServerId = _appHost.SystemId,
-                Policy = user.Policy
-            };
-
-            if (!hasPassword && _users.Count == 1)
-            {
-                dto.EnableAutoLogin = true;
-            }
-
-            ItemImageInfo image = user.GetImageInfo(ImageType.Primary, 0);
-
-            if (image != null)
-            {
-                dto.PrimaryImageTag = GetImageCacheTag(user, image);
-
-                try
-                {
-                    DtoService.AttachPrimaryImageAspectRatio(dto, user);
-                }
-                catch (Exception ex)
-                {
-                    // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
-                    _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {User}", user.Name);
-                }
-            }
-
-            return dto;
-        }
-
-        public UserDto GetOfflineUserDto(User user)
-        {
-            var dto = GetUserDto(user);
-
-            dto.ServerName = _appHost.FriendlyName;
-
-            return dto;
-        }
-
-        private string GetImageCacheTag(BaseItem item, ItemImageInfo image)
-        {
-            try
-            {
-                return _imageProcessor.GetImageCacheTag(item, image);
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error getting {ImageType} image info for {ImagePath}", image.Type, image.Path);
-                return null;
-            }
-        }
-
-        /// <summary>
-        /// Refreshes metadata for each user
-        /// </summary>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public async Task RefreshUsersMetadata(CancellationToken cancellationToken)
-        {
-            foreach (var user in Users)
-            {
-                await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
-            }
-        }
-
-        /// <summary>
-        /// Renames the user.
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="newName">The new name.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="ArgumentNullException">user</exception>
-        /// <exception cref="ArgumentException"></exception>
-        public async Task RenameUser(User user, string newName)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException(nameof(user));
-            }
-
-            if (string.IsNullOrWhiteSpace(newName))
-            {
-                throw new ArgumentException("Invalid username", nameof(newName));
-            }
-
-            if (user.Name.Equals(newName, StringComparison.Ordinal))
-            {
-                throw new ArgumentException("The new and old names must be different.");
-            }
-
-            if (Users.Any(
-                u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)))
-            {
-                throw new ArgumentException(string.Format(
-                    CultureInfo.InvariantCulture,
-                    "A user with the name '{0}' already exists.",
-                    newName));
-            }
-
-            await user.Rename(newName).ConfigureAwait(false);
-
-            OnUserUpdated(user);
-        }
-
-        /// <summary>
-        /// Updates the user.
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <exception cref="ArgumentNullException">user</exception>
-        /// <exception cref="ArgumentException"></exception>
-        public void UpdateUser(User user)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException(nameof(user));
-            }
-
-            if (user.Id == Guid.Empty)
-            {
-                throw new ArgumentException("Id can't be empty.", nameof(user));
-            }
-
-            if (!_users.ContainsKey(user.Id))
-            {
-                throw new ArgumentException(
-                    string.Format(
-                        CultureInfo.InvariantCulture,
-                        "A user '{0}' with Id {1} does not exist.",
-                        user.Name,
-                        user.Id),
-                    nameof(user));
-            }
-
-            user.DateModified = DateTime.UtcNow;
-            user.DateLastSaved = DateTime.UtcNow;
-
-            _userRepository.UpdateUser(user);
-
-            OnUserUpdated(user);
-        }
-
-        /// <summary>
-        /// Creates the user.
-        /// </summary>
-        /// <param name="name">The name.</param>
-        /// <returns>User.</returns>
-        /// <exception cref="ArgumentNullException">name</exception>
-        /// <exception cref="ArgumentException"></exception>
-        public User CreateUser(string name)
-        {
-            if (string.IsNullOrWhiteSpace(name))
-            {
-                throw new ArgumentNullException(nameof(name));
-            }
-
-            if (!IsValidUsername(name))
-            {
-                throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)");
-            }
-
-            if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase)))
-            {
-                throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name));
-            }
-
-            var user = InstantiateNewUser(name);
-
-            _users[user.Id] = user;
-
-            user.DateLastSaved = DateTime.UtcNow;
-
-            _userRepository.CreateUser(user);
-
-            EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User>(user), _logger);
-
-            return user;
-        }
-
-        /// <inheritdoc />
-        /// <exception cref="ArgumentNullException">The <c>user</c> is <c>null</c>.</exception>
-        /// <exception cref="ArgumentException">The <c>user</c> doesn't exist, or is the last administrator.</exception>
-        /// <exception cref="InvalidOperationException">The <c>user</c> can't be deleted; there are no other users.</exception>
-        public void DeleteUser(User user)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException(nameof(user));
-            }
-
-            if (!_users.ContainsKey(user.Id))
-            {
-                throw new ArgumentException(string.Format(
-                    CultureInfo.InvariantCulture,
-                    "The user cannot be deleted because there is no user with the Name {0} and Id {1}.",
-                    user.Name,
-                    user.Id));
-            }
-
-            if (_users.Count == 1)
-            {
-                throw new InvalidOperationException(string.Format(
-                    CultureInfo.InvariantCulture,
-                    "The user '{0}' cannot be deleted because there must be at least one user in the system.",
-                    user.Name));
-            }
-
-            if (user.Policy.IsAdministrator
-                && Users.Count(i => i.Policy.IsAdministrator) == 1)
-            {
-                throw new ArgumentException(
-                    string.Format(
-                        CultureInfo.InvariantCulture,
-                        "The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
-                        user.Name),
-                    nameof(user));
-            }
-
-            var configPath = GetConfigurationFilePath(user);
-
-            _userRepository.DeleteUser(user);
-
-            // Delete user config dir
-            lock (_configSyncLock)
-                lock (_policySyncLock)
-                {
-                    try
-                    {
-                        Directory.Delete(user.ConfigurationDirectoryPath, true);
-                    }
-                    catch (IOException ex)
-                    {
-                        _logger.LogError(ex, "Error deleting user config dir: {Path}", user.ConfigurationDirectoryPath);
-                    }
-                }
-
-            _users.TryRemove(user.Id, out _);
-
-            OnUserDeleted(user);
-        }
-
-        /// <summary>
-        /// Resets the password by clearing it.
-        /// </summary>
-        /// <returns>Task.</returns>
-        public Task ResetPassword(User user)
-        {
-            return ChangePassword(user, string.Empty);
-        }
-
-        public void ResetEasyPassword(User user)
-        {
-            ChangeEasyPassword(user, string.Empty, null);
-        }
-
-        public async Task ChangePassword(User user, string newPassword)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException(nameof(user));
-            }
-
-            await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
-
-            UpdateUser(user);
-
-            UserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user));
-        }
-
-        public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException(nameof(user));
-            }
-
-            GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordHash);
-
-            UpdateUser(user);
-
-            UserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user));
-        }
-
-        /// <summary>
-        /// Instantiates the new user.
-        /// </summary>
-        /// <param name="name">The name.</param>
-        /// <returns>User.</returns>
-        private static User InstantiateNewUser(string name)
-        {
-            return new User
-            {
-                Name = name,
-                Id = Guid.NewGuid(),
-                DateCreated = DateTime.UtcNow,
-                DateModified = DateTime.UtcNow
-            };
-        }
-
-        public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork)
-        {
-            var user = string.IsNullOrWhiteSpace(enteredUsername) ?
-                null :
-                GetUserByName(enteredUsername);
-
-            var action = ForgotPasswordAction.InNetworkRequired;
-
-            if (user != null && isInNetwork)
-            {
-                var passwordResetProvider = GetPasswordResetProvider(user);
-                return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false);
-            }
-            else
-            {
-                return new ForgotPasswordResult
-                {
-                    Action = action,
-                    PinFile = string.Empty
-                };
-            }
-        }
-
-        public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
-        {
-            foreach (var provider in _passwordResetProviders)
-            {
-                var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false);
-                if (result.Success)
-                {
-                    return result;
-                }
-            }
-
-            return new PinRedeemResult
-            {
-                Success = false,
-                UsersReset = Array.Empty<string>()
-            };
-        }
-
-        public UserPolicy GetUserPolicy(User user)
-        {
-            var path = GetPolicyFilePath(user);
-            if (!File.Exists(path))
-            {
-                return GetDefaultPolicy();
-            }
-
-            try
-            {
-                lock (_policySyncLock)
-                {
-                    return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path);
-                }
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error reading policy file: {Path}", path);
-
-                return GetDefaultPolicy();
-            }
-        }
-
-        private static UserPolicy GetDefaultPolicy()
-        {
-            return new UserPolicy
-            {
-                EnableContentDownloading = true,
-                EnableSyncTranscoding = true
-            };
-        }
-
-        public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy)
-        {
-            var user = GetUserById(userId);
-            UpdateUserPolicy(user, userPolicy, true);
-        }
-
-        private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent)
-        {
-            // The xml serializer will output differently if the type is not exact
-            if (userPolicy.GetType() != typeof(UserPolicy))
-            {
-                var json = _jsonSerializer.SerializeToString(userPolicy);
-                userPolicy = _jsonSerializer.DeserializeFromString<UserPolicy>(json);
-            }
-
-            var path = GetPolicyFilePath(user);
-
-            Directory.CreateDirectory(Path.GetDirectoryName(path));
-
-            lock (_policySyncLock)
-            {
-                _xmlSerializer.SerializeToFile(userPolicy, path);
-                user.Policy = userPolicy;
-            }
-
-            if (fireEvent)
-            {
-                UserPolicyUpdated?.Invoke(this, new GenericEventArgs<User>(user));
-            }
-        }
-
-        private static string GetPolicyFilePath(User user)
-        {
-            return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml");
-        }
-
-        private static string GetConfigurationFilePath(User user)
-        {
-            return Path.Combine(user.ConfigurationDirectoryPath, "config.xml");
-        }
-
-        public UserConfiguration GetUserConfiguration(User user)
-        {
-            var path = GetConfigurationFilePath(user);
-
-            if (!File.Exists(path))
-            {
-                return new UserConfiguration();
-            }
-
-            try
-            {
-                lock (_configSyncLock)
-                {
-                    return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path);
-                }
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error reading policy file: {Path}", path);
-
-                return new UserConfiguration();
-            }
-        }
-
-        public void UpdateConfiguration(Guid userId, UserConfiguration config)
-        {
-            var user = GetUserById(userId);
-            UpdateConfiguration(user, config);
-        }
-
-        public void UpdateConfiguration(User user, UserConfiguration config)
-        {
-            UpdateConfiguration(user, config, true);
-        }
-
-        private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent)
-        {
-            var path = GetConfigurationFilePath(user);
-
-            // The xml serializer will output differently if the type is not exact
-            if (config.GetType() != typeof(UserConfiguration))
-            {
-                var json = _jsonSerializer.SerializeToString(config);
-                config = _jsonSerializer.DeserializeFromString<UserConfiguration>(json);
-            }
-
-            Directory.CreateDirectory(Path.GetDirectoryName(path));
-
-            lock (_configSyncLock)
-            {
-                _xmlSerializer.SerializeToFile(config, path);
-                user.Configuration = config;
-            }
-
-            if (fireEvent)
-            {
-                UserConfigurationUpdated?.Invoke(this, new GenericEventArgs<User>(user));
-            }
-        }
-    }
-
-    public class DeviceAccessEntryPoint : IServerEntryPoint
-    {
-        private IUserManager _userManager;
-        private IAuthenticationRepository _authRepo;
-        private IDeviceManager _deviceManager;
-        private ISessionManager _sessionManager;
-
-        public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager)
-        {
-            _userManager = userManager;
-            _authRepo = authRepo;
-            _deviceManager = deviceManager;
-            _sessionManager = sessionManager;
-        }
-
-        public Task RunAsync()
-        {
-            _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated;
-
-            return Task.CompletedTask;
-        }
-
-        private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
-        {
-            var user = e.Argument;
-            if (!user.Policy.EnableAllDevices)
-            {
-                UpdateDeviceAccess(user);
-            }
-        }
-
-        private void UpdateDeviceAccess(User user)
-        {
-            var existing = _authRepo.Get(new AuthenticationInfoQuery
-            {
-                UserId = user.Id
-
-            }).Items;
-
-            foreach (var authInfo in existing)
-            {
-                if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId))
-                {
-                    _sessionManager.Logout(authInfo);
-                }
-            }
-        }
-
-        public void Dispose()
-        {
-
-        }
-    }
-}

+ 20 - 10
Emby.Server.Implementations/Library/UserViewManager.cs

@@ -5,6 +5,8 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using System.Threading;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dto;
@@ -17,6 +19,8 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Library;
 using MediaBrowser.Model.Querying;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Person = MediaBrowser.Controller.Entities.Person;
 
 namespace Emby.Server.Implementations.Library
 {
@@ -125,12 +129,12 @@ namespace Emby.Server.Implementations.Library
 
             if (!query.IncludeHidden)
             {
-                list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList();
+                list = list.Where(i => !user.GetPreference(PreferenceKind.MyMediaExcludes).Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList();
             }
 
             var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
 
-            var orders = user.Configuration.OrderedViews.ToList();
+            var orders = user.GetPreference(PreferenceKind.OrderedViews).ToList();
 
             return list
                 .OrderBy(i =>
@@ -165,7 +169,13 @@ namespace Emby.Server.Implementations.Library
             return GetUserSubViewWithName(name, parentId, type, sortName);
         }
 
-        private Folder GetUserView(List<ICollectionFolder> parents, string viewType, string localizationKey, string sortName, User user, string[] presetViews)
+        private Folder GetUserView(
+            List<ICollectionFolder> parents,
+            string viewType,
+            string localizationKey,
+            string sortName,
+            Jellyfin.Data.Entities.User user,
+            string[] presetViews)
         {
             if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
             {
@@ -270,7 +280,8 @@ namespace Emby.Server.Implementations.Library
             {
                 parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
                     .Where(i => i is Folder)
-                    .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
+                    .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes)
+                        .Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
                     .ToList();
             }
 
@@ -331,12 +342,11 @@ namespace Emby.Server.Implementations.Library
 
             var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 ? new[]
             {
-                typeof(Person).Name,
-                typeof(Studio).Name,
-                typeof(Year).Name,
-                typeof(MusicGenre).Name,
-                typeof(Genre).Name
-
+                nameof(Person),
+                nameof(Studio),
+                nameof(Year),
+                nameof(MusicGenre),
+                nameof(Genre)
             } : Array.Empty<string>();
 
             var query = new InternalItemsQuery(user)

+ 2 - 2
Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs

@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators
         /// The _library manager.
         /// </summary>
         private readonly ILibraryManager _libraryManager;
-        private readonly ILogger _logger;
+        private readonly ILogger<ArtistsValidator> _logger;
         private readonly IItemRepository _itemRepo;
 
         /// <summary>
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators
         /// <param name="itemRepo">The item repository.</param>
         public ArtistsPostScanTask(
             ILibraryManager libraryManager,
-            ILogger<ArtistsPostScanTask> logger,
+            ILogger<ArtistsValidator> logger,
             IItemRepository itemRepo)
         {
             _libraryManager = libraryManager;

+ 2 - 3
Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs

@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Validators
         /// <summary>
         /// The logger.
         /// </summary>
-        private readonly ILogger _logger;
+        private readonly ILogger<ArtistsValidator> _logger;
         private readonly IItemRepository _itemRepo;
 
         /// <summary>
@@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Validators
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="itemRepo">The item repository.</param>
-        public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+        public ArtistsValidator(ILibraryManager libraryManager, ILogger<ArtistsValidator> logger, IItemRepository itemRepo)
         {
             _libraryManager = libraryManager;
             _logger = logger;
@@ -98,7 +98,6 @@ namespace Emby.Server.Implementations.Library.Validators
                 _libraryManager.DeleteItem(item, new DeleteOptions
                 {
                     DeleteFileLocation = false
-
                 }, false);
             }
 

+ 2 - 2
Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs

@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators
         /// The _library manager.
         /// </summary>
         private readonly ILibraryManager _libraryManager;
-        private readonly ILogger _logger;
+        private readonly ILogger<GenresValidator> _logger;
         private readonly IItemRepository _itemRepo;
 
         /// <summary>
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators
         /// <param name="itemRepo">The item repository.</param>
         public GenresPostScanTask(
             ILibraryManager libraryManager,
-            ILogger<GenresPostScanTask> logger,
+            ILogger<GenresValidator> logger,
             IItemRepository itemRepo)
         {
             _libraryManager = libraryManager;

+ 2 - 2
Emby.Server.Implementations/Library/Validators/GenresValidator.cs

@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Library.Validators
         /// <summary>
         /// The logger.
         /// </summary>
-        private readonly ILogger _logger;
+        private readonly ILogger<GenresValidator> _logger;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="GenresValidator"/> class.
@@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Library.Validators
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="itemRepo">The item repository.</param>
-        public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+        public GenresValidator(ILibraryManager libraryManager, ILogger<GenresValidator> logger, IItemRepository itemRepo)
         {
             _libraryManager = libraryManager;
             _logger = logger;

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