Browse Source

Merge remote-tracking branch 'upstream/master' into random

Bond-009 5 years ago
parent
commit
a2c35e6dba
100 changed files with 1079 additions and 1207 deletions
  1. 3 3
      .ci/azure-pipelines.yml
  2. 59 8
      .copr/Makefile
  3. 1 0
      .github/ISSUE_TEMPLATE/bug_report.md
  4. 8 5
      .github/stale.yml
  5. 3 0
      .gitignore
  6. 2 2
      .vscode/launch.json
  7. 17 11
      Dockerfile
  8. 8 8
      Dockerfile.arm
  9. 8 8
      Dockerfile.arm64
  10. 29 10
      Emby.Dlna/Api/DlnaServerService.cs
  11. 1 1
      Emby.Dlna/Emby.Dlna.csproj
  12. 1 1
      Emby.Dlna/PlayTo/PlayToManager.cs
  13. 9 14
      Emby.Dlna/Ssdp/DeviceDiscovery.cs
  14. 1 6
      Emby.Drawing/Emby.Drawing.csproj
  15. 1 1
      Emby.Naming/AudioBook/AudioBookFileInfo.cs
  16. 1 1
      Emby.Naming/AudioBook/AudioBookInfo.cs
  17. 21 8
      Emby.Naming/Common/NamingOptions.cs
  18. 5 4
      Emby.Naming/Emby.Naming.csproj
  19. 2 2
      Emby.Naming/TV/SeasonPathParser.cs
  20. 1 1
      Emby.Naming/Video/CleanDateTimeParser.cs
  21. 1 1
      Emby.Naming/Video/VideoFileInfo.cs
  22. 1 1
      Emby.Naming/Video/VideoInfo.cs
  23. 1 1
      Emby.Naming/Video/VideoResolver.cs
  24. 1 1
      Emby.Notifications/Emby.Notifications.csproj
  25. 2 2
      Emby.Photos/Emby.Photos.csproj
  26. 2 2
      Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
  27. 30 15
      Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
  28. 2 2
      Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
  29. 70 106
      Emby.Server.Implementations/ApplicationHost.cs
  30. 4 4
      Emby.Server.Implementations/Channels/ChannelManager.cs
  31. 6 6
      Emby.Server.Implementations/Collections/CollectionManager.cs
  32. 1 32
      Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
  33. 3 7
      Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
  34. 5 5
      Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
  35. 52 64
      Emby.Server.Implementations/Data/SqliteExtensions.cs
  36. 30 26
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  37. 5 10
      Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
  38. 1 1
      Emby.Server.Implementations/Data/SqliteUserRepository.cs
  39. 0 1
      Emby.Server.Implementations/Devices/DeviceManager.cs
  40. 11 12
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  41. 85 174
      Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
  42. 9 6
      Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
  43. 1 1
      Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
  44. 1 1
      Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs
  45. 1 1
      Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
  46. 6 4
      Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
  47. 1 1
      Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
  48. 3 1
      Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
  49. 16 1
      Emby.Server.Implementations/HttpServer/Security/AuthService.cs
  50. 6 3
      Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
  51. 0 1
      Emby.Server.Implementations/Library/InvalidAuthProvider.cs
  52. 18 11
      Emby.Server.Implementations/Library/LibraryManager.cs
  53. 7 6
      Emby.Server.Implementations/Library/MediaSourceManager.cs
  54. 1 1
      Emby.Server.Implementations/Library/SearchEngine.cs
  55. 21 29
      Emby.Server.Implementations/Library/UserManager.cs
  56. 10 10
      Emby.Server.Implementations/Library/UserViewManager.cs
  57. 2 1
      Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
  58. 3 2
      Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs
  59. 2 1
      Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs
  60. 18 10
      Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
  61. 3 1
      Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs
  62. 12 10
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  63. 5 5
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
  64. 20 15
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  65. 2 2
      Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
  66. 6 7
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  67. 3 2
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
  68. 7 5
      Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
  69. 2 2
      Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
  70. 3 2
      Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
  71. 96 0
      Emby.Server.Implementations/Localization/Core/af.json
  72. 19 19
      Emby.Server.Implementations/Localization/Core/bg-BG.json
  73. 1 1
      Emby.Server.Implementations/Localization/Core/cs.json
  74. 4 4
      Emby.Server.Implementations/Localization/Core/de.json
  75. 6 6
      Emby.Server.Implementations/Localization/Core/fr.json
  76. 30 30
      Emby.Server.Implementations/Localization/Core/he.json
  77. 19 19
      Emby.Server.Implementations/Localization/Core/hu.json
  78. 1 0
      Emby.Server.Implementations/Localization/Core/is.json
  79. 7 7
      Emby.Server.Implementations/Localization/Core/it.json
  80. 3 3
      Emby.Server.Implementations/Localization/Core/nl.json
  81. 3 3
      Emby.Server.Implementations/Localization/Core/sk.json
  82. 78 78
      Emby.Server.Implementations/Localization/Core/tr.json
  83. 1 1
      Emby.Server.Implementations/Localization/Core/zh-CN.json
  84. 1 1
      Emby.Server.Implementations/Localization/Core/zh-HK.json
  85. 1 1
      Emby.Server.Implementations/Localization/Core/zh-TW.json
  86. 0 2
      Emby.Server.Implementations/Localization/LocalizationManager.cs
  87. 2 2
      Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs
  88. 19 145
      Emby.Server.Implementations/Networking/NetworkManager.cs
  89. 4 5
      Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
  90. 13 16
      Emby.Server.Implementations/Playlists/PlaylistManager.cs
  91. 5 4
      Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  92. 16 16
      Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
  93. 8 7
      Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
  94. 8 20
      Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
  95. 17 9
      Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
  96. 2 3
      Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
  97. 5 31
      Emby.Server.Implementations/Serialization/MyXmlSerializer.cs
  98. 6 47
      Emby.Server.Implementations/ServerApplicationPaths.cs
  99. 19 16
      Emby.Server.Implementations/Services/ServicePath.cs
  100. 2 2
      Emby.Server.Implementations/Services/SwaggerService.cs

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

@@ -200,8 +200,8 @@ jobs:
       persistCredentials: true
       persistCredentials: true
 
 
     - task: CmdLine@2
     - task: CmdLine@2
-      displayName: "Check out web"
-      condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+      displayName: "Check out web (master, release or tag)"
+      condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
       inputs:
       inputs:
         script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
         script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
 
 
@@ -245,7 +245,7 @@ jobs:
       inputs:
       inputs:
         targetType: 'filePath' # Optional. Options: filePath, inline
         targetType: 'filePath' # Optional. Options: filePath, inline
         filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
         filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
-        arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
+        arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
         #script: '# Write your PowerShell commands here.Write-Host Hello World' # Required when targetType == Inline
         #script: '# Write your PowerShell commands here.Write-Host Hello World' # Required when targetType == Inline
         errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
         errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
         #failOnStderr: false # Optional
         #failOnStderr: false # Optional

+ 59 - 8
.copr/Makefile

@@ -1,8 +1,59 @@
-srpm:
-	dnf -y install git
-	git submodule update --init --recursive
-	cd deployment/fedora-package-x64;                    \
-	./create_tarball.sh;                                 \
-	rpmbuild -bs pkg-src/jellyfin.spec                   \
-	         --define "_sourcedir $$PWD/pkg-src/"        \
-		 --define "_srcrpmdir $(outdir)"
+VERSION := $(shell sed -ne '/^Version:/s/.*  *//p'                      \
+                   deployment/fedora-package-x64/pkg-src/jellyfin.spec)
+
+deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
+	curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
+         https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
+	|| curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
+         https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
+
+srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
+	cd deployment/fedora-package-x64;                                             \
+    SOURCE_DIR=../..                                                              \
+    WORKDIR="$${PWD}";                                                            \
+    package_temporary_dir="$${WORKDIR}/pkg-dist-tmp";                             \
+    pkg_src_dir="$${WORKDIR}/pkg-src";                                            \
+    GNU_TAR=1;                                                                    \
+    tar                                                                           \
+    --transform "s,^\.,jellyfin-$(VERSION),"                                      \
+    --exclude='.git*'                                                             \
+    --exclude='**/.git'                                                           \
+    --exclude='**/.hg'                                                            \
+    --exclude='**/.vs'                                                            \
+    --exclude='**/.vscode'                                                        \
+    --exclude='deployment'                                                        \
+    --exclude='**/bin'                                                            \
+    --exclude='**/obj'                                                            \
+    --exclude='**/.nuget'                                                         \
+    --exclude='*.deb'                                                             \
+    --exclude='*.rpm'                                                             \
+    -czf "pkg-src/jellyfin-$(VERSION).tar.gz"                                     \
+    -C $${SOURCE_DIR} ./ || GNU_TAR=0;                                            \
+    if [ $${GNU_TAR} -eq 0 ]; then                                                \
+        package_temporary_dir="$$(mktemp -d)";                                    \
+        mkdir -p "$${package_temporary_dir}/jellyfin";                            \
+        tar                                                                       \
+        --exclude='.git*'                                                         \
+        --exclude='**/.git'                                                       \
+        --exclude='**/.hg'                                                        \
+        --exclude='**/.vs'                                                        \
+        --exclude='**/.vscode'                                                    \
+        --exclude='deployment'                                                    \
+        --exclude='**/bin'                                                        \
+        --exclude='**/obj'                                                        \
+        --exclude='**/.nuget'                                                     \
+        --exclude='*.deb'                                                         \
+        --exclude='*.rpm'                                                         \
+        -czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"      \
+        -C $${SOURCE_DIR} ./;                                                     \
+        mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)";                 \
+        tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"  \
+            -C "$${package_temporary_dir}/jellyfin-$(VERSION);                    \
+        rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz";    \
+        tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz"      \
+            -C "$${package_temporary_dir}" "jellyfin-$(VERSION);                  \
+        rm -rf $${package_temporary_dir};                                         \
+	fi;                                                                           \
+	rpmbuild -bs pkg-src/jellyfin.spec                                            \
+	         --define "_sourcedir $$PWD/pkg-src/"                                 \
+	         --define "_srcrpmdir $(outdir)"

+ 1 - 0
.github/ISSUE_TEMPLATE/bug_report.md

@@ -30,6 +30,7 @@ assignees: ''
  - OS: [e.g. Docker, Debian, Windows]
  - OS: [e.g. Docker, Debian, Windows]
  - Browser: [e.g. Firefox, Chrome, Safari]
  - Browser: [e.g. Firefox, Chrome, Safari]
  - Jellyfin Version: [e.g. 10.0.1]
  - Jellyfin Version: [e.g. 10.0.1]
+ - Installed Plugins: [e.g. none, Fanart, Anime, etc.]
  - Reverse proxy: [e.g. no, nginx, apache, etc.]
  - Reverse proxy: [e.g. no, nginx, apache, etc.]
 
 
 **Additional context**
 **Additional context**

+ 8 - 5
.github/stale.yml

@@ -1,7 +1,7 @@
 # Number of days of inactivity before an issue becomes stale
 # Number of days of inactivity before an issue becomes stale
-daysUntilStale: 90
+daysUntilStale: 120
 # Number of days of inactivity before a stale issue is closed
 # Number of days of inactivity before a stale issue is closed
-daysUntilClose: 14
+daysUntilClose: 21
 # Issues with these labels will never be considered stale
 # Issues with these labels will never be considered stale
 exemptLabels:
 exemptLabels:
   - regression
   - regression
@@ -11,12 +11,15 @@ exemptLabels:
   - future
   - future
   - feature
   - feature
   - enhancement
   - enhancement
+  - confirmed
 # Label to use when marking an issue as stale
 # Label to use when marking an issue as stale
 staleLabel: stale
 staleLabel: stale
 # Comment to post when marking an issue as stale. Set to `false` to disable
 # Comment to post when marking an issue as stale. Set to `false` to disable
 markComment: >
 markComment: >
-  Issues go stale after 90d of inactivity. Mark the issue as fresh by adding a comment or commit. Stale issues close after an additional 14d of inactivity.
-  If this issue is safe to close now please do so.
-  If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
+  This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
+  
+  If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or nightlies, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
+
+  This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
 # Comment to post when closing a stale issue. Set to `false` to disable
 # Comment to post when closing a stale issue. Set to `false` to disable
 closeComment: false
 closeComment: false

+ 3 - 0
.gitignore

@@ -268,3 +268,6 @@ doc/
 # Deployment artifacts
 # Deployment artifacts
 dist
 dist
 *.exe
 *.exe
+
+# BenchmarkDotNet artifacts
+BenchmarkDotNet.Artifacts

+ 2 - 2
.vscode/launch.json

@@ -10,7 +10,7 @@
             "request": "launch",
             "request": "launch",
             "preLaunchTask": "build",
             "preLaunchTask": "build",
             // If you have changed target frameworks, make sure to update the program path.
             // If you have changed target frameworks, make sure to update the program path.
-            "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin.dll",
+            "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp3.0/jellyfin.dll",
             "args": [],
             "args": [],
             "cwd": "${workspaceFolder}/Jellyfin.Server",
             "cwd": "${workspaceFolder}/Jellyfin.Server",
             // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
             // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
@@ -25,4 +25,4 @@
             "processId": "${command:pickProcess}"
             "processId": "${command:pickProcess}"
         }
         }
     ,]
     ,]
-}
+}

+ 17 - 11
Dockerfile

@@ -1,8 +1,8 @@
-ARG DOTNET_VERSION=2.2
+ARG DOTNET_VERSION=3.0
 ARG FFMPEG_VERSION=latest
 ARG FFMPEG_VERSION=latest
 
 
 FROM node:alpine as web-builder
 FROM node:alpine as web-builder
-ARG JELLYFIN_WEB_VERSION=v10.5.0
+ARG JELLYFIN_WEB_VERSION=master
 RUN apk add curl \
 RUN apk add curl \
  && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
  && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
  && cd jellyfin-web-* \
  && cd jellyfin-web-* \
@@ -10,33 +10,39 @@ RUN apk add curl \
  && yarn build \
  && yarn build \
  && mv dist /dist
  && mv dist /dist
 
 
-FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
+FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder
 WORKDIR /repo
 WORKDIR /repo
 COPY . .
 COPY . .
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
 RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
 
 
 FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
 FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
+FROM debian:buster-slim
 
 
-FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}
-COPY --from=ffmpeg / /
+COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg
 COPY --from=builder /jellyfin /jellyfin
 COPY --from=builder /jellyfin /jellyfin
 COPY --from=web-builder /dist /jellyfin/jellyfin-web
 COPY --from=web-builder /dist /jellyfin/jellyfin-web
 # Install dependencies:
 # Install dependencies:
 #   libfontconfig1: needed for Skia
 #   libfontconfig1: needed for Skia
+#   libgomp1: needed for ffmpeg
+#   libva-drm2: needed for ffmpeg
 #   mesa-va-drivers: needed for VAAPI
 #   mesa-va-drivers: needed for VAAPI
 RUN apt-get update \
 RUN apt-get update \
  && apt-get install --no-install-recommends --no-install-suggests -y \
  && apt-get install --no-install-recommends --no-install-suggests -y \
-   libfontconfig1 mesa-va-drivers \
+   libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl \
  && apt-get clean autoclean \
  && apt-get clean autoclean \
  && apt-get autoremove \
  && apt-get autoremove \
  && rm -rf /var/lib/apt/lists/* \
  && rm -rf /var/lib/apt/lists/* \
  && mkdir -p /cache /config /media \
  && mkdir -p /cache /config /media \
- && chmod 777 /cache /config /media
+ && chmod 777 /cache /config /media \
+ && ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \
+ && ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin
+
+ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
 
 
 EXPOSE 8096
 EXPOSE 8096
 VOLUME /cache /config /media
 VOLUME /cache /config /media
-ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
-    --datadir /config \
-    --cachedir /cache \
-    --ffmpeg /usr/local/bin/ffmpeg
+ENTRYPOINT ["./jellyfin/jellyfin", \
+    "--datadir", "/config", \
+    "--cachedir", "/cache", \
+    "--ffmpeg", "/usr/local/bin/ffmpeg"]

+ 8 - 8
Dockerfile.arm

@@ -4,7 +4,7 @@ ARG DOTNET_VERSION=3.0
 
 
 
 
 FROM node:alpine as web-builder
 FROM node:alpine as web-builder
-ARG JELLYFIN_WEB_VERSION=v10.5.0
+ARG JELLYFIN_WEB_VERSION=master
 RUN apk add curl \
 RUN apk add curl \
  && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
  && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
  && cd jellyfin-web-* \
  && cd jellyfin-web-* \
@@ -17,8 +17,6 @@ FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
 WORKDIR /repo
 WORKDIR /repo
 COPY . .
 COPY . .
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# TODO Remove or update the sed line when we update dotnet version.
-RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
 # Discard objs - may cause failures if exists
 # Discard objs - may cause failures if exists
 RUN find . -type d -name obj | xargs -r rm -r
 RUN find . -type d -name obj | xargs -r rm -r
 # Build
 # Build
@@ -26,7 +24,7 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin"
 
 
 
 
 FROM multiarch/qemu-user-static:x86_64-arm as qemu
 FROM multiarch/qemu-user-static:x86_64-arm as qemu
-FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7
+FROM debian:stretch-slim-arm32v7
 COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
 COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
 RUN apt-get update \
 RUN apt-get update \
  && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
  && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
@@ -36,9 +34,11 @@ RUN apt-get update \
 COPY --from=builder /jellyfin /jellyfin
 COPY --from=builder /jellyfin /jellyfin
 COPY --from=web-builder /dist /jellyfin/jellyfin-web
 COPY --from=web-builder /dist /jellyfin/jellyfin-web
 
 
+ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
+
 EXPOSE 8096
 EXPOSE 8096
 VOLUME /cache /config /media
 VOLUME /cache /config /media
-ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
-    --datadir /config \
-    --cachedir /cache \
-    --ffmpeg /usr/bin/ffmpeg
+ENTRYPOINT ["./jellyfin/jellyfin", \
+    "--datadir", "/config", \
+    "--cachedir", "/cache", \
+    "--ffmpeg", "/usr/bin/ffmpeg"]

+ 8 - 8
Dockerfile.arm64

@@ -4,7 +4,7 @@ ARG DOTNET_VERSION=3.0
 
 
 
 
 FROM node:alpine as web-builder
 FROM node:alpine as web-builder
-ARG JELLYFIN_WEB_VERSION=v10.5.0
+ARG JELLYFIN_WEB_VERSION=master
 RUN apk add curl \
 RUN apk add curl \
  && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
  && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
  && cd jellyfin-web-* \
  && cd jellyfin-web-* \
@@ -17,8 +17,6 @@ FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
 WORKDIR /repo
 WORKDIR /repo
 COPY . .
 COPY . .
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# TODO Remove or update the sed line when we update dotnet version.
-RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
 # Discard objs - may cause failures if exists
 # Discard objs - may cause failures if exists
 RUN find . -type d -name obj | xargs -r rm -r
 RUN find . -type d -name obj | xargs -r rm -r
 # Build
 # Build
@@ -26,7 +24,7 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin"
 
 
 
 
 FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
 FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
-FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8
+FROM debian:stretch-slim-arm64v8
 COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
 COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
 RUN apt-get update \
 RUN apt-get update \
  && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
  && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
@@ -36,9 +34,11 @@ RUN apt-get update \
 COPY --from=builder /jellyfin /jellyfin
 COPY --from=builder /jellyfin /jellyfin
 COPY --from=web-builder /dist /jellyfin/jellyfin-web
 COPY --from=web-builder /dist /jellyfin/jellyfin-web
 
 
+ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
+
 EXPOSE 8096
 EXPOSE 8096
 VOLUME /cache /config /media
 VOLUME /cache /config /media
-ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
-    --datadir /config \
-    --cachedir /cache \
-    --ffmpeg /usr/bin/ffmpeg
+ENTRYPOINT ["./jellyfin/jellyfin", \
+    "--datadir", "/config", \
+    "--cachedir", "/cache", \
+    "--ffmpeg", "/usr/bin/ffmpeg"]

+ 29 - 10
Emby.Dlna/Api/DlnaServerService.cs

@@ -6,6 +6,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Emby.Dlna.Main;
 using Emby.Dlna.Main;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.Services;
@@ -108,12 +109,13 @@ namespace Emby.Dlna.Api
 
 
     public class DlnaServerService : IService, IRequiresRequest
     public class DlnaServerService : IService, IRequiresRequest
     {
     {
-        private readonly IDlnaManager _dlnaManager;
-
         private const string XMLContentType = "text/xml; charset=UTF-8";
         private const string XMLContentType = "text/xml; charset=UTF-8";
 
 
+        private readonly IDlnaManager _dlnaManager;
+        private readonly IHttpResultFactory _resultFactory;
+        private readonly IServerConfigurationManager _configurationManager;
+
         public IRequest Request { get; set; }
         public IRequest Request { get; set; }
-        private IHttpResultFactory _resultFactory;
 
 
         private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
         private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
 
 
@@ -121,10 +123,14 @@ namespace Emby.Dlna.Api
 
 
         private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
         private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
 
 
-        public DlnaServerService(IDlnaManager dlnaManager, IHttpResultFactory httpResultFactory)
+        public DlnaServerService(
+            IDlnaManager dlnaManager,
+            IHttpResultFactory httpResultFactory,
+            IServerConfigurationManager configurationManager)
         {
         {
             _dlnaManager = dlnaManager;
             _dlnaManager = dlnaManager;
             _resultFactory = httpResultFactory;
             _resultFactory = httpResultFactory;
+            _configurationManager = configurationManager;
         }
         }
 
 
         private string GetHeader(string name)
         private string GetHeader(string name)
@@ -205,19 +211,32 @@ namespace Emby.Dlna.Api
             var pathInfo = Parse(Request.PathInfo);
             var pathInfo = Parse(Request.PathInfo);
             var first = pathInfo[0];
             var first = pathInfo[0];
 
 
+            string baseUrl = _configurationManager.Configuration.BaseUrl;
+
             // backwards compatibility
             // backwards compatibility
-            // TODO: Work out what this is doing.
-            if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
-                string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase) ||
-                string.Equals(first, "jellyfin", StringComparison.OrdinalIgnoreCase))
+            if (baseUrl.Length == 0)
+            {
+                if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase)
+                    || string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))
+                {
+                    index++;
+                }
+            }
+            else if (string.Equals(first, baseUrl.Remove(0, 1)))
             {
             {
                 index++;
                 index++;
+                var second = pathInfo[1];
+                if (string.Equals(second, "mediabrowser", StringComparison.OrdinalIgnoreCase)
+                    || string.Equals(second, "emby", StringComparison.OrdinalIgnoreCase))
+                {
+                    index++;
+                }
             }
             }
 
 
             return pathInfo[index];
             return pathInfo[index];
         }
         }
 
 
-        private List<string> Parse(string pathUri)
+        private static string[] Parse(string pathUri)
         {
         {
             var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None);
             var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None);
 
 
@@ -231,7 +250,7 @@ namespace Emby.Dlna.Api
 
 
             var args = pathInfo.Split('/');
             var args = pathInfo.Split('/');
 
 
-            return args.Skip(1).ToList();
+            return args.Skip(1).ToArray();
         }
         }
 
 
         public object Get(GetIcon request)
         public object Get(GetIcon request)

+ 1 - 1
Emby.Dlna/Emby.Dlna.csproj

@@ -12,7 +12,7 @@
   </ItemGroup>
   </ItemGroup>
 
 
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>
   </PropertyGroup>

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

@@ -160,7 +160,7 @@ namespace Emby.Dlna.PlayTo
                 uuid = location.GetMD5().ToString("N", CultureInfo.InvariantCulture);
                 uuid = location.GetMD5().ToString("N", CultureInfo.InvariantCulture);
             }
             }
 
 
-            var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null);
+            var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null);
 
 
             var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
             var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
 
 

+ 9 - 14
Emby.Dlna/Ssdp/DeviceDiscovery.cs

@@ -4,8 +4,6 @@ using System.Linq;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Net;
-using Microsoft.Extensions.Logging;
 using Rssdp;
 using Rssdp;
 using Rssdp.Infrastructure;
 using Rssdp.Infrastructure;
 
 
@@ -15,13 +13,14 @@ namespace Emby.Dlna.Ssdp
     {
     {
         private bool _disposed;
         private bool _disposed;
 
 
-        private readonly ILogger _logger;
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
 
 
         private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
         private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
 
 
         private int _listenerCount;
         private int _listenerCount;
         private object _syncLock = new object();
         private object _syncLock = new object();
+
+        /// <inheritdoc />
         public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered
         public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered
         {
         {
             add
             add
@@ -31,6 +30,7 @@ namespace Emby.Dlna.Ssdp
                     _listenerCount++;
                     _listenerCount++;
                     DeviceDiscoveredInternal += value;
                     DeviceDiscoveredInternal += value;
                 }
                 }
+
                 StartInternal();
                 StartInternal();
             }
             }
             remove
             remove
@@ -43,21 +43,16 @@ namespace Emby.Dlna.Ssdp
             }
             }
         }
         }
 
 
+        /// <inheritdoc />
         public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
         public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
 
 
         private SsdpDeviceLocator _deviceLocator;
         private SsdpDeviceLocator _deviceLocator;
 
 
-        private readonly ISocketFactory _socketFactory;
         private ISsdpCommunicationsServer _commsServer;
         private ISsdpCommunicationsServer _commsServer;
 
 
-        public DeviceDiscovery(
-            ILoggerFactory loggerFactory,
-            IServerConfigurationManager config,
-            ISocketFactory socketFactory)
+        public DeviceDiscovery(IServerConfigurationManager config)
         {
         {
-            _logger = loggerFactory.CreateLogger(nameof(DeviceDiscovery));
             _config = config;
             _config = config;
-            _socketFactory = socketFactory;
         }
         }
 
 
         // Call this method from somewhere in your code to start the search.
         // Call this method from somewhere in your code to start the search.
@@ -82,8 +77,8 @@ namespace Emby.Dlna.Ssdp
                     //_DeviceLocator.NotificationFilter = "upnp:rootdevice";
                     //_DeviceLocator.NotificationFilter = "upnp:rootdevice";
 
 
                     // Connect our event handler so we process devices as they are found
                     // Connect our event handler so we process devices as they are found
-                    _deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
-                    _deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
+                    _deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable;
+                    _deviceLocator.DeviceUnavailable += OnDeviceLocatorDeviceUnavailable;
 
 
                     var dueTime = TimeSpan.FromSeconds(5);
                     var dueTime = TimeSpan.FromSeconds(5);
                     var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);
                     var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);
@@ -94,7 +89,7 @@ namespace Emby.Dlna.Ssdp
         }
         }
 
 
         // Process each found device in the event handler
         // Process each found device in the event handler
-        void deviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs e)
+        private void OnDeviceLocatorDeviceAvailable(object sender, DeviceAvailableEventArgs e)
         {
         {
             var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
             var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
 
 
@@ -115,7 +110,7 @@ namespace Emby.Dlna.Ssdp
             DeviceDiscoveredInternal?.Invoke(this, args);
             DeviceDiscoveredInternal?.Invoke(this, args);
         }
         }
 
 
-        private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
+        private void OnDeviceLocatorDeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
         {
         {
             var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
             var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
 
 

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

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 <Project Sdk="Microsoft.NET.Sdk">
 
 
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
@@ -17,9 +17,4 @@
     <Compile Include="..\SharedVersion.cs" />
     <Compile Include="..\SharedVersion.cs" />
   </ItemGroup>
   </ItemGroup>
 
 
-  <PropertyGroup>
-      <!-- We need at least C# 7.1 for the "default literal" feature-->
-    <LangVersion>latest</LangVersion>
-  </PropertyGroup>
-
 </Project>
 </Project>

+ 1 - 1
Emby.Naming/AudioBook/AudioBookFileInfo.cs

@@ -3,7 +3,7 @@ using System;
 namespace Emby.Naming.AudioBook
 namespace Emby.Naming.AudioBook
 {
 {
     /// <summary>
     /// <summary>
-    /// Represents a single video file
+    /// Represents a single video file.
     /// </summary>
     /// </summary>
     public class AudioBookFileInfo : IComparable<AudioBookFileInfo>
     public class AudioBookFileInfo : IComparable<AudioBookFileInfo>
     {
     {

+ 1 - 1
Emby.Naming/AudioBook/AudioBookInfo.cs

@@ -3,7 +3,7 @@ using System.Collections.Generic;
 namespace Emby.Naming.AudioBook
 namespace Emby.Naming.AudioBook
 {
 {
     /// <summary>
     /// <summary>
-    /// Represents a complete video, including all parts and subtitles
+    /// Represents a complete video, including all parts and subtitles.
     /// </summary>
     /// </summary>
     public class AudioBookInfo
     public class AudioBookInfo
     {
     {

+ 21 - 8
Emby.Naming/Common/NamingOptions.cs

@@ -311,6 +311,14 @@ namespace Emby.Naming.Common
                     }
                     }
                 },
                 },
 
 
+                // This isn't a Kodi naming rule, but the expression below causes false positives,
+                // so we make sure this one gets tested first.
+                // "Foo Bar 889"
+                new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>(\w+\s*?)*)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$")
+                {
+                    IsNamed = true
+                },
+
                 new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")
                 new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")
                 {
                 {
                     SupportsAbsoluteEpisodeNumbers = true
                     SupportsAbsoluteEpisodeNumbers = true
@@ -328,28 +336,33 @@ namespace Emby.Naming.Common
 
 
                 // *** End Kodi Standard Naming
                 // *** End Kodi Standard Naming
 
 
-                new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})[^\\\/]*$")
+                // [bar] Foo - 1 [baz]
+                new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>(\w+\s*?)+?)[-\s_]+(?<epnumber>\d+).*$")
+                {
+                    IsNamed = true
+                },
+                new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d+)[xX](?<epnumber>\d+)[^\\\/]*$")
                 {
                 {
                     IsNamed = true
                     IsNamed = true
                 },
                 },
 
 
-                new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d{1,4})[x,X]?[eE](?<epnumber>\d{1,3})[^\\\/]*$")
+                new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d+)[x,X]?[eE](?<epnumber>\d+)[^\\\/]*$")
                 {
                 {
                     IsNamed = true
                     IsNamed = true
                 },
                 },
 
 
-                new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))[^\\\/]*$")
+                new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d+))[^\\\/]*$")
                 {
                 {
                     IsNamed = true
                     IsNamed = true
                 },
                 },
 
 
-                new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})[^\\\/]*$")
+                new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d+)[^\\\/]*$")
                 {
                 {
                     IsNamed = true
                     IsNamed = true
                 },
                 },
 
 
                 // "01.avi"
                 // "01.avi"
-                new EpisodeExpression(@".*[\\\/](?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.\w+$")
+                new EpisodeExpression(@".*[\\\/](?<epnumber>\d+)(-(?<endingepnumber>\d+))*\.\w+$")
                 {
                 {
                     IsOptimistic = true,
                     IsOptimistic = true,
                     IsNamed = true
                     IsNamed = true
@@ -654,9 +667,9 @@ namespace Emby.Naming.Common
                 @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
                 @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
                 @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$"
                 @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$"
             }.Select(i => new EpisodeExpression(i)
             }.Select(i => new EpisodeExpression(i)
-                {
-                    IsNamed = true
-                }).ToArray();
+            {
+                IsNamed = true
+            }).ToArray();
 
 
             VideoFileExtensions = extensions
             VideoFileExtensions = extensions
                 .Distinct(StringComparer.OrdinalIgnoreCase)
                 .Distinct(StringComparer.OrdinalIgnoreCase)

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

@@ -3,6 +3,7 @@
   <PropertyGroup>
   <PropertyGroup>
     <TargetFramework>netstandard2.1</TargetFramework>
     <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
@@ -18,14 +19,14 @@
     <PackageId>Jellyfin.Naming</PackageId>
     <PackageId>Jellyfin.Naming</PackageId>
     <PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
     <PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
     <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
     <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
-    <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <!-- Code analysers-->
   <!-- Code analysers-->
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
-    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" />
-    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
-    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" />
+    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
   </ItemGroup>
   </ItemGroup>
 
 
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

+ 2 - 2
Emby.Naming/TV/SeasonPathParser.cs

@@ -25,7 +25,7 @@ namespace Emby.Naming.TV
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// A season folder must contain one of these somewhere in the name
+        /// A season folder must contain one of these somewhere in the name.
         /// </summary>
         /// </summary>
         private static readonly string[] _seasonFolderNames =
         private static readonly string[] _seasonFolderNames =
         {
         {
@@ -124,7 +124,7 @@ namespace Emby.Naming.TV
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel")
+        /// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel").
         /// </summary>
         /// </summary>
         /// <param name="path">The path.</param>
         /// <param name="path">The path.</param>
         /// <returns>System.Nullable{System.Int32}.</returns>
         /// <returns>System.Nullable{System.Int32}.</returns>

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

@@ -8,7 +8,7 @@ using Emby.Naming.Common;
 namespace Emby.Naming.Video
 namespace Emby.Naming.Video
 {
 {
     /// <summary>
     /// <summary>
-    /// http://kodi.wiki/view/Advancedsettings.xml#video
+    /// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
     /// </summary>
     /// </summary>
     public class CleanDateTimeParser
     public class CleanDateTimeParser
     {
     {

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

@@ -1,7 +1,7 @@
 namespace Emby.Naming.Video
 namespace Emby.Naming.Video
 {
 {
     /// <summary>
     /// <summary>
-    /// Represents a single video file
+    /// Represents a single video file.
     /// </summary>
     /// </summary>
     public class VideoFileInfo
     public class VideoFileInfo
     {
     {

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

@@ -3,7 +3,7 @@ using System.Collections.Generic;
 namespace Emby.Naming.Video
 namespace Emby.Naming.Video
 {
 {
     /// <summary>
     /// <summary>
-    /// Represents a complete video, including all parts and subtitles
+    /// Represents a complete video, including all parts and subtitles.
     /// </summary>
     /// </summary>
     public class VideoInfo
     public class VideoInfo
     {
     {

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

@@ -41,7 +41,7 @@ namespace Emby.Naming.Video
         /// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
         /// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
         /// <param name="parseName">Whether or not the name should be parsed for info</param>
         /// <param name="parseName">Whether or not the name should be parsed for info</param>
         /// <returns>VideoFileInfo.</returns>
         /// <returns>VideoFileInfo.</returns>
-        /// <exception cref="ArgumentNullException">path</exception>
+        /// <exception cref="ArgumentNullException"><c>path</c> is <c>null</c>.</exception>
         public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true)
         public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true)
         {
         {
             if (string.IsNullOrEmpty(path))
             if (string.IsNullOrEmpty(path))

+ 1 - 1
Emby.Notifications/Emby.Notifications.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 <Project Sdk="Microsoft.NET.Sdk">
 
 
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>
   </PropertyGroup>

+ 2 - 2
Emby.Photos/Emby.Photos.csproj

@@ -14,7 +14,7 @@
   </ItemGroup>
   </ItemGroup>
 
 
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
@@ -22,7 +22,7 @@
 
 
   <!-- Code analysers-->
   <!-- Code analysers-->
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
-    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" />
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
     <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
     <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
     <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
     <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
   </ItemGroup>
   </ItemGroup>

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

@@ -616,8 +616,8 @@ namespace Emby.Server.Implementations.Activity
         /// <summary>
         /// <summary>
         /// Constructs a string description of a time-span value.
         /// Constructs a string description of a time-span value.
         /// </summary>
         /// </summary>
-        /// <param name="value">The value of this item</param>
-        /// <param name="description">The name of this item (singular form)</param>
+        /// <param name="value">The value of this item.</param>
+        /// <param name="description">The name of this item (singular form).</param>
         private static string CreateValueString(int value, string description)
         private static string CreateValueString(int value, string description)
         {
         {
             return string.Format(
             return string.Format(

+ 30 - 15
Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs

@@ -4,7 +4,6 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
-using System.Threading;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
@@ -16,7 +15,7 @@ using Microsoft.Extensions.Logging;
 namespace Emby.Server.Implementations.AppBase
 namespace Emby.Server.Implementations.AppBase
 {
 {
     /// <summary>
     /// <summary>
-    /// Class BaseConfigurationManager
+    /// Class BaseConfigurationManager.
     /// </summary>
     /// </summary>
     public abstract class BaseConfigurationManager : IConfigurationManager
     public abstract class BaseConfigurationManager : IConfigurationManager
     {
     {
@@ -35,7 +34,7 @@ namespace Emby.Server.Implementations.AppBase
         /// <summary>
         /// <summary>
         /// The _configuration sync lock.
         /// The _configuration sync lock.
         /// </summary>
         /// </summary>
-        private object _configurationSyncLock = new object();
+        private readonly object _configurationSyncLock = new object();
 
 
         /// <summary>
         /// <summary>
         /// The _configuration.
         /// The _configuration.
@@ -48,7 +47,7 @@ namespace Emby.Server.Implementations.AppBase
         /// <param name="applicationPaths">The application paths.</param>
         /// <param name="applicationPaths">The application paths.</param>
         /// <param name="loggerFactory">The logger factory.</param>
         /// <param name="loggerFactory">The logger factory.</param>
         /// <param name="xmlSerializer">The XML serializer.</param>
         /// <param name="xmlSerializer">The XML serializer.</param>
-        /// <param name="fileSystem">The file system</param>
+        /// <param name="fileSystem">The file system.</param>
         protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
         protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
         {
         {
             CommonApplicationPaths = applicationPaths;
             CommonApplicationPaths = applicationPaths;
@@ -85,6 +84,7 @@ namespace Emby.Server.Implementations.AppBase
         /// </summary>
         /// </summary>
         /// <value>The logger.</value>
         /// <value>The logger.</value>
         protected ILogger Logger { get; private set; }
         protected ILogger Logger { get; private set; }
+
         /// <summary>
         /// <summary>
         /// Gets the XML serializer.
         /// Gets the XML serializer.
         /// </summary>
         /// </summary>
@@ -92,23 +92,39 @@ namespace Emby.Server.Implementations.AppBase
         protected IXmlSerializer XmlSerializer { get; private set; }
         protected IXmlSerializer XmlSerializer { get; private set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the application paths.
+        /// Gets the application paths.
         /// </summary>
         /// </summary>
         /// <value>The application paths.</value>
         /// <value>The application paths.</value>
         public IApplicationPaths CommonApplicationPaths { get; private set; }
         public IApplicationPaths CommonApplicationPaths { get; private set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the system configuration
+        /// Gets or sets the system configuration.
         /// </summary>
         /// </summary>
         /// <value>The configuration.</value>
         /// <value>The configuration.</value>
         public BaseApplicationConfiguration CommonConfiguration
         public BaseApplicationConfiguration CommonConfiguration
         {
         {
             get
             get
             {
             {
-                // Lazy load
-                LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
-                return _configuration;
+                if (_configurationLoaded)
+                {
+                    return _configuration;
+                }
+
+                lock (_configurationSyncLock)
+                {
+                    if (_configurationLoaded)
+                    {
+                        return _configuration;
+                    }
+
+                    _configuration = (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer);
+
+                    _configurationLoaded = true;
+
+                    return _configuration;
+                }
             }
             }
+
             protected set
             protected set
             {
             {
                 _configuration = value;
                 _configuration = value;
@@ -158,7 +174,7 @@ namespace Emby.Server.Implementations.AppBase
         /// Replaces the configuration.
         /// Replaces the configuration.
         /// </summary>
         /// </summary>
         /// <param name="newConfiguration">The new configuration.</param>
         /// <param name="newConfiguration">The new configuration.</param>
-        /// <exception cref="ArgumentNullException">newConfiguration</exception>
+        /// <exception cref="ArgumentNullException"><c>newConfiguration</c> is <c>null</c>.</exception>
         public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
         public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
         {
         {
             if (newConfiguration == null)
             if (newConfiguration == null)
@@ -201,7 +217,7 @@ namespace Emby.Server.Implementations.AppBase
                 cachePath = CommonConfiguration.CachePath;
                 cachePath = CommonConfiguration.CachePath;
             }
             }
 
 
-            Logger.LogInformation("Setting cache path to " + cachePath);
+            Logger.LogInformation("Setting cache path: {Path}", cachePath);
             ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
             ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
         }
         }
 
 
@@ -209,7 +225,7 @@ namespace Emby.Server.Implementations.AppBase
         /// Replaces the cache path.
         /// Replaces the cache path.
         /// </summary>
         /// </summary>
         /// <param name="newConfig">The new configuration.</param>
         /// <param name="newConfig">The new configuration.</param>
-        /// <exception cref="DirectoryNotFoundException"></exception>
+        /// <exception cref="DirectoryNotFoundException">The new cache path doesn't exist.</exception>
         private void ValidateCachePath(BaseApplicationConfiguration newConfig)
         private void ValidateCachePath(BaseApplicationConfiguration newConfig)
         {
         {
             var newPath = newConfig.CachePath;
             var newPath = newConfig.CachePath;
@@ -220,7 +236,7 @@ namespace Emby.Server.Implementations.AppBase
                 // Validate
                 // Validate
                 if (!Directory.Exists(newPath))
                 if (!Directory.Exists(newPath))
                 {
                 {
-                    throw new FileNotFoundException(
+                    throw new DirectoryNotFoundException(
                         string.Format(
                         string.Format(
                             CultureInfo.InvariantCulture,
                             CultureInfo.InvariantCulture,
                             "{0} does not exist.",
                             "{0} does not exist.",
@@ -299,8 +315,7 @@ namespace Emby.Server.Implementations.AppBase
                 throw new ArgumentException("Expected configuration type is " + configurationType.Name);
                 throw new ArgumentException("Expected configuration type is " + configurationType.Name);
             }
             }
 
 
-            var validatingStore = configurationStore as IValidatingConfiguration;
-            if (validatingStore != null)
+            if (configurationStore is IValidatingConfiguration validatingStore)
             {
             {
                 var currentConfiguration = GetConfiguration(key);
                 var currentConfiguration = GetConfiguration(key);
 
 

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

@@ -6,13 +6,13 @@ using MediaBrowser.Model.Serialization;
 namespace Emby.Server.Implementations.AppBase
 namespace Emby.Server.Implementations.AppBase
 {
 {
     /// <summary>
     /// <summary>
-    /// Class ConfigurationHelper
+    /// Class ConfigurationHelper.
     /// </summary>
     /// </summary>
     public static class ConfigurationHelper
     public static class ConfigurationHelper
     {
     {
         /// <summary>
         /// <summary>
         /// Reads an xml configuration file from the file system
         /// Reads an xml configuration file from the file system
-        /// It will immediately re-serialize and save if new serialization data is available due to property changes
+        /// It will immediately re-serialize and save if new serialization data is available due to property changes.
         /// </summary>
         /// </summary>
         /// <param name="type">The type.</param>
         /// <param name="type">The type.</param>
         /// <param name="path">The path.</param>
         /// <param name="path">The path.</param>

+ 70 - 106
Emby.Server.Implementations/ApplicationHost.cs

@@ -88,7 +88,6 @@ using MediaBrowser.Model.Cryptography;
 using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
@@ -110,9 +109,8 @@ using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Extensions;
 using Microsoft.AspNetCore.Http.Extensions;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
-using ServiceStack;
+using Microsoft.OpenApi.Models;
 using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
 using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
 
 
 namespace Emby.Server.Implementations
 namespace Emby.Server.Implementations
@@ -232,7 +230,25 @@ namespace Emby.Server.Implementations
             }
             }
         }
         }
 
 
-        protected IServiceProvider _serviceProvider;
+        /// <summary>
+        /// Gets or sets the service provider.
+        /// </summary>
+        public IServiceProvider ServiceProvider { get; set; }
+
+        /// <summary>
+        /// Gets the http port for the webhost.
+        /// </summary>
+        public int HttpPort { get; private set; }
+
+        /// <summary>
+        /// Gets the https port for the webhost.
+        /// </summary>
+        public int HttpsPort { get; private set; }
+
+        /// <summary>
+        /// Gets the content root for the webhost.
+        /// </summary>
+        public string ContentRoot { get; private set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets the server configuration manager.
         /// Gets the server configuration manager.
@@ -321,7 +337,7 @@ namespace Emby.Server.Implementations
         private readonly IConfiguration _configuration;
         private readonly IConfiguration _configuration;
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the installation manager.
+        /// Gets the installation manager.
         /// </summary>
         /// </summary>
         /// <value>The installation manager.</value>
         /// <value>The installation manager.</value>
         protected IInstallationManager InstallationManager { get; private set; }
         protected IInstallationManager InstallationManager { get; private set; }
@@ -362,7 +378,7 @@ namespace Emby.Server.Implementations
         {
         {
             _configuration = configuration;
             _configuration = configuration;
 
 
-            XmlSerializer = new MyXmlSerializer(fileSystem, loggerFactory);
+            XmlSerializer = new MyXmlSerializer();
 
 
             NetworkManager = networkManager;
             NetworkManager = networkManager;
             networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
             networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
@@ -410,13 +426,17 @@ namespace Emby.Server.Implementations
             _validAddressResults.Clear();
             _validAddressResults.Clear();
         }
         }
 
 
-        public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
+        /// <inheritdoc />
+        public Version ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version;
+
+        /// <inheritdoc />
+        public string ApplicationVersionString { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
 
 
         /// <summary>
         /// <summary>
         /// Gets the current application user agent.
         /// Gets the current application user agent.
         /// </summary>
         /// </summary>
         /// <value>The application user agent.</value>
         /// <value>The application user agent.</value>
-        public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion;
+        public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersionString;
 
 
         /// <summary>
         /// <summary>
         /// Gets the email address for use within a comment section of a user agent field.
         /// Gets the email address for use within a comment section of a user agent field.
@@ -452,20 +472,20 @@ namespace Emby.Server.Implementations
         public string Name => ApplicationProductName;
         public string Name => ApplicationProductName;
 
 
         /// <summary>
         /// <summary>
-        /// Creates an instance of type and resolves all constructor dependencies
+        /// Creates an instance of type and resolves all constructor dependencies.
         /// </summary>
         /// </summary>
         /// <param name="type">The type.</param>
         /// <param name="type">The type.</param>
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object CreateInstance(Type type)
         public object CreateInstance(Type type)
-            => ActivatorUtilities.CreateInstance(_serviceProvider, type);
+            => ActivatorUtilities.CreateInstance(ServiceProvider, type);
 
 
         /// <summary>
         /// <summary>
-        /// Creates an instance of type and resolves all constructor dependencies
+        /// Creates an instance of type and resolves all constructor dependencies.
         /// </summary>
         /// </summary>
         /// /// <typeparam name="T">The type.</typeparam>
         /// /// <typeparam name="T">The type.</typeparam>
         /// <returns>T.</returns>
         /// <returns>T.</returns>
         public T CreateInstance<T>()
         public T CreateInstance<T>()
-            => ActivatorUtilities.CreateInstance<T>(_serviceProvider);
+            => ActivatorUtilities.CreateInstance<T>(ServiceProvider);
 
 
         /// <summary>
         /// <summary>
         /// Creates the instance safe.
         /// Creates the instance safe.
@@ -477,7 +497,7 @@ namespace Emby.Server.Implementations
             try
             try
             {
             {
                 Logger.LogDebug("Creating instance of {Type}", type);
                 Logger.LogDebug("Creating instance of {Type}", type);
-                return ActivatorUtilities.CreateInstance(_serviceProvider, type);
+                return ActivatorUtilities.CreateInstance(ServiceProvider, type);
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
@@ -491,12 +511,12 @@ namespace Emby.Server.Implementations
         /// </summary>
         /// </summary>
         /// <typeparam name="T">The type</typeparam>
         /// <typeparam name="T">The type</typeparam>
         /// <returns>``0.</returns>
         /// <returns>``0.</returns>
-        public T Resolve<T>() => _serviceProvider.GetService<T>();
+        public T Resolve<T>() => ServiceProvider.GetService<T>();
 
 
         /// <summary>
         /// <summary>
         /// Gets the export types.
         /// Gets the export types.
         /// </summary>
         /// </summary>
-        /// <typeparam name="T">The type</typeparam>
+        /// <typeparam name="T">The type.</typeparam>
         /// <returns>IEnumerable{Type}.</returns>
         /// <returns>IEnumerable{Type}.</returns>
         public IEnumerable<Type> GetExportTypes<T>()
         public IEnumerable<Type> GetExportTypes<T>()
         {
         {
@@ -508,11 +528,12 @@ namespace Emby.Server.Implementations
         /// <inheritdoc />
         /// <inheritdoc />
         public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true)
         public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true)
         {
         {
+            // Convert to list so this isn't executed for each iteration
             var parts = GetExportTypes<T>()
             var parts = GetExportTypes<T>()
                 .Select(CreateInstanceSafe)
                 .Select(CreateInstanceSafe)
                 .Where(i => i != null)
                 .Where(i => i != null)
                 .Cast<T>()
                 .Cast<T>()
-                .ToList(); // Convert to list so this isn't executed for each iteration
+                .ToList();
 
 
             if (manageLifetime)
             if (manageLifetime)
             {
             {
@@ -607,77 +628,14 @@ namespace Emby.Server.Implementations
 
 
             await RegisterResources(serviceCollection).ConfigureAwait(false);
             await RegisterResources(serviceCollection).ConfigureAwait(false);
 
 
-            FindParts();
-
-            string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
-            if (string.IsNullOrEmpty(contentRoot))
+            ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
+            if (string.IsNullOrEmpty(ContentRoot))
             {
             {
-                contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath;
-            }
-
-            var host = new WebHostBuilder()
-                .UseKestrel(options =>
-                {
-                    var addresses = ServerConfigurationManager
-                        .Configuration
-                        .LocalNetworkAddresses
-                        .Select(NormalizeConfiguredLocalAddress)
-                        .Where(i => i != null)
-                        .ToList();
-                    if (addresses.Any())
-                    {
-                        foreach (var address in addresses)
-                        {
-                            Logger.LogInformation("Kestrel listening on {ipaddr}", address);
-                            options.Listen(address, HttpPort);
-
-                            if (EnableHttps && Certificate != null)
-                            {
-                                options.Listen(address, HttpsPort, listenOptions => listenOptions.UseHttps(Certificate));
-                            }
-                        }
-                    }
-                    else
-                    {
-                        Logger.LogInformation("Kestrel listening on all interfaces");
-                        options.ListenAnyIP(HttpPort);
-
-                        if (EnableHttps && Certificate != null)
-                        {
-                            options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate));
-                        }
-                    }
-                })
-                .UseContentRoot(contentRoot)
-                .ConfigureServices(services =>
-                {
-                    services.AddResponseCompression();
-                    services.AddHttpContextAccessor();
-                })
-                .Configure(app =>
-                {
-                    app.UseWebSockets();
-
-                    app.UseResponseCompression();
-
-                    // TODO app.UseMiddleware<WebSocketMiddleware>();
-                    app.Use(ExecuteWebsocketHandlerAsync);
-                    app.Use(ExecuteHttpHandlerAsync);
-                })
-                .Build();
-
-            try
-            {
-                await host.StartAsync().ConfigureAwait(false);
-            }
-            catch
-            {
-                Logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again.");
-                throw;
+                ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath;
             }
             }
         }
         }
 
 
-        private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
+        public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
         {
         {
             if (!context.WebSockets.IsWebSocketRequest)
             if (!context.WebSockets.IsWebSocketRequest)
             {
             {
@@ -688,7 +646,7 @@ namespace Emby.Server.Implementations
             await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
             await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
         }
         }
 
 
-        private async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
+        public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
         {
         {
             if (context.WebSockets.IsWebSocketRequest)
             if (context.WebSockets.IsWebSocketRequest)
             {
             {
@@ -749,7 +707,8 @@ namespace Emby.Server.Implementations
 
 
             serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
             serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
 
 
-            serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
+            var cryptoProvider = new CryptographyProvider();
+            serviceCollection.AddSingleton<ICryptoProvider>(cryptoProvider);
 
 
             SocketFactory = new SocketFactory();
             SocketFactory = new SocketFactory();
             serviceCollection.AddSingleton(SocketFactory);
             serviceCollection.AddSingleton(SocketFactory);
@@ -788,7 +747,17 @@ namespace Emby.Server.Implementations
 
 
             _userRepository = GetUserRepository();
             _userRepository = GetUserRepository();
 
 
-            UserManager = new UserManager(LoggerFactory.CreateLogger<UserManager>(), _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
+            UserManager = new UserManager(
+                LoggerFactory.CreateLogger<UserManager>(),
+                _userRepository,
+                XmlSerializer,
+                NetworkManager,
+                () => ImageProcessor,
+                () => DtoService,
+                this,
+                JsonSerializer,
+                FileSystemManager,
+                cryptoProvider);
 
 
             serviceCollection.AddSingleton(UserManager);
             serviceCollection.AddSingleton(UserManager);
 
 
@@ -866,8 +835,7 @@ namespace Emby.Server.Implementations
             NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
             NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
             serviceCollection.AddSingleton(NotificationManager);
             serviceCollection.AddSingleton(NotificationManager);
 
 
-            serviceCollection.AddSingleton<IDeviceDiscovery>(
-                new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
+            serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
 
 
             ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
             ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
             serviceCollection.AddSingleton(ChapterManager);
             serviceCollection.AddSingleton(ChapterManager);
@@ -896,7 +864,7 @@ namespace Emby.Server.Implementations
             serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
             serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
             serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
             serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
 
 
-            AuthService = new AuthService(authContext, ServerConfigurationManager, SessionManager, NetworkManager);
+            AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager);
             serviceCollection.AddSingleton(AuthService);
             serviceCollection.AddSingleton(AuthService);
 
 
             SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
             SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
@@ -906,7 +874,7 @@ namespace Emby.Server.Implementations
 
 
             _displayPreferencesRepository.Initialize();
             _displayPreferencesRepository.Initialize();
 
 
-            var userDataRepo = new SqliteUserDataRepository(LoggerFactory, ApplicationPaths);
+            var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger<SqliteUserDataRepository>(), ApplicationPaths);
 
 
             SetStaticProperties();
             SetStaticProperties();
 
 
@@ -915,8 +883,6 @@ namespace Emby.Server.Implementations
             ((UserDataManager)UserDataManager).Repository = userDataRepo;
             ((UserDataManager)UserDataManager).Repository = userDataRepo;
             ItemRepository.Initialize(userDataRepo, UserManager);
             ItemRepository.Initialize(userDataRepo, UserManager);
             ((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
             ((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
-
-            _serviceProvider = serviceCollection.BuildServiceProvider();
         }
         }
 
 
         public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
         public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
@@ -1007,7 +973,7 @@ namespace Emby.Server.Implementations
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Dirty hacks
+        /// Dirty hacks.
         /// </summary>
         /// </summary>
         private void SetStaticProperties()
         private void SetStaticProperties()
         {
         {
@@ -1073,9 +1039,9 @@ namespace Emby.Server.Implementations
         /// <summary>
         /// <summary>
         /// Finds the parts.
         /// Finds the parts.
         /// </summary>
         /// </summary>
-        protected void FindParts()
+        public void FindParts()
         {
         {
-            InstallationManager = _serviceProvider.GetService<IInstallationManager>();
+            InstallationManager = ServiceProvider.GetService<IInstallationManager>();
             InstallationManager.PluginInstalled += PluginInstalled;
             InstallationManager.PluginInstalled += PluginInstalled;
 
 
             if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
             if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
@@ -1204,7 +1170,7 @@ namespace Emby.Server.Implementations
 
 
         private CertificateInfo CertificateInfo { get; set; }
         private CertificateInfo CertificateInfo { get; set; }
 
 
-        protected X509Certificate2 Certificate { get; private set; }
+        public X509Certificate2 Certificate { get; private set; }
 
 
         private IEnumerable<string> GetUrlPrefixes()
         private IEnumerable<string> GetUrlPrefixes()
         {
         {
@@ -1415,17 +1381,18 @@ namespace Emby.Server.Implementations
         /// <summary>
         /// <summary>
         /// Gets the system status.
         /// Gets the system status.
         /// </summary>
         /// </summary>
-        /// <param name="cancellationToken">The cancellation token</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>SystemInfo.</returns>
         /// <returns>SystemInfo.</returns>
         public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken)
         public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken)
         {
         {
             var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
             var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
+            var transcodingTempPath = ConfigurationManager.GetTranscodePath();
 
 
             return new SystemInfo
             return new SystemInfo
             {
             {
                 HasPendingRestart = HasPendingRestart,
                 HasPendingRestart = HasPendingRestart,
                 IsShuttingDown = IsShuttingDown,
                 IsShuttingDown = IsShuttingDown,
-                Version = ApplicationVersion,
+                Version = ApplicationVersionString,
                 WebSocketPortNumber = HttpPort,
                 WebSocketPortNumber = HttpPort,
                 CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
                 CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
                 Id = SystemId,
                 Id = SystemId,
@@ -1443,7 +1410,7 @@ namespace Emby.Server.Implementations
                 CanSelfRestart = CanSelfRestart,
                 CanSelfRestart = CanSelfRestart,
                 CanLaunchWebBrowser = CanLaunchWebBrowser,
                 CanLaunchWebBrowser = CanLaunchWebBrowser,
                 HasUpdateAvailable = HasUpdateAvailable,
                 HasUpdateAvailable = HasUpdateAvailable,
-                TranscodingTempPath = ApplicationPaths.TranscodingTempPath,
+                TranscodingTempPath = transcodingTempPath,
                 ServerName = FriendlyName,
                 ServerName = FriendlyName,
                 LocalAddress = localAddress,
                 LocalAddress = localAddress,
                 SupportsLibraryMonitor = true,
                 SupportsLibraryMonitor = true,
@@ -1465,7 +1432,7 @@ namespace Emby.Server.Implementations
 
 
             return new PublicSystemInfo
             return new PublicSystemInfo
             {
             {
-                Version = ApplicationVersion,
+                Version = ApplicationVersionString,
                 ProductName = ApplicationProductName,
                 ProductName = ApplicationProductName,
                 Id = SystemId,
                 Id = SystemId,
                 OperatingSystem = OperatingSystem.Id.ToString(),
                 OperatingSystem = OperatingSystem.Id.ToString(),
@@ -1588,7 +1555,7 @@ namespace Emby.Server.Implementations
             return resultList;
             return resultList;
         }
         }
 
 
-        private IPAddress NormalizeConfiguredLocalAddress(string address)
+        public IPAddress NormalizeConfiguredLocalAddress(string address)
         {
         {
             var index = address.Trim('/').IndexOf('/');
             var index = address.Trim('/').IndexOf('/');
 
 
@@ -1664,10 +1631,6 @@ namespace Emby.Server.Implementations
                 ? Environment.MachineName
                 ? Environment.MachineName
                 : ServerConfigurationManager.Configuration.ServerName;
                 : ServerConfigurationManager.Configuration.ServerName;
 
 
-        public int HttpPort { get; private set; }
-
-        public int HttpsPort { get; private set; }
-
         /// <summary>
         /// <summary>
         /// Shuts down.
         /// Shuts down.
         /// </summary>
         /// </summary>
@@ -1730,7 +1693,7 @@ namespace Emby.Server.Implementations
         /// dns is prefixed with a valid Uri prefix.
         /// dns is prefixed with a valid Uri prefix.
         /// </summary>
         /// </summary>
         /// <param name="externalDns">The external dns prefix to get the hostname of.</param>
         /// <param name="externalDns">The external dns prefix to get the hostname of.</param>
-        /// <returns>The hostname in <paramref name="externalDns"/></returns>
+        /// <returns>The hostname in <paramref name="externalDns"/>.</returns>
         private static string GetHostnameFromExternalDns(string externalDns)
         private static string GetHostnameFromExternalDns(string externalDns)
         {
         {
             if (string.IsNullOrEmpty(externalDns))
             if (string.IsNullOrEmpty(externalDns))
@@ -1844,6 +1807,7 @@ namespace Emby.Server.Implementations
     internal class CertificateInfo
     internal class CertificateInfo
     {
     {
         public string Path { get; set; }
         public string Path { get; set; }
+
         public string Password { get; set; }
         public string Password { get; set; }
     }
     }
 }
 }

+ 4 - 4
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -470,10 +470,10 @@ namespace Emby.Server.Implementations.Channels
                 _libraryManager.CreateItem(item, null);
                 _libraryManager.CreateItem(item, null);
             }
             }
 
 
-            await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
+            await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
             {
             {
                 ForceSave = !isNew && forceUpdate
                 ForceSave = !isNew && forceUpdate
-            }, cancellationToken);
+            }, cancellationToken).ConfigureAwait(false);
 
 
             return item;
             return item;
         }
         }
@@ -636,7 +636,7 @@ namespace Emby.Server.Implementations.Channels
 
 
         private async Task RefreshLatestChannelItems(IChannel channel, CancellationToken cancellationToken)
         private async Task RefreshLatestChannelItems(IChannel channel, CancellationToken cancellationToken)
         {
         {
-            var internalChannel = await GetChannel(channel, cancellationToken);
+            var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
 
 
             var query = new InternalItemsQuery();
             var query = new InternalItemsQuery();
             query.Parent = internalChannel;
             query.Parent = internalChannel;
@@ -1156,7 +1156,7 @@ namespace Emby.Server.Implementations.Channels
 
 
             if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
             if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
             {
             {
-                _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.Normal);
+                _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
             }
             }
 
 
             return item;
             return item;

+ 6 - 6
Emby.Server.Implementations/Collections/CollectionManager.cs

@@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Collections
             // This could cause it to get re-resolved as a plain folder
             // This could cause it to get re-resolved as a plain folder
             var folderName = _fileSystem.GetValidFilename(name) + " [boxset]";
             var folderName = _fileSystem.GetValidFilename(name) + " [boxset]";
 
 
-            var parentFolder = GetCollectionsFolder(true).Result;
+            var parentFolder = GetCollectionsFolder(true).GetAwaiter().GetResult();
 
 
             if (parentFolder == null)
             if (parentFolder == null)
             {
             {
@@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Collections
 
 
                 if (options.ItemIdList.Length > 0)
                 if (options.ItemIdList.Length > 0)
                 {
                 {
-                    AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
+                    AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
                     {
                     {
                         // The initial adding of items is going to create a local metadata file
                         // The initial adding of items is going to create a local metadata file
                         // This will cause internet metadata to be skipped as a result
                         // This will cause internet metadata to be skipped as a result
@@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Collections
                 }
                 }
                 else
                 else
                 {
                 {
-                    _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.High);
+                    _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
                 }
                 }
 
 
                 CollectionCreated?.Invoke(this, new CollectionCreatedEventArgs
                 CollectionCreated?.Invoke(this, new CollectionCreatedEventArgs
@@ -178,12 +178,12 @@ namespace Emby.Server.Implementations.Collections
 
 
         public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
         public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
         {
         {
-            AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)));
+            AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
         }
         }
 
 
         public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
         public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
         {
         {
-            AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)));
+            AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
         }
         }
 
 
         private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
         private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
@@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.Collections
             }
             }
 
 
             collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
             collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
-            _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
+            _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
             {
             {
                 ForceSave = true
                 ForceSave = true
             }, RefreshPriority.High);
             }, RefreshPriority.High);

+ 1 - 32
Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs

@@ -1,5 +1,4 @@
 using System;
 using System;
-using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using Emby.Server.Implementations.AppBase;
 using Emby.Server.Implementations.AppBase;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
@@ -14,7 +13,7 @@ using Microsoft.Extensions.Logging;
 namespace Emby.Server.Implementations.Configuration
 namespace Emby.Server.Implementations.Configuration
 {
 {
     /// <summary>
     /// <summary>
-    /// Class ServerConfigurationManager
+    /// Class ServerConfigurationManager.
     /// </summary>
     /// </summary>
     public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager
     public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager
     {
     {
@@ -62,13 +61,6 @@ namespace Emby.Server.Implementations.Configuration
             base.OnConfigurationUpdated();
             base.OnConfigurationUpdated();
         }
         }
 
 
-        public override void AddParts(IEnumerable<IConfigurationFactory> factories)
-        {
-            base.AddParts(factories);
-
-            UpdateTranscodePath();
-        }
-
         /// <summary>
         /// <summary>
         /// Updates the metadata path.
         /// Updates the metadata path.
         /// </summary>
         /// </summary>
@@ -84,28 +76,6 @@ namespace Emby.Server.Implementations.Configuration
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Updates the transcoding temporary path.
-        /// </summary>
-        private void UpdateTranscodePath()
-        {
-            var encodingConfig = this.GetConfiguration<EncodingOptions>("encoding");
-
-            ((ServerApplicationPaths)ApplicationPaths).TranscodingTempPath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ?
-                null :
-                Path.Combine(encodingConfig.TranscodingTempPath, "transcodes");
-        }
-
-        protected override void OnNamedConfigurationUpdated(string key, object configuration)
-        {
-            base.OnNamedConfigurationUpdated(key, configuration);
-
-            if (string.Equals(key, "encoding", StringComparison.OrdinalIgnoreCase))
-            {
-                UpdateTranscodePath();
-            }
-        }
-
         /// <summary>
         /// <summary>
         /// Replaces the configuration.
         /// Replaces the configuration.
         /// </summary>
         /// </summary>
@@ -123,7 +93,6 @@ namespace Emby.Server.Implementations.Configuration
             base.ReplaceConfiguration(newConfiguration);
             base.ReplaceConfiguration(newConfiguration);
         }
         }
 
 
-
         /// <summary>
         /// <summary>
         /// Validates the SSL certificate.
         /// Validates the SSL certificate.
         /// </summary>
         /// </summary>

+ 3 - 7
Emby.Server.Implementations/Cryptography/CryptographyProvider.cs

@@ -30,6 +30,9 @@ namespace Emby.Server.Implementations.Cryptography
 
 
         private bool _disposed = false;
         private bool _disposed = false;
 
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CryptographyProvider"/> class.
+        /// </summary>
         public CryptographyProvider()
         public CryptographyProvider()
         {
         {
             // FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
             // FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
@@ -59,12 +62,6 @@ namespace Emby.Server.Implementations.Cryptography
             throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
             throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
         }
         }
 
 
-        public byte[] ComputeHash(string hashMethod, byte[] bytes)
-            => ComputeHash(hashMethod, bytes, Array.Empty<byte>());
-
-        public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
-            => ComputeHash(DefaultHashMethod, bytes);
-
         public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
         public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
         {
         {
             if (hashMethod == DefaultHashMethod)
             if (hashMethod == DefaultHashMethod)
@@ -90,7 +87,6 @@ namespace Emby.Server.Implementations.Cryptography
             }
             }
 
 
             throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
             throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
-
         }
         }
 
 
         public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
         public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)

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

@@ -110,8 +110,8 @@ namespace Emby.Server.Implementations.Data
 
 
             using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)"))
             using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)"))
             {
             {
-                statement.TryBind("@id", displayPreferences.Id.ToGuidBlob());
-                statement.TryBind("@userId", userId.ToGuidBlob());
+                statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray());
+                statement.TryBind("@userId", userId.ToByteArray());
                 statement.TryBind("@client", client);
                 statement.TryBind("@client", client);
                 statement.TryBind("@data", serialized);
                 statement.TryBind("@data", serialized);
 
 
@@ -170,8 +170,8 @@ namespace Emby.Server.Implementations.Data
             {
             {
                 using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client"))
                 using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client"))
                 {
                 {
-                    statement.TryBind("@id", guidId.ToGuidBlob());
-                    statement.TryBind("@userId", userId.ToGuidBlob());
+                    statement.TryBind("@id", guidId.ToByteArray());
+                    statement.TryBind("@userId", userId.ToByteArray());
                     statement.TryBind("@client", client);
                     statement.TryBind("@client", client);
 
 
                     foreach (var row in statement.ExecuteQuery())
                     foreach (var row in statement.ExecuteQuery())
@@ -200,7 +200,7 @@ namespace Emby.Server.Implementations.Data
             using (var connection = GetConnection(true))
             using (var connection = GetConnection(true))
             using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
             using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
             {
             {
-                statement.TryBind("@userId", userId.ToGuidBlob());
+                statement.TryBind("@userId", userId.ToByteArray());
 
 
                 foreach (var row in statement.ExecuteQuery())
                 foreach (var row in statement.ExecuteQuery())
                 {
                 {

+ 52 - 64
Emby.Server.Implementations/Data/SqliteExtensions.cs

@@ -9,6 +9,47 @@ namespace Emby.Server.Implementations.Data
 {
 {
     public static class SqliteExtensions
     public static class SqliteExtensions
     {
     {
+        private const string DatetimeFormatUtc = "yyyy-MM-dd HH:mm:ss.FFFFFFFK";
+        private const string DatetimeFormatLocal = "yyyy-MM-dd HH:mm:ss.FFFFFFF";
+
+        /// <summary>
+        /// An array of ISO-8601 DateTime formats that we support parsing.
+        /// </summary>
+        private static readonly string[] _datetimeFormats = new string[]
+        {
+            "THHmmssK",
+            "THHmmK",
+            "HH:mm:ss.FFFFFFFK",
+            "HH:mm:ssK",
+            "HH:mmK",
+            DatetimeFormatUtc,
+            "yyyy-MM-dd HH:mm:ssK",
+            "yyyy-MM-dd HH:mmK",
+            "yyyy-MM-ddTHH:mm:ss.FFFFFFFK",
+            "yyyy-MM-ddTHH:mmK",
+            "yyyy-MM-ddTHH:mm:ssK",
+            "yyyyMMddHHmmssK",
+            "yyyyMMddHHmmK",
+            "yyyyMMddTHHmmssFFFFFFFK",
+            "THHmmss",
+            "THHmm",
+            "HH:mm:ss.FFFFFFF",
+            "HH:mm:ss",
+            "HH:mm",
+            DatetimeFormatLocal,
+            "yyyy-MM-dd HH:mm:ss",
+            "yyyy-MM-dd HH:mm",
+            "yyyy-MM-ddTHH:mm:ss.FFFFFFF",
+            "yyyy-MM-ddTHH:mm",
+            "yyyy-MM-ddTHH:mm:ss",
+            "yyyyMMddHHmmss",
+            "yyyyMMddHHmm",
+            "yyyyMMddTHHmmssFFFFFFF",
+            "yyyy-MM-dd",
+            "yyyyMMdd",
+            "yy-MM-dd"
+        };
+
         public static void RunQueries(this SQLiteDatabaseConnection connection, string[] queries)
         public static void RunQueries(this SQLiteDatabaseConnection connection, string[] queries)
         {
         {
             if (queries == null)
             if (queries == null)
@@ -22,16 +63,6 @@ namespace Emby.Server.Implementations.Data
             });
             });
         }
         }
 
 
-        public static byte[] ToGuidBlob(this string str)
-        {
-            return ToGuidBlob(new Guid(str));
-        }
-
-        public static byte[] ToGuidBlob(this Guid guid)
-        {
-            return guid.ToByteArray();
-        }
-
         public static Guid ReadGuidFromBlob(this IResultSetValue result)
         public static Guid ReadGuidFromBlob(this IResultSetValue result)
         {
         {
             return new Guid(result.ToBlob());
             return new Guid(result.ToBlob());
@@ -50,58 +81,16 @@ namespace Emby.Server.Implementations.Data
                     CultureInfo.InvariantCulture);
                     CultureInfo.InvariantCulture);
         }
         }
 
 
-        private static string GetDateTimeKindFormat(
-           DateTimeKind kind)
-        {
-            return (kind == DateTimeKind.Utc) ? _datetimeFormatUtc : _datetimeFormatLocal;
-        }
-
-        /// <summary>
-        /// An array of ISO-8601 DateTime formats that we support parsing.
-        /// </summary>
-        private static string[] _datetimeFormats = new string[] {
-      "THHmmssK",
-      "THHmmK",
-      "HH:mm:ss.FFFFFFFK",
-      "HH:mm:ssK",
-      "HH:mmK",
-      "yyyy-MM-dd HH:mm:ss.FFFFFFFK", /* NOTE: UTC default (5). */
-      "yyyy-MM-dd HH:mm:ssK",
-      "yyyy-MM-dd HH:mmK",
-      "yyyy-MM-ddTHH:mm:ss.FFFFFFFK",
-      "yyyy-MM-ddTHH:mmK",
-      "yyyy-MM-ddTHH:mm:ssK",
-      "yyyyMMddHHmmssK",
-      "yyyyMMddHHmmK",
-      "yyyyMMddTHHmmssFFFFFFFK",
-      "THHmmss",
-      "THHmm",
-      "HH:mm:ss.FFFFFFF",
-      "HH:mm:ss",
-      "HH:mm",
-      "yyyy-MM-dd HH:mm:ss.FFFFFFF", /* NOTE: Non-UTC default (19). */
-      "yyyy-MM-dd HH:mm:ss",
-      "yyyy-MM-dd HH:mm",
-      "yyyy-MM-ddTHH:mm:ss.FFFFFFF",
-      "yyyy-MM-ddTHH:mm",
-      "yyyy-MM-ddTHH:mm:ss",
-      "yyyyMMddHHmmss",
-      "yyyyMMddHHmm",
-      "yyyyMMddTHHmmssFFFFFFF",
-      "yyyy-MM-dd",
-      "yyyyMMdd",
-      "yy-MM-dd"
-    };
-
-        private static string _datetimeFormatUtc = _datetimeFormats[5];
-        private static string _datetimeFormatLocal = _datetimeFormats[19];
+        private static string GetDateTimeKindFormat(DateTimeKind kind)
+            => (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
 
 
         public static DateTime ReadDateTime(this IResultSetValue result)
         public static DateTime ReadDateTime(this IResultSetValue result)
         {
         {
             var dateText = result.ToString();
             var dateText = result.ToString();
 
 
             return DateTime.ParseExact(
             return DateTime.ParseExact(
-                dateText, _datetimeFormats,
+                dateText,
+                _datetimeFormats,
                 DateTimeFormatInfo.InvariantInfo,
                 DateTimeFormatInfo.InvariantInfo,
                 DateTimeStyles.None).ToUniversalTime();
                 DateTimeStyles.None).ToUniversalTime();
         }
         }
@@ -139,7 +128,10 @@ namespace Emby.Server.Implementations.Data
 
 
         public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
         public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
         {
         {
-            var commandText = string.Format("attach @path as {0};", alias);
+            var commandText = string.Format(
+                CultureInfo.InvariantCulture,
+                "attach @path as {0};",
+                alias);
 
 
             using (var statement = db.PrepareStatement(commandText))
             using (var statement = db.PrepareStatement(commandText))
             {
             {
@@ -186,10 +178,7 @@ namespace Emby.Server.Implementations.Data
         private static void CheckName(string name)
         private static void CheckName(string name)
         {
         {
 #if DEBUG
 #if DEBUG
-            //if (!name.IndexOf("@", StringComparison.OrdinalIgnoreCase) != 0)
-            {
-                throw new Exception("Invalid param name: " + name);
-            }
+            throw new ArgumentException("Invalid param name: " + name, nameof(name));
 #endif
 #endif
         }
         }
 
 
@@ -264,7 +253,7 @@ namespace Emby.Server.Implementations.Data
         {
         {
             if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
             if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
             {
             {
-                bindParam.Bind(value.ToGuidBlob());
+                bindParam.Bind(value.ToByteArray());
             }
             }
             else
             else
             {
             {
@@ -392,8 +381,7 @@ namespace Emby.Server.Implementations.Data
             }
             }
         }
         }
 
 
-        public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(
-            this IStatement This)
+        public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This)
         {
         {
             while (This.MoveNext())
             while (This.MoveNext())
             {
             {

+ 30 - 26
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -27,7 +27,6 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Serialization;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 using SQLitePCL.pretty;
 using SQLitePCL.pretty;
 
 
@@ -548,7 +547,7 @@ namespace Emby.Server.Implementations.Data
                 {
                 {
                     using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id"))
                     using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id"))
                     {
                     {
-                        saveImagesStatement.TryBind("@Id", item.Id.ToGuidBlob());
+                        saveImagesStatement.TryBind("@Id", item.Id.ToByteArray());
                         saveImagesStatement.TryBind("@Images", SerializeImages(item));
                         saveImagesStatement.TryBind("@Images", SerializeImages(item));
 
 
                         saveImagesStatement.MoveNext();
                         saveImagesStatement.MoveNext();
@@ -658,12 +657,14 @@ namespace Emby.Server.Implementations.Data
 
 
         private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, IStatement saveItemStatement)
         private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, IStatement saveItemStatement)
         {
         {
+            Type type = item.GetType();
+
             saveItemStatement.TryBind("@guid", item.Id);
             saveItemStatement.TryBind("@guid", item.Id);
-            saveItemStatement.TryBind("@type", item.GetType().FullName);
+            saveItemStatement.TryBind("@type", type.FullName);
 
 
-            if (TypeRequiresDeserialization(item.GetType()))
+            if (TypeRequiresDeserialization(type))
             {
             {
-                saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, _jsonOptions));
+                saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, type, _jsonOptions));
             }
             }
             else
             else
             {
             {
@@ -1177,7 +1178,7 @@ namespace Emby.Server.Implementations.Data
         {
         {
             if (id == Guid.Empty)
             if (id == Guid.Empty)
             {
             {
-                throw new ArgumentException(nameof(id), "Guid can't be empty");
+                throw new ArgumentException("Guid can't be empty", nameof(id));
             }
             }
 
 
             CheckDisposed();
             CheckDisposed();
@@ -1988,7 +1989,7 @@ namespace Emby.Server.Implementations.Data
                 throw new ArgumentNullException(nameof(chapters));
                 throw new ArgumentNullException(nameof(chapters));
             }
             }
 
 
-            var idBlob = id.ToGuidBlob();
+            var idBlob = id.ToByteArray();
 
 
             using (var connection = GetConnection())
             using (var connection = GetConnection())
             {
             {
@@ -3760,7 +3761,7 @@ namespace Emby.Server.Implementations.Data
 
 
                     if (statement != null)
                     if (statement != null)
                     {
                     {
-                        statement.TryBind(paramName, personId.ToGuidBlob());
+                        statement.TryBind(paramName, personId.ToByteArray());
                     }
                     }
                     index++;
                     index++;
                 }
                 }
@@ -3971,7 +3972,7 @@ namespace Emby.Server.Implementations.Data
                     clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
                     clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
                     if (statement != null)
                     if (statement != null)
                     {
                     {
-                        statement.TryBind(paramName, artistId.ToGuidBlob());
+                        statement.TryBind(paramName, artistId.ToByteArray());
                     }
                     }
                     index++;
                     index++;
                 }
                 }
@@ -3990,7 +3991,7 @@ namespace Emby.Server.Implementations.Data
                     clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
                     clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
                     if (statement != null)
                     if (statement != null)
                     {
                     {
-                        statement.TryBind(paramName, artistId.ToGuidBlob());
+                        statement.TryBind(paramName, artistId.ToByteArray());
                     }
                     }
                     index++;
                     index++;
                 }
                 }
@@ -4009,7 +4010,7 @@ namespace Emby.Server.Implementations.Data
                     clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))");
                     clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))");
                     if (statement != null)
                     if (statement != null)
                     {
                     {
-                        statement.TryBind(paramName, artistId.ToGuidBlob());
+                        statement.TryBind(paramName, artistId.ToByteArray());
                     }
                     }
                     index++;
                     index++;
                 }
                 }
@@ -4028,7 +4029,7 @@ namespace Emby.Server.Implementations.Data
                     clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")");
                     clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")");
                     if (statement != null)
                     if (statement != null)
                     {
                     {
-                        statement.TryBind(paramName, albumId.ToGuidBlob());
+                        statement.TryBind(paramName, albumId.ToByteArray());
                     }
                     }
                     index++;
                     index++;
                 }
                 }
@@ -4047,7 +4048,7 @@ namespace Emby.Server.Implementations.Data
                     clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
                     clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
                     if (statement != null)
                     if (statement != null)
                     {
                     {
-                        statement.TryBind(paramName, artistId.ToGuidBlob());
+                        statement.TryBind(paramName, artistId.ToByteArray());
                     }
                     }
                     index++;
                     index++;
                 }
                 }
@@ -4066,7 +4067,7 @@ namespace Emby.Server.Implementations.Data
                     clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
                     clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
                     if (statement != null)
                     if (statement != null)
                     {
                     {
-                        statement.TryBind(paramName, genreId.ToGuidBlob());
+                        statement.TryBind(paramName, genreId.ToByteArray());
                     }
                     }
                     index++;
                     index++;
                 }
                 }
@@ -4137,7 +4138,7 @@ namespace Emby.Server.Implementations.Data
 
 
                     if (statement != null)
                     if (statement != null)
                     {
                     {
-                        statement.TryBind(paramName, studioId.ToGuidBlob());
+                        statement.TryBind(paramName, studioId.ToByteArray());
                     }
                     }
                     index++;
                     index++;
                 }
                 }
@@ -4913,7 +4914,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
             {
             {
                 connection.RunInTransaction(db =>
                 connection.RunInTransaction(db =>
                 {
                 {
-                    var idBlob = id.ToGuidBlob();
+                    var idBlob = id.ToByteArray();
 
 
                     // Delete people
                     // Delete people
                     ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob);
                     ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob);
@@ -5032,7 +5033,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                 whereClauses.Add("ItemId=@ItemId");
                 whereClauses.Add("ItemId=@ItemId");
                 if (statement != null)
                 if (statement != null)
                 {
                 {
-                    statement.TryBind("@ItemId", query.ItemId.ToGuidBlob());
+                    statement.TryBind("@ItemId", query.ItemId.ToByteArray());
                 }
                 }
             }
             }
             if (!query.AppearsInItemId.Equals(Guid.Empty))
             if (!query.AppearsInItemId.Equals(Guid.Empty))
@@ -5040,7 +5041,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                 whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)");
                 whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)");
                 if (statement != null)
                 if (statement != null)
                 {
                 {
-                    statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidBlob());
+                    statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray());
                 }
                 }
             }
             }
             var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
             var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
@@ -5109,7 +5110,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
 
 
             CheckDisposed();
             CheckDisposed();
 
 
-            var itemIdBlob = itemId.ToGuidBlob();
+            var itemIdBlob = itemId.ToByteArray();
 
 
             // First delete
             // First delete
             deleteAncestorsStatement.Reset();
             deleteAncestorsStatement.Reset();
@@ -5143,7 +5144,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
 
 
                     var ancestorId = ancestorIds[i];
                     var ancestorId = ancestorIds[i];
 
 
-                    statement.TryBind("@AncestorId" + index, ancestorId.ToGuidBlob());
+                    statement.TryBind("@AncestorId" + index, ancestorId.ToByteArray());
                     statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture));
                     statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture));
                 }
                 }
 
 
@@ -5608,7 +5609,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
 
 
             CheckDisposed();
             CheckDisposed();
 
 
-            var guidBlob = itemId.ToGuidBlob();
+            var guidBlob = itemId.ToByteArray();
 
 
             // First delete
             // First delete
             db.Execute("delete from ItemValues where ItemId=@Id", guidBlob);
             db.Execute("delete from ItemValues where ItemId=@Id", guidBlob);
@@ -5632,10 +5633,13 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                 {
                 {
                     if (isSubsequentRow)
                     if (isSubsequentRow)
                     {
                     {
-                        insertText.Append(",");
+                        insertText.Append(',');
                     }
                     }
 
 
-                    insertText.AppendFormat("(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})", i.ToString(CultureInfo.InvariantCulture));
+                    insertText.AppendFormat(
+                        CultureInfo.InvariantCulture,
+                        "(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})",
+                        i);
                     isSubsequentRow = true;
                     isSubsequentRow = true;
                 }
                 }
 
 
@@ -5688,7 +5692,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
             {
             {
                 connection.RunInTransaction(db =>
                 connection.RunInTransaction(db =>
                 {
                 {
-                    var itemIdBlob = itemId.ToGuidBlob();
+                    var itemIdBlob = itemId.ToByteArray();
 
 
                     // First delete chapters
                     // First delete chapters
                     db.Execute("delete from People where ItemId=@ItemId", itemIdBlob);
                     db.Execute("delete from People where ItemId=@ItemId", itemIdBlob);
@@ -5807,7 +5811,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
 
 
                 using (var statement = PrepareStatement(connection, cmdText))
                 using (var statement = PrepareStatement(connection, cmdText))
                 {
                 {
-                    statement.TryBind("@ItemId", query.ItemId.ToGuidBlob());
+                    statement.TryBind("@ItemId", query.ItemId.ToByteArray());
 
 
                     if (query.Type.HasValue)
                     if (query.Type.HasValue)
                     {
                     {
@@ -5849,7 +5853,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
             {
             {
                 connection.RunInTransaction(db =>
                 connection.RunInTransaction(db =>
                 {
                 {
-                    var itemIdBlob = id.ToGuidBlob();
+                    var itemIdBlob = id.ToByteArray();
 
 
                     // First delete chapters
                     // First delete chapters
                     db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
                     db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);

+ 5 - 10
Emby.Server.Implementations/Data/SqliteUserDataRepository.cs

@@ -1,7 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
-using System.Linq;
 using System.Threading;
 using System.Threading;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
@@ -15,23 +14,19 @@ namespace Emby.Server.Implementations.Data
     public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository
     public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository
     {
     {
         public SqliteUserDataRepository(
         public SqliteUserDataRepository(
-            ILoggerFactory loggerFactory,
+            ILogger<SqliteUserDataRepository> logger,
             IApplicationPaths appPaths)
             IApplicationPaths appPaths)
-            : base(loggerFactory.CreateLogger(nameof(SqliteUserDataRepository)))
+            : base(logger)
         {
         {
             DbFilePath = Path.Combine(appPaths.DataPath, "library.db");
             DbFilePath = Path.Combine(appPaths.DataPath, "library.db");
         }
         }
 
 
-        /// <summary>
-        /// Gets the name of the repository
-        /// </summary>
-        /// <value>The name.</value>
+        /// <inheritdoc />
         public string Name => "SQLite";
         public string Name => "SQLite";
 
 
         /// <summary>
         /// <summary>
-        /// Opens the connection to the database
+        /// Opens the connection to the database.
         /// </summary>
         /// </summary>
-        /// <returns>Task.</returns>
         public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection)
         public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection)
         {
         {
             WriteLock.Dispose();
             WriteLock.Dispose();
@@ -97,7 +92,7 @@ namespace Emby.Server.Implementations.Data
                         continue;
                         continue;
                     }
                     }
 
 
-                    statement.TryBind("@UserId", user.Id.ToGuidBlob());
+                    statement.TryBind("@UserId", user.Id.ToByteArray());
                     statement.TryBind("@InternalUserId", user.InternalId);
                     statement.TryBind("@InternalUserId", user.InternalId);
 
 
                     statement.MoveNext();
                     statement.MoveNext();

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

@@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.Data
                 {
                 {
                     using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
                     using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
                     {
                     {
-                        statement.TryBind("@guid", user.Id.ToGuidBlob());
+                        statement.TryBind("@guid", user.Id.ToByteArray());
                         statement.TryBind("@data", serialized);
                         statement.TryBind("@data", serialized);
 
 
                         statement.MoveNext();
                         statement.MoveNext();

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

@@ -130,7 +130,6 @@ namespace Emby.Server.Implementations.Devices
             var session = _authRepo.Get(new AuthenticationInfoQuery
             var session = _authRepo.Get(new AuthenticationInfoQuery
             {
             {
                 DeviceId = id
                 DeviceId = id
-
             }).Items.FirstOrDefault();
             }).Items.FirstOrDefault();
 
 
             var device = session == null ? null : ToDeviceInfo(session);
             var device = session == null ? null : ToDeviceInfo(session);

+ 11 - 12
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -3,6 +3,7 @@
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
     <ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
     <ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
     <ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
+    <ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" />
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@@ -10,7 +11,6 @@
     <ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
     <ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
     <ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
     <ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
     <ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
     <ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
-    <ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj" />
     <ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
     <ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
     <ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" />
     <ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" />
     <ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
     <ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
@@ -29,12 +29,15 @@
     <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" 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.AspNetCore.WebSockets" Version="2.2.1" />
-    <PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
-    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
-    <PackageReference Include="ServiceStack.Text.Core" Version="5.6.0" />
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.1" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
+    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" />
+    <PackageReference Include="Mono.Nat" Version="2.0.0" />
+    <PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" />
     <PackageReference Include="sharpcompress" Version="0.24.0" />
     <PackageReference Include="sharpcompress" Version="0.24.0" />
     <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
     <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
+    <PackageReference Include="System.Interactive.Async" Version="4.0.0" />
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
@@ -47,16 +50,12 @@
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>
   </PropertyGroup>
 
 
-  <PropertyGroup>
-    <!-- We need at least C# 7.3 to compare tuples-->
-    <LangVersion>latest</LangVersion>
-  </PropertyGroup>
-
   <!-- Code analysers-->
   <!-- Code analysers-->
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
-    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" />
-    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
     <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
     <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
+    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
+    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
   </ItemGroup>
   </ItemGroup>
 
 
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

+ 85 - 174
Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs

@@ -1,10 +1,9 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Globalization;
 using System.Net;
 using System.Net;
+using System.Text;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Plugins;
@@ -15,209 +14,134 @@ using Mono.Nat;
 
 
 namespace Emby.Server.Implementations.EntryPoints
 namespace Emby.Server.Implementations.EntryPoints
 {
 {
+    /// <summary>
+    /// Server entrypoint handling external port forwarding.
+    /// </summary>
     public class ExternalPortForwarding : IServerEntryPoint
     public class ExternalPortForwarding : IServerEntryPoint
     {
     {
         private readonly IServerApplicationHost _appHost;
         private readonly IServerApplicationHost _appHost;
         private readonly ILogger _logger;
         private readonly ILogger _logger;
-        private readonly IHttpClient _httpClient;
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IDeviceDiscovery _deviceDiscovery;
         private readonly IDeviceDiscovery _deviceDiscovery;
 
 
+        private readonly object _createdRulesLock = new object();
+        private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
         private Timer _timer;
         private Timer _timer;
+        private string _lastConfigIdentifier;
 
 
-        private NatManager _natManager;
+        private bool _disposed = false;
 
 
-        public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient)
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ExternalPortForwarding"/> class.
+        /// </summary>
+        /// <param name="logger">The logger.</param>
+        /// <param name="appHost">The application host.</param>
+        /// <param name="config">The configuration manager.</param>
+        /// <param name="deviceDiscovery">The device discovery.</param>
+        public ExternalPortForwarding(
+            ILogger<ExternalPortForwarding> logger,
+            IServerApplicationHost appHost,
+            IServerConfigurationManager config,
+            IDeviceDiscovery deviceDiscovery)
         {
         {
-            _logger = loggerFactory.CreateLogger("PortMapper");
+            _logger = logger;
             _appHost = appHost;
             _appHost = appHost;
             _config = config;
             _config = config;
             _deviceDiscovery = deviceDiscovery;
             _deviceDiscovery = deviceDiscovery;
-            _httpClient = httpClient;
-            _config.ConfigurationUpdated += _config_ConfigurationUpdated1;
-        }
-
-        private void _config_ConfigurationUpdated1(object sender, EventArgs e)
-        {
-            _config_ConfigurationUpdated(sender, e);
         }
         }
 
 
-        private string _lastConfigIdentifier;
         private string GetConfigIdentifier()
         private string GetConfigIdentifier()
         {
         {
-            var values = new List<string>();
+            const char Separator = '|';
             var config = _config.Configuration;
             var config = _config.Configuration;
 
 
-            values.Add(config.EnableUPnP.ToString());
-            values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture));
-            values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture));
-            values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture));
-            values.Add(_appHost.EnableHttps.ToString());
-            values.Add((config.EnableRemoteAccess).ToString());
-
-            return string.Join("|", values.ToArray());
+            return new StringBuilder(32)
+                .Append(config.EnableUPnP).Append(Separator)
+                .Append(config.PublicPort).Append(Separator)
+                .Append(_appHost.HttpPort).Append(Separator)
+                .Append(_appHost.HttpsPort).Append(Separator)
+                .Append(_appHost.EnableHttps).Append(Separator)
+                .Append(config.EnableRemoteAccess).Append(Separator)
+                .ToString();
         }
         }
 
 
-        private async void _config_ConfigurationUpdated(object sender, EventArgs e)
+        private void OnConfigurationUpdated(object sender, EventArgs e)
         {
         {
             if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
             if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
             {
             {
-                DisposeNat();
-
-                await RunAsync();
+                Stop();
+                Start();
             }
             }
         }
         }
 
 
+        /// <inheritdoc />
         public Task RunAsync()
         public Task RunAsync()
         {
         {
-            if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess)
-            {
-                Start();
-            }
+            Start();
 
 
-            _config.ConfigurationUpdated -= _config_ConfigurationUpdated;
-            _config.ConfigurationUpdated += _config_ConfigurationUpdated;
+            _config.ConfigurationUpdated += OnConfigurationUpdated;
 
 
             return Task.CompletedTask;
             return Task.CompletedTask;
         }
         }
 
 
         private void Start()
         private void Start()
         {
         {
-            _logger.LogDebug("Starting NAT discovery");
-            if (_natManager == null)
+            if (!_config.Configuration.EnableUPnP || !_config.Configuration.EnableRemoteAccess)
             {
             {
-                _natManager = new NatManager(_logger, _httpClient);
-                _natManager.DeviceFound += NatUtility_DeviceFound;
-                _natManager.StartDiscovery();
+                return;
             }
             }
 
 
+            _logger.LogDebug("Starting NAT discovery");
+
+            NatUtility.DeviceFound += OnNatUtilityDeviceFound;
+            NatUtility.StartDiscovery();
+
             _timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
             _timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
 
 
-            _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
+            _deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
 
 
             _lastConfigIdentifier = GetConfigIdentifier();
             _lastConfigIdentifier = GetConfigIdentifier();
         }
         }
 
 
-        private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
+        private void Stop()
         {
         {
-            if (_disposed)
-            {
-                return;
-            }
-
-            var info = e.Argument;
-
-            if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty;
-
-            if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty;
-
-            // Filter device type
-            if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
-                     nt.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
-                     usn.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
-                     nt.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1)
-            {
-                return;
-            }
-
-            var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
-
-            if (info.Location == null)
-            {
-                return;
-            }
-
-            lock (_usnsHandled)
-            {
-                if (_usnsHandled.Contains(identifier))
-                {
-                    return;
-                }
-                _usnsHandled.Add(identifier);
-            }
-
-            _logger.LogDebug("Found NAT device: " + identifier);
-
-            if (IPAddress.TryParse(info.Location.Host, out var address))
-            {
-                // The Handle method doesn't need the port
-                var endpoint = new IPEndPoint(address, info.Location.Port);
-
-                IPAddress localAddress = null;
+            _logger.LogDebug("Stopping NAT discovery");
 
 
-                try
-                {
-                    var localAddressString = await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false);
+            NatUtility.StopDiscovery();
+            NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
 
 
-                    if (Uri.TryCreate(localAddressString, UriKind.Absolute, out var uri))
-                    {
-                        localAddressString = uri.Host;
+            _timer?.Dispose();
 
 
-                        if (!IPAddress.TryParse(localAddressString, out localAddress))
-                        {
-                            return;
-                        }
-                    }
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error");
-                    return;
-                }
-
-                if (_disposed)
-                {
-                    return;
-                }
-
-                // This should never happen, but the Handle method will throw ArgumentNullException if it does
-                if (localAddress == null)
-                {
-                    return;
-                }
-
-                var natManager = _natManager;
-                if (natManager != null)
-                {
-                    await natManager.Handle(localAddress, info, endpoint, NatProtocol.Upnp).ConfigureAwait(false);
-                }
-            }
+            _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
         }
         }
 
 
         private void ClearCreatedRules(object state)
         private void ClearCreatedRules(object state)
         {
         {
-            lock (_createdRules)
+            lock (_createdRulesLock)
             {
             {
                 _createdRules.Clear();
                 _createdRules.Clear();
             }
             }
-            lock (_usnsHandled)
-            {
-                _usnsHandled.Clear();
-            }
         }
         }
 
 
-        void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
+        private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
         {
         {
-            if (_disposed)
-            {
-                return;
-            }
+            NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
+        }
 
 
+        private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
+        {
             try
             try
             {
             {
                 var device = e.Device;
                 var device = e.Device;
 
 
                 CreateRules(device);
                 CreateRules(device);
             }
             }
-            catch
+            catch (Exception ex)
             {
             {
-                // Commenting out because users are reporting problems out of our control
-                //_logger.LogError(ex, "Error creating port forwarding rules");
+                _logger.LogError(ex, "Error creating port forwarding rules");
             }
             }
         }
         }
 
 
-        private List<string> _createdRules = new List<string>();
-        private List<string> _usnsHandled = new List<string>();
         private async void CreateRules(INatDevice device)
         private async void CreateRules(INatDevice device)
         {
         {
             if (_disposed)
             if (_disposed)
@@ -227,15 +151,13 @@ namespace Emby.Server.Implementations.EntryPoints
 
 
             // On some systems the device discovered event seems to fire repeatedly
             // On some systems the device discovered event seems to fire repeatedly
             // This check will help ensure we're not trying to port map the same device over and over
             // This check will help ensure we're not trying to port map the same device over and over
-            var address = device.LocalAddress;
-
-            var addressString = address.ToString();
+            var address = device.DeviceEndpoint;
 
 
-            lock (_createdRules)
+            lock (_createdRulesLock)
             {
             {
-                if (!_createdRules.Contains(addressString))
+                if (!_createdRules.Contains(address))
                 {
                 {
-                    _createdRules.Add(addressString);
+                    _createdRules.Add(address);
                 }
                 }
                 else
                 else
                 {
                 {
@@ -263,54 +185,43 @@ namespace Emby.Server.Implementations.EntryPoints
             }
             }
         }
         }
 
 
-        private Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
+        private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort)
         {
         {
-            _logger.LogDebug("Creating port map on local port {0} to public port {1} with device {2}", privatePort, publicPort, device.LocalAddress.ToString());
-
-            return device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort)
-            {
-                Description = _appHost.Name
-            });
+            _logger.LogDebug(
+                "Creating port map on local port {0} to public port {1} with device {2}",
+                privatePort,
+                publicPort,
+                device.DeviceEndpoint);
+
+            return device.CreatePortMapAsync(
+                new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
         }
         }
 
 
-        private bool _disposed = false;
+        /// <inheritdoc />
         public void Dispose()
         public void Dispose()
         {
         {
-            _disposed = true;
-            DisposeNat();
+            Dispose(true);
+            GC.SuppressFinalize(this);
         }
         }
 
 
-        private void DisposeNat()
+        /// <summary>
+        /// Releases unmanaged and - optionally - managed resources.
+        /// </summary>
+        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+        protected virtual void Dispose(bool dispose)
         {
         {
-            _logger.LogDebug("Stopping NAT discovery");
-
-            if (_timer != null)
+            if (_disposed)
             {
             {
-                _timer.Dispose();
-                _timer = null;
+                return;
             }
             }
 
 
-            _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
+            _config.ConfigurationUpdated -= OnConfigurationUpdated;
 
 
-            var natManager = _natManager;
+            Stop();
 
 
-            if (natManager != null)
-            {
-                _natManager = null;
+            _timer = null;
 
 
-                using (natManager)
-                {
-                    try
-                    {
-                        natManager.StopDiscovery();
-                        natManager.DeviceFound -= NatUtility_DeviceFound;
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.LogError(ex, "Error stopping NAT Discovery");
-                    }
-                }
-            }
+            _disposed = true;
         }
         }
     }
     }
 }
 }

+ 9 - 6
Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs

@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints
     public class LibraryChangedNotifier : IServerEntryPoint
     public class LibraryChangedNotifier : IServerEntryPoint
     {
     {
         /// <summary>
         /// <summary>
-        /// The _library manager
+        /// The library manager.
         /// </summary>
         /// </summary>
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
@@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.EntryPoints
         private readonly ILogger _logger;
         private readonly ILogger _logger;
 
 
         /// <summary>
         /// <summary>
-        /// The _library changed sync lock
+        /// The library changed sync lock.
         /// </summary>
         /// </summary>
         private readonly object _libraryChangedSyncLock = new object();
         private readonly object _libraryChangedSyncLock = new object();
 
 
@@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.EntryPoints
         private Timer LibraryUpdateTimer { get; set; }
         private Timer LibraryUpdateTimer { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// The library update duration
+        /// The library update duration.
         /// </summary>
         /// </summary>
         private const int LibraryUpdateDuration = 30000;
         private const int LibraryUpdateDuration = 30000;
 
 
@@ -188,8 +188,11 @@ namespace Emby.Server.Implementations.EntryPoints
             {
             {
                 if (LibraryUpdateTimer == null)
                 if (LibraryUpdateTimer == null)
                 {
                 {
-                    LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
-                                                   Timeout.Infinite);
+                    LibraryUpdateTimer = new Timer(
+                        LibraryUpdateTimerCallback,
+                        null,
+                        LibraryUpdateDuration,
+                        Timeout.Infinite);
                 }
                 }
                 else
                 else
                 {
                 {
@@ -452,7 +455,7 @@ namespace Emby.Server.Implementations.EntryPoints
                 return new[] { item };
                 return new[] { item };
             }
             }
 
 
-            return new T[] { };
+            return Array.Empty<T>();
         }
         }
 
 
         /// <summary>
         /// <summary>

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

@@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.EntryPoints
             {
             {
                 cancellationToken.ThrowIfCancellationRequested();
                 cancellationToken.ThrowIfCancellationRequested();
 
 
-                await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false);
+                await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
             }
             }
         }
         }
 
 

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

@@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.EntryPoints
         {
         {
             try
             try
             {
             {
-                await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None);
+                await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None).ConfigureAwait(false);
             }
             }
             catch (Exception)
             catch (Exception)
             {
             {

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

@@ -325,7 +325,7 @@ namespace Emby.Server.Implementations.HttpClientManager
 
 
             if (options.LogErrorResponseBody)
             if (options.LogErrorResponseBody)
             {
             {
-                var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+                string msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                 _logger.LogError("HTTP request failed with message: {Message}", msg);
                 _logger.LogError("HTTP request failed with message: {Message}", msg);
             }
             }
 
 

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

@@ -7,7 +7,6 @@ using System.Net.Sockets;
 using System.Reflection;
 using System.Reflection;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using Emby.Server.Implementations.Configuration;
 using Emby.Server.Implementations.Net;
 using Emby.Server.Implementations.Net;
 using Emby.Server.Implementations.Services;
 using Emby.Server.Implementations.Services;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
@@ -16,11 +15,9 @@ using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.Services;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Http.Internal;
 using Microsoft.AspNetCore.WebUtilities;
 using Microsoft.AspNetCore.WebUtilities;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
@@ -166,7 +163,7 @@ namespace Emby.Server.Implementations.HttpServer
             {
             {
                 OnReceive = ProcessWebSocketMessageReceived,
                 OnReceive = ProcessWebSocketMessageReceived,
                 Url = e.Url,
                 Url = e.Url,
-                QueryString = e.QueryString ?? new QueryCollection()
+                QueryString = e.QueryString
             };
             };
 
 
             connection.Closed += OnConnectionClosed;
             connection.Closed += OnConnectionClosed;
@@ -539,6 +536,11 @@ namespace Emby.Server.Implementations.HttpServer
             }
             }
             finally
             finally
             {
             {
+                if (httpRes.StatusCode >= 500)
+                {
+                    _logger.LogDebug("Sending HTTP Response 500 in response to {Url}", urlToLog);
+                }
+
                 stopWatch.Stop();
                 stopWatch.Stop();
                 var elapsed = stopWatch.Elapsed;
                 var elapsed = stopWatch.Elapsed;
                 if (elapsed.TotalMilliseconds > 500)
                 if (elapsed.TotalMilliseconds > 500)

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

@@ -460,7 +460,7 @@ namespace Emby.Server.Implementations.HttpServer
 
 
             if (string.IsNullOrEmpty(path))
             if (string.IsNullOrEmpty(path))
             {
             {
-                throw new ArgumentNullException(nameof(path));
+                throw new ArgumentException("Path can't be empty.", nameof(options));
             }
             }
 
 
             if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite)
             if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite)

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

@@ -48,12 +48,14 @@ namespace Emby.Server.Implementations.HttpServer
         public IDictionary<string, string> Headers => _options;
         public IDictionary<string, string> Headers => _options;
 
 
         /// <summary>
         /// <summary>
-        /// Initializes a new instance of the <see cref="StreamWriter" /> class.
+        /// Initializes a new instance of the <see cref="RangeRequestWriter" /> class.
         /// </summary>
         /// </summary>
         /// <param name="rangeHeader">The range header.</param>
         /// <param name="rangeHeader">The range header.</param>
+        /// <param name="contentLength">The content length.</param>
         /// <param name="source">The source.</param>
         /// <param name="source">The source.</param>
         /// <param name="contentType">Type of the content.</param>
         /// <param name="contentType">Type of the content.</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
+        /// <param name="logger">The logger instance.</param>
         public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger)
         public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger)
         {
         {
             if (string.IsNullOrEmpty(contentType))
             if (string.IsNullOrEmpty(contentType))

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

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Linq;
 using System.Linq;
+using Emby.Server.Implementations.SocketSharp;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
@@ -7,22 +8,27 @@ using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
 
 
 namespace Emby.Server.Implementations.HttpServer.Security
 namespace Emby.Server.Implementations.HttpServer.Security
 {
 {
     public class AuthService : IAuthService
     public class AuthService : IAuthService
     {
     {
+        private readonly ILogger<AuthService> _logger;
         private readonly IAuthorizationContext _authorizationContext;
         private readonly IAuthorizationContext _authorizationContext;
         private readonly ISessionManager _sessionManager;
         private readonly ISessionManager _sessionManager;
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly INetworkManager _networkManager;
         private readonly INetworkManager _networkManager;
 
 
         public AuthService(
         public AuthService(
+            ILogger<AuthService> logger,
             IAuthorizationContext authorizationContext,
             IAuthorizationContext authorizationContext,
             IServerConfigurationManager config,
             IServerConfigurationManager config,
             ISessionManager sessionManager,
             ISessionManager sessionManager,
             INetworkManager networkManager)
             INetworkManager networkManager)
         {
         {
+            _logger = logger;
             _authorizationContext = authorizationContext;
             _authorizationContext = authorizationContext;
             _config = config;
             _config = config;
             _sessionManager = sessionManager;
             _sessionManager = sessionManager;
@@ -34,7 +40,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
             ValidateUser(request, authAttribtues);
             ValidateUser(request, authAttribtues);
         }
         }
 
 
-        private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
+        public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
+        {
+            var req = new WebSocketSharpRequest(request, null, request.Path, _logger);
+            var user = ValidateUser(req, authAttributes);
+            return user;
+        }
+
+        private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
         {
         {
             // This code is executed before the service
             // This code is executed before the service
             var auth = _authorizationContext.GetAuthorizationInfo(request);
             var auth = _authorizationContext.GetAuthorizationInfo(request);
@@ -81,6 +94,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
                     request.RemoteIp,
                     request.RemoteIp,
                     user);
                     user);
             }
             }
+
+            return user;
         }
         }
 
 
         private void ValidateUserAccess(
         private void ValidateUserAccess(

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

@@ -2,11 +2,11 @@ using System;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Common;
 using MediaBrowser.Common.Cryptography;
 using MediaBrowser.Common.Cryptography;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Cryptography;
 using MediaBrowser.Model.Cryptography;
-using static MediaBrowser.Common.HexHelper;
 
 
 namespace Emby.Server.Implementations.Library
 namespace Emby.Server.Implementations.Library
 {
 {
@@ -59,7 +59,10 @@ namespace Emby.Server.Implementations.Library
             if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
             if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
                 || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
                 || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
             {
             {
-                byte[] calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.Salt);
+                byte[] calculatedHash = _cryptographyProvider.ComputeHash(
+                    readyHash.Id,
+                    passwordbytes,
+                    readyHash.Salt);
 
 
                 if (calculatedHash.SequenceEqual(readyHash.Hash))
                 if (calculatedHash.SequenceEqual(readyHash.Hash))
                 {
                 {
@@ -122,7 +125,7 @@ namespace Emby.Server.Implementations.Library
         {
         {
             return string.IsNullOrEmpty(user.EasyPassword)
             return string.IsNullOrEmpty(user.EasyPassword)
                 ? null
                 ? null
-                : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash);
+                : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash);
         }
         }
 
 
         /// <summary>
         /// <summary>

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

@@ -1,7 +1,6 @@
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Net;
 
 
 namespace Emby.Server.Implementations.Library
 namespace Emby.Server.Implementations.Library
 {
 {

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

@@ -519,7 +519,7 @@ namespace Emby.Server.Implementations.Library
         }
         }
 
 
         public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
         public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
-            => ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent);
+            => ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
 
 
         private BaseItem ResolvePath(
         private BaseItem ResolvePath(
             FileSystemMetadata fileInfo,
             FileSystemMetadata fileInfo,
@@ -1045,7 +1045,7 @@ namespace Emby.Server.Implementations.Library
             await RootFolder.ValidateChildren(
             await RootFolder.ValidateChildren(
                 new SimpleProgress<double>(),
                 new SimpleProgress<double>(),
                 cancellationToken,
                 cancellationToken,
-                new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
+                new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
                 recursive: false).ConfigureAwait(false);
                 recursive: false).ConfigureAwait(false);
 
 
             await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
             await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
@@ -1053,7 +1053,7 @@ namespace Emby.Server.Implementations.Library
             await GetUserRootFolder().ValidateChildren(
             await GetUserRootFolder().ValidateChildren(
                 new SimpleProgress<double>(),
                 new SimpleProgress<double>(),
                 cancellationToken,
                 cancellationToken,
-                new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
+                new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
                 recursive: false).ConfigureAwait(false);
                 recursive: false).ConfigureAwait(false);
 
 
             // Quickly scan CollectionFolders for changes
             // Quickly scan CollectionFolders for changes
@@ -1074,7 +1074,7 @@ namespace Emby.Server.Implementations.Library
             innerProgress.RegisterAction(pct => progress.Report(pct * .96));
             innerProgress.RegisterAction(pct => progress.Report(pct * .96));
 
 
             // Now validate the entire media library
             // Now validate the entire media library
-            await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: true).ConfigureAwait(false);
+            await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
 
 
             progress.Report(96);
             progress.Report(96);
 
 
@@ -1899,7 +1899,7 @@ namespace Emby.Server.Implementations.Library
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
         public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
         {
         {
-            UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
+            UpdateItems(new[] { item }, parent, updateReason, cancellationToken);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -2135,7 +2135,7 @@ namespace Emby.Server.Implementations.Library
             if (refresh)
             if (refresh)
             {
             {
                 item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
                 item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
-                _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.Normal);
+                _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
             }
             }
 
 
             return item;
             return item;
@@ -2175,7 +2175,6 @@ namespace Emby.Server.Implementations.Library
                     DisplayParentId = parentId
                     DisplayParentId = parentId
                 };
                 };
 
 
-
                 CreateItem(item, null);
                 CreateItem(item, null);
 
 
                 isNew = true;
                 isNew = true;
@@ -2193,11 +2192,10 @@ namespace Emby.Server.Implementations.Library
             {
             {
                 _providerManagerFactory().QueueRefresh(
                 _providerManagerFactory().QueueRefresh(
                     item.Id,
                     item.Id,
-                    new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
+                    new MetadataRefreshOptions(new DirectoryService(_fileSystem))
                     {
                     {
                         // Need to force save to increment DateLastSaved
                         // Need to force save to increment DateLastSaved
                         ForceSave = true
                         ForceSave = true
-
                     },
                     },
                     RefreshPriority.Normal);
                     RefreshPriority.Normal);
             }
             }
@@ -2261,7 +2259,7 @@ namespace Emby.Server.Implementations.Library
             {
             {
                 _providerManagerFactory().QueueRefresh(
                 _providerManagerFactory().QueueRefresh(
                     item.Id,
                     item.Id,
-                    new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
+                    new MetadataRefreshOptions(new DirectoryService(_fileSystem))
                     {
                     {
                         // Need to force save to increment DateLastSaved
                         // Need to force save to increment DateLastSaved
                         ForceSave = true
                         ForceSave = true
@@ -2338,7 +2336,7 @@ namespace Emby.Server.Implementations.Library
             {
             {
                 _providerManagerFactory().QueueRefresh(
                 _providerManagerFactory().QueueRefresh(
                     item.Id,
                     item.Id,
-                    new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
+                    new MetadataRefreshOptions(new DirectoryService(_fileSystem))
                     {
                     {
                         // Need to force save to increment DateLastSaved
                         // Need to force save to increment DateLastSaved
                         ForceSave = true
                         ForceSave = true
@@ -2487,6 +2485,15 @@ namespace Emby.Server.Implementations.Library
                 {
                 {
                     episode.ParentIndexNumber = season.IndexNumber;
                     episode.ParentIndexNumber = season.IndexNumber;
                 }
                 }
+                else
+                {
+                    /*
+                    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
+                    episode.ParentIndexNumber = 1;
+                }
 
 
                 if (episode.ParentIndexNumber.HasValue)
                 if (episode.ParentIndexNumber.HasValue)
                 {
                 {

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

@@ -134,12 +134,13 @@ namespace Emby.Server.Implementations.Library
 
 
             if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
             if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
             {
             {
-                await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
-                {
-                    EnableRemoteContentProbe = true,
-                    MetadataRefreshMode = MediaBrowser.Controller.Providers.MetadataRefreshMode.FullRefresh
-
-                }, cancellationToken).ConfigureAwait(false);
+                await item.RefreshMetadata(
+                    new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+                    {
+                        EnableRemoteContentProbe = true,
+                        MetadataRefreshMode = MetadataRefreshMode.FullRefresh
+                    },
+                    cancellationToken).ConfigureAwait(false);
 
 
                 mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
                 mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
             }
             }

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

@@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.Library
 
 
             if (string.IsNullOrEmpty(searchTerm))
             if (string.IsNullOrEmpty(searchTerm))
             {
             {
-                throw new ArgumentNullException(nameof(searchTerm));
+                throw new ArgumentNullException("SearchTerm can't be empty.", nameof(searchTerm));
             }
             }
 
 
             searchTerm = searchTerm.Trim().RemoveDiacritics();
             searchTerm = searchTerm.Trim().RemoveDiacritics();

+ 21 - 29
Emby.Server.Implementations/Library/UserManager.cs

@@ -24,6 +24,7 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Cryptography;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
@@ -31,7 +32,6 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Users;
 using MediaBrowser.Model.Users;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
-using static MediaBrowser.Common.HexHelper;
 
 
 namespace Emby.Server.Implementations.Library
 namespace Emby.Server.Implementations.Library
 {
 {
@@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.Library
         private readonly Func<IDtoService> _dtoServiceFactory;
         private readonly Func<IDtoService> _dtoServiceFactory;
         private readonly IServerApplicationHost _appHost;
         private readonly IServerApplicationHost _appHost;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
+        private readonly ICryptoProvider _cryptoProvider;
 
 
         private ConcurrentDictionary<Guid, User> _users;
         private ConcurrentDictionary<Guid, User> _users;
 
 
@@ -80,7 +81,8 @@ namespace Emby.Server.Implementations.Library
             Func<IDtoService> dtoServiceFactory,
             Func<IDtoService> dtoServiceFactory,
             IServerApplicationHost appHost,
             IServerApplicationHost appHost,
             IJsonSerializer jsonSerializer,
             IJsonSerializer jsonSerializer,
-            IFileSystem fileSystem)
+            IFileSystem fileSystem,
+            ICryptoProvider cryptoProvider)
         {
         {
             _logger = logger;
             _logger = logger;
             _userRepository = userRepository;
             _userRepository = userRepository;
@@ -91,6 +93,7 @@ namespace Emby.Server.Implementations.Library
             _appHost = appHost;
             _appHost = appHost;
             _jsonSerializer = jsonSerializer;
             _jsonSerializer = jsonSerializer;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
+            _cryptoProvider = cryptoProvider;
             _users = null;
             _users = null;
         }
         }
 
 
@@ -179,12 +182,7 @@ namespace Emby.Server.Implementations.Library
             _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
             _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
         }
         }
 
 
-        /// <summary>
-        /// Gets a User by Id.
-        /// </summary>
-        /// <param name="id">The id.</param>
-        /// <returns>User.</returns>
-        /// <exception cref="ArgumentException"></exception>
+        /// <inheritdoc />
         public User GetUserById(Guid id)
         public User GetUserById(Guid id)
         {
         {
             if (id == Guid.Empty)
             if (id == Guid.Empty)
@@ -196,11 +194,7 @@ namespace Emby.Server.Implementations.Library
             return user;
             return user;
         }
         }
 
 
-        /// <summary>
-        /// Gets the user by identifier.
-        /// </summary>
-        /// <param name="id">The identifier.</param>
-        /// <returns>User.</returns>
+        /// <inheritdoc />
         public User GetUserById(string id)
         public User GetUserById(string id)
             => GetUserById(new Guid(id));
             => GetUserById(new Guid(id));
 
 
@@ -428,7 +422,6 @@ namespace Emby.Server.Implementations.Library
         {
         {
             try
             try
             {
             {
-
                 var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
                 var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
                     ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false)
                     ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false)
                     : await provider.Authenticate(username, password).ConfigureAwait(false);
                     : await provider.Authenticate(username, password).ConfigureAwait(false);
@@ -475,24 +468,21 @@ namespace Emby.Server.Implementations.Library
 
 
             if (!success
             if (!success
                 && _networkManager.IsInLocalNetwork(remoteEndPoint)
                 && _networkManager.IsInLocalNetwork(remoteEndPoint)
-                && user.Configuration.EnableLocalPassword)
+                && user.Configuration.EnableLocalPassword
+                && !string.IsNullOrEmpty(user.EasyPassword))
             {
             {
-                success = string.Equals(
-                    GetLocalPasswordHash(user),
-                    _defaultAuthenticationProvider.GetHashedString(user, password),
-                    StringComparison.OrdinalIgnoreCase);
+                // Check easy password
+                var passwordHash = PasswordHash.Parse(user.EasyPassword);
+                var hash = _cryptoProvider.ComputeHash(
+                    passwordHash.Id,
+                    Encoding.UTF8.GetBytes(password),
+                    passwordHash.Salt);
+                success = passwordHash.Hash.SequenceEqual(hash);
             }
             }
 
 
             return (authenticationProvider, username, success);
             return (authenticationProvider, username, success);
         }
         }
 
 
-        private string GetLocalPasswordHash(User user)
-        {
-            return string.IsNullOrEmpty(user.EasyPassword)
-                ? null
-                : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash);
-        }
-
         private void ResetInvalidLoginAttemptCount(User user)
         private void ResetInvalidLoginAttemptCount(User user)
         {
         {
             user.Policy.InvalidLoginAttemptCount = 0;
             user.Policy.InvalidLoginAttemptCount = 0;
@@ -538,6 +528,8 @@ namespace Emby.Server.Implementations.Library
                 defaultName = "MyJellyfinUser";
                 defaultName = "MyJellyfinUser";
             }
             }
 
 
+            _logger.LogWarning("No users, creating one with username {UserName}", defaultName);
+
             var name = MakeValidUsername(defaultName);
             var name = MakeValidUsername(defaultName);
 
 
             var user = InstantiateNewUser(name);
             var user = InstantiateNewUser(name);
@@ -601,7 +593,7 @@ namespace Emby.Server.Implementations.Library
                 catch (Exception ex)
                 catch (Exception ex)
                 {
                 {
                     // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
                     // 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);
+                    _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {User}", user.Name);
                 }
                 }
             }
             }
 
 
@@ -625,7 +617,7 @@ namespace Emby.Server.Implementations.Library
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
-                _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path);
+                _logger.LogError(ex, "Error getting {ImageType} image info for {ImagePath}", image.Type, image.Path);
                 return null;
                 return null;
             }
             }
         }
         }
@@ -639,7 +631,7 @@ namespace Emby.Server.Implementations.Library
         {
         {
             foreach (var user in Users)
             foreach (var user in Users)
             {
             {
-                await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false);
+                await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
             }
             }
         }
         }
 
 

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

@@ -42,6 +42,11 @@ namespace Emby.Server.Implementations.Library
         {
         {
             var user = _userManager.GetUserById(query.UserId);
             var user = _userManager.GetUserById(query.UserId);
 
 
+            if (user == null)
+            {
+                throw new ArgumentException("User Id specified in the query does not exist.", nameof(query));
+            }
+
             var folders = _libraryManager.GetUserRootFolder()
             var folders = _libraryManager.GetUserRootFolder()
                 .GetChildren(user, true)
                 .GetChildren(user, true)
                 .OfType<Folder>()
                 .OfType<Folder>()
@@ -54,7 +59,7 @@ namespace Emby.Server.Implementations.Library
             foreach (var folder in folders)
             foreach (var folder in folders)
             {
             {
                 var collectionFolder = folder as ICollectionFolder;
                 var collectionFolder = folder as ICollectionFolder;
-                var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType;
+                var folderViewType = collectionFolder?.CollectionType;
 
 
                 if (UserView.IsUserSpecific(folder))
                 if (UserView.IsUserSpecific(folder))
                 {
                 {
@@ -130,16 +135,11 @@ namespace Emby.Server.Implementations.Library
                 {
                 {
                     var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture));
                     var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture));
 
 
-                    if (index == -1)
+                    if (index == -1
+                        && i is UserView view
+                        && view.DisplayParentId != Guid.Empty)
                     {
                     {
-                        var view = i as UserView;
-                        if (view != null)
-                        {
-                            if (!view.DisplayParentId.Equals(Guid.Empty))
-                            {
-                                index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture));
-                            }
-                        }
+                        index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture));
                     }
                     }
 
 
                     return index == -1 ? int.MaxValue : index;
                     return index == -1 ? int.MaxValue : index;

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

@@ -28,10 +28,11 @@ namespace Emby.Server.Implementations.Library.Validators
         private readonly IItemRepository _itemRepo;
         private readonly IItemRepository _itemRepo;
 
 
         /// <summary>
         /// <summary>
-        /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
+        /// Initializes a new instance of the <see cref="ArtistsValidator" /> class.
         /// </summary>
         /// </summary>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="logger">The logger.</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 logger, IItemRepository itemRepo)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;

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

@@ -10,17 +10,18 @@ namespace Emby.Server.Implementations.Library.Validators
     public class GenresPostScanTask : ILibraryPostScanTask
     public class GenresPostScanTask : ILibraryPostScanTask
     {
     {
         /// <summary>
         /// <summary>
-        /// The _library manager
+        /// The _library manager.
         /// </summary>
         /// </summary>
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
         private readonly ILogger _logger;
         private readonly ILogger _logger;
         private readonly IItemRepository _itemRepo;
         private readonly IItemRepository _itemRepo;
 
 
         /// <summary>
         /// <summary>
-        /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
+        /// Initializes a new instance of the <see cref="GenresPostScanTask" /> class.
         /// </summary>
         /// </summary>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="logger">The logger.</param>
+        /// <param name="itemRepo">The item repository.</param>
         public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
         public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;

+ 2 - 1
Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs

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

+ 18 - 10
Emby.Server.Implementations/Library/Validators/PeopleValidator.cs

@@ -11,16 +11,17 @@ using Microsoft.Extensions.Logging;
 namespace Emby.Server.Implementations.Library.Validators
 namespace Emby.Server.Implementations.Library.Validators
 {
 {
     /// <summary>
     /// <summary>
-    /// Class PeopleValidator
+    /// Class PeopleValidator.
     /// </summary>
     /// </summary>
     public class PeopleValidator
     public class PeopleValidator
     {
     {
         /// <summary>
         /// <summary>
-        /// The _library manager
+        /// The _library manager.
         /// </summary>
         /// </summary>
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
+
         /// <summary>
         /// <summary>
-        /// The _logger
+        /// The _logger.
         /// </summary>
         /// </summary>
         private readonly ILogger _logger;
         private readonly ILogger _logger;
 
 
@@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Validators
                 {
                 {
                     var item = _libraryManager.GetPerson(person);
                     var item = _libraryManager.GetPerson(person);
 
 
-                    var options = new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
+                    var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
                     {
                     {
                         ImageRefreshMode = MetadataRefreshMode.ValidationOnly,
                         ImageRefreshMode = MetadataRefreshMode.ValidationOnly,
                         MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
                         MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
@@ -96,12 +97,19 @@ namespace Emby.Server.Implementations.Library.Validators
 
 
             foreach (var item in deadEntities)
             foreach (var item in deadEntities)
             {
             {
-                _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
-
-                _libraryManager.DeleteItem(item, new DeleteOptions
-                {
-                    DeleteFileLocation = false
-                }, false);
+                _logger.LogInformation(
+                    "Deleting dead {2} {0} {1}.",
+                    item.Id.ToString("N", CultureInfo.InvariantCulture),
+                    item.Name,
+                    item.GetType().Name);
+
+                _libraryManager.DeleteItem(
+                    item,
+                    new DeleteOptions
+                    {
+                        DeleteFileLocation = false
+                    },
+                    false);
             }
             }
 
 
             progress.Report(100);
             progress.Report(100);

+ 3 - 1
Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs

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

+ 12 - 10
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
             if (requiresRefresh)
             if (requiresRefresh)
             {
             {
-                await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None);
+                await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None).ConfigureAwait(false);
             }
             }
         }
         }
 
 
@@ -1489,16 +1489,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             {
             {
                 _logger.LogInformation("Refreshing recording parent {path}", item.Path);
                 _logger.LogInformation("Refreshing recording parent {path}", item.Path);
 
 
-                _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
-                {
-                    RefreshPaths = new string[]
+                _providerManager.QueueRefresh(
+                    item.Id,
+                    new MetadataRefreshOptions(new DirectoryService(_fileSystem))
                     {
                     {
-                        path,
-                        Path.GetDirectoryName(path),
-                        Path.GetDirectoryName(Path.GetDirectoryName(path))
-                    }
-
-                }, RefreshPriority.High);
+                        RefreshPaths = new string[]
+                        {
+                            path,
+                            Path.GetDirectoryName(path),
+                            Path.GetDirectoryName(Path.GetDirectoryName(path))
+                        }
+                    },
+                    RefreshPriority.High);
             }
             }
         }
         }
 
 

+ 5 - 5
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs

@@ -501,7 +501,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
 
         public async Task<List<NameIdPair>> GetHeadends(ListingsProviderInfo info, string country, string location, CancellationToken cancellationToken)
         public async Task<List<NameIdPair>> GetHeadends(ListingsProviderInfo info, string country, string location, CancellationToken cancellationToken)
         {
         {
-            var token = await GetToken(info, cancellationToken);
+            var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
 
 
             var lineups = new List<NameIdPair>();
             var lineups = new List<NameIdPair>();
 
 
@@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
 
         private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
         private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
         {
         {
-            var token = await GetToken(info, cancellationToken);
+            var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
 
 
             if (string.IsNullOrEmpty(token))
             if (string.IsNullOrEmpty(token))
             {
             {
@@ -738,7 +738,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
 
             httpOptions.RequestHeaders["token"] = token;
             httpOptions.RequestHeaders["token"] = token;
 
 
-            using (await _httpClient.SendAsync(httpOptions, "PUT"))
+            using (await _httpClient.SendAsync(httpOptions, "PUT").ConfigureAwait(false))
             {
             {
             }
             }
         }
         }
@@ -750,7 +750,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 throw new ArgumentException("Listings Id required");
                 throw new ArgumentException("Listings Id required");
             }
             }
 
 
-            var token = await GetToken(info, cancellationToken);
+            var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
 
 
             if (string.IsNullOrEmpty(token))
             if (string.IsNullOrEmpty(token))
             {
             {
@@ -833,7 +833,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 throw new Exception("ListingsId required");
                 throw new Exception("ListingsId required");
             }
             }
 
 
-            var token = await GetToken(info, cancellationToken);
+            var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
 
 
             if (string.IsNullOrEmpty(token))
             if (string.IsNullOrEmpty(token))
             {
             {

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

@@ -1226,12 +1226,13 @@ namespace Emby.Server.Implementations.LiveTv
                         currentChannel.AddTag("Kids");
                         currentChannel.AddTag("Kids");
                     }
                     }
 
 
-                    //currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
-                    await currentChannel.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
-                    {
-                        ForceSave = true
-
-                    }, cancellationToken).ConfigureAwait(false);
+                    currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
+                    await currentChannel.RefreshMetadata(
+                        new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+                        {
+                            ForceSave = true
+                        },
+                        cancellationToken).ConfigureAwait(false);
                 }
                 }
                 catch (OperationCanceledException)
                 catch (OperationCanceledException)
                 {
                 {
@@ -1245,7 +1246,7 @@ namespace Emby.Server.Implementations.LiveTv
                 numComplete++;
                 numComplete++;
                 double percent = numComplete / (double)allChannelsList.Count;
                 double percent = numComplete / (double)allChannelsList.Count;
 
 
-                progress.Report(85 * percent + 15);
+                progress.Report((85 * percent) + 15);
             }
             }
 
 
             progress.Report(100);
             progress.Report(100);
@@ -1278,12 +1279,14 @@ namespace Emby.Server.Implementations.LiveTv
 
 
                     if (item != null)
                     if (item != null)
                     {
                     {
-                        _libraryManager.DeleteItem(item, new DeleteOptions
-                        {
-                            DeleteFileLocation = false,
-                            DeleteFromExternalProvider = false
-
-                        }, false);
+                        _libraryManager.DeleteItem(
+                            item,
+                            new DeleteOptions
+                            {
+                                DeleteFileLocation = false,
+                                DeleteFromExternalProvider = false
+                            },
+                            false);
                     }
                     }
                 }
                 }
 
 
@@ -2301,8 +2304,10 @@ namespace Emby.Server.Implementations.LiveTv
             if (provider == null)
             if (provider == null)
             {
             {
                 throw new ResourceNotFoundException(
                 throw new ResourceNotFoundException(
-                    string.Format("Couldn't find provider of type: '{0}'", info.Type)
-                );
+                    string.Format(
+                        CultureInfo.InvariantCulture,
+                        "Couldn't find provider of type: '{0}'",
+                        info.Type));
             }
             }
 
 
             await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);
             await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);

+ 2 - 2
Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs

@@ -38,8 +38,8 @@ namespace Emby.Server.Implementations.LiveTv
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
         public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
         public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
         {
         {
-            return new[] {
-
+            return new[]
+            {
                 // Every so often
                 // Every so often
                 new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
                 new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
             };
             };

+ 6 - 7
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

@@ -185,7 +185,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
                 Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
                 CancellationToken = cancellationToken,
                 CancellationToken = cancellationToken,
                 BufferContent = false
                 BufferContent = false
-            }, HttpMethod.Get))
+            }, HttpMethod.Get).ConfigureAwait(false))
             using (var stream = response.Content)
             using (var stream = response.Content)
             using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
             using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
             {
             {
@@ -259,7 +259,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 for (int i = 0; i < model.TunerCount; ++i)
                 for (int i = 0; i < model.TunerCount; ++i)
                 {
                 {
                     var name = string.Format("Tuner {0}", i + 1);
                     var name = string.Format("Tuner {0}", i + 1);
-                    var currentChannel = "none"; /// @todo Get current channel and map back to Station Id
+                    var currentChannel = "none"; // @todo Get current channel and map back to Station Id
                     var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false);
                     var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false);
                     var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv;
                     var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv;
                     tuners.Add(new LiveTvTunerInfo
                     tuners.Add(new LiveTvTunerInfo
@@ -298,7 +298,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
         public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
         public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
         {
         {
             // TODO Need faster way to determine UDP vs HTTP
             // TODO Need faster way to determine UDP vs HTTP
-            var channels = await GetChannels(info, true, cancellationToken);
+            var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false);
 
 
             var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo;
             var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo;
 
 
@@ -582,11 +582,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                     modelInfo.TunerCount,
                     modelInfo.TunerCount,
                     FileSystem,
                     FileSystem,
                     Logger,
                     Logger,
-                    Config.ApplicationPaths,
+                    Config,
                     _appHost,
                     _appHost,
                     _networkManager,
                     _networkManager,
                     _streamHelper);
                     _streamHelper);
-
             }
             }
 
 
             var enableHttpStream = true;
             var enableHttpStream = true;
@@ -611,7 +610,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                     FileSystem,
                     FileSystem,
                     _httpClient,
                     _httpClient,
                     Logger,
                     Logger,
-                    Config.ApplicationPaths,
+                    Config,
                     _appHost,
                     _appHost,
                     _streamHelper);
                     _streamHelper);
             }
             }
@@ -624,7 +623,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 modelInfo.TunerCount,
                 modelInfo.TunerCount,
                 FileSystem,
                 FileSystem,
                 Logger,
                 Logger,
-                Config.ApplicationPaths,
+                Config,
                 _appHost,
                 _appHost,
                 _networkManager,
                 _networkManager,
                 _streamHelper);
                 _streamHelper);

+ 3 - 2
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs

@@ -6,6 +6,7 @@ using System.Net.Sockets;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
@@ -33,11 +34,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             int numTuners,
             int numTuners,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             ILogger logger,
             ILogger logger,
-            IServerApplicationPaths appPaths,
+            IConfigurationManager configurationManager,
             IServerApplicationHost appHost,
             IServerApplicationHost appHost,
             INetworkManager networkManager,
             INetworkManager networkManager,
             IStreamHelper streamHelper)
             IStreamHelper streamHelper)
-            : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
+            : base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
         {
         {
             _appHost = appHost;
             _appHost = appHost;
             _networkManager = networkManager;
             _networkManager = networkManager;

+ 7 - 5
Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs

@@ -5,8 +5,8 @@ using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.LiveTv;
@@ -16,8 +16,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 {
 {
     public class LiveStream : ILiveStream
     public class LiveStream : ILiveStream
     {
     {
+        private readonly IConfigurationManager _configurationManager;
+
         protected readonly IFileSystem FileSystem;
         protected readonly IFileSystem FileSystem;
-        protected readonly IServerApplicationPaths AppPaths;
+
         protected readonly IStreamHelper StreamHelper;
         protected readonly IStreamHelper StreamHelper;
 
 
         protected string TempFilePath;
         protected string TempFilePath;
@@ -29,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             TunerHostInfo tuner,
             TunerHostInfo tuner,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             ILogger logger,
             ILogger logger,
-            IServerApplicationPaths appPaths,
+            IConfigurationManager configurationManager,
             IStreamHelper streamHelper)
             IStreamHelper streamHelper)
         {
         {
             OriginalMediaSource = mediaSource;
             OriginalMediaSource = mediaSource;
@@ -44,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
                 TunerHostId = tuner.Id;
                 TunerHostId = tuner.Id;
             }
             }
 
 
-            AppPaths = appPaths;
+            _configurationManager = configurationManager;
             StreamHelper = streamHelper;
             StreamHelper = streamHelper;
 
 
             ConsumerCount = 1;
             ConsumerCount = 1;
@@ -68,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
 
         protected void SetTempFilePath(string extension)
         protected void SetTempFilePath(string extension)
         {
         {
-            TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
+            TempFilePath = Path.Combine(_configurationManager.GetTranscodePath(), UniqueId + "." + extension);
         }
         }
 
 
         public virtual Task Open(CancellationToken openCancellationToken)
         public virtual Task Open(CancellationToken openCancellationToken)

+ 2 - 2
Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs

@@ -114,11 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
 
                 if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
                 if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
                 {
                 {
-                    return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
+                    return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config, _appHost, _streamHelper);
                 }
                 }
             }
             }
 
 
-            return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths, _streamHelper);
+            return new LiveStream(mediaSource, info, FileSystem, Logger, Config, _streamHelper);
         }
         }
 
 
         public async Task Validate(TunerHostInfo info)
         public async Task Validate(TunerHostInfo info)

+ 3 - 2
Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -26,10 +27,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             IHttpClient httpClient,
             IHttpClient httpClient,
             ILogger logger,
             ILogger logger,
-            IServerApplicationPaths appPaths,
+            IConfigurationManager configurationManager,
             IServerApplicationHost appHost,
             IServerApplicationHost appHost,
             IStreamHelper streamHelper)
             IStreamHelper streamHelper)
-            : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
+            : base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
         {
         {
             _httpClient = httpClient;
             _httpClient = httpClient;
             _appHost = appHost;
             _appHost = appHost;

+ 96 - 0
Emby.Server.Implementations/Localization/Core/af.json

@@ -0,0 +1,96 @@
+{
+    "Artists": "Kunstenare",
+    "Channels": "Kanale",
+    "Folders": "Fouers",
+    "Favorites": "Gunstelinge",
+    "HeaderFavoriteShows": "Gunsteling Vertonings",
+    "ValueSpecialEpisodeName": "Spesiaal - {0}",
+    "HeaderAlbumArtists": "Album Kunstenaars",
+    "Books": "Boeke",
+    "HeaderNextUp": "Volgende",
+    "Movies": "Rolprente",
+    "Shows": "Program",
+    "HeaderContinueWatching": "Hou Aan Kyk",
+    "HeaderFavoriteEpisodes": "Gunsteling Episodes",
+    "Photos": "Fotos",
+    "Playlists": "Speellysse",
+    "HeaderFavoriteArtists": "Gunsteling Kunstenaars",
+    "HeaderFavoriteAlbums": "Gunsteling Albums",
+    "Sync": "Sinkroniseer",
+    "HeaderFavoriteSongs": "Gunsteling Liedjies",
+    "Songs": "Liedjies",
+    "DeviceOnlineWithName": "{0} is verbind",
+    "DeviceOfflineWithName": "{0} het afgesluit",
+    "Collections": "Versamelings",
+    "Inherit": "Ontvang",
+    "HeaderLiveTV": "Live TV",
+    "Application": "Program",
+    "AppDeviceValues": "App: {0}, Toestel: {1}",
+    "VersionNumber": "Weergawe {0}",
+    "ValueHasBeenAddedToLibrary": "{0} is by jou media biblioteek bygevoeg",
+    "UserStoppedPlayingItemWithValues": "{0} het klaar {1} op {2} gespeel",
+    "UserStartedPlayingItemWithValues": "{0} is besig om {1} op {2} te speel",
+    "UserPolicyUpdatedWithName": "Gebruiker beleid is verander vir {0}",
+    "UserPasswordChangedWithName": "Gebruiker {0} se wagwoord is verander",
+    "UserOnlineFromDevice": "{0} is aanlyn van {1}",
+    "UserOfflineFromDevice": "{0} is ontkoppel van {1}",
+    "UserLockedOutWithName": "Gebruiker {0} is uitgesluit",
+    "UserDownloadingItemWithValues": "{0} is besig om {1} af te laai",
+    "UserDeletedWithName": "Gebruiker {0} is verwyder",
+    "UserCreatedWithName": "Gebruiker {0} is geskep",
+    "User": "Gebruiker",
+    "TvShows": "TV Programme",
+    "System": "Stelsel",
+    "SubtitlesDownloadedForItem": "Ondertitels afgelaai vir {0}",
+    "SubtitleDownloadFailureFromForItem": "Ondertitels het misluk om af te laai van {0} vir {1}",
+    "StartupEmbyServerIsLoading": "Jellyfin Bediener is besig om te laai. Probeer weer in 'n kort tyd.",
+    "ServerNameNeedsToBeRestarted": "{0} moet herbegin word",
+    "ScheduledTaskStartedWithName": "{0} het begin",
+    "ScheduledTaskFailedWithName": "{0} het misluk",
+    "ProviderValue": "Voorsiener: {0}",
+    "PluginUpdatedWithName": "{0} was opgedateer",
+    "PluginUninstalledWithName": "{0} was verwyder",
+    "PluginInstalledWithName": "{0} is geïnstalleer",
+    "Plugin": "Inprop module",
+    "NotificationOptionVideoPlaybackStopped": "Video terugspeel het gestop",
+    "NotificationOptionVideoPlayback": "Video terugspeel het begin",
+    "NotificationOptionUserLockedOut": "Gebruiker uitgeslyt",
+    "NotificationOptionTaskFailed": "Geskeduleerde taak het misluk",
+    "NotificationOptionServerRestartRequired": "Bediener herbegin nodig",
+    "NotificationOptionPluginUpdateInstalled": "Nuwe inprop module geïnstalleer",
+    "NotificationOptionPluginUninstalled": "Inprop module verwyder",
+    "NotificationOptionPluginInstalled": "Inprop module geïnstalleer",
+    "NotificationOptionPluginError": "Inprop module het misluk",
+    "NotificationOptionNewLibraryContent": "Nuwe inhoud bygevoeg",
+    "NotificationOptionInstallationFailed": "Installering het misluk",
+    "NotificationOptionCameraImageUploaded": "Kamera foto is opgelaai",
+    "NotificationOptionAudioPlaybackStopped": "Oudio terugspeel het gestop",
+    "NotificationOptionAudioPlayback": "Oudio terugspeel het begin",
+    "NotificationOptionApplicationUpdateInstalled": "Nuwe program weergawe geïnstalleer",
+    "NotificationOptionApplicationUpdateAvailable": "Nuwe program weergawe beskikbaar",
+    "NewVersionIsAvailable": "'n Nuwe Jellyfin Bedienaar weergawe kan afgelaai word.",
+    "NameSeasonUnknown": "Seisoen Onbekend",
+    "NameSeasonNumber": "Seisoen {0}",
+    "NameInstallFailed": "{0} installering het misluk",
+    "MusicVideos": "Musiek videos",
+    "Music": "Musiek",
+    "MixedContent": "Gemengde inhoud",
+    "MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
+    "MessageNamedServerConfigurationUpdatedWithValue": "Bediener konfigurasie seksie {0} is opgedateer",
+    "MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
+    "MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
+    "Latest": "Nuutste",
+    "LabelRunningTimeValue": "Lopende tyd: {0}",
+    "LabelIpAddressValue": "IP adres: {0}",
+    "ItemRemovedWithName": "{0} is uit versameling verwyder",
+    "ItemAddedWithName": "{0} is in die versameling",
+    "HomeVideos": "Tuis opnames",
+    "HeaderRecordingGroups": "Groep Opnames",
+    "HeaderCameraUploads": "Kamera Oplaai",
+    "Genres": "Genres",
+    "FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
+    "ChapterNameValue": "Hoofstuk",
+    "CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
+    "AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
+    "Albums": "Albums"
+}

+ 19 - 19
Emby.Server.Implementations/Localization/Core/bg-BG.json

@@ -1,22 +1,22 @@
 {
 {
     "Albums": "Албуми",
     "Albums": "Албуми",
-    "AppDeviceValues": "Програма: {0}, Устройство: {1}",
+    "AppDeviceValues": "Програма: {0}, устройство: {1}",
     "Application": "Програма",
     "Application": "Програма",
     "Artists": "Изпълнители",
     "Artists": "Изпълнители",
     "AuthenticationSucceededWithUserName": "{0} се удостовери успешно",
     "AuthenticationSucceededWithUserName": "{0} се удостовери успешно",
     "Books": "Книги",
     "Books": "Книги",
-    "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
+    "CameraImageUploadedFrom": "",
     "Channels": "Канали",
     "Channels": "Канали",
     "ChapterNameValue": "Глава {0}",
     "ChapterNameValue": "Глава {0}",
     "Collections": "Колекции",
     "Collections": "Колекции",
     "DeviceOfflineWithName": "{0} се разкачи",
     "DeviceOfflineWithName": "{0} се разкачи",
     "DeviceOnlineWithName": "{0} е свързан",
     "DeviceOnlineWithName": "{0} е свързан",
-    "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
+    "FailedLoginAttemptWithUserName": "",
     "Favorites": "Любими",
     "Favorites": "Любими",
     "Folders": "Папки",
     "Folders": "Папки",
     "Genres": "Жанрове",
     "Genres": "Жанрове",
     "HeaderAlbumArtists": "Изпълнители на албуми",
     "HeaderAlbumArtists": "Изпълнители на албуми",
-    "HeaderCameraUploads": "Camera Uploads",
+    "HeaderCameraUploads": "",
     "HeaderContinueWatching": "Продължаване на гледането",
     "HeaderContinueWatching": "Продължаване на гледането",
     "HeaderFavoriteAlbums": "Любими албуми",
     "HeaderFavoriteAlbums": "Любими албуми",
     "HeaderFavoriteArtists": "Любими изпълнители",
     "HeaderFavoriteArtists": "Любими изпълнители",
@@ -25,26 +25,26 @@
     "HeaderFavoriteSongs": "Любими песни",
     "HeaderFavoriteSongs": "Любими песни",
     "HeaderLiveTV": "Телевизия на живо",
     "HeaderLiveTV": "Телевизия на живо",
     "HeaderNextUp": "Следва",
     "HeaderNextUp": "Следва",
-    "HeaderRecordingGroups": "Recording Groups",
+    "HeaderRecordingGroups": "",
     "HomeVideos": "Домашни клипове",
     "HomeVideos": "Домашни клипове",
     "Inherit": "Наследяване",
     "Inherit": "Наследяване",
     "ItemAddedWithName": "{0} е добавено към библиотеката",
     "ItemAddedWithName": "{0} е добавено към библиотеката",
     "ItemRemovedWithName": "{0} е премахнато от библиотеката",
     "ItemRemovedWithName": "{0} е премахнато от библиотеката",
     "LabelIpAddressValue": "ИП адрес: {0}",
     "LabelIpAddressValue": "ИП адрес: {0}",
-    "LabelRunningTimeValue": "Running time: {0}",
+    "LabelRunningTimeValue": "",
     "Latest": "Последни",
     "Latest": "Последни",
     "MessageApplicationUpdated": "Сървърът е обновен",
     "MessageApplicationUpdated": "Сървърът е обновен",
-    "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
-    "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
-    "MessageServerConfigurationUpdated": "Server configuration has been updated",
+    "MessageApplicationUpdatedTo": "",
+    "MessageNamedServerConfigurationUpdatedWithValue": "",
+    "MessageServerConfigurationUpdated": "",
     "MixedContent": "Смесено съдържание",
     "MixedContent": "Смесено съдържание",
     "Movies": "Филми",
     "Movies": "Филми",
     "Music": "Музика",
     "Music": "Музика",
     "MusicVideos": "Музикални клипове",
     "MusicVideos": "Музикални клипове",
-    "NameInstallFailed": "{0} installation failed",
+    "NameInstallFailed": "",
     "NameSeasonNumber": "Сезон {0}",
     "NameSeasonNumber": "Сезон {0}",
-    "NameSeasonUnknown": "Season Unknown",
-    "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
+    "NameSeasonUnknown": "Неразпознат сезон",
+    "NewVersionIsAvailable": "",
     "NotificationOptionApplicationUpdateAvailable": "Налично е обновление на програмата",
     "NotificationOptionApplicationUpdateAvailable": "Налично е обновление на програмата",
     "NotificationOptionApplicationUpdateInstalled": "Обновлението на програмата е инсталирано",
     "NotificationOptionApplicationUpdateInstalled": "Обновлението на програмата е инсталирано",
     "NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна",
     "NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна",
@@ -58,7 +58,7 @@
     "NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано",
     "NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано",
     "NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра",
     "NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра",
     "NotificationOptionTaskFailed": "Грешка в планирана задача",
     "NotificationOptionTaskFailed": "Грешка в планирана задача",
-    "NotificationOptionUserLockedOut": "User locked out",
+    "NotificationOptionUserLockedOut": "",
     "NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
     "NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
     "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
     "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
     "Photos": "Снимки",
     "Photos": "Снимки",
@@ -70,12 +70,12 @@
     "ProviderValue": "Доставчик: {0}",
     "ProviderValue": "Доставчик: {0}",
     "ScheduledTaskFailedWithName": "{0} се провали",
     "ScheduledTaskFailedWithName": "{0} се провали",
     "ScheduledTaskStartedWithName": "{0} започна",
     "ScheduledTaskStartedWithName": "{0} започна",
-    "ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
+    "ServerNameNeedsToBeRestarted": "",
     "Shows": "Сериали",
     "Shows": "Сериали",
     "Songs": "Песни",
     "Songs": "Песни",
     "StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
     "StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
     "SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}",
     "SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}",
-    "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
+    "SubtitleDownloadFailureFromForItem": "",
     "SubtitlesDownloadedForItem": "Изтеглени са субтитри за {0}",
     "SubtitlesDownloadedForItem": "Изтеглени са субтитри за {0}",
     "Sync": "Синхронизиране",
     "Sync": "Синхронизиране",
     "System": "Система",
     "System": "Система",
@@ -83,15 +83,15 @@
     "User": "Потребител",
     "User": "Потребител",
     "UserCreatedWithName": "Потребителят {0} е създаден",
     "UserCreatedWithName": "Потребителят {0} е създаден",
     "UserDeletedWithName": "Потребителят {0} е изтрит",
     "UserDeletedWithName": "Потребителят {0} е изтрит",
-    "UserDownloadingItemWithValues": "{0} is downloading {1}",
-    "UserLockedOutWithName": "User {0} has been locked out",
+    "UserDownloadingItemWithValues": "",
+    "UserLockedOutWithName": "",
     "UserOfflineFromDevice": "{0} се разкачи от {1}",
     "UserOfflineFromDevice": "{0} се разкачи от {1}",
     "UserOnlineFromDevice": "{0} е на линия от {1}",
     "UserOnlineFromDevice": "{0} е на линия от {1}",
     "UserPasswordChangedWithName": "Паролата на потребителя {0} е променена",
     "UserPasswordChangedWithName": "Паролата на потребителя {0} е променена",
-    "UserPolicyUpdatedWithName": "User policy has been updated for {0}",
+    "UserPolicyUpdatedWithName": "",
     "UserStartedPlayingItemWithValues": "{0} пусна {1}",
     "UserStartedPlayingItemWithValues": "{0} пусна {1}",
     "UserStoppedPlayingItemWithValues": "{0} спря {1}",
     "UserStoppedPlayingItemWithValues": "{0} спря {1}",
-    "ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
+    "ValueHasBeenAddedToLibrary": "",
     "ValueSpecialEpisodeName": "Специални - {0}",
     "ValueSpecialEpisodeName": "Специални - {0}",
     "VersionNumber": "Версия {0}"
     "VersionNumber": "Версия {0}"
 }
 }

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

@@ -23,7 +23,7 @@
     "HeaderFavoriteEpisodes": "Oblíbené epizody",
     "HeaderFavoriteEpisodes": "Oblíbené epizody",
     "HeaderFavoriteShows": "Oblíbené seriály",
     "HeaderFavoriteShows": "Oblíbené seriály",
     "HeaderFavoriteSongs": "Oblíbená hudba",
     "HeaderFavoriteSongs": "Oblíbená hudba",
-    "HeaderLiveTV": "Live TV",
+    "HeaderLiveTV": "Živá TV",
     "HeaderNextUp": "Nadcházející",
     "HeaderNextUp": "Nadcházející",
     "HeaderRecordingGroups": "Skupiny nahrávek",
     "HeaderRecordingGroups": "Skupiny nahrávek",
     "HomeVideos": "Domáci videa",
     "HomeVideos": "Domáci videa",

+ 4 - 4
Emby.Server.Implementations/Localization/Core/de.json

@@ -3,14 +3,14 @@
     "AppDeviceValues": "App: {0}, Gerät: {1}",
     "AppDeviceValues": "App: {0}, Gerät: {1}",
     "Application": "Anwendung",
     "Application": "Anwendung",
     "Artists": "Interpreten",
     "Artists": "Interpreten",
-    "AuthenticationSucceededWithUserName": "{0} hat sich angemeldet",
+    "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
     "Books": "Bücher",
     "Books": "Bücher",
     "CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
     "CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
     "Channels": "Kanäle",
     "Channels": "Kanäle",
     "ChapterNameValue": "Kapitel {0}",
     "ChapterNameValue": "Kapitel {0}",
     "Collections": "Sammlungen",
     "Collections": "Sammlungen",
     "DeviceOfflineWithName": "{0} wurde getrennt",
     "DeviceOfflineWithName": "{0} wurde getrennt",
-    "DeviceOnlineWithName": "{0} hat sich verbunden",
+    "DeviceOnlineWithName": "{0} ist verbunden",
     "FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
     "FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
     "Favorites": "Favoriten",
     "Favorites": "Favoriten",
     "Folders": "Verzeichnisse",
     "Folders": "Verzeichnisse",
@@ -23,7 +23,7 @@
     "HeaderFavoriteEpisodes": "Lieblingsepisoden",
     "HeaderFavoriteEpisodes": "Lieblingsepisoden",
     "HeaderFavoriteShows": "Lieblingsserien",
     "HeaderFavoriteShows": "Lieblingsserien",
     "HeaderFavoriteSongs": "Lieblingslieder",
     "HeaderFavoriteSongs": "Lieblingslieder",
-    "HeaderLiveTV": "Live-TV",
+    "HeaderLiveTV": "Live TV",
     "HeaderNextUp": "Als Nächstes",
     "HeaderNextUp": "Als Nächstes",
     "HeaderRecordingGroups": "Aufnahme-Gruppen",
     "HeaderRecordingGroups": "Aufnahme-Gruppen",
     "HomeVideos": "Heimvideos",
     "HomeVideos": "Heimvideos",
@@ -35,7 +35,7 @@
     "Latest": "Neueste",
     "Latest": "Neueste",
     "MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert",
     "MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert",
     "MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert",
     "MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert",
-    "MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert",
+    "MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert",
     "MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert",
     "MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert",
     "MixedContent": "Gemischte Inhalte",
     "MixedContent": "Gemischte Inhalte",
     "Movies": "Filme",
     "Movies": "Filme",

+ 6 - 6
Emby.Server.Implementations/Localization/Core/fr.json

@@ -5,7 +5,7 @@
     "Artists": "Artistes",
     "Artists": "Artistes",
     "AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès",
     "AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès",
     "Books": "Livres",
     "Books": "Livres",
-    "CameraImageUploadedFrom": "Une image de caméra a été chargée depuis {0}",
+    "CameraImageUploadedFrom": "Une nouvelle image de caméra a été chargée depuis {0}",
     "Channels": "Chaînes",
     "Channels": "Chaînes",
     "ChapterNameValue": "Chapitre {0}",
     "ChapterNameValue": "Chapitre {0}",
     "Collections": "Collections",
     "Collections": "Collections",
@@ -17,7 +17,7 @@
     "Genres": "Genres",
     "Genres": "Genres",
     "HeaderAlbumArtists": "Artistes de l'album",
     "HeaderAlbumArtists": "Artistes de l'album",
     "HeaderCameraUploads": "Photos transférées",
     "HeaderCameraUploads": "Photos transférées",
-    "HeaderContinueWatching": "Reprendre",
+    "HeaderContinueWatching": "Continuer à regarder",
     "HeaderFavoriteAlbums": "Albums favoris",
     "HeaderFavoriteAlbums": "Albums favoris",
     "HeaderFavoriteArtists": "Artistes favoris",
     "HeaderFavoriteArtists": "Artistes favoris",
     "HeaderFavoriteEpisodes": "Épisodes favoris",
     "HeaderFavoriteEpisodes": "Épisodes favoris",
@@ -34,14 +34,14 @@
     "LabelRunningTimeValue": "Durée : {0}",
     "LabelRunningTimeValue": "Durée : {0}",
     "Latest": "Derniers",
     "Latest": "Derniers",
     "MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
     "MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
-    "MessageApplicationUpdatedTo": "Jellyfin Serveur a été mis à jour en version {0}",
+    "MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour vers {0}",
     "MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour",
     "MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour",
     "MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour",
     "MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour",
     "MixedContent": "Contenu mixte",
     "MixedContent": "Contenu mixte",
     "Movies": "Films",
     "Movies": "Films",
     "Music": "Musique",
     "Music": "Musique",
     "MusicVideos": "Vidéos musicales",
     "MusicVideos": "Vidéos musicales",
-    "NameInstallFailed": "{0} échec d'installation",
+    "NameInstallFailed": "{0} échec de l'installation",
     "NameSeasonNumber": "Saison {0}",
     "NameSeasonNumber": "Saison {0}",
     "NameSeasonUnknown": "Saison Inconnue",
     "NameSeasonUnknown": "Saison Inconnue",
     "NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.",
     "NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.",
@@ -50,7 +50,7 @@
     "NotificationOptionAudioPlayback": "Lecture audio démarrée",
     "NotificationOptionAudioPlayback": "Lecture audio démarrée",
     "NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée",
     "NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée",
     "NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été transférée",
     "NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été transférée",
-    "NotificationOptionInstallationFailed": "Échec d'installation",
+    "NotificationOptionInstallationFailed": "Échec de l'installation",
     "NotificationOptionNewLibraryContent": "Nouveau contenu ajouté",
     "NotificationOptionNewLibraryContent": "Nouveau contenu ajouté",
     "NotificationOptionPluginError": "Erreur d'extension",
     "NotificationOptionPluginError": "Erreur d'extension",
     "NotificationOptionPluginInstalled": "Extension installée",
     "NotificationOptionPluginInstalled": "Extension installée",
@@ -91,7 +91,7 @@
     "UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}",
     "UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}",
     "UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}",
     "UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}",
     "UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
     "UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
-    "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre librairie",
+    "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
     "ValueSpecialEpisodeName": "Spécial - {0}",
     "ValueSpecialEpisodeName": "Spécial - {0}",
     "VersionNumber": "Version {0}"
     "VersionNumber": "Version {0}"
 }
 }

+ 30 - 30
Emby.Server.Implementations/Localization/Core/he.json

@@ -1,41 +1,41 @@
 {
 {
     "Albums": "אלבומים",
     "Albums": "אלבומים",
-    "AppDeviceValues": "App: {0}, Device: {1}",
+    "AppDeviceValues": "יישום: {0}, מכשיר: {1}",
     "Application": "אפליקציה",
     "Application": "אפליקציה",
     "Artists": "אמנים",
     "Artists": "אמנים",
-    "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
+    "AuthenticationSucceededWithUserName": "{0} זוהה בהצלחה",
     "Books": "ספרים",
     "Books": "ספרים",
-    "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
-    "Channels": "Channels",
-    "ChapterNameValue": "Chapter {0}",
-    "Collections": "Collections",
-    "DeviceOfflineWithName": "{0} has disconnected",
-    "DeviceOnlineWithName": "{0} is connected",
-    "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
-    "Favorites": "Favorites",
-    "Folders": "Folders",
+    "CameraImageUploadedFrom": "תמונה חדשה הועלתה מ{0}",
+    "Channels": "ערוצים",
+    "ChapterNameValue": "פרק {0}",
+    "Collections": "קולקציות",
+    "DeviceOfflineWithName": "{0} התנתק",
+    "DeviceOnlineWithName": "{0} מחובר",
+    "FailedLoginAttemptWithUserName": "ניסיון כניסה שגוי מ{0}",
+    "Favorites": "אהובים",
+    "Folders": "תיקיות",
     "Genres": "ז'אנרים",
     "Genres": "ז'אנרים",
-    "HeaderAlbumArtists": "Album Artists",
-    "HeaderCameraUploads": "Camera Uploads",
-    "HeaderContinueWatching": "המשך בצפייה",
-    "HeaderFavoriteAlbums": "Favorite Albums",
-    "HeaderFavoriteArtists": "Favorite Artists",
-    "HeaderFavoriteEpisodes": "Favorite Episodes",
-    "HeaderFavoriteShows": "Favorite Shows",
-    "HeaderFavoriteSongs": "Favorite Songs",
-    "HeaderLiveTV": "Live TV",
-    "HeaderNextUp": "Next Up",
+    "HeaderAlbumArtists": "אמני האלבום",
+    "HeaderCameraUploads": "העלאות ממצלמה",
+    "HeaderContinueWatching": "המשך לצפות",
+    "HeaderFavoriteAlbums": "אלבומים שאהבתי",
+    "HeaderFavoriteArtists": "אמנים שאהבתי",
+    "HeaderFavoriteEpisodes": "פרקים אהובים",
+    "HeaderFavoriteShows": "תוכניות אהובות",
+    "HeaderFavoriteSongs": "שירים שאהבתי",
+    "HeaderLiveTV": "טלוויזיה בשידור חי",
+    "HeaderNextUp": "הבא",
     "HeaderRecordingGroups": "קבוצות הקלטה",
     "HeaderRecordingGroups": "קבוצות הקלטה",
-    "HomeVideos": "Home videos",
-    "Inherit": "Inherit",
+    "HomeVideos": "סרטונים בייתים",
+    "Inherit": "הורש",
     "ItemAddedWithName": "{0} was added to the library",
     "ItemAddedWithName": "{0} was added to the library",
-    "ItemRemovedWithName": "{0} was removed from the library",
-    "LabelIpAddressValue": "Ip address: {0}",
-    "LabelRunningTimeValue": "Running time: {0}",
+    "ItemRemovedWithName": "{0} נמחק מהספרייה",
+    "LabelIpAddressValue": "Ip כתובת: {0}",
+    "LabelRunningTimeValue": "משך צפייה: {0}",
     "Latest": "אחרון",
     "Latest": "אחרון",
-    "MessageApplicationUpdated": "Jellyfin Server has been updated",
-    "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
-    "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
+    "MessageApplicationUpdated": "שרת הJellyfin עודכן",
+    "MessageApplicationUpdatedTo": "שרת הJellyfin עודכן לגרסא {0}",
+    "MessageNamedServerConfigurationUpdatedWithValue": "הגדרת השרת {0} שונתה",
     "MessageServerConfigurationUpdated": "Server configuration has been updated",
     "MessageServerConfigurationUpdated": "Server configuration has been updated",
     "MixedContent": "תוכן מעורב",
     "MixedContent": "תוכן מעורב",
     "Movies": "סרטים",
     "Movies": "סרטים",
@@ -50,7 +50,7 @@
     "NotificationOptionAudioPlayback": "Audio playback started",
     "NotificationOptionAudioPlayback": "Audio playback started",
     "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
     "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
     "NotificationOptionCameraImageUploaded": "Camera image uploaded",
     "NotificationOptionCameraImageUploaded": "Camera image uploaded",
-    "NotificationOptionInstallationFailed": "Installation failure",
+    "NotificationOptionInstallationFailed": "התקנה נכשלה",
     "NotificationOptionNewLibraryContent": "New content added",
     "NotificationOptionNewLibraryContent": "New content added",
     "NotificationOptionPluginError": "Plugin failure",
     "NotificationOptionPluginError": "Plugin failure",
     "NotificationOptionPluginInstalled": "Plugin installed",
     "NotificationOptionPluginInstalled": "Plugin installed",

+ 19 - 19
Emby.Server.Implementations/Localization/Core/hu.json

@@ -5,7 +5,7 @@
     "Artists": "Előadók",
     "Artists": "Előadók",
     "AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva",
     "AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva",
     "Books": "Könyvek",
     "Books": "Könyvek",
-    "CameraImageUploadedFrom": "Új kamerakép került feltöltésre {0}",
+    "CameraImageUploadedFrom": "Új kamerakép került feltöltésre innen: {0}",
     "Channels": "Csatornák",
     "Channels": "Csatornák",
     "ChapterNameValue": "Jelenet {0}",
     "ChapterNameValue": "Jelenet {0}",
     "Collections": "Gyűjtemények",
     "Collections": "Gyűjtemények",
@@ -15,14 +15,14 @@
     "Favorites": "Kedvencek",
     "Favorites": "Kedvencek",
     "Folders": "Könyvtárak",
     "Folders": "Könyvtárak",
     "Genres": "Műfajok",
     "Genres": "Műfajok",
-    "HeaderAlbumArtists": "Album Előadók",
+    "HeaderAlbumArtists": "Album előadók",
     "HeaderCameraUploads": "Kamera feltöltések",
     "HeaderCameraUploads": "Kamera feltöltések",
     "HeaderContinueWatching": "Folyamatban lévő filmek",
     "HeaderContinueWatching": "Folyamatban lévő filmek",
-    "HeaderFavoriteAlbums": "Kedvenc Albumok",
-    "HeaderFavoriteArtists": "Kedvenc Előadók",
-    "HeaderFavoriteEpisodes": "Kedvenc Epizódok",
-    "HeaderFavoriteShows": "Kedvenc Sorozatok",
-    "HeaderFavoriteSongs": "Kedvenc Dalok",
+    "HeaderFavoriteAlbums": "Kedvenc albumok",
+    "HeaderFavoriteArtists": "Kedvenc előadók",
+    "HeaderFavoriteEpisodes": "Kedvenc epizódok",
+    "HeaderFavoriteShows": "Kedvenc sorozatok",
+    "HeaderFavoriteSongs": "Kedvenc dalok",
     "HeaderLiveTV": "Élő TV",
     "HeaderLiveTV": "Élő TV",
     "HeaderNextUp": "Következik",
     "HeaderNextUp": "Következik",
     "HeaderRecordingGroups": "Felvételi csoportok",
     "HeaderRecordingGroups": "Felvételi csoportok",
@@ -34,21 +34,21 @@
     "LabelRunningTimeValue": "Futási idő: {0}",
     "LabelRunningTimeValue": "Futási idő: {0}",
     "Latest": "Legújabb",
     "Latest": "Legújabb",
     "MessageApplicationUpdated": "Jellyfin Szerver frissítve",
     "MessageApplicationUpdated": "Jellyfin Szerver frissítve",
-    "MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre {0}",
-    "MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész {0} frissítve",
+    "MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre: {0}",
+    "MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész frissítve: {0}",
     "MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve",
     "MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve",
     "MixedContent": "Vegyes tartalom",
     "MixedContent": "Vegyes tartalom",
     "Movies": "Filmek",
     "Movies": "Filmek",
     "Music": "Zene",
     "Music": "Zene",
-    "MusicVideos": "Zenei Videók",
+    "MusicVideos": "Zenei videók",
     "NameInstallFailed": "{0} sikertelen telepítés",
     "NameInstallFailed": "{0} sikertelen telepítés",
     "NameSeasonNumber": "Évad {0}",
     "NameSeasonNumber": "Évad {0}",
     "NameSeasonUnknown": "Ismeretlen évad",
     "NameSeasonUnknown": "Ismeretlen évad",
     "NewVersionIsAvailable": "Letölthető a Jellyfin Szerver új verziója.",
     "NewVersionIsAvailable": "Letölthető a Jellyfin Szerver új verziója.",
-    "NotificationOptionApplicationUpdateAvailable": "Új programfrissítés érhető el",
-    "NotificationOptionApplicationUpdateInstalled": "Programfrissítés telepítve",
+    "NotificationOptionApplicationUpdateAvailable": "Frissítés érhető el az alkalmazáshoz",
+    "NotificationOptionApplicationUpdateInstalled": "Alkalmazásfrissítés telepítve",
     "NotificationOptionAudioPlayback": "Audió lejátszás elkezdve",
     "NotificationOptionAudioPlayback": "Audió lejátszás elkezdve",
-    "NotificationOptionAudioPlaybackStopped": "Audió lejátszás befejezve",
+    "NotificationOptionAudioPlaybackStopped": "Audió lejátszás leállítva",
     "NotificationOptionCameraImageUploaded": "Kamera kép feltöltve",
     "NotificationOptionCameraImageUploaded": "Kamera kép feltöltve",
     "NotificationOptionInstallationFailed": "Telepítési hiba",
     "NotificationOptionInstallationFailed": "Telepítési hiba",
     "NotificationOptionNewLibraryContent": "Új tartalom hozzáadva",
     "NotificationOptionNewLibraryContent": "Új tartalom hozzáadva",
@@ -60,15 +60,15 @@
     "NotificationOptionTaskFailed": "Ütemezett feladat hiba",
     "NotificationOptionTaskFailed": "Ütemezett feladat hiba",
     "NotificationOptionUserLockedOut": "Felhasználó tiltva",
     "NotificationOptionUserLockedOut": "Felhasználó tiltva",
     "NotificationOptionVideoPlayback": "Videó lejátszás elkezdve",
     "NotificationOptionVideoPlayback": "Videó lejátszás elkezdve",
-    "NotificationOptionVideoPlaybackStopped": "Videó lejátszás befejezve",
+    "NotificationOptionVideoPlaybackStopped": "Videó lejátszás leállítva",
     "Photos": "Fényképek",
     "Photos": "Fényképek",
     "Playlists": "Lejátszási listák",
     "Playlists": "Lejátszási listák",
     "Plugin": "Bővítmény",
     "Plugin": "Bővítmény",
     "PluginInstalledWithName": "{0} telepítve",
     "PluginInstalledWithName": "{0} telepítve",
     "PluginUninstalledWithName": "{0} eltávolítva",
     "PluginUninstalledWithName": "{0} eltávolítva",
     "PluginUpdatedWithName": "{0} frissítve",
     "PluginUpdatedWithName": "{0} frissítve",
-    "ProviderValue": "Provider: {0}",
-    "ScheduledTaskFailedWithName": "{0} hiba",
+    "ProviderValue": "Szolgáltató: {0}",
+    "ScheduledTaskFailedWithName": "{0} sikertelen",
     "ScheduledTaskStartedWithName": "{0} elkezdve",
     "ScheduledTaskStartedWithName": "{0} elkezdve",
     "ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
     "ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
     "Shows": "Műsorok",
     "Shows": "Műsorok",
@@ -76,10 +76,10 @@
     "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.",
     "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.",
     "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
     "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
     "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen:  {0} ehhez: {1}",
     "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen:  {0} ehhez: {1}",
-    "SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz {0}",
+    "SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz: {0}",
     "Sync": "Szinkronizál",
     "Sync": "Szinkronizál",
     "System": "Rendszer",
     "System": "Rendszer",
-    "TvShows": "TV Műsorok",
+    "TvShows": "TV műsorok",
     "User": "Felhasználó",
     "User": "Felhasználó",
     "UserCreatedWithName": "{0} felhasználó létrehozva",
     "UserCreatedWithName": "{0} felhasználó létrehozva",
     "UserDeletedWithName": "{0} felhasználó törölve",
     "UserDeletedWithName": "{0} felhasználó törölve",
@@ -88,7 +88,7 @@
     "UserOfflineFromDevice": "{0} kijelentkezett innen:  {1}",
     "UserOfflineFromDevice": "{0} kijelentkezett innen:  {1}",
     "UserOnlineFromDevice": "{0} online itt:  {1}",
     "UserOnlineFromDevice": "{0} online itt:  {1}",
     "UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}",
     "UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}",
-    "UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett {0}",
+    "UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett neki: {0}",
     "UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt:  {2}",
     "UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt:  {2}",
     "UserStoppedPlayingItemWithValues": "{0} befejezte a következőt: {1} itt:  {2}",
     "UserStoppedPlayingItemWithValues": "{0} befejezte a következőt: {1} itt:  {2}",
     "ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz",
     "ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz",

+ 1 - 0
Emby.Server.Implementations/Localization/Core/is.json

@@ -0,0 +1 @@
+{}

+ 7 - 7
Emby.Server.Implementations/Localization/Core/it.json

@@ -9,13 +9,13 @@
     "Channels": "Canali",
     "Channels": "Canali",
     "ChapterNameValue": "Capitolo {0}",
     "ChapterNameValue": "Capitolo {0}",
     "Collections": "Collezioni",
     "Collections": "Collezioni",
-    "DeviceOfflineWithName": "{0} è stato disconnesso",
+    "DeviceOfflineWithName": "{0} ha disconnesso",
     "DeviceOnlineWithName": "{0} è connesso",
     "DeviceOnlineWithName": "{0} è connesso",
     "FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
     "FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
     "Favorites": "Preferiti",
     "Favorites": "Preferiti",
     "Folders": "Cartelle",
     "Folders": "Cartelle",
     "Genres": "Generi",
     "Genres": "Generi",
-    "HeaderAlbumArtists": "Artisti Album",
+    "HeaderAlbumArtists": "Artisti dell' Album",
     "HeaderCameraUploads": "Caricamenti Fotocamera",
     "HeaderCameraUploads": "Caricamenti Fotocamera",
     "HeaderContinueWatching": "Continua a guardare",
     "HeaderContinueWatching": "Continua a guardare",
     "HeaderFavoriteAlbums": "Album preferiti",
     "HeaderFavoriteAlbums": "Album preferiti",
@@ -32,7 +32,7 @@
     "ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
     "ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
     "LabelIpAddressValue": "Indirizzo IP: {0}",
     "LabelIpAddressValue": "Indirizzo IP: {0}",
     "LabelRunningTimeValue": "Durata: {0}",
     "LabelRunningTimeValue": "Durata: {0}",
-    "Latest": "Più recenti",
+    "Latest": "Novità",
     "MessageApplicationUpdated": "Il Server Jellyfin è stato aggiornato",
     "MessageApplicationUpdated": "Il Server Jellyfin è stato aggiornato",
     "MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}",
     "MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}",
     "MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata",
     "MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata",
@@ -43,7 +43,7 @@
     "MusicVideos": "Video musicali",
     "MusicVideos": "Video musicali",
     "NameInstallFailed": "{0} installazione fallita",
     "NameInstallFailed": "{0} installazione fallita",
     "NameSeasonNumber": "Stagione {0}",
     "NameSeasonNumber": "Stagione {0}",
-    "NameSeasonUnknown": "Stagione sconosciuto",
+    "NameSeasonUnknown": "Stagione sconosciuta",
     "NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.",
     "NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.",
     "NotificationOptionApplicationUpdateAvailable": "Aggiornamento dell'applicazione disponibile",
     "NotificationOptionApplicationUpdateAvailable": "Aggiornamento dell'applicazione disponibile",
     "NotificationOptionApplicationUpdateInstalled": "Aggiornamento dell'applicazione installato",
     "NotificationOptionApplicationUpdateInstalled": "Aggiornamento dell'applicazione installato",
@@ -88,9 +88,9 @@
     "UserOfflineFromDevice": "{0} è stato disconnesso da {1}",
     "UserOfflineFromDevice": "{0} è stato disconnesso da {1}",
     "UserOnlineFromDevice": "{0} è online da {1}",
     "UserOnlineFromDevice": "{0} è online da {1}",
     "UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}",
     "UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}",
-    "UserPolicyUpdatedWithName": "La politica dell'utente è stata aggiornata per {0}",
-    "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1}",
-    "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1}",
+    "UserPolicyUpdatedWithName": "La policy dell'utente è stata aggiornata per {0}",
+    "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1} su {2}",
+    "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}",
     "ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale",
     "ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale",
     "ValueSpecialEpisodeName": "Speciale - {0}",
     "ValueSpecialEpisodeName": "Speciale - {0}",
     "VersionNumber": "Versione {0}"
     "VersionNumber": "Versione {0}"

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

@@ -1,7 +1,7 @@
 {
 {
     "Albums": "Albums",
     "Albums": "Albums",
     "AppDeviceValues": "App: {0}, Apparaat: {1}",
     "AppDeviceValues": "App: {0}, Apparaat: {1}",
-    "Application": "Toepassing",
+    "Application": "Applicatie",
     "Artists": "Artiesten",
     "Artists": "Artiesten",
     "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
     "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
     "Books": "Boeken",
     "Books": "Boeken",
@@ -30,7 +30,7 @@
     "Inherit": "Overerven",
     "Inherit": "Overerven",
     "ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
     "ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
     "ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
     "ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
-    "LabelIpAddressValue": "IP adres: {0}",
+    "LabelIpAddressValue": "IP-adres: {0}",
     "LabelRunningTimeValue": "Looptijd: {0}",
     "LabelRunningTimeValue": "Looptijd: {0}",
     "Latest": "Nieuwste",
     "Latest": "Nieuwste",
     "MessageApplicationUpdated": "Jellyfin Server is bijgewerkt",
     "MessageApplicationUpdated": "Jellyfin Server is bijgewerkt",
@@ -50,7 +50,7 @@
     "NotificationOptionAudioPlayback": "Muziek gestart",
     "NotificationOptionAudioPlayback": "Muziek gestart",
     "NotificationOptionAudioPlaybackStopped": "Muziek gestopt",
     "NotificationOptionAudioPlaybackStopped": "Muziek gestopt",
     "NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload",
     "NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload",
-    "NotificationOptionInstallationFailed": "Installatie mislukt",
+    "NotificationOptionInstallationFailed": "Installatie mislukking",
     "NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd",
     "NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd",
     "NotificationOptionPluginError": "Plug-in fout",
     "NotificationOptionPluginError": "Plug-in fout",
     "NotificationOptionPluginInstalled": "Plug-in geïnstalleerd",
     "NotificationOptionPluginInstalled": "Plug-in geïnstalleerd",

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

@@ -5,7 +5,7 @@
     "Artists": "Umelci",
     "Artists": "Umelci",
     "AuthenticationSucceededWithUserName": "{0} úspešne overený",
     "AuthenticationSucceededWithUserName": "{0} úspešne overený",
     "Books": "Knihy",
     "Books": "Knihy",
-    "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
+    "CameraImageUploadedFrom": "Z {0} bola nahraná nová fotografia",
     "Channels": "Kanály",
     "Channels": "Kanály",
     "ChapterNameValue": "Kapitola {0}",
     "ChapterNameValue": "Kapitola {0}",
     "Collections": "Zbierky",
     "Collections": "Zbierky",
@@ -15,9 +15,9 @@
     "Favorites": "Obľúbené",
     "Favorites": "Obľúbené",
     "Folders": "Priečinky",
     "Folders": "Priečinky",
     "Genres": "Žánre",
     "Genres": "Žánre",
-    "HeaderAlbumArtists": "Album Artists",
+    "HeaderAlbumArtists": "Albumy umelcov",
     "HeaderCameraUploads": "Nahrané fotografie",
     "HeaderCameraUploads": "Nahrané fotografie",
-    "HeaderContinueWatching": "Pokračujte v pozeraní",
+    "HeaderContinueWatching": "Pokračovať v pozeraní",
     "HeaderFavoriteAlbums": "Obľúbené albumy",
     "HeaderFavoriteAlbums": "Obľúbené albumy",
     "HeaderFavoriteArtists": "Obľúbení umelci",
     "HeaderFavoriteArtists": "Obľúbení umelci",
     "HeaderFavoriteEpisodes": "Obľúbené epizódy",
     "HeaderFavoriteEpisodes": "Obľúbené epizódy",

+ 78 - 78
Emby.Server.Implementations/Localization/Core/tr.json

@@ -5,93 +5,93 @@
     "Artists": "Sanatçılar",
     "Artists": "Sanatçılar",
     "AuthenticationSucceededWithUserName": "{0} kimlik başarıyla doğrulandı",
     "AuthenticationSucceededWithUserName": "{0} kimlik başarıyla doğrulandı",
     "Books": "Kitaplar",
     "Books": "Kitaplar",
-    "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
+    "CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi",
     "Channels": "Kanallar",
     "Channels": "Kanallar",
-    "ChapterNameValue": "Chapter {0}",
-    "Collections": "Collections",
-    "DeviceOfflineWithName": "{0} has disconnected",
-    "DeviceOnlineWithName": "{0} is connected",
-    "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
-    "Favorites": "Favorites",
-    "Folders": "Folders",
-    "Genres": "Genres",
-    "HeaderAlbumArtists": "Album Artists",
-    "HeaderCameraUploads": "Camera Uploads",
+    "ChapterNameValue": "Bölüm {0}",
+    "Collections": "Koleksiyonlar",
+    "DeviceOfflineWithName": "{0} bağlantısı kesildi",
+    "DeviceOnlineWithName": "{0} bağlı",
+    "FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu",
+    "Favorites": "Favoriler",
+    "Folders": "Klasörler",
+    "Genres": "Türler",
+    "HeaderAlbumArtists": "Albüm Sanatçıları",
+    "HeaderCameraUploads": "Kamera Yüklemeleri",
     "HeaderContinueWatching": "İzlemeye Devam Et",
     "HeaderContinueWatching": "İzlemeye Devam Et",
     "HeaderFavoriteAlbums": "Favori Albümler",
     "HeaderFavoriteAlbums": "Favori Albümler",
-    "HeaderFavoriteArtists": "Favorite Artists",
-    "HeaderFavoriteEpisodes": "Favorite Episodes",
-    "HeaderFavoriteShows": "Favori Showlar",
-    "HeaderFavoriteSongs": "Favorite Songs",
-    "HeaderLiveTV": "Live TV",
-    "HeaderNextUp": "Next Up",
-    "HeaderRecordingGroups": "Recording Groups",
-    "HomeVideos": "Home videos",
-    "Inherit": "Inherit",
-    "ItemAddedWithName": "{0} was added to the library",
-    "ItemRemovedWithName": "{0} was removed from the library",
-    "LabelIpAddressValue": "Ip adresi: {0}",
+    "HeaderFavoriteArtists": "Favori Sanatçılar",
+    "HeaderFavoriteEpisodes": "Favori Bölümler",
+    "HeaderFavoriteShows": "Favori Diziler",
+    "HeaderFavoriteSongs": "Favori Şarkılar",
+    "HeaderLiveTV": "Canlı TV",
+    "HeaderNextUp": "Sonraki hafta",
+    "HeaderRecordingGroups": "Kayıt Grupları",
+    "HomeVideos": "Ev videoları",
+    "Inherit": "Devral",
+    "ItemAddedWithName": "{0} kütüphaneye eklendi",
+    "ItemRemovedWithName": "{0} kütüphaneden silindi",
+    "LabelIpAddressValue": "IP adresi: {0}",
     "LabelRunningTimeValue": "Çalışma süresi: {0}",
     "LabelRunningTimeValue": "Çalışma süresi: {0}",
-    "Latest": "Latest",
+    "Latest": "En son",
     "MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi",
     "MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi",
-    "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
-    "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
-    "MessageServerConfigurationUpdated": "Server configuration has been updated",
-    "MixedContent": "Mixed content",
-    "Movies": "Movies",
+    "MessageApplicationUpdatedTo": "Jellyfin Sunucusu {0} olarak güncellendi",
+    "MessageNamedServerConfigurationUpdatedWithValue": "Sunucu ayarları kısım {0} güncellendi",
+    "MessageServerConfigurationUpdated": "Sunucu ayarları güncellendi",
+    "MixedContent": "Karışık içerik",
+    "Movies": "Filmler",
     "Music": "Müzik",
     "Music": "Müzik",
     "MusicVideos": "Müzik videoları",
     "MusicVideos": "Müzik videoları",
-    "NameInstallFailed": "{0} kurulum başarısız",
+    "NameInstallFailed": "{0} kurulumu başarısız",
     "NameSeasonNumber": "Sezon {0}",
     "NameSeasonNumber": "Sezon {0}",
     "NameSeasonUnknown": "Bilinmeyen Sezon",
     "NameSeasonUnknown": "Bilinmeyen Sezon",
     "NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.",
     "NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.",
-    "NotificationOptionApplicationUpdateAvailable": "Application update available",
-    "NotificationOptionApplicationUpdateInstalled": "Application update installed",
-    "NotificationOptionAudioPlayback": "Audio playback started",
-    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
-    "NotificationOptionCameraImageUploaded": "Camera image uploaded",
-    "NotificationOptionInstallationFailed": "Yükleme hatası",
-    "NotificationOptionNewLibraryContent": "New content added",
-    "NotificationOptionPluginError": "Plugin failure",
-    "NotificationOptionPluginInstalled": "Plugin installed",
-    "NotificationOptionPluginUninstalled": "Plugin uninstalled",
-    "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
-    "NotificationOptionServerRestartRequired": "Server restart required",
-    "NotificationOptionTaskFailed": "Scheduled task failure",
-    "NotificationOptionUserLockedOut": "User locked out",
-    "NotificationOptionVideoPlayback": "Video playback started",
-    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
-    "Photos": "Photos",
-    "Playlists": "Playlists",
-    "Plugin": "Plugin",
-    "PluginInstalledWithName": "{0} was installed",
-    "PluginUninstalledWithName": "{0} was uninstalled",
-    "PluginUpdatedWithName": "{0} was updated",
-    "ProviderValue": "Provider: {0}",
-    "ScheduledTaskFailedWithName": "{0} failed",
-    "ScheduledTaskStartedWithName": "{0} started",
-    "ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
-    "Shows": "Shows",
-    "Songs": "Songs",
-    "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
+    "NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut",
+    "NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi",
+    "NotificationOptionAudioPlayback": "Ses çalma başladı",
+    "NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu",
+    "NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi",
+    "NotificationOptionInstallationFailed": "Yükleme başarısız oldu",
+    "NotificationOptionNewLibraryContent": "Yeni içerik eklendi",
+    "NotificationOptionPluginError": "Eklenti hatası",
+    "NotificationOptionPluginInstalled": "Eklenti yüklendi",
+    "NotificationOptionPluginUninstalled": "Eklenti kaldırıldı",
+    "NotificationOptionPluginUpdateInstalled": "Eklenti güncellemesi yüklendi",
+    "NotificationOptionServerRestartRequired": "Sunucu yeniden başlatma gerekli",
+    "NotificationOptionTaskFailed": "Zamanlanmış görev hatası",
+    "NotificationOptionUserLockedOut": "Kullanıcı kitlendi",
+    "NotificationOptionVideoPlayback": "Video oynatma başladı",
+    "NotificationOptionVideoPlaybackStopped": "Video oynatma durduruldu",
+    "Photos": "Fotoğraflar",
+    "Playlists": "Çalma listeleri",
+    "Plugin": "Eklenti",
+    "PluginInstalledWithName": "{0} yüklendi",
+    "PluginUninstalledWithName": "{0} kaldırıldı",
+    "PluginUpdatedWithName": "{0} güncellendi",
+    "ProviderValue": "Sağlayıcı: {0}",
+    "ScheduledTaskFailedWithName": "{0} başarısız oldu",
+    "ScheduledTaskStartedWithName": "{0} başladı",
+    "ServerNameNeedsToBeRestarted": "{0} yeniden başlatılması gerekiyor",
+    "Shows": "Diziler",
+    "Songs": "Şarkılar",
+    "StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.",
     "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
     "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
-    "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
-    "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
-    "Sync": "Sync",
-    "System": "System",
-    "TvShows": "TV Shows",
-    "User": "User",
-    "UserCreatedWithName": "User {0} has been created",
-    "UserDeletedWithName": "User {0} has been deleted",
-    "UserDownloadingItemWithValues": "{0} is downloading {1}",
-    "UserLockedOutWithName": "User {0} has been locked out",
-    "UserOfflineFromDevice": "{0} has disconnected from {1}",
-    "UserOnlineFromDevice": "{0} is online from {1}",
-    "UserPasswordChangedWithName": "Password has been changed for user {0}",
-    "UserPolicyUpdatedWithName": "User policy has been updated for {0}",
-    "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
-    "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
-    "ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
-    "ValueSpecialEpisodeName": "Special - {0}",
-    "VersionNumber": "Version {0}"
+    "SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi",
+    "SubtitlesDownloadedForItem": "{0} için altyazılar indirildi",
+    "Sync": "Eşitle",
+    "System": "Sistem",
+    "TvShows": "Diziler",
+    "User": "Kullanıcı",
+    "UserCreatedWithName": "{0} kullanıcısı oluşturuldu",
+    "UserDeletedWithName": "Kullanıcı {0} silindi",
+    "UserDownloadingItemWithValues": "{0} indiriliyor {1}",
+    "UserLockedOutWithName": "Kullanıcı {0} kitlendi",
+    "UserOfflineFromDevice": "{0}, {1} ile bağlantısı kesildi",
+    "UserOnlineFromDevice": "{0}, {1} çevrimiçi",
+    "UserPasswordChangedWithName": "{0} kullanıcısı için şifre değiştirildi",
+    "UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi",
+    "UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor",
+    "UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
+    "ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
+    "ValueSpecialEpisodeName": "Özel - {0}",
+    "VersionNumber": "Versiyon {0}"
 }
 }

+ 1 - 1
Emby.Server.Implementations/Localization/Core/zh-CN.json

@@ -5,7 +5,7 @@
     "Artists": "艺术家",
     "Artists": "艺术家",
     "AuthenticationSucceededWithUserName": "{0} 认证成功",
     "AuthenticationSucceededWithUserName": "{0} 认证成功",
     "Books": "书籍",
     "Books": "书籍",
-    "CameraImageUploadedFrom": "已从 {0} 上传了一张新的相机图像",
+    "CameraImageUploadedFrom": "已从 {0} 上传了一张新的相",
     "Channels": "频道",
     "Channels": "频道",
     "ChapterNameValue": "章节 {0}",
     "ChapterNameValue": "章节 {0}",
     "Collections": "合集",
     "Collections": "合集",

+ 1 - 1
Emby.Server.Implementations/Localization/Core/zh-HK.json

@@ -2,7 +2,7 @@
     "Albums": "Albums",
     "Albums": "Albums",
     "AppDeviceValues": "App: {0}, Device: {1}",
     "AppDeviceValues": "App: {0}, Device: {1}",
     "Application": "Application",
     "Application": "Application",
-    "Artists": "Artists",
+    "Artists": "藝人",
     "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
     "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
     "Books": "Books",
     "Books": "Books",
     "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
     "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",

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

@@ -1,6 +1,6 @@
 {
 {
     "Albums": "專輯",
     "Albums": "專輯",
-    "AppDeviceValues": "應用: {0}, 裝置: {1}",
+    "AppDeviceValues": "軟體: {0}, 裝置: {1}",
     "Application": "應用程式",
     "Application": "應用程式",
     "Artists": "演出者",
     "Artists": "演出者",
     "AuthenticationSucceededWithUserName": "{0} 成功授權",
     "AuthenticationSucceededWithUserName": "{0} 成功授權",

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

@@ -5,11 +5,9 @@ using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
-using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;

+ 2 - 2
Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs

@@ -27,12 +27,12 @@ namespace Emby.Server.Implementations.Middleware
                 var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
                 var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
                 if (webSocketContext != null)
                 if (webSocketContext != null)
                 {
                 {
-                    await _webSocketManager.OnWebSocketConnected(webSocketContext);
+                    await _webSocketManager.OnWebSocketConnected(webSocketContext).ConfigureAwait(false);
                 }
                 }
             }
             }
             else
             else
             {
             {
-                await _next.Invoke(httpContext);
+                await _next.Invoke(httpContext).ConfigureAwait(false);
             }
             }
         }
         }
     }
     }

+ 19 - 145
Emby.Server.Implementations/Networking/NetworkManager.cs

@@ -7,8 +7,6 @@ using System.Net.NetworkInformation;
 using System.Net.Sockets;
 using System.Net.Sockets;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
 namespace Emby.Server.Implementations.Networking
 namespace Emby.Server.Implementations.Networking
@@ -20,6 +18,9 @@ namespace Emby.Server.Implementations.Networking
         private IPAddress[] _localIpAddresses;
         private IPAddress[] _localIpAddresses;
         private readonly object _localIpAddressSyncLock = new object();
         private readonly object _localIpAddressSyncLock = new object();
 
 
+        private readonly object _subnetLookupLock = new object();
+        private Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
+
         public NetworkManager(ILogger<NetworkManager> logger)
         public NetworkManager(ILogger<NetworkManager> logger)
         {
         {
             _logger = logger;
             _logger = logger;
@@ -28,10 +29,10 @@ namespace Emby.Server.Implementations.Networking
             NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
             NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
         }
         }
 
 
-        public Func<string[]> LocalSubnetsFn { get; set; }
-
         public event EventHandler NetworkChanged;
         public event EventHandler NetworkChanged;
 
 
+        public Func<string[]> LocalSubnetsFn { get; set; }
+
         private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
         private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
         {
         {
             _logger.LogDebug("NetworkAvailabilityChanged");
             _logger.LogDebug("NetworkAvailabilityChanged");
@@ -52,10 +53,7 @@ namespace Emby.Server.Implementations.Networking
                 _macAddresses = null;
                 _macAddresses = null;
             }
             }
 
 
-            if (NetworkChanged != null)
-            {
-                NetworkChanged(this, EventArgs.Empty);
-            }
+            NetworkChanged?.Invoke(this, EventArgs.Empty);
         }
         }
 
 
         public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
         public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
@@ -179,10 +177,9 @@ namespace Emby.Server.Implementations.Networking
             return false;
             return false;
         }
         }
 
 
-        private Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
         private List<string> GetSubnets(string endpointFirstPart)
         private List<string> GetSubnets(string endpointFirstPart)
         {
         {
-            lock (_subnetLookup)
+            lock (_subnetLookupLock)
             {
             {
                 if (_subnetLookup.TryGetValue(endpointFirstPart, out var subnets))
                 if (_subnetLookup.TryGetValue(endpointFirstPart, out var subnets))
                 {
                 {
@@ -200,7 +197,11 @@ namespace Emby.Server.Implementations.Networking
                             int subnet_Test = 0;
                             int subnet_Test = 0;
                             foreach (string part in unicastIPAddressInformation.IPv4Mask.ToString().Split('.'))
                             foreach (string part in unicastIPAddressInformation.IPv4Mask.ToString().Split('.'))
                             {
                             {
-                                if (part.Equals("0")) break;
+                                if (part.Equals("0", StringComparison.Ordinal))
+                                {
+                                    break;
+                                }
+
                                 subnet_Test++;
                                 subnet_Test++;
                             }
                             }
 
 
@@ -255,10 +256,10 @@ namespace Emby.Server.Implementations.Networking
                     return true;
                     return true;
                 }
                 }
 
 
-                if (normalizedSubnet.IndexOf('/') != -1)
+                if (normalizedSubnet.Contains('/', StringComparison.Ordinal))
                 {
                 {
-                    var ipnetwork = IPNetwork.Parse(normalizedSubnet);
-                    if (ipnetwork.Contains(address))
+                    var ipNetwork = IPNetwork.Parse(normalizedSubnet);
+                    if (ipNetwork.Contains(address))
                     {
                     {
                         return true;
                         return true;
                     }
                     }
@@ -447,119 +448,11 @@ namespace Emby.Server.Implementations.Networking
                 .Select(x => x.GetPhysicalAddress())
                 .Select(x => x.GetPhysicalAddress())
                 .Where(x => x != null && x != PhysicalAddress.None);
                 .Where(x => x != null && x != PhysicalAddress.None);
 
 
-        /// <summary>
-        /// Parses the specified endpointstring.
-        /// </summary>
-        /// <param name="endpointstring">The endpointstring.</param>
-        /// <returns>IPEndPoint.</returns>
-        public IPEndPoint Parse(string endpointstring)
-        {
-            return Parse(endpointstring, -1).Result;
-        }
-
-        /// <summary>
-        /// Parses the specified endpointstring.
-        /// </summary>
-        /// <param name="endpointstring">The endpointstring.</param>
-        /// <param name="defaultport">The defaultport.</param>
-        /// <returns>IPEndPoint.</returns>
-        /// <exception cref="ArgumentException">Endpoint descriptor may not be empty.</exception>
-        /// <exception cref="FormatException"></exception>
-        private static async Task<IPEndPoint> Parse(string endpointstring, int defaultport)
-        {
-            if (string.IsNullOrEmpty(endpointstring)
-                || endpointstring.Trim().Length == 0)
-            {
-                throw new ArgumentException("Endpoint descriptor may not be empty.");
-            }
-
-            if (defaultport != -1 &&
-                (defaultport < IPEndPoint.MinPort
-                || defaultport > IPEndPoint.MaxPort))
-            {
-                throw new ArgumentException(string.Format("Invalid default port '{0}'", defaultport));
-            }
-
-            string[] values = endpointstring.Split(new char[] { ':' });
-            IPAddress ipaddy;
-            int port = -1;
-
-            //check if we have an IPv6 or ports
-            if (values.Length <= 2) // ipv4 or hostname
-            {
-                port = values.Length == 1 ? defaultport : GetPort(values[1]);
-
-                //try to use the address as IPv4, otherwise get hostname
-                if (!IPAddress.TryParse(values[0], out ipaddy))
-                    ipaddy = await GetIPfromHost(values[0]).ConfigureAwait(false);
-            }
-            else if (values.Length > 2) //ipv6
-            {
-                //could [a:b:c]:d
-                if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]"))
-                {
-                    string ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray());
-                    ipaddy = IPAddress.Parse(ipaddressstring);
-                    port = GetPort(values[values.Length - 1]);
-                }
-                else //[a:b:c] or a:b:c
-                {
-                    ipaddy = IPAddress.Parse(endpointstring);
-                    port = defaultport;
-                }
-            }
-            else
-            {
-                throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", endpointstring));
-            }
-
-            if (port == -1)
-                throw new ArgumentException(string.Format("No port specified: '{0}'", endpointstring));
-
-            return new IPEndPoint(ipaddy, port);
-        }
-
-        protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
-        /// <summary>
-        /// Gets the port.
-        /// </summary>
-        /// <param name="p">The p.</param>
-        /// <returns>System.Int32.</returns>
-        /// <exception cref="FormatException"></exception>
-        private static int GetPort(string p)
-        {
-            if (!int.TryParse(p, out var port)
-             || port < IPEndPoint.MinPort
-             || port > IPEndPoint.MaxPort)
-            {
-                throw new FormatException(string.Format("Invalid end point port '{0}'", p));
-            }
-
-            return port;
-        }
-
-        /// <summary>
-        /// Gets the I pfrom host.
-        /// </summary>
-        /// <param name="p">The p.</param>
-        /// <returns>IPAddress.</returns>
-        /// <exception cref="ArgumentException"></exception>
-        private static async Task<IPAddress> GetIPfromHost(string p)
-        {
-            var hosts = await Dns.GetHostAddressesAsync(p).ConfigureAwait(false);
-
-            if (hosts == null || hosts.Length == 0)
-                throw new ArgumentException(string.Format("Host not found: {0}", p));
-
-            return hosts[0];
-        }
-
         public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
         public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
         {
         {
-             IPAddress network1 = GetNetworkAddress(address1, subnetMask);
-             IPAddress network2 = GetNetworkAddress(address2, subnetMask);
-             return network1.Equals(network2);
+            IPAddress network1 = GetNetworkAddress(address1, subnetMask);
+            IPAddress network2 = GetNetworkAddress(address2, subnetMask);
+            return network1.Equals(network2);
         }
         }
 
 
         private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
         private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
@@ -575,7 +468,7 @@ namespace Emby.Server.Implementations.Networking
             byte[] broadcastAddress = new byte[ipAdressBytes.Length];
             byte[] broadcastAddress = new byte[ipAdressBytes.Length];
             for (int i = 0; i < broadcastAddress.Length; i++)
             for (int i = 0; i < broadcastAddress.Length; i++)
             {
             {
-                broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
+                broadcastAddress[i] = (byte)(ipAdressBytes[i] & subnetMaskBytes[i]);
             }
             }
 
 
             return new IPAddress(broadcastAddress);
             return new IPAddress(broadcastAddress);
@@ -615,24 +508,5 @@ namespace Emby.Server.Implementations.Networking
 
 
             return null;
             return null;
         }
         }
-
-        /// <summary>
-        /// Gets the network shares.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>IEnumerable{NetworkShare}.</returns>
-        public virtual IEnumerable<NetworkShare> GetNetworkShares(string path)
-        {
-            return new List<NetworkShare>();
-        }
-
-        /// <summary>
-        /// Gets available devices within the domain
-        /// </summary>
-        /// <returns>PC's in the Domain</returns>
-        public virtual IEnumerable<FileSystemEntryInfo> GetNetworkDevices()
-        {
-            return new List<FileSystemEntryInfo>();
-        }
     }
     }
 }
 }

+ 4 - 5
Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs

@@ -1,9 +1,9 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Text.Json.Serialization;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Serialization;
 
 
 namespace Emby.Server.Implementations.Playlists
 namespace Emby.Server.Implementations.Playlists
 {
 {
@@ -24,13 +24,13 @@ namespace Emby.Server.Implementations.Playlists
             return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>();
             return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>();
         }
         }
 
 
-        [IgnoreDataMember]
+        [JsonIgnore]
         public override bool IsHidden => true;
         public override bool IsHidden => true;
 
 
-        [IgnoreDataMember]
+        [JsonIgnore]
         public override bool SupportsInheritedParentImages => false;
         public override bool SupportsInheritedParentImages => false;
 
 
-        [IgnoreDataMember]
+        [JsonIgnore]
         public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists;
         public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists;
 
 
         protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
         protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
@@ -48,4 +48,3 @@ namespace Emby.Server.Implementations.Playlists
         }
         }
     }
     }
 }
 }
-

+ 13 - 16
Emby.Server.Implementations/Playlists/PlaylistManager.cs

@@ -90,8 +90,7 @@ namespace Emby.Server.Implementations.Playlists
                     }
                     }
                     else
                     else
                     {
                     {
-                        var folder = item as Folder;
-                        if (folder != null)
+                        if (item is Folder folder)
                         {
                         {
                             options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist)
                             options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist)
                                 .Select(i => i.MediaType)
                                 .Select(i => i.MediaType)
@@ -140,7 +139,7 @@ namespace Emby.Server.Implementations.Playlists
 
 
                 parentFolder.AddChild(playlist, CancellationToken.None);
                 parentFolder.AddChild(playlist, CancellationToken.None);
 
 
-                await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) { ForceSave = true }, CancellationToken.None)
+                await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, CancellationToken.None)
                     .ConfigureAwait(false);
                     .ConfigureAwait(false);
 
 
                 if (options.ItemIdList.Length > 0)
                 if (options.ItemIdList.Length > 0)
@@ -201,7 +200,7 @@ namespace Emby.Server.Implementations.Playlists
 
 
             var list = new List<LinkedChild>();
             var list = new List<LinkedChild>();
 
 
-            var items = (GetPlaylistItems(itemIds, playlist.MediaType, user, options))
+            var items = GetPlaylistItems(itemIds, playlist.MediaType, user, options)
                 .Where(i => i.SupportsAddingToPlaylist)
                 .Where(i => i.SupportsAddingToPlaylist)
                 .ToList();
                 .ToList();
 
 
@@ -221,18 +220,18 @@ namespace Emby.Server.Implementations.Playlists
                 SavePlaylistFile(playlist);
                 SavePlaylistFile(playlist);
             }
             }
 
 
-            _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
-            {
-                ForceSave = true
-
-            }, RefreshPriority.High);
+            _providerManager.QueueRefresh(
+                playlist.Id,
+                new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+                {
+                    ForceSave = true
+                },
+                RefreshPriority.High);
         }
         }
 
 
         public void RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds)
         public void RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds)
         {
         {
-            var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
-
-            if (playlist == null)
+            if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
             {
             {
                 throw new ArgumentException("No Playlist exists with the supplied Id");
                 throw new ArgumentException("No Playlist exists with the supplied Id");
             }
             }
@@ -254,7 +253,7 @@ namespace Emby.Server.Implementations.Playlists
                 SavePlaylistFile(playlist);
                 SavePlaylistFile(playlist);
             }
             }
 
 
-            _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
+            _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
             {
             {
                 ForceSave = true
                 ForceSave = true
 
 
@@ -263,9 +262,7 @@ namespace Emby.Server.Implementations.Playlists
 
 
         public void MoveItem(string playlistId, string entryId, int newIndex)
         public void MoveItem(string playlistId, string entryId, int newIndex)
         {
         {
-            var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
-
-            if (playlist == null)
+            if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
             {
             {
                 throw new ArgumentException("No Playlist exists with the supplied Id");
                 throw new ArgumentException("No Playlist exists with the supplied Id");
             }
             }

+ 5 - 4
Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         public event EventHandler<GenericEventArgs<double>> TaskProgress;
         public event EventHandler<GenericEventArgs<double>> TaskProgress;
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the scheduled task.
+        /// Gets the scheduled task.
         /// </summary>
         /// </summary>
         /// <value>The scheduled task.</value>
         /// <value>The scheduled task.</value>
         public IScheduledTask ScheduledTask { get; private set; }
         public IScheduledTask ScheduledTask { get; private set; }
@@ -215,11 +215,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
         public double? CurrentProgress { get; private set; }
         public double? CurrentProgress { get; private set; }
 
 
         /// <summary>
         /// <summary>
-        /// The _triggers
+        /// The _triggers.
         /// </summary>
         /// </summary>
         private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers;
         private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers;
+
         /// <summary>
         /// <summary>
-        /// Gets the triggers that define when the task will run
+        /// Gets the triggers that define when the task will run.
         /// </summary>
         /// </summary>
         /// <value>The triggers.</value>
         /// <value>The triggers.</value>
         private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
         private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
@@ -245,7 +246,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the triggers that define when the task will run
+        /// Gets the triggers that define when the task will run.
         /// </summary>
         /// </summary>
         /// <value>The triggers.</value>
         /// <value>The triggers.</value>
         /// <exception cref="ArgumentNullException">value</exception>
         /// <exception cref="ArgumentNullException">value</exception>

+ 16 - 16
Emby.Server.Implementations/ScheduledTasks/TaskManager.cs

@@ -36,19 +36,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// Gets or sets the json serializer.
         /// Gets or sets the json serializer.
         /// </summary>
         /// </summary>
         /// <value>The json serializer.</value>
         /// <value>The json serializer.</value>
-        private IJsonSerializer JsonSerializer { get; set; }
+        private readonly IJsonSerializer _jsonSerializer;
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the application paths.
         /// Gets or sets the application paths.
         /// </summary>
         /// </summary>
         /// <value>The application paths.</value>
         /// <value>The application paths.</value>
-        private IApplicationPaths ApplicationPaths { get; set; }
+        private readonly IApplicationPaths _applicationPaths;
 
 
         /// <summary>
         /// <summary>
         /// Gets the logger.
         /// Gets the logger.
         /// </summary>
         /// </summary>
         /// <value>The logger.</value>
         /// <value>The logger.</value>
-        private ILogger Logger { get; set; }
+        private readonly ILogger _logger;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
@@ -57,19 +57,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// <param name="applicationPaths">The application paths.</param>
         /// <param name="applicationPaths">The application paths.</param>
         /// <param name="jsonSerializer">The json serializer.</param>
         /// <param name="jsonSerializer">The json serializer.</param>
         /// <param name="loggerFactory">The logger factory.</param>
         /// <param name="loggerFactory">The logger factory.</param>
-        /// <exception cref="System.ArgumentException">kernel</exception>
+        /// <param name="fileSystem">The filesystem manager.</param>
         public TaskManager(
         public TaskManager(
             IApplicationPaths applicationPaths,
             IApplicationPaths applicationPaths,
             IJsonSerializer jsonSerializer,
             IJsonSerializer jsonSerializer,
             ILoggerFactory loggerFactory,
             ILoggerFactory loggerFactory,
             IFileSystem fileSystem)
             IFileSystem fileSystem)
         {
         {
-            ApplicationPaths = applicationPaths;
-            JsonSerializer = jsonSerializer;
-            Logger = loggerFactory.CreateLogger(nameof(TaskManager));
+            _applicationPaths = applicationPaths;
+            _jsonSerializer = jsonSerializer;
+            _logger = loggerFactory.CreateLogger(nameof(TaskManager));
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
 
 
-            ScheduledTasks = new IScheduledTaskWorker[] { };
+            ScheduledTasks = Array.Empty<IScheduledTaskWorker>();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// <typeparam name="T"></typeparam>
         /// <typeparam name="T"></typeparam>
         /// <param name="options">Task options.</param>
         /// <param name="options">Task options.</param>
         public void CancelIfRunningAndQueue<T>(TaskOptions options)
         public void CancelIfRunningAndQueue<T>(TaskOptions options)
-                 where T : IScheduledTask
+            where T : IScheduledTask
         {
         {
             var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
             var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
             ((ScheduledTaskWorker)task).CancelIfRunning();
             ((ScheduledTaskWorker)task).CancelIfRunning();
@@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
 
 
             if (scheduledTask == null)
             if (scheduledTask == null)
             {
             {
-                Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name);
+                _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name);
             }
             }
             else
             else
             {
             {
@@ -147,13 +147,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
 
 
             if (scheduledTask == null)
             if (scheduledTask == null)
             {
             {
-                Logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name);
+                _logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name);
             }
             }
             else
             else
             {
             {
                 var type = scheduledTask.ScheduledTask.GetType();
                 var type = scheduledTask.ScheduledTask.GetType();
 
 
-                Logger.LogInformation("Queueing task {0}", type.Name);
+                _logger.LogInformation("Queueing task {0}", type.Name);
 
 
                 lock (_taskQueue)
                 lock (_taskQueue)
                 {
                 {
@@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
 
 
             if (scheduledTask == null)
             if (scheduledTask == null)
             {
             {
-                Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name);
+                _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name);
             }
             }
             else
             else
             {
             {
@@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         {
         {
             var type = task.ScheduledTask.GetType();
             var type = task.ScheduledTask.GetType();
 
 
-            Logger.LogInformation("Queueing task {0}", type.Name);
+            _logger.LogInformation("Queueing task {0}", type.Name);
 
 
             lock (_taskQueue)
             lock (_taskQueue)
             {
             {
@@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// <param name="tasks">The tasks.</param>
         /// <param name="tasks">The tasks.</param>
         public void AddTasks(IEnumerable<IScheduledTask> tasks)
         public void AddTasks(IEnumerable<IScheduledTask> tasks)
         {
         {
-            var list = tasks.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem));
+            var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger, _fileSystem));
 
 
             ScheduledTasks = ScheduledTasks.Concat(list).ToArray();
             ScheduledTasks = ScheduledTasks.Concat(list).ToArray();
         }
         }
@@ -281,7 +281,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// </summary>
         /// </summary>
         private void ExecuteQueuedTasks()
         private void ExecuteQueuedTasks()
         {
         {
-            Logger.LogInformation("ExecuteQueuedTasks");
+            _logger.LogInformation("ExecuteQueuedTasks");
 
 
             // Execute queued tasks
             // Execute queued tasks
             lock (_taskQueue)
             lock (_taskQueue)

+ 8 - 7
Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs

@@ -19,16 +19,17 @@ using Microsoft.Extensions.Logging;
 namespace Emby.Server.Implementations.ScheduledTasks
 namespace Emby.Server.Implementations.ScheduledTasks
 {
 {
     /// <summary>
     /// <summary>
-    /// Class ChapterImagesTask
+    /// Class ChapterImagesTask.
     /// </summary>
     /// </summary>
     public class ChapterImagesTask : IScheduledTask
     public class ChapterImagesTask : IScheduledTask
     {
     {
         /// <summary>
         /// <summary>
-        /// The _logger
+        /// The _logger.
         /// </summary>
         /// </summary>
         private readonly ILogger _logger;
         private readonly ILogger _logger;
+
         /// <summary>
         /// <summary>
-        /// The _library manager
+        /// The _library manager.
         /// </summary>
         /// </summary>
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
@@ -53,12 +54,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Creates the triggers that define when the task will run
+        /// Creates the triggers that define when the task will run.
         /// </summary>
         /// </summary>
         public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
         public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
         {
         {
-            return new[] {
-
+            return new[]
+            {
                 new TaskTriggerInfo
                 new TaskTriggerInfo
                 {
                 {
                     Type = TaskTriggerInfo.TriggerDaily,
                     Type = TaskTriggerInfo.TriggerDaily,
@@ -117,7 +118,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
                 previouslyFailedImages = new List<string>();
                 previouslyFailedImages = new List<string>();
             }
             }
 
 
-            var directoryService = new DirectoryService(_logger, _fileSystem);
+            var directoryService = new DirectoryService(_fileSystem);
 
 
             foreach (var video in videos)
             foreach (var video in videos)
             {
             {

+ 8 - 20
Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs

@@ -4,6 +4,7 @@ using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
@@ -15,24 +16,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
     /// </summary>
     /// </summary>
     public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask
     public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask
     {
     {
-        /// <summary>
-        /// Gets or sets the application paths.
-        /// </summary>
-        /// <value>The application paths.</value>
-        private ServerApplicationPaths ApplicationPaths { get; set; }
-
         private readonly ILogger _logger;
         private readonly ILogger _logger;
-
+        private readonly IConfigurationManager _configurationManager;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="DeleteTranscodeFileTask" /> class.
         /// Initializes a new instance of the <see cref="DeleteTranscodeFileTask" /> class.
         /// </summary>
         /// </summary>
-        public DeleteTranscodeFileTask(ServerApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
+        public DeleteTranscodeFileTask(ILogger logger, IFileSystem fileSystem, IConfigurationManager configurationManager)
         {
         {
-            ApplicationPaths = appPaths;
             _logger = logger;
             _logger = logger;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
+            _configurationManager = configurationManager;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -52,14 +47,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
             var minDateModified = DateTime.UtcNow.AddDays(-1);
             var minDateModified = DateTime.UtcNow.AddDays(-1);
             progress.Report(50);
             progress.Report(50);
 
 
-            try
-            {
-                DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodingTempPath, minDateModified, progress);
-            }
-            catch (DirectoryNotFoundException)
-            {
-                // No biggie here. Nothing to delete
-            }
+            DeleteTempFilesFromDirectory(cancellationToken, _configurationManager.GetTranscodePath(), minDateModified, progress);
 
 
             return Task.CompletedTask;
             return Task.CompletedTask;
         }
         }
@@ -138,13 +126,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
             }
             }
         }
         }
 
 
-        public string Name => "Transcoding temp cleanup";
+        public string Name => "Transcode file cleanup";
 
 
-        public string Description => "Deletes transcoding temp files older than 24 hours.";
+        public string Description => "Deletes transcode files more than 24 hours old.";
 
 
         public string Category => "Maintenance";
         public string Category => "Maintenance";
 
 
-        public string Key => "DeleteTranscodingTempFiles";
+        public string Key => "DeleteTranscodeFiles";
 
 
         public bool IsHidden => false;
         public bool IsHidden => false;
 
 

+ 17 - 9
Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs

@@ -1,24 +1,23 @@
-using MediaBrowser.Common.Updates;
-using MediaBrowser.Model.Net;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Progress;
+using MediaBrowser.Common.Updates;
+using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
 namespace Emby.Server.Implementations.ScheduledTasks
 namespace Emby.Server.Implementations.ScheduledTasks
 {
 {
     /// <summary>
     /// <summary>
-    /// Plugin Update Task
+    /// Plugin Update Task.
     /// </summary>
     /// </summary>
     public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask
     public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask
     {
     {
         /// <summary>
         /// <summary>
-        /// The _logger
+        /// The _logger.
         /// </summary>
         /// </summary>
         private readonly ILogger _logger;
         private readonly ILogger _logger;
 
 
@@ -31,7 +30,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Creates the triggers that define when the task will run
+        /// Creates the triggers that define when the task will run.
         /// </summary>
         /// </summary>
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
         public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
         public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
@@ -44,16 +43,18 @@ namespace Emby.Server.Implementations.ScheduledTasks
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Update installed plugins
+        /// Update installed plugins.
         /// </summary>
         /// </summary>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="progress">The progress.</param>
         /// <param name="progress">The progress.</param>
-        /// <returns>Task.</returns>
+        /// <returns><see cref="Task" />.</returns>
         public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         {
         {
             progress.Report(0);
             progress.Report(0);
 
 
-            var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(typeof(PluginUpdateTask).Assembly.GetName().Version, true, cancellationToken).ConfigureAwait(false)).ToList();
+            var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken)
+                .ToListAsync(cancellationToken)
+                .ConfigureAwait(false);
 
 
             progress.Report(10);
             progress.Report(10);
 
 
@@ -94,18 +95,25 @@ namespace Emby.Server.Implementations.ScheduledTasks
             progress.Report(100);
             progress.Report(100);
         }
         }
 
 
+        /// <inheritdoc />
         public string Name => "Check for plugin updates";
         public string Name => "Check for plugin updates";
 
 
+        /// <inheritdoc />
         public string Description => "Downloads and installs updates for plugins that are configured to update automatically.";
         public string Description => "Downloads and installs updates for plugins that are configured to update automatically.";
 
 
+        /// <inheritdoc />
         public string Category => "Application";
         public string Category => "Application";
 
 
+        /// <inheritdoc />
         public string Key => "PluginUpdates";
         public string Key => "PluginUpdates";
 
 
+        /// <inheritdoc />
         public bool IsHidden => false;
         public bool IsHidden => false;
 
 
+        /// <inheritdoc />
         public bool IsEnabled => true;
         public bool IsEnabled => true;
 
 
+        /// <inheritdoc />
         public bool IsLogged => true;
         public bool IsLogged => true;
     }
     }
 }
 }

+ 2 - 3
Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs

@@ -1,5 +1,4 @@
 using System;
 using System;
-using System.Globalization;
 using System.Threading;
 using System.Threading;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
@@ -7,12 +6,12 @@ using Microsoft.Extensions.Logging;
 namespace Emby.Server.Implementations.ScheduledTasks
 namespace Emby.Server.Implementations.ScheduledTasks
 {
 {
     /// <summary>
     /// <summary>
-    /// Represents a task trigger that fires everyday
+    /// Represents a task trigger that fires everyday.
     /// </summary>
     /// </summary>
     public class DailyTrigger : ITaskTrigger
     public class DailyTrigger : ITaskTrigger
     {
     {
         /// <summary>
         /// <summary>
-        /// Get the time of day to trigger the task to run
+        /// Get the time of day to trigger the task to run.
         /// </summary>
         /// </summary>
         /// <value>The time of day.</value>
         /// <value>The time of day.</value>
         public TimeSpan TimeOfDay { get; set; }
         public TimeSpan TimeOfDay { get; set; }

+ 5 - 31
Emby.Server.Implementations/Serialization/XmlSerializer.cs → Emby.Server.Implementations/Serialization/MyXmlSerializer.cs

@@ -1,11 +1,9 @@
 using System;
 using System;
-using System.Collections.Generic;
+using System.Collections.Concurrent;
 using System.IO;
 using System.IO;
 using System.Xml;
 using System.Xml;
 using System.Xml.Serialization;
 using System.Xml.Serialization;
-using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
-using Microsoft.Extensions.Logging;
 
 
 namespace Emby.Server.Implementations.Serialization
 namespace Emby.Server.Implementations.Serialization
 {
 {
@@ -14,35 +12,13 @@ namespace Emby.Server.Implementations.Serialization
     /// </summary>
     /// </summary>
     public class MyXmlSerializer : IXmlSerializer
     public class MyXmlSerializer : IXmlSerializer
     {
     {
-        private readonly IFileSystem _fileSystem;
-        private readonly ILogger _logger;
-
-        public MyXmlSerializer(
-            IFileSystem fileSystem,
-            ILoggerFactory loggerFactory)
-        {
-            _fileSystem = fileSystem;
-            _logger = loggerFactory.CreateLogger("XmlSerializer");
-        }
-
         // Need to cache these
         // Need to cache these
         // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
         // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
-        private readonly Dictionary<string, XmlSerializer> _serializers =
-            new Dictionary<string, XmlSerializer>();
+        private static readonly ConcurrentDictionary<string, XmlSerializer> _serializers =
+            new ConcurrentDictionary<string, XmlSerializer>();
 
 
-        private XmlSerializer GetSerializer(Type type)
-        {
-            var key = type.FullName;
-            lock (_serializers)
-            {
-                if (!_serializers.TryGetValue(key, out var serializer))
-                {
-                    serializer = new XmlSerializer(type);
-                    _serializers[key] = serializer;
-                }
-                return serializer;
-            }
-        }
+        private static XmlSerializer GetSerializer(Type type)
+            => _serializers.GetOrAdd(type.FullName, _ => new XmlSerializer(type));
 
 
         /// <summary>
         /// <summary>
         /// Serializes to writer.
         /// Serializes to writer.
@@ -91,7 +67,6 @@ namespace Emby.Server.Implementations.Serialization
         /// <param name="file">The file.</param>
         /// <param name="file">The file.</param>
         public void SerializeToFile(object obj, string file)
         public void SerializeToFile(object obj, string file)
         {
         {
-            _logger.LogDebug("Serializing to file {0}", file);
             using (var stream = new FileStream(file, FileMode.Create))
             using (var stream = new FileStream(file, FileMode.Create))
             {
             {
                 SerializeToStream(obj, stream);
                 SerializeToStream(obj, stream);
@@ -106,7 +81,6 @@ namespace Emby.Server.Implementations.Serialization
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object DeserializeFromFile(Type type, string file)
         public object DeserializeFromFile(Type type, string file)
         {
         {
-            _logger.LogDebug("Deserializing file {0}", file);
             using (var stream = File.OpenRead(file))
             using (var stream = File.OpenRead(file))
             {
             {
                 return DeserializeFromStream(type, stream);
                 return DeserializeFromStream(type, stream);

+ 6 - 47
Emby.Server.Implementations/ServerApplicationPaths.cs

@@ -1,4 +1,3 @@
-using System;
 using System.IO;
 using System.IO;
 using Emby.Server.Implementations.AppBase;
 using Emby.Server.Implementations.AppBase;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
@@ -6,12 +5,10 @@ using MediaBrowser.Controller;
 namespace Emby.Server.Implementations
 namespace Emby.Server.Implementations
 {
 {
     /// <summary>
     /// <summary>
-    /// Extends BaseApplicationPaths to add paths that are only applicable on the server
+    /// Extends BaseApplicationPaths to add paths that are only applicable on the server.
     /// </summary>
     /// </summary>
     public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
     public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
     {
     {
-        private string _defaultTranscodingTempPath;
-        private string _transcodingTempPath;
         private string _internalMetadataPath;
         private string _internalMetadataPath;
 
 
         /// <summary>
         /// <summary>
@@ -23,7 +20,8 @@ namespace Emby.Server.Implementations
             string configurationDirectoryPath,
             string configurationDirectoryPath,
             string cacheDirectoryPath,
             string cacheDirectoryPath,
             string webDirectoryPath)
             string webDirectoryPath)
-            : base(programDataPath,
+            : base(
+                programDataPath,
                 logDirectoryPath,
                 logDirectoryPath,
                 configurationDirectoryPath,
                 configurationDirectoryPath,
                 cacheDirectoryPath,
                 cacheDirectoryPath,
@@ -31,8 +29,6 @@ namespace Emby.Server.Implementations
         {
         {
         }
         }
 
 
-        public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
-
         /// <summary>
         /// <summary>
         /// Gets the path to the base root media directory.
         /// Gets the path to the base root media directory.
         /// </summary>
         /// </summary>
@@ -45,18 +41,13 @@ namespace Emby.Server.Implementations
         /// <value>The default user views path.</value>
         /// <value>The default user views path.</value>
         public string DefaultUserViewsPath => Path.Combine(RootFolderPath, "default");
         public string DefaultUserViewsPath => Path.Combine(RootFolderPath, "default");
 
 
-        /// <summary>
-        /// Gets the path to localization data.
-        /// </summary>
-        /// <value>The localization path.</value>
-        public string LocalizationPath => Path.Combine(ProgramDataPath, "localization");
-
         /// <summary>
         /// <summary>
         /// Gets the path to the People directory.
         /// Gets the path to the People directory.
         /// </summary>
         /// </summary>
         /// <value>The people path.</value>
         /// <value>The people path.</value>
         public string PeoplePath => Path.Combine(InternalMetadataPath, "People");
         public string PeoplePath => Path.Combine(InternalMetadataPath, "People");
 
 
+        /// <inheritdoc />
         public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists");
         public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists");
 
 
         /// <summary>
         /// <summary>
@@ -107,46 +98,14 @@ namespace Emby.Server.Implementations
         /// <value>The user configuration directory path.</value>
         /// <value>The user configuration directory path.</value>
         public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
         public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
 
 
-        public string DefaultTranscodingTempPath => _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp"));
-
-        public string TranscodingTempPath
-        {
-            get => _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath);
-            set => _transcodingTempPath = value;
-        }
-
-        public string GetTranscodingTempPath()
-        {
-            var path = TranscodingTempPath;
-
-            if (!string.Equals(path, DefaultTranscodingTempPath, StringComparison.OrdinalIgnoreCase))
-            {
-                try
-                {
-                    Directory.CreateDirectory(path);
-
-                    var testPath = Path.Combine(path, Guid.NewGuid().ToString());
-                    Directory.CreateDirectory(testPath);
-                    Directory.Delete(testPath);
-
-                    return path;
-                }
-                catch
-                {
-                }
-            }
-
-            path = DefaultTranscodingTempPath;
-            Directory.CreateDirectory(path);
-            return path;
-        }
-
+        /// <inheritdoc />
         public string InternalMetadataPath
         public string InternalMetadataPath
         {
         {
             get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
             get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
             set => _internalMetadataPath = value;
             set => _internalMetadataPath = value;
         }
         }
 
 
+        /// <inheritdoc />
         public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
         public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
     }
     }
 }
 }

+ 19 - 16
Emby.Server.Implementations/Services/ServicePath.cs

@@ -1,9 +1,11 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
 using System.Text;
 using System.Text;
+using System.Text.Json.Serialization;
 
 
 namespace Emby.Server.Implementations.Services
 namespace Emby.Server.Implementations.Services
 {
 {
@@ -28,6 +30,13 @@ namespace Emby.Server.Implementations.Services
         private readonly bool[] isWildcard;
         private readonly bool[] isWildcard;
         private readonly int wildcardCount = 0;
         private readonly int wildcardCount = 0;
 
 
+        internal static string[] IgnoreAttributesNamed = new[]
+        {
+            nameof(JsonIgnoreAttribute)
+        };
+
+        private static Type _excludeType = typeof(Stream);
+
         public int VariableArgsCount { get; set; }
         public int VariableArgsCount { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -37,8 +46,8 @@ namespace Emby.Server.Implementations.Services
         public int PathComponentsCount { get; set; }
         public int PathComponentsCount { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// The total number of segments after subparts have been exploded ('.')
-        /// e.g. /path/to/here.ext == 4
+        /// Gets or sets the total number of segments after subparts have been exploded ('.')
+        /// e.g. /path/to/here.ext == 4.
         /// </summary>
         /// </summary>
         public int TotalComponentsCount { get; set; }
         public int TotalComponentsCount { get; set; }
 
 
@@ -190,21 +199,12 @@ namespace Emby.Server.Implementations.Services
                     StringComparer.OrdinalIgnoreCase);
                     StringComparer.OrdinalIgnoreCase);
         }
         }
 
 
-        internal static string[] IgnoreAttributesNamed = new[]
-        {
-            "IgnoreDataMemberAttribute",
-            "JsonIgnoreAttribute"
-        };
-
-
-        private static Type excludeType = typeof(Stream);
-
         internal static IEnumerable<PropertyInfo> GetSerializableProperties(Type type)
         internal static IEnumerable<PropertyInfo> GetSerializableProperties(Type type)
         {
         {
             foreach (var prop in GetPublicProperties(type))
             foreach (var prop in GetPublicProperties(type))
             {
             {
                 if (prop.GetMethod == null
                 if (prop.GetMethod == null
-                    || excludeType == prop.PropertyType)
+                    || _excludeType == prop.PropertyType)
                 {
                 {
                     continue;
                     continue;
                 }
                 }
@@ -280,7 +280,7 @@ namespace Emby.Server.Implementations.Services
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Provide for quick lookups based on hashes that can be determined from a request url
+        /// Provide for quick lookups based on hashes that can be determined from a request url.
         /// </summary>
         /// </summary>
         public string FirstMatchHashKey { get; private set; }
         public string FirstMatchHashKey { get; private set; }
 
 
@@ -437,9 +437,12 @@ namespace Emby.Server.Implementations.Services
                     && requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount;
                     && requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount;
 
 
                 if (!isValidWildCardPath)
                 if (!isValidWildCardPath)
-                    throw new ArgumentException(string.Format(
-                        "Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
-                        pathInfo, this.restPath));
+                    throw new ArgumentException(
+                        string.Format(
+                            CultureInfo.InvariantCulture,
+                            "Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
+                            pathInfo,
+                            this.restPath));
             }
             }
 
 
             var requestKeyValuesMap = new Dictionary<string, string>();
             var requestKeyValuesMap = new Dictionary<string, string>();

+ 2 - 2
Emby.Server.Implementations/Services/SwaggerService.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
@@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.Services
 
 
         private SwaggerTag[] GetTags()
         private SwaggerTag[] GetTags()
         {
         {
-            return new SwaggerTag[] { };
+            return Array.Empty<SwaggerTag>();
         }
         }
 
 
         private Dictionary<string, SwaggerDefinition> GetDefinitions()
         private Dictionary<string, SwaggerDefinition> GetDefinitions()

Some files were not shown because too many files changed in this diff