aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrobibero <cody@robibe.ro>2020-09-05 09:00:55 -0600
committercrobibero <cody@robibe.ro>2020-09-05 09:00:55 -0600
commit8a08111adcc03d93acec60c783d204f98e99c83a (patch)
treed7591de2a59fd6be64573995c6fd6de44eae67f1
parent1de22af6466a691e023dd70b2032d5e3af3bdc6b (diff)
parent6d154041b9e456ab97e1066f525e4d9732fb672a (diff)
Merge remote-tracking branch 'upstream/master' into dynamic-cors
-rw-r--r--.ci/azure-pipelines-main.yml8
-rw-r--r--.ci/azure-pipelines-test.yml9
-rw-r--r--.vscode/tasks.json2
-rw-r--r--CONTRIBUTORS.md2
-rw-r--r--Dockerfile2
-rw-r--r--Dockerfile.arm2
-rw-r--r--Dockerfile.arm642
-rw-r--r--Emby.Dlna/ConnectionManager/ConnectionManagerService.cs6
-rw-r--r--Emby.Dlna/ContentDirectory/ContentDirectoryService.cs4
-rw-r--r--Emby.Dlna/ContentDirectory/ControlHandler.cs2
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs4
-rw-r--r--Emby.Dlna/Emby.Dlna.csproj1
-rw-r--r--Emby.Dlna/Eventing/DlnaEventManager.cs30
-rw-r--r--Emby.Dlna/Main/DlnaEntryPoint.cs15
-rw-r--r--Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs6
-rw-r--r--Emby.Dlna/PlayTo/Device.cs41
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs9
-rw-r--r--Emby.Dlna/PlayTo/SsdpHttpClient.cs116
-rw-r--r--Emby.Dlna/Service/BaseService.cs10
-rw-r--r--Emby.Naming/Common/NamingOptions.cs4
-rw-r--r--Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs2
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs47
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs2
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs2
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj8
-rw-r--r--Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs335
-rw-r--r--Emby.Server.Implementations/IO/FileRefresher.cs7
-rw-r--r--Emby.Server.Implementations/IO/LibraryMonitor.cs2
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs32
-rw-r--r--Emby.Server.Implementations/IO/StreamHelper.cs32
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs51
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs31
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs43
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs393
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs31
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs98
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs13
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs16
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs47
-rw-r--r--Emby.Server.Implementations/Localization/Core/nb.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/nn.json63
-rw-r--r--Emby.Server.Implementations/Localization/Core/ta.json32
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs59
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs42
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs12
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs14
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs15
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs17
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs88
-rw-r--r--Jellyfin.Api/BaseJellyfinApiController.cs6
-rw-r--r--Jellyfin.Api/Controllers/DashboardController.cs102
-rw-r--r--Jellyfin.Api/Controllers/DisplayPreferencesController.cs4
-rw-r--r--Jellyfin.Api/Controllers/DlnaServerController.cs20
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs7
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs3
-rw-r--r--Jellyfin.Api/Controllers/RemoteImageController.cs3
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs22
-rw-r--r--Jellyfin.Api/Helpers/AudioHelper.cs3
-rw-r--r--Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs2
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj4
-rw-r--r--Jellyfin.Api/MvcRoutePrefix.cs56
-rw-r--r--Jellyfin.Data/Entities/ActivityLog.cs78
-rw-r--r--Jellyfin.Data/Entities/DisplayPreferences.cs4
-rw-r--r--Jellyfin.Data/Entities/Group.cs61
-rw-r--r--Jellyfin.Data/Entities/ImageInfo.cs45
-rw-r--r--Jellyfin.Data/Entities/ItemDisplayPreferences.cs7
-rw-r--r--Jellyfin.Data/Entities/Libraries/Artwork.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/Book.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/BookMetadata.cs4
-rw-r--r--Jellyfin.Data/Entities/Libraries/Chapter.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/Collection.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/CollectionItem.cs4
-rw-r--r--Jellyfin.Data/Entities/Libraries/Company.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/CustomItem.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/Episode.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/Genre.cs10
-rw-r--r--Jellyfin.Data/Entities/Libraries/ItemMetadata.cs (renamed from Jellyfin.Data/Entities/Libraries/Metadata.cs)12
-rw-r--r--Jellyfin.Data/Entities/Libraries/MediaFile.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs10
-rw-r--r--Jellyfin.Data/Entities/Libraries/Movie.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/MovieMetadata.cs4
-rw-r--r--Jellyfin.Data/Entities/Libraries/MusicAlbum.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs4
-rw-r--r--Jellyfin.Data/Entities/Libraries/Person.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/PersonRole.cs12
-rw-r--r--Jellyfin.Data/Entities/Libraries/Photo.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/Rating.cs10
-rw-r--r--Jellyfin.Data/Entities/Libraries/Release.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/Season.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/Series.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs4
-rw-r--r--Jellyfin.Data/Entities/Libraries/Track.cs2
-rw-r--r--Jellyfin.Data/Entities/Libraries/TrackMetadata.cs2
-rw-r--r--Jellyfin.Data/Entities/Permission.cs36
-rw-r--r--Jellyfin.Data/Entities/Preference.cs26
-rw-r--r--Jellyfin.Data/Entities/ProviderMapping.cs129
-rw-r--r--Jellyfin.Data/Entities/User.cs49
-rw-r--r--Jellyfin.Data/Enums/ArtKind.cs34
-rw-r--r--Jellyfin.Data/Enums/DynamicDayOfWeek.cs44
-rw-r--r--Jellyfin.Data/Enums/IndexingKind.cs7
-rw-r--r--Jellyfin.Data/Enums/MediaFileKind.cs34
-rw-r--r--Jellyfin.Data/Enums/PersonRoleType.cs76
-rw-r--r--Jellyfin.Data/Enums/SubtitlePlaybackMode.cs26
-rw-r--r--Jellyfin.Data/Enums/UnratedItem.cs58
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj10
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj4
-rw-r--r--Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs36
-rw-r--r--Jellyfin.Server/CoreAppHost.cs10
-rw-r--r--Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs15
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs9
-rw-r--r--Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs2
-rw-r--r--Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs5
-rw-r--r--Jellyfin.Server/Formatters/XmlOutputFormatter.cs3
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj18
-rw-r--r--Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs6
-rw-r--r--Jellyfin.Server/Middleware/CorsPolicyProvider.cs7
-rw-r--r--Jellyfin.Server/Program.cs2
-rw-r--r--Jellyfin.Server/Startup.cs94
-rw-r--r--Jellyfin.Server/wwwroot/api-docs/banner-dark.svg34
-rw-r--r--Jellyfin.Server/wwwroot/api-docs/redoc/custom.css0
-rw-r--r--Jellyfin.Server/wwwroot/api-docs/swagger/custom.css15
-rw-r--r--MediaBrowser.Common/Configuration/IApplicationPaths.cs5
-rw-r--r--MediaBrowser.Common/Json/JsonDefaults.cs10
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj4
-rw-r--r--MediaBrowser.Common/Net/CacheMode.cs11
-rw-r--r--MediaBrowser.Common/Net/CompressionMethods.cs15
-rw-r--r--MediaBrowser.Common/Net/HttpRequestOptions.cs105
-rw-r--r--MediaBrowser.Common/Net/HttpResponseInfo.cs80
-rw-r--r--MediaBrowser.Common/Net/IHttpClient.cs53
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs59
-rw-r--r--MediaBrowser.Controller/IDisplayPreferencesManager.cs11
-rw-r--r--MediaBrowser.Controller/LiveTv/ChannelInfo.cs1
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs230
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj1
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs14
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs10
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs27
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs35
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs6
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs18
-rw-r--r--MediaBrowser.Model/IO/FileSystemMetadata.cs2
-rw-r--r--MediaBrowser.Model/IO/IFileSystem.cs4
-rw-r--r--MediaBrowser.Model/IO/IShortcutHandler.cs1
-rw-r--r--MediaBrowser.Model/IO/IStreamHelper.cs2
-rw-r--r--MediaBrowser.Model/IO/IZipClient.cs1
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj2
-rw-r--r--MediaBrowser.Model/Net/MimeTypes.cs2
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs2
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj6
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs10
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs11
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs21
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs3
-rw-r--r--MediaBrowser.Providers/Studios/StudiosImageProvider.cs5
-rw-r--r--MediaBrowser.sln6
-rwxr-xr-xdebian/rules2
-rw-r--r--deployment/Dockerfile.debian.amd642
-rw-r--r--deployment/Dockerfile.debian.arm642
-rw-r--r--deployment/Dockerfile.debian.armhf2
-rw-r--r--deployment/Dockerfile.docker.amd642
-rw-r--r--deployment/Dockerfile.docker.arm642
-rw-r--r--deployment/Dockerfile.docker.armhf2
-rw-r--r--deployment/Dockerfile.linux.amd642
-rw-r--r--deployment/Dockerfile.macos2
-rw-r--r--deployment/Dockerfile.portable2
-rw-r--r--deployment/Dockerfile.ubuntu.amd642
-rw-r--r--deployment/Dockerfile.ubuntu.arm642
-rw-r--r--deployment/Dockerfile.ubuntu.armhf2
-rw-r--r--deployment/Dockerfile.windows.amd642
-rwxr-xr-xdeployment/build.linux.amd642
-rwxr-xr-xdeployment/build.macos2
-rwxr-xr-xdeployment/build.portable2
-rwxr-xr-xdeployment/build.windows.amd642
-rw-r--r--fedora/jellyfin.spec2
-rw-r--r--tests/Jellyfin.Api.Tests/BrandingServiceTests.cs (renamed from tests/MediaBrowser.Api.Tests/BrandingServiceTests.cs)2
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj11
-rw-r--r--tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs (renamed from tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs)2
-rw-r--r--tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs42
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj4
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj4
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj4
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj4
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs4
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj5
-rw-r--r--tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj34
-rw-r--r--tests/jellyfin-tests.ruleset4
-rw-r--r--windows/build-jellyfin.ps12
215 files changed, 1739 insertions, 2417 deletions
diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml
index 2a1c0e6f2..7617f0f5a 100644
--- a/.ci/azure-pipelines-main.yml
+++ b/.ci/azure-pipelines-main.yml
@@ -64,28 +64,28 @@ jobs:
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: false
- - task: PublishPipelineArtifact@0
+ - task: PublishPipelineArtifact@1
displayName: 'Publish Artifact Naming'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
artifactName: 'Jellyfin.Naming'
- - task: PublishPipelineArtifact@0
+ - task: PublishPipelineArtifact@1
displayName: 'Publish Artifact Controller'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
artifactName: 'Jellyfin.Controller'
- - task: PublishPipelineArtifact@0
+ - task: PublishPipelineArtifact@1
displayName: 'Publish Artifact Model'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
artifactName: 'Jellyfin.Model'
- - task: PublishPipelineArtifact@0
+ - task: PublishPipelineArtifact@1
displayName: 'Publish Artifact Common'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml
index a3c7f8526..eca8aa90f 100644
--- a/.ci/azure-pipelines-test.yml
+++ b/.ci/azure-pipelines-test.yml
@@ -74,7 +74,6 @@ jobs:
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
displayName: 'Run ReportGenerator'
- enabled: false
inputs:
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
targetdir: "$(Agent.TempDirectory)/merged/"
@@ -84,10 +83,16 @@ jobs:
- task: PublishCodeCoverageResults@1
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
displayName: 'Publish Code Coverage'
- enabled: false
inputs:
codeCoverageTool: "cobertura"
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
pathToSources: $(Build.SourcesDirectory)
failIfCoverageEmpty: true
+
+ - task: PublishPipelineArtifact@1
+ displayName: 'Publish OpenAPI Artifact'
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+ inputs:
+ targetPath: "tests/Jellyfin.Api.Tests/bin/Release/netcoreapp3.1/openapi.json"
+ artifactName: 'OpenAPI Spec'
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 7ddc49d5c..09c523eb2 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -17,7 +17,7 @@
"type": "process",
"args": [
"test",
- "${workspaceFolder}/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj"
+ "${workspaceFolder}/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj"
],
"problemMatcher": "$msCompile"
}
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 1b4fdc8c4..f0724b412 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -57,6 +57,7 @@
- [Larvitar](https://github.com/Larvitar)
- [LeoVerto](https://github.com/LeoVerto)
- [Liggy](https://github.com/Liggy)
+ - [lmaonator](https://github.com/lmaonator)
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
- [loli10K](https://github.com/loli10K)
- [lostmypillow](https://github.com/lostmypillow)
@@ -78,6 +79,7 @@
- [nvllsvm](https://github.com/nvllsvm)
- [nyanmisaka](https://github.com/nyanmisaka)
- [oddstr13](https://github.com/oddstr13)
+ - [orryverducci](https://github.com/orryverducci)
- [petermcneil](https://github.com/petermcneil)
- [Phlogi](https://github.com/Phlogi)
- [pjeanjean](https://github.com/pjeanjean)
diff --git a/Dockerfile b/Dockerfile
index d3fb138a8..69af9b77b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,7 +14,7 @@ COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
FROM debian:buster-slim
diff --git a/Dockerfile.arm b/Dockerfile.arm
index 59b8a8c98..efeed25df 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
FROM multiarch/qemu-user-static:x86_64-arm as qemu
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index 1a691b572..1f2c2ec36 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM arm64v8/debian:buster-slim
diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs
index 12338e2b4..f5a7eca72 100644
--- a/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs
+++ b/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs
@@ -1,8 +1,8 @@
#pragma warning disable CS1591
+using System.Net.Http;
using System.Threading.Tasks;
using Emby.Dlna.Service;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using Microsoft.Extensions.Logging;
@@ -18,8 +18,8 @@ namespace Emby.Dlna.ConnectionManager
IDlnaManager dlna,
IServerConfigurationManager config,
ILogger<ConnectionManagerService> logger,
- IHttpClient httpClient)
- : base(logger, httpClient)
+ IHttpClientFactory httpClientFactory)
+ : base(logger, httpClientFactory)
{
_dlna = dlna;
_config = config;
diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
index 72732823a..5760f260c 100644
--- a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
+++ b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
@@ -2,11 +2,11 @@
using System;
using System.Linq;
+using System.Net.Http;
using System.Threading.Tasks;
using Emby.Dlna.Service;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
@@ -41,7 +41,7 @@ namespace Emby.Dlna.ContentDirectory
IServerConfigurationManager config,
IUserManager userManager,
ILogger<ContentDirectoryService> logger,
- IHttpClient httpClient,
+ IHttpClientFactory httpClient,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
IUserViewManager userViewManager,
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index be1ed7872..4b108b89e 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -1363,7 +1363,7 @@ namespace Emby.Dlna.ContentDirectory
};
}
- Logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
+ Logger.LogError("Error parsing item Id: {Id}. Returning user root folder.", id);
return new ServerItem(_libraryManager.GetUserRootFolder());
}
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index bd09a8051..5b8a89d8f 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -948,7 +948,7 @@ namespace Emby.Dlna.Didl
}
catch (XmlException ex)
{
- _logger.LogError(ex, "Error adding xml value: {value}", name);
+ _logger.LogError(ex, "Error adding xml value: {Value}", name);
}
}
@@ -960,7 +960,7 @@ namespace Emby.Dlna.Didl
}
catch (XmlException ex)
{
- _logger.LogError(ex, "Error adding xml value: {value}", value);
+ _logger.LogError(ex, "Error adding xml value: {Value}", value);
}
}
diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj
index 8538f580c..6ed49944c 100644
--- a/Emby.Dlna/Emby.Dlna.csproj
+++ b/Emby.Dlna/Emby.Dlna.csproj
@@ -80,6 +80,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
</ItemGroup>
</Project>
diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs
index b66e966df..7d8da86ef 100644
--- a/Emby.Dlna/Eventing/DlnaEventManager.cs
+++ b/Emby.Dlna/Eventing/DlnaEventManager.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
+using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
@@ -20,13 +21,13 @@ namespace Emby.Dlna.Eventing
new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
private readonly ILogger _logger;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- public DlnaEventManager(ILogger logger, IHttpClient httpClient)
+ public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_logger = logger;
}
@@ -167,24 +168,17 @@ namespace Emby.Dlna.Eventing
builder.Append("</e:propertyset>");
- var options = new HttpRequestOptions
- {
- RequestContent = builder.ToString(),
- RequestContentType = "text/xml",
- Url = subscription.CallbackUrl,
- BufferContent = false
- };
-
- options.RequestHeaders.Add("NT", subscription.NotificationType);
- options.RequestHeaders.Add("NTS", "upnp:propchange");
- options.RequestHeaders.Add("SID", subscription.Id);
- options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture));
+ using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl);
+ options.Content = new StringContent(builder.ToString(), Encoding.UTF8, MediaTypeNames.Text.Xml);
+ options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType);
+ options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange");
+ options.Headers.TryAddWithoutValidation("SID", subscription.Id);
+ options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(_usCulture));
try
{
- using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
- {
- }
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index 0ad82022d..40c2cc0e0 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -2,6 +2,7 @@
using System;
using System.Globalization;
+using System.Net.Http;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
@@ -36,7 +37,7 @@ namespace Emby.Dlna.Main
private readonly ILogger<DlnaEntryPoint> _logger;
private readonly IServerApplicationHost _appHost;
private readonly ISessionManager _sessionManager;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager;
@@ -61,7 +62,7 @@ namespace Emby.Dlna.Main
ILoggerFactory loggerFactory,
IServerApplicationHost appHost,
ISessionManager sessionManager,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager,
IUserManager userManager,
IDlnaManager dlnaManager,
@@ -79,7 +80,7 @@ namespace Emby.Dlna.Main
_config = config;
_appHost = appHost;
_sessionManager = sessionManager;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
_userManager = userManager;
_dlnaManager = dlnaManager;
@@ -101,7 +102,7 @@ namespace Emby.Dlna.Main
config,
userManager,
loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(),
- httpClient,
+ httpClientFactory,
localizationManager,
mediaSourceManager,
userViewManager,
@@ -112,11 +113,11 @@ namespace Emby.Dlna.Main
dlnaManager,
config,
loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(),
- httpClient);
+ httpClientFactory);
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService(
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(),
- httpClient,
+ httpClientFactory,
config);
Current = this;
}
@@ -364,7 +365,7 @@ namespace Emby.Dlna.Main
_appHost,
_imageProcessor,
_deviceDiscovery,
- _httpClient,
+ _httpClientFactory,
_config,
_userDataManager,
_localization,
diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs
index 28de2fef5..e6d845e1e 100644
--- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs
+++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs
@@ -1,8 +1,8 @@
#pragma warning disable CS1591
+using System.Net.Http;
using System.Threading.Tasks;
using Emby.Dlna.Service;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using Microsoft.Extensions.Logging;
@@ -14,9 +14,9 @@ namespace Emby.Dlna.MediaReceiverRegistrar
public MediaReceiverRegistrarService(
ILogger<MediaReceiverRegistrarService> logger,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
IServerConfigurationManager config)
- : base(logger, httpClient)
+ : base(logger, httpClientFactory)
{
_config = config;
}
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 5462e7abc..c97acdb02 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Net.Http;
using System.Security;
using System.Threading;
using System.Threading.Tasks;
@@ -21,7 +22,7 @@ namespace Emby.Dlna.PlayTo
{
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
@@ -34,10 +35,10 @@ namespace Emby.Dlna.PlayTo
private int _connectFailureCount;
private bool _disposed;
- public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger)
+ public Device(DeviceInfo deviceProperties, IHttpClientFactory httpClientFactory, ILogger logger)
{
Properties = deviceProperties;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_logger = logger;
}
@@ -236,7 +237,7 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("Setting mute");
var value = mute ? 1 : 0;
- await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
+ await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
.ConfigureAwait(false);
IsMuted = mute;
@@ -271,7 +272,7 @@ namespace Emby.Dlna.PlayTo
// Remote control will perform better
Volume = value;
- await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
+ await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
.ConfigureAwait(false);
}
@@ -292,7 +293,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service");
}
- await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
+ await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
.ConfigureAwait(false);
RestartTimer(true);
@@ -326,7 +327,7 @@ namespace Emby.Dlna.PlayTo
}
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
- await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
+ await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
.ConfigureAwait(false);
await Task.Delay(50).ConfigureAwait(false);
@@ -368,7 +369,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service");
}
- return new SsdpHttpClient(_httpClient).SendCommandAsync(
+ return new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -397,7 +398,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService();
- await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
+ await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
.ConfigureAwait(false);
RestartTimer(true);
@@ -415,7 +416,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService();
- await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
+ await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
.ConfigureAwait(false);
TransportState = TransportState.Paused;
@@ -542,7 +543,7 @@ namespace Emby.Dlna.PlayTo
return;
}
- var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -592,7 +593,7 @@ namespace Emby.Dlna.PlayTo
return;
}
- var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -625,7 +626,7 @@ namespace Emby.Dlna.PlayTo
return null;
}
- var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -667,7 +668,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -734,7 +735,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -912,7 +913,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
- var httpClient = new SsdpHttpClient(_httpClient);
+ var httpClient = new SsdpHttpClient(_httpClientFactory);
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
@@ -940,7 +941,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
- var httpClient = new SsdpHttpClient(_httpClient);
+ var httpClient = new SsdpHttpClient(_httpClientFactory);
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
@@ -969,9 +970,9 @@ namespace Emby.Dlna.PlayTo
return baseUrl + url;
}
- public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, ILogger logger, CancellationToken cancellationToken)
+ public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken)
{
- var ssdpHttpClient = new SsdpHttpClient(httpClient);
+ var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
@@ -1079,7 +1080,7 @@ namespace Emby.Dlna.PlayTo
}
}
- return new Device(deviceProperties, httpClient, logger);
+ return new Device(deviceProperties, httpClientFactory, logger);
}
private static DeviceIcon CreateIcon(XElement element)
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index ff801f263..3d1dd3e73 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -4,6 +4,7 @@ using System;
using System.Globalization;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Events;
@@ -33,7 +34,7 @@ namespace Emby.Dlna.PlayTo
private readonly IDlnaManager _dlnaManager;
private readonly IServerApplicationHost _appHost;
private readonly IImageProcessor _imageProcessor;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _config;
private readonly IUserDataManager _userDataManager;
private readonly ILocalizationManager _localization;
@@ -46,7 +47,7 @@ namespace Emby.Dlna.PlayTo
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
- public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
+ public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
{
_logger = logger;
_sessionManager = sessionManager;
@@ -56,7 +57,7 @@ namespace Emby.Dlna.PlayTo
_appHost = appHost;
_imageProcessor = imageProcessor;
_deviceDiscovery = deviceDiscovery;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_config = config;
_userDataManager = userDataManager;
_localization = localization;
@@ -174,7 +175,7 @@ namespace Emby.Dlna.PlayTo
if (controller == null)
{
- var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _logger, cancellationToken).ConfigureAwait(false);
+ var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
string deviceName = device.Properties.Name;
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index ab262bebf..8683c8997 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -4,6 +4,8 @@ using System;
using System.Globalization;
using System.IO;
using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net.Mime;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -20,11 +22,11 @@ namespace Emby.Dlna.PlayTo
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
- public SsdpHttpClient(IHttpClient httpClient)
+ public SsdpHttpClient(IHttpClientFactory httpClientFactory)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
}
public async Task<XDocument> SendCommandAsync(
@@ -36,20 +38,18 @@ namespace Emby.Dlna.PlayTo
CancellationToken cancellationToken = default)
{
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
- using (var response = await PostSoapDataAsync(
- url,
- $"\"{service.ServiceType}#{command}\"",
- postData,
- header,
- cancellationToken)
- .ConfigureAwait(false))
- using (var stream = response.Content)
- using (var reader = new StreamReader(stream, Encoding.UTF8))
- {
- return XDocument.Parse(
- await reader.ReadToEndAsync().ConfigureAwait(false),
- LoadOptions.PreserveWhitespace);
- }
+ using var response = await PostSoapDataAsync(
+ url,
+ $"\"{service.ServiceType}#{command}\"",
+ postData,
+ header,
+ cancellationToken)
+ .ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ using var reader = new StreamReader(stream, Encoding.UTF8);
+ return XDocument.Parse(
+ await reader.ReadToEndAsync().ConfigureAwait(false),
+ LoadOptions.PreserveWhitespace);
}
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
@@ -76,49 +76,32 @@ namespace Emby.Dlna.PlayTo
int eventport,
int timeOut = 3600)
{
- var options = new HttpRequestOptions
- {
- Url = url,
- UserAgent = USERAGENT,
- LogErrorResponseBody = true,
- BufferContent = false,
- };
-
- options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture);
- options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport.ToString(_usCulture) + ">";
- options.RequestHeaders["NT"] = "upnp:event";
- options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture);
-
- using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
- {
- }
+ using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url);
+ options.Headers.UserAgent.ParseAdd(USERAGENT);
+ options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(_usCulture));
+ options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(_usCulture) + ">");
+ options.Headers.TryAddWithoutValidation("NT", "upnp:event");
+ options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(_usCulture));
+
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .SendAsync(options, HttpCompletionOption.ResponseHeadersRead)
+ .ConfigureAwait(false);
}
public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken)
{
- var options = new HttpRequestOptions
- {
- Url = url,
- UserAgent = USERAGENT,
- LogErrorResponseBody = true,
- BufferContent = false,
-
- CancellationToken = cancellationToken
- };
-
- options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
-
- using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
- using (var stream = response.Content)
- using (var reader = new StreamReader(stream, Encoding.UTF8))
- {
- return XDocument.Parse(
- await reader.ReadToEndAsync().ConfigureAwait(false),
- LoadOptions.PreserveWhitespace);
- }
+ using var options = new HttpRequestMessage(HttpMethod.Get, url);
+ options.Headers.UserAgent.ParseAdd(USERAGENT);
+ options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ using var reader = new StreamReader(stream, Encoding.UTF8);
+ return XDocument.Parse(
+ await reader.ReadToEndAsync().ConfigureAwait(false),
+ LoadOptions.PreserveWhitespace);
}
- private Task<HttpResponseInfo> PostSoapDataAsync(
+ private Task<HttpResponseMessage> PostSoapDataAsync(
string url,
string soapAction,
string postData,
@@ -130,29 +113,20 @@ namespace Emby.Dlna.PlayTo
soapAction = $"\"{soapAction}\"";
}
- var options = new HttpRequestOptions
- {
- Url = url,
- UserAgent = USERAGENT,
- LogErrorResponseBody = true,
- BufferContent = false,
-
- CancellationToken = cancellationToken
- };
-
- options.RequestHeaders["SOAPAction"] = soapAction;
- options.RequestHeaders["Pragma"] = "no-cache";
- options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
+ using var options = new HttpRequestMessage(HttpMethod.Post, url);
+ options.Headers.UserAgent.ParseAdd(USERAGENT);
+ options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction);
+ options.Headers.TryAddWithoutValidation("Pragma", "no-cache");
+ options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
if (!string.IsNullOrEmpty(header))
{
- options.RequestHeaders["contentFeatures.dlna.org"] = header;
+ options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
}
- options.RequestContentType = "text/xml";
- options.RequestContent = postData;
+ options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml);
- return _httpClient.Post(options);
+ return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
}
}
}
diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs
index 40d069e7c..a97c4d63a 100644
--- a/Emby.Dlna/Service/BaseService.cs
+++ b/Emby.Dlna/Service/BaseService.cs
@@ -1,25 +1,21 @@
#pragma warning disable CS1591
+using System.Net.Http;
using Emby.Dlna.Eventing;
-using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.Service
{
public class BaseService : IDlnaEventManager
{
- protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient)
+ protected BaseService(ILogger<BaseService> logger, IHttpClientFactory httpClientFactory)
{
Logger = logger;
- HttpClient = httpClient;
-
- EventManager = new DlnaEventManager(logger, HttpClient);
+ EventManager = new DlnaEventManager(logger, httpClientFactory);
}
protected IDlnaEventManager EventManager { get; }
- protected IHttpClient HttpClient { get; }
-
protected ILogger Logger { get; }
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index d1e17f416..fd4244f64 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -136,8 +136,8 @@ namespace Emby.Naming.Common
CleanDateTimes = new[]
{
- @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
- @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
+ @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
+ @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
};
CleanStrings = new[]
diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
index d4a8268b9..4ab0a2a3f 100644
--- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
+++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
@@ -308,7 +308,7 @@ namespace Emby.Server.Implementations.AppBase
}
catch (Exception ex)
{
- Logger.LogError(ex, "Error loading configuration file: {path}", path);
+ Logger.LogError(ex, "Error loading configuration file: {Path}", path);
return Activator.CreateInstance(configurationType);
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 8e9a581ea..c37e87d96 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -49,6 +49,7 @@ using Jellyfin.Api.Helpers;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
@@ -122,8 +123,8 @@ namespace Emby.Server.Implementations
private IMediaEncoder _mediaEncoder;
private ISessionManager _sessionManager;
+ private IHttpClientFactory _httpClientFactory;
private IWebSocketManager _webSocketManager;
- private IHttpClient _httpClient;
private string[] _urlPrefixes;
@@ -279,6 +280,10 @@ namespace Emby.Server.Implementations
Password = ServerConfigurationManager.Configuration.CertificatePassword
};
Certificate = GetCertificate(CertificateInfo);
+
+ ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
+ ApplicationVersionString = ApplicationVersion.ToString(3);
+ ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
}
public string ExpandVirtualPath(string path)
@@ -308,16 +313,16 @@ namespace Emby.Server.Implementations
}
/// <inheritdoc />
- public Version ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version;
+ public Version ApplicationVersion { get; }
/// <inheritdoc />
- public string ApplicationVersionString { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
+ public string ApplicationVersionString { get; }
/// <summary>
/// Gets the current application user agent.
/// </summary>
/// <value>The application user agent.</value>
- public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersionString;
+ public string ApplicationUserAgent { get; }
/// <summary>
/// Gets the email address for use within a comment section of a user agent field.
@@ -522,8 +527,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton(_fileSystemManager);
ServiceCollection.AddSingleton<TvdbClientManager>();
- ServiceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>();
-
ServiceCollection.AddSingleton(_networkManager);
ServiceCollection.AddSingleton<IIsoManager, IsoManager>();
@@ -650,8 +653,8 @@ namespace Emby.Server.Implementations
_mediaEncoder = Resolve<IMediaEncoder>();
_sessionManager = Resolve<ISessionManager>();
+ _httpClientFactory = Resolve<IHttpClientFactory>();
_webSocketManager = Resolve<IWebSocketManager>();
- _httpClient = Resolve<IHttpClient>();
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
@@ -1296,25 +1299,17 @@ namespace Emby.Server.Implementations
try
{
- using (var response = await _httpClient.SendAsync(
- new HttpRequestOptions
- {
- Url = apiUrl,
- LogErrorResponseBody = false,
- BufferContent = false,
- CancellationToken = cancellationToken
- }, HttpMethod.Post).ConfigureAwait(false))
- {
- using (var reader = new StreamReader(response.Content))
- {
- var result = await reader.ReadToEndAsync().ConfigureAwait(false);
- var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
+ using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
- Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid);
- return valid;
- }
- }
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var result = await System.Text.Json.JsonSerializer.DeserializeAsync<string>(stream, JsonDefaults.GetOptions(), cancellationToken).ConfigureAwait(false);
+ var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
+
+ _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
+ Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid);
+ return valid;
}
catch (OperationCanceledException)
{
@@ -1401,7 +1396,7 @@ namespace Emby.Server.Implementations
foreach (var assembly in assemblies)
{
- Logger.LogDebug("Found API endpoints in plugin {name}", assembly.FullName);
+ Logger.LogDebug("Found API endpoints in plugin {Name}", assembly.FullName);
yield return assembly;
}
}
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 26fc1bee4..fb1bb65a0 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -890,7 +890,7 @@ namespace Emby.Server.Implementations.Channels
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error writing to channel cache file: {path}", path);
+ _logger.LogError(ex, "Error writing to channel cache file: {Path}", path);
}
}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index f2c7118fe..57c1398e9 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -197,7 +197,7 @@ namespace Emby.Server.Implementations.Dto
catch (Exception ex)
{
// Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
- _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {itemName}", item.Name);
+ _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {ItemName}", item.Name);
}
}
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 56fc57327..0a348f0d0 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -32,10 +32,10 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
- <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.6" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
- <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.7" />
<PackageReference Include="Mono.Nat" Version="2.0.2" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />
diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
deleted file mode 100644
index 25adc5812..000000000
--- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
+++ /dev/null
@@ -1,335 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Threading.Tasks;
-using MediaBrowser.Common;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace Emby.Server.Implementations.HttpClientManager
-{
- /// <summary>
- /// Class HttpClientManager.
- /// </summary>
- public class HttpClientManager : IHttpClient
- {
- private readonly ILogger<HttpClientManager> _logger;
- private readonly IApplicationPaths _appPaths;
- private readonly IFileSystem _fileSystem;
- private readonly IApplicationHost _appHost;
-
- /// <summary>
- /// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests.
- /// DON'T dispose it after use.
- /// </summary>
- /// <value>The HTTP clients.</value>
- private readonly ConcurrentDictionary<string, HttpClient> _httpClients = new ConcurrentDictionary<string, HttpClient>();
-
- /// <summary>
- /// Initializes a new instance of the <see cref="HttpClientManager" /> class.
- /// </summary>
- public HttpClientManager(
- IApplicationPaths appPaths,
- ILogger<HttpClientManager> logger,
- IFileSystem fileSystem,
- IApplicationHost appHost)
- {
- _logger = logger ?? throw new ArgumentNullException(nameof(logger));
- _fileSystem = fileSystem;
- _appPaths = appPaths ?? throw new ArgumentNullException(nameof(appPaths));
- _appHost = appHost;
- }
-
- /// <summary>
- /// Gets the correct http client for the given url.
- /// </summary>
- /// <param name="url">The url.</param>
- /// <returns>HttpClient.</returns>
- private HttpClient GetHttpClient(string url)
- {
- var key = GetHostFromUrl(url);
-
- if (!_httpClients.TryGetValue(key, out var client))
- {
- client = new HttpClient()
- {
- BaseAddress = new Uri(url)
- };
-
- _httpClients.TryAdd(key, client);
- }
-
- return client;
- }
-
- private HttpRequestMessage GetRequestMessage(HttpRequestOptions options, HttpMethod method)
- {
- string url = options.Url;
- var uriAddress = new Uri(url);
- string userInfo = uriAddress.UserInfo;
- if (!string.IsNullOrWhiteSpace(userInfo))
- {
- _logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url);
- url = url.Replace(userInfo + '@', string.Empty, StringComparison.Ordinal);
- }
-
- var request = new HttpRequestMessage(method, url);
-
- foreach (var header in options.RequestHeaders)
- {
- request.Headers.TryAddWithoutValidation(header.Key, header.Value);
- }
-
- if (options.EnableDefaultUserAgent
- && !request.Headers.TryGetValues(HeaderNames.UserAgent, out _))
- {
- request.Headers.Add(HeaderNames.UserAgent, _appHost.ApplicationUserAgent);
- }
-
- switch (options.DecompressionMethod)
- {
- case CompressionMethods.Deflate | CompressionMethods.Gzip:
- request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" });
- break;
- case CompressionMethods.Deflate:
- request.Headers.Add(HeaderNames.AcceptEncoding, "deflate");
- break;
- case CompressionMethods.Gzip:
- request.Headers.Add(HeaderNames.AcceptEncoding, "gzip");
- break;
- default:
- break;
- }
-
- if (options.EnableKeepAlive)
- {
- request.Headers.Add(HeaderNames.Connection, "Keep-Alive");
- }
-
- // request.Headers.Add(HeaderNames.CacheControl, "no-cache");
-
- /*
- if (!string.IsNullOrWhiteSpace(userInfo))
- {
- var parts = userInfo.Split(':');
- if (parts.Length == 2)
- {
- request.Headers.Add(HeaderNames., GetCredential(url, parts[0], parts[1]);
- }
- }
- */
-
- return request;
- }
-
- /// <summary>
- /// Gets the response internal.
- /// </summary>
- /// <param name="options">The options.</param>
- /// <returns>Task{HttpResponseInfo}.</returns>
- public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options)
- => SendAsync(options, HttpMethod.Get);
-
- /// <summary>
- /// Performs a GET request and returns the resulting stream.
- /// </summary>
- /// <param name="options">The options.</param>
- /// <returns>Task{Stream}.</returns>
- public async Task<Stream> Get(HttpRequestOptions options)
- {
- var response = await GetResponse(options).ConfigureAwait(false);
- return response.Content;
- }
-
- /// <summary>
- /// send as an asynchronous operation.
- /// </summary>
- /// <param name="options">The options.</param>
- /// <param name="httpMethod">The HTTP method.</param>
- /// <returns>Task{HttpResponseInfo}.</returns>
- public Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
- => SendAsync(options, new HttpMethod(httpMethod));
-
- /// <summary>
- /// send as an asynchronous operation.
- /// </summary>
- /// <param name="options">The options.</param>
- /// <param name="httpMethod">The HTTP method.</param>
- /// <returns>Task{HttpResponseInfo}.</returns>
- public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod)
- {
- if (options.CacheMode == CacheMode.None)
- {
- return await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
- }
-
- var url = options.Url;
- var urlHash = url.ToUpperInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
-
- var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
-
- var response = GetCachedResponse(responseCachePath, options.CacheLength, url);
- if (response != null)
- {
- return response;
- }
-
- response = await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
-
- if (response.StatusCode == HttpStatusCode.OK)
- {
- await CacheResponse(response, responseCachePath).ConfigureAwait(false);
- }
-
- return response;
- }
-
- private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url)
- {
- if (File.Exists(responseCachePath)
- && _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
- {
- var stream = new FileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true);
-
- return new HttpResponseInfo
- {
- ResponseUrl = url,
- Content = stream,
- StatusCode = HttpStatusCode.OK,
- ContentLength = stream.Length
- };
- }
-
- return null;
- }
-
- private async Task CacheResponse(HttpResponseInfo response, string responseCachePath)
- {
- Directory.CreateDirectory(Path.GetDirectoryName(responseCachePath));
-
- using (var fileStream = new FileStream(
- responseCachePath,
- FileMode.Create,
- FileAccess.Write,
- FileShare.None,
- IODefaults.FileStreamBufferSize,
- true))
- {
- await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
-
- response.Content.Position = 0;
- }
- }
-
- private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, HttpMethod httpMethod)
- {
- ValidateParams(options);
-
- options.CancellationToken.ThrowIfCancellationRequested();
-
- var client = GetHttpClient(options.Url);
-
- var httpWebRequest = GetRequestMessage(options, httpMethod);
-
- if (!string.IsNullOrEmpty(options.RequestContent)
- || httpMethod == HttpMethod.Post)
- {
- if (options.RequestContent != null)
- {
- httpWebRequest.Content = new StringContent(
- options.RequestContent,
- null,
- options.RequestContentType);
- }
- else
- {
- httpWebRequest.Content = new ByteArrayContent(Array.Empty<byte>());
- }
- }
-
- options.CancellationToken.ThrowIfCancellationRequested();
-
- var response = await client.SendAsync(
- httpWebRequest,
- options.BufferContent || options.CacheMode == CacheMode.Unconditional ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead,
- options.CancellationToken).ConfigureAwait(false);
-
- await EnsureSuccessStatusCode(response, options).ConfigureAwait(false);
-
- options.CancellationToken.ThrowIfCancellationRequested();
-
- var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
- return new HttpResponseInfo(response.Headers, response.Content.Headers)
- {
- Content = stream,
- StatusCode = response.StatusCode,
- ContentType = response.Content.Headers.ContentType?.MediaType,
- ContentLength = response.Content.Headers.ContentLength,
- ResponseUrl = response.Content.Headers.ContentLocation?.ToString()
- };
- }
-
- /// <inheritdoc />
- public Task<HttpResponseInfo> Post(HttpRequestOptions options)
- => SendAsync(options, HttpMethod.Post);
-
- private void ValidateParams(HttpRequestOptions options)
- {
- if (string.IsNullOrEmpty(options.Url))
- {
- throw new ArgumentNullException(nameof(options));
- }
- }
-
- /// <summary>
- /// Gets the host from URL.
- /// </summary>
- /// <param name="url">The URL.</param>
- /// <returns>System.String.</returns>
- private static string GetHostFromUrl(string url)
- {
- var index = url.IndexOf("://", StringComparison.OrdinalIgnoreCase);
-
- if (index != -1)
- {
- url = url.Substring(index + 3);
- var host = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
-
- if (!string.IsNullOrWhiteSpace(host))
- {
- return host;
- }
- }
-
- return url;
- }
-
- private async Task EnsureSuccessStatusCode(HttpResponseMessage response, HttpRequestOptions options)
- {
- if (response.IsSuccessStatusCode)
- {
- return;
- }
-
- if (options.LogErrorResponseBody)
- {
- string msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
- _logger.LogError("HTTP request failed with message: {Message}", msg);
- }
-
- throw new HttpException(response.ReasonPhrase)
- {
- StatusCode = response.StatusCode
- };
- }
- }
-}
diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs
index fe74f1de7..7435e9d0b 100644
--- a/Emby.Server.Implementations/IO/FileRefresher.cs
+++ b/Emby.Server.Implementations/IO/FileRefresher.cs
@@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.IO
continue;
}
- _logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
+ _logger.LogInformation("{Name} ({Path}) will be refreshed.", item.Name, item.Path);
try
{
@@ -160,11 +160,11 @@ namespace Emby.Server.Implementations.IO
// For now swallow and log.
// Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
// Should we remove it from it's parent?
- _logger.LogError(ex, "Error refreshing {name}", item.Name);
+ _logger.LogError(ex, "Error refreshing {Name}", item.Name);
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error refreshing {name}", item.Name);
+ _logger.LogError(ex, "Error refreshing {Name}", item.Name);
}
}
}
@@ -214,6 +214,7 @@ namespace Emby.Server.Implementations.IO
}
}
+ /// <inheritdoc />
public void Dispose()
{
_disposed = true;
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index 9290dfcd0..3353fae9d 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.IO
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
+ _logger.LogError(ex, "Error in ReportFileSystemChanged for {Path}", path);
}
}
}
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index ab6483bf9..3cb025111 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -398,30 +398,6 @@ namespace Emby.Server.Implementations.IO
}
}
- public virtual void SetReadOnly(string path, bool isReadOnly)
- {
- if (OperatingSystem.Id != OperatingSystemId.Windows)
- {
- return;
- }
-
- var info = GetExtendedFileSystemInfo(path);
-
- if (info.Exists && info.IsReadOnly != isReadOnly)
- {
- if (isReadOnly)
- {
- File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.ReadOnly);
- }
- else
- {
- var attributes = File.GetAttributes(path);
- attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly);
- File.SetAttributes(path, attributes);
- }
- }
- }
-
public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
{
if (OperatingSystem.Id != OperatingSystemId.Windows)
@@ -707,14 +683,6 @@ namespace Emby.Server.Implementations.IO
return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
}
- public virtual void SetExecutable(string path)
- {
- if (OperatingSystem.Id == OperatingSystemId.Darwin)
- {
- RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path));
- }
- }
-
private static void RunProcess(string path, string args, string workingDirectory)
{
using (var process = Process.Start(new ProcessStartInfo
diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs
index 40b397edc..c16ebd61b 100644
--- a/Emby.Server.Implementations/IO/StreamHelper.cs
+++ b/Emby.Server.Implementations/IO/StreamHelper.cs
@@ -11,8 +11,6 @@ namespace Emby.Server.Implementations.IO
{
public class StreamHelper : IStreamHelper
{
- private const int StreamCopyToBufferSize = 81920;
-
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
@@ -83,37 +81,9 @@ namespace Emby.Server.Implementations.IO
}
}
- public async Task<int> CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken)
- {
- byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
- try
- {
- int totalBytesRead = 0;
-
- int bytesRead;
- while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
- {
- var bytesToWrite = bytesRead;
-
- if (bytesToWrite > 0)
- {
- await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
-
- totalBytesRead += bytesRead;
- }
- }
-
- return totalBytesRead;
- }
- finally
- {
- ArrayPool<byte>.Shared.Return(buffer);
- }
- }
-
public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
{
- byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
+ byte[] buffer = ArrayPool<byte>.Shared.Rent(IODefaults.CopyToBufferSize);
try
{
int bytesRead;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index 2e13a3bb3..44560d1e2 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -16,13 +16,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public class DirectRecorder : IRecorder
{
private readonly ILogger _logger;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IStreamHelper _streamHelper;
- public DirectRecorder(ILogger logger, IHttpClient httpClient, IStreamHelper streamHelper)
+ public DirectRecorder(ILogger logger, IHttpClientFactory httpClientFactory, IStreamHelper streamHelper)
{
_logger = logger;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_streamHelper = streamHelper;
}
@@ -52,10 +52,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("Copying recording stream to file {0}", targetFile);
// The media source is infinite so we need to handle stopping ourselves
- var durationToken = new CancellationTokenSource(duration);
- cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ using var durationToken = new CancellationTokenSource(duration);
+ using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
- await directStreamProvider.CopyToAsync(output, cancellationToken).ConfigureAwait(false);
+ await directStreamProvider.CopyToAsync(output, cancellationTokenSource.Token).ConfigureAwait(false);
}
_logger.LogInformation("Recording completed to file {0}", targetFile);
@@ -63,37 +63,28 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private async Task RecordFromMediaSource(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
- var httpRequestOptions = new HttpRequestOptions
- {
- Url = mediaSource.Path,
- BufferContent = false,
-
- // Some remote urls will expect a user agent to be supplied
- UserAgent = "Emby/3.0",
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetAsync(mediaSource.Path, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- // Shouldn't matter but may cause issues
- DecompressionMethod = CompressionMethods.None
- };
+ _logger.LogInformation("Opened recording stream from tuner provider");
- using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false))
- {
- _logger.LogInformation("Opened recording stream from tuner provider");
+ Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
- Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
+ await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read);
- using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
- {
- onStarted();
+ onStarted();
- _logger.LogInformation("Copying recording stream to file {0}", targetFile);
+ _logger.LogInformation("Copying recording stream to file {0}", targetFile);
- // The media source if infinite so we need to handle stopping ourselves
- var durationToken = new CancellationTokenSource(duration);
- cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ // The media source if infinite so we need to handle stopping ourselves
+ var durationToken = new CancellationTokenSource(duration);
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
- await _streamHelper.CopyUntilCancelled(response.Content, output, 81920, cancellationToken).ConfigureAwait(false);
- }
- }
+ await _streamHelper.CopyUntilCancelled(
+ await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
+ output,
+ IODefaults.CopyToBufferSize,
+ cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Recording completed to file {0}", targetFile);
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 09c52d95b..ca60c3366 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -48,7 +49,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IServerApplicationHost _appHost;
private readonly ILogger<EmbyTV> _logger;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _config;
private readonly IJsonSerializer _jsonSerializer;
@@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IMediaSourceManager mediaSourceManager,
ILogger<EmbyTV> logger,
IJsonSerializer jsonSerializer,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
IServerConfigurationManager config,
ILiveTvManager liveTvManager,
IFileSystem fileSystem,
@@ -94,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_appHost = appHost;
_logger = logger;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_config = config;
_fileSystem = fileSystem;
_libraryManager = libraryManager;
@@ -604,11 +605,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return Task.CompletedTask;
}
- public Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
- {
- return Task.CompletedTask;
- }
-
public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
{
throw new NotImplementedException();
@@ -808,11 +804,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return null;
}
- public IEnumerable<ActiveRecordingInfo> GetAllActiveRecordings()
- {
- return _activeRecordings.Values.Where(i => i.Timer.Status == RecordingStatus.InProgress && !i.CancellationTokenSource.IsCancellationRequested);
- }
-
public ActiveRecordingInfo GetActiveRecordingInfo(string path)
{
if (string.IsNullOrWhiteSpace(path))
@@ -1015,16 +1006,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new Exception("Tuner not found.");
}
- private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, bool enableStreamSharing)
- {
- var json = _jsonSerializer.SerializeToString(mediaSource);
- mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
-
- mediaSource.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_" + mediaSource.Id;
-
- return mediaSource;
- }
-
public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(channelId))
@@ -1654,10 +1635,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
{
- return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
+ return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer);
}
- return new DirectRecorder(_logger, _httpClient, _streamHelper);
+ return new DirectRecorder(_logger, _httpClientFactory, _streamHelper);
}
private void OnSuccessfulRecording(TimerInfo timer, string path)
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 612dc5238..3e5457dbd 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -8,12 +8,9 @@ using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
@@ -26,26 +23,24 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly ILogger _logger;
private readonly IMediaEncoder _mediaEncoder;
private readonly IServerApplicationPaths _appPaths;
+ private readonly IJsonSerializer _json;
+ private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
+
private bool _hasExited;
private Stream _logFileStream;
private string _targetPath;
private Process _process;
- private readonly IJsonSerializer _json;
- private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
- private readonly IServerConfigurationManager _config;
public EncodedRecorder(
ILogger logger,
IMediaEncoder mediaEncoder,
IServerApplicationPaths appPaths,
- IJsonSerializer json,
- IServerConfigurationManager config)
+ IJsonSerializer json)
{
_logger = logger;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
_json = json;
- _config = config;
}
private static bool CopySubtitles => false;
@@ -58,19 +53,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public async Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
// The media source is infinite so we need to handle stopping ourselves
- var durationToken = new CancellationTokenSource(duration);
- cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ using var durationToken = new CancellationTokenSource(duration);
+ using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
- await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false);
+ await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
_logger.LogInformation("Recording completed to file {0}", targetFile);
}
- private EncodingOptions GetEncodingOptions()
- {
- return _config.GetConfiguration<EncodingOptions>("encoding");
- }
-
private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
_targetPath = targetFile;
@@ -108,7 +98,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
StartInfo = processStartInfo,
EnableRaisingEvents = true
};
- _process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
+ _process.Exited += (sender, args) => OnFfMpegProcessExited(_process);
_process.Start();
@@ -221,20 +211,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
protected string GetOutputSizeParam()
- {
- var filters = new List<string>();
-
- filters.Add("yadif=0:-1:0");
-
- var output = string.Empty;
-
- if (filters.Count > 0)
- {
- output += string.Format(CultureInfo.InvariantCulture, " -vf \"{0}\"", string.Join(",", filters.ToArray()));
- }
-
- return output;
- }
+ => "-vf \"yadif=0:-1:0\"";
private void Stop()
{
@@ -291,7 +268,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
/// <summary>
/// Processes the exited.
/// </summary>
- private void OnFfMpegProcessExited(Process process, string inputFile)
+ private void OnFfMpegProcessExited(Process process)
{
using (process)
{
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index c4d5cc58a..f9ae55af8 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -8,6 +8,8 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Mime;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
@@ -24,23 +26,23 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
public class SchedulesDirect : IListingsProvider
{
+ private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
+
private readonly ILogger<SchedulesDirect> _logger;
private readonly IJsonSerializer _jsonSerializer;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
private readonly IApplicationHost _appHost;
- private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
-
public SchedulesDirect(
ILogger<SchedulesDirect> logger,
IJsonSerializer jsonSerializer,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
IApplicationHost appHost)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_appHost = appHost;
}
@@ -61,7 +63,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
while (start <= end)
{
- dates.Add(start.ToString("yyyy-MM-dd"));
+ dates.Add(start.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
start = start.AddDays(1);
}
@@ -102,95 +104,78 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var requestString = _jsonSerializer.SerializeToString(requestList);
_logger.LogDebug("Request string for schedules is: {RequestString}", requestString);
- var httpOptions = new HttpRequestOptions()
- {
- Url = ApiUrl + "/schedules",
- UserAgent = UserAgent,
- CancellationToken = cancellationToken,
- LogErrorResponseBody = true,
- RequestContent = requestString
- };
+ using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules");
+ options.Content = new StringContent(requestString, Encoding.UTF8, MediaTypeNames.Application.Json);
+ options.Headers.TryAddWithoutValidation("token", token);
+ using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
+ await using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Day>>(responseStream).ConfigureAwait(false);
+ _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
- httpOptions.RequestHeaders["token"] = token;
+ using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
+ programRequestOptions.Headers.TryAddWithoutValidation("token", token);
- using (var response = await Post(httpOptions, true, info).ConfigureAwait(false))
- {
- var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Day>>(response.Content).ConfigureAwait(false);
- _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
+ var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct();
+ programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json);
- httpOptions = new HttpRequestOptions()
- {
- Url = ApiUrl + "/programs",
- UserAgent = UserAgent,
- CancellationToken = cancellationToken,
- LogErrorResponseBody = true
- };
-
- httpOptions.RequestHeaders["token"] = token;
+ using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
+ await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var programDetails = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream).ConfigureAwait(false);
+ var programDict = programDetails.ToDictionary(p => p.programID, y => y);
- var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct();
- httpOptions.RequestContent = "[\"" + string.Join("\", \"", programsID) + "\"]";
+ var programIdsWithImages =
+ programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID)
+ .ToList();
- using (var innerResponse = await Post(httpOptions, true, info).ConfigureAwait(false))
- {
- var programDetails = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ProgramDetails>>(innerResponse.Content).ConfigureAwait(false);
- var programDict = programDetails.ToDictionary(p => p.programID, y => y);
-
- var programIdsWithImages =
- programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID)
- .ToList();
+ var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
- var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
+ var programsInfo = new List<ProgramInfo>();
+ foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs))
+ {
+ // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
+ // " which corresponds to channel " + channelNumber + " and program id " +
+ // schedule.programID + " which says it has images? " +
+ // programDict[schedule.programID].hasImageArtwork);
- var programsInfo = new List<ProgramInfo>();
- foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs))
+ if (images != null)
+ {
+ var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
+ if (imageIndex > -1)
{
- // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
- // " which corresponds to channel " + channelNumber + " and program id " +
- // schedule.programID + " which says it has images? " +
- // programDict[schedule.programID].hasImageArtwork);
-
- if (images != null)
- {
- var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
- if (imageIndex > -1)
- {
- var programEntry = programDict[schedule.programID];
+ var programEntry = programDict[schedule.programID];
- var allImages = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
- var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase));
- var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase));
+ var allImages = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
+ var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase));
+ var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase));
- const double DesiredAspect = 2.0 / 3;
+ const double DesiredAspect = 2.0 / 3;
- programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ??
- GetProgramImage(ApiUrl, allImages, true, DesiredAspect);
+ programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ??
+ GetProgramImage(ApiUrl, allImages, true, DesiredAspect);
- const double WideAspect = 16.0 / 9;
+ const double WideAspect = 16.0 / 9;
- programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect);
+ programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect);
- // Don't supply the same image twice
- if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal))
- {
- programEntry.thumbImage = null;
- }
-
- programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
-
- // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
- // GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
- // GetProgramImage(ApiUrl, data, "Banner-LO", false) ??
- // GetProgramImage(ApiUrl, data, "Banner-LOT", false);
- }
+ // Don't supply the same image twice
+ if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal))
+ {
+ programEntry.thumbImage = null;
}
- programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID]));
- }
+ programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
- return programsInfo;
+ // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
+ // GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
+ // GetProgramImage(ApiUrl, data, "Banner-LO", false) ??
+ // GetProgramImage(ApiUrl, data, "Banner-LOT", false);
+ }
}
+
+ programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID]));
}
+
+ return programsInfo;
}
private static int GetSizeOrder(ScheduleDirect.ImageData image)
@@ -367,13 +352,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (!string.IsNullOrWhiteSpace(details.originalAirDate))
{
- info.OriginalAirDate = DateTime.Parse(details.originalAirDate);
+ info.OriginalAirDate = DateTime.Parse(details.originalAirDate, CultureInfo.InvariantCulture);
info.ProductionYear = info.OriginalAirDate.Value.Year;
}
if (details.movie != null)
{
- if (!string.IsNullOrEmpty(details.movie.year) && int.TryParse(details.movie.year, out int year))
+ if (!string.IsNullOrEmpty(details.movie.year)
+ && int.TryParse(details.movie.year, out int year))
{
info.ProductionYear = year;
}
@@ -482,22 +468,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
imageIdString = imageIdString.TrimEnd(',') + "]";
- var httpOptions = new HttpRequestOptions()
- {
- Url = ApiUrl + "/metadata/programs",
- UserAgent = UserAgent,
- CancellationToken = cancellationToken,
- RequestContent = imageIdString,
- LogErrorResponseBody = true,
- };
+ using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs");
+ message.Content = new StringContent(imageIdString, Encoding.UTF8, MediaTypeNames.Application.Json);
try
{
- using (var innerResponse2 = await Post(httpOptions, true, info).ConfigureAwait(false))
- {
- return await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ShowImages>>(
- innerResponse2.Content).ConfigureAwait(false);
- }
+ using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
+ await using var response = await innerResponse2.Content.ReadAsStreamAsync();
+ return await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ShowImages>>(
+ response).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -518,41 +497,33 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return lineups;
}
- var options = new HttpRequestOptions()
- {
- Url = ApiUrl + "/headends?country=" + country + "&postalcode=" + location,
- UserAgent = UserAgent,
- CancellationToken = cancellationToken,
- LogErrorResponseBody = true
- };
-
- options.RequestHeaders["token"] = token;
+ using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/headends?country=" + country + "&postalcode=" + location);
+ options.Headers.TryAddWithoutValidation("token", token);
try
{
- using (var httpResponse = await Get(options, false, info).ConfigureAwait(false))
- using (Stream responce = httpResponse.Content)
- {
- var root = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Headends>>(responce).ConfigureAwait(false);
+ using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
+ await using var response = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
- if (root != null)
+ var root = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Headends>>(response).ConfigureAwait(false);
+
+ if (root != null)
+ {
+ foreach (ScheduleDirect.Headends headend in root)
{
- foreach (ScheduleDirect.Headends headend in root)
+ foreach (ScheduleDirect.Lineup lineup in headend.lineups)
{
- foreach (ScheduleDirect.Lineup lineup in headend.lineups)
+ lineups.Add(new NameIdPair
{
- lineups.Add(new NameIdPair
- {
- Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name,
- Id = lineup.uri.Substring(18)
- });
- }
+ Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name,
+ Id = lineup.uri.Substring(18)
+ });
}
}
- else
- {
- _logger.LogInformation("No lineups available");
- }
+ }
+ else
+ {
+ _logger.LogInformation("No lineups available");
}
}
catch (Exception ex)
@@ -587,7 +558,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return null;
}
- NameValuePair savedToken = null;
+ NameValuePair savedToken;
if (!_tokens.TryGetValue(username, out savedToken))
{
savedToken = new NameValuePair();
@@ -633,16 +604,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- private async Task<HttpResponseInfo> Post(HttpRequestOptions options,
+ private async Task<HttpResponseMessage> Send(
+ HttpRequestMessage options,
bool enableRetry,
- ListingsProviderInfo providerInfo)
+ ListingsProviderInfo providerInfo,
+ CancellationToken cancellationToken,
+ HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
{
- // Schedules direct requires that the client support compression and will return a 400 response without it
- options.DecompressionMethod = CompressionMethods.Deflate;
-
try
{
- return await _httpClient.Post(options).ConfigureAwait(false);
+ return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, completionOption, cancellationToken).ConfigureAwait(false);
}
catch (HttpException ex)
{
@@ -659,65 +630,28 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false);
- return await Post(options, false, providerInfo).ConfigureAwait(false);
+ options.Headers.TryAddWithoutValidation("token", await GetToken(providerInfo, cancellationToken).ConfigureAwait(false));
+ return await Send(options, false, providerInfo, cancellationToken).ConfigureAwait(false);
}
- private async Task<HttpResponseInfo> Get(HttpRequestOptions options,
- bool enableRetry,
- ListingsProviderInfo providerInfo)
- {
- // Schedules direct requires that the client support compression and will return a 400 response without it
- options.DecompressionMethod = CompressionMethods.Deflate;
-
- try
- {
- return await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
- }
- catch (HttpException ex)
- {
- _tokens.Clear();
-
- if (!ex.StatusCode.HasValue || (int)ex.StatusCode.Value >= 500)
- {
- enableRetry = false;
- }
-
- if (!enableRetry)
- {
- throw;
- }
- }
-
- options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false);
- return await Get(options, false, providerInfo).ConfigureAwait(false);
- }
-
- private async Task<string> GetTokenInternal(string username, string password,
+ private async Task<string> GetTokenInternal(
+ string username,
+ string password,
CancellationToken cancellationToken)
{
- var httpOptions = new HttpRequestOptions()
- {
- Url = ApiUrl + "/token",
- UserAgent = UserAgent,
- RequestContent = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}",
- CancellationToken = cancellationToken,
- LogErrorResponseBody = true
- };
- // _logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
- // httpOptions.RequestContent);
+ using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token");
+ options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json);
- using (var response = await Post(httpOptions, false, null).ConfigureAwait(false))
+ using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(stream).ConfigureAwait(false);
+ if (root.message == "OK")
{
- var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(response.Content).ConfigureAwait(false);
- if (root.message == "OK")
- {
- _logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
- return root.token;
- }
-
- throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message);
+ _logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
+ return root.token;
}
+
+ throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message);
}
private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
@@ -736,20 +670,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
_logger.LogInformation("Adding new LineUp ");
- var httpOptions = new HttpRequestOptions()
- {
- Url = ApiUrl + "/lineups/" + info.ListingsId,
- UserAgent = UserAgent,
- CancellationToken = cancellationToken,
- LogErrorResponseBody = true,
- BufferContent = false
- };
-
- httpOptions.RequestHeaders["token"] = token;
-
- using (await _httpClient.SendAsync(httpOptions, HttpMethod.Put).ConfigureAwait(false))
- {
- }
+ using var options = new HttpRequestMessage(HttpMethod.Put, ApiUrl + "/lineups/" + info.ListingsId);
+ options.Headers.TryAddWithoutValidation("token", token);
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
}
private async Task<bool> HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken)
@@ -768,25 +691,17 @@ namespace Emby.Server.Implementations.LiveTv.Listings
_logger.LogInformation("Headends on account ");
- var options = new HttpRequestOptions()
- {
- Url = ApiUrl + "/lineups",
- UserAgent = UserAgent,
- CancellationToken = cancellationToken,
- LogErrorResponseBody = true
- };
-
- options.RequestHeaders["token"] = token;
+ using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups");
+ options.Headers.TryAddWithoutValidation("token", token);
try
{
- using (var httpResponse = await Get(options, false, null).ConfigureAwait(false))
- using (var response = httpResponse.Content)
- {
- var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(response).ConfigureAwait(false);
+ using var httpResponse = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
+ await using var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ using var response = httpResponse.Content;
+ var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(stream).ConfigureAwait(false);
- return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
- }
+ return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
}
catch (HttpException ex)
{
@@ -851,55 +766,43 @@ namespace Emby.Server.Implementations.LiveTv.Listings
throw new Exception("token required");
}
- var httpOptions = new HttpRequestOptions()
- {
- Url = ApiUrl + "/lineups/" + listingsId,
- UserAgent = UserAgent,
- CancellationToken = cancellationToken,
- LogErrorResponseBody = true,
- };
-
- httpOptions.RequestHeaders["token"] = token;
+ using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups/" + listingsId);
+ options.Headers.TryAddWithoutValidation("token", token);
var list = new List<ChannelInfo>();
- using (var httpResponse = await Get(httpOptions, true, info).ConfigureAwait(false))
- using (var response = httpResponse.Content)
- {
- var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(response).ConfigureAwait(false);
- _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
- _logger.LogInformation("Mapping Stations to Channel");
-
- var allStations = root.stations ?? Enumerable.Empty<ScheduleDirect.Station>();
+ using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
+ await using var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(stream).ConfigureAwait(false);
+ _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
+ _logger.LogInformation("Mapping Stations to Channel");
- foreach (ScheduleDirect.Map map in root.map)
- {
- var channelNumber = GetChannelNumber(map);
+ var allStations = root.stations ?? Enumerable.Empty<ScheduleDirect.Station>();
- var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase));
- if (station == null)
- {
- station = new ScheduleDirect.Station
- {
- stationID = map.stationID
- };
- }
+ foreach (ScheduleDirect.Map map in root.map)
+ {
+ var channelNumber = GetChannelNumber(map);
- var channelInfo = new ChannelInfo
- {
- Id = station.stationID,
- CallSign = station.callsign,
- Number = channelNumber,
- Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name
- };
+ var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase));
+ if (station == null)
+ {
+ station = new ScheduleDirect.Station { stationID = map.stationID };
+ }
- if (station.logo != null)
- {
- channelInfo.ImageUrl = station.logo.URL;
- }
+ var channelInfo = new ChannelInfo
+ {
+ Id = station.stationID,
+ CallSign = station.callsign,
+ Number = channelNumber,
+ Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name
+ };
- list.Add(channelInfo);
+ if (station.logo != null)
+ {
+ channelInfo.ImageUrl = station.logo.URL;
}
+
+ list.Add(channelInfo);
}
return list;
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index f33d07174..2d6f453bd 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -25,20 +25,20 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class XmlTvListingsProvider : IListingsProvider
{
private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<XmlTvListingsProvider> _logger;
private readonly IFileSystem _fileSystem;
private readonly IZipClient _zipClient;
public XmlTvListingsProvider(
IServerConfigurationManager config,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
ILogger<XmlTvListingsProvider> logger,
IFileSystem fileSystem,
IZipClient zipClient)
{
_config = config;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_logger = logger;
_fileSystem = fileSystem;
_zipClient = zipClient;
@@ -78,28 +78,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
- using (var res = await _httpClient.SendAsync(
- new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = path,
- DecompressionMethod = CompressionMethods.Gzip,
- },
- HttpMethod.Get).ConfigureAwait(false))
- using (var stream = res.Content)
- using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew))
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(path, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew))
{
- if (res.ContentHeaders.ContentEncoding.Contains("gzip"))
- {
- using (var gzStream = new GZipStream(stream, CompressionMode.Decompress))
- {
- await gzStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
- }
- }
- else
- {
- await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
- }
+ await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
return UnzipIfNeeded(path, cacheFile);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 2b5f69d41..28e30fac8 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
@@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
IServerConfigurationManager config,
ILogger<HdHomerunHost> logger,
IFileSystem fileSystem,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
IServerApplicationHost appHost,
ISocketFactory socketFactory,
INetworkManager networkManager,
@@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
IMemoryCache memoryCache)
: base(config, logger, fileSystem, memoryCache)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_appHost = appHost;
_socketFactory = socketFactory;
_networkManager = networkManager;
@@ -71,15 +71,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- var options = new HttpRequestOptions
- {
- Url = model.LineupURL,
- CancellationToken = cancellationToken,
- BufferContent = false
- };
-
- using var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
- await using var stream = response.Content;
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false) ?? new List<Channels>();
@@ -133,14 +126,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
try
{
- using var response = await _httpClient.SendAsync(
- new HttpRequestOptions
- {
- Url = string.Format(CultureInfo.InvariantCulture, "{0}/discover.json", GetApiUrl(info)),
- CancellationToken = cancellationToken,
- BufferContent = false
- }, HttpMethod.Get).ConfigureAwait(false);
- await using var stream = response.Content;
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/discover.json", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken)
+ .ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var discoverResponse = await JsonSerializer.DeserializeAsync<DiscoverResponse>(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false);
@@ -183,48 +172,41 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- using (var response = await _httpClient.SendAsync(
- new HttpRequestOptions()
- {
- Url = string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)),
- CancellationToken = cancellationToken,
- BufferContent = false
- },
- HttpMethod.Get).ConfigureAwait(false))
- using (var stream = response.Content)
- using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
- {
- var tuners = new List<LiveTvTunerInfo>();
- while (!sr.EndOfStream)
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken)
+ .ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ using var sr = new StreamReader(stream, System.Text.Encoding.UTF8);
+ var tuners = new List<LiveTvTunerInfo>();
+ while (!sr.EndOfStream)
+ {
+ string line = StripXML(sr.ReadLine());
+ if (line.Contains("Channel", StringComparison.Ordinal))
{
- string line = StripXML(sr.ReadLine());
- if (line.Contains("Channel", StringComparison.Ordinal))
+ LiveTvTunerStatus status;
+ var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
+ var name = line.Substring(0, index - 1);
+ var currentChannel = line.Substring(index + 7);
+ if (currentChannel != "none")
{
- LiveTvTunerStatus status;
- var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
- var name = line.Substring(0, index - 1);
- var currentChannel = line.Substring(index + 7);
- if (currentChannel != "none")
- {
- status = LiveTvTunerStatus.LiveTv;
- }
- else
- {
- status = LiveTvTunerStatus.Available;
- }
-
- tuners.Add(new LiveTvTunerInfo
- {
- Name = name,
- SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
- ProgramName = currentChannel,
- Status = status
- });
+ status = LiveTvTunerStatus.LiveTv;
+ }
+ else
+ {
+ status = LiveTvTunerStatus.Available;
}
- }
- return tuners;
+ tuners.Add(new LiveTvTunerInfo
+ {
+ Name = name,
+ SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
+ ProgramName = currentChannel,
+ Status = status
+ });
+ }
}
+
+ return tuners;
}
private static string StripXML(string source)
@@ -634,7 +616,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
info,
streamId,
FileSystem,
- _httpClient,
+ _httpClientFactory,
Logger,
Config,
_appHost,
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 8fc29fb4a..8107bc427 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
@@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost;
private readonly INetworkManager _networkManager;
private readonly IMediaSourceManager _mediaSourceManager;
@@ -37,14 +38,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
IMediaSourceManager mediaSourceManager,
ILogger<M3UTunerHost> logger,
IFileSystem fileSystem,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
IServerApplicationHost appHost,
INetworkManager networkManager,
IStreamHelper streamHelper,
IMemoryCache memoryCache)
: base(config, logger, fileSystem, memoryCache)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_appHost = appHost;
_networkManager = networkManager;
_mediaSourceManager = mediaSourceManager;
@@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
var channelIdPrefix = GetFullChannelIdPrefix(info);
- return await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
+ return await new M3uParser(Logger, _httpClientFactory, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
}
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
@@ -116,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config, _appHost, _streamHelper);
+ return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
}
}
@@ -125,7 +126,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task Validate(TunerHostInfo info)
{
- using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
+ using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
{
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 875977219..f066a749e 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@@ -19,13 +20,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public class M3uParser
{
private readonly ILogger _logger;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost;
- public M3uParser(ILogger logger, IHttpClient httpClient, IServerApplicationHost appHost)
+ public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory, IServerApplicationHost appHost)
{
_logger = logger;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_appHost = appHost;
}
@@ -51,13 +52,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
- return _httpClient.Get(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- // Some data providers will require a user agent
- UserAgent = _appHost.ApplicationUserAgent
- });
+ return _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetStreamAsync(url);
}
return Task.FromResult((Stream)File.OpenRead(url));
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index bc4dcd894..6c10fca8c 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class SharedHttpStream : LiveStream, IDirectStreamProvider
{
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost;
public SharedHttpStream(
@@ -29,14 +29,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
TunerHostInfo tunerHostInfo,
string originalStreamId,
IFileSystem fileSystem,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
ILogger logger,
IConfigurationManager configurationManager,
IServerApplicationHost appHost,
IStreamHelper streamHelper)
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_appHost = appHost;
OriginalStreamId = originalStreamId;
EnableStreamSharing = true;
@@ -55,25 +55,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var typeName = GetType().Name;
Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url);
- var httpRequestOptions = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None,
- BufferContent = false,
- DecompressionMethod = CompressionMethods.None
- };
-
- foreach (var header in mediaSource.RequiredHttpHeaders)
- {
- httpRequestOptions.RequestHeaders[header.Key] = header.Value;
- }
-
- var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false);
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None)
+ .ConfigureAwait(false);
var extension = "ts";
var requiresRemux = false;
- var contentType = response.ContentType ?? string.Empty;
+ var contentType = response.Content.Headers.ContentType.ToString();
if (contentType.IndexOf("matroska", StringComparison.OrdinalIgnoreCase) != -1)
{
requiresRemux = true;
@@ -132,24 +121,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
- private Task StartStreaming(HttpResponseInfo response, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
+ private Task StartStreaming(HttpResponseMessage response, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
return Task.Run(async () =>
{
try
{
Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath);
- using (response)
- using (var stream = response.Content)
- using (var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
- {
- await StreamHelper.CopyToAsync(
- stream,
- fileStream,
- IODefaults.CopyToBufferSize,
- () => Resolve(openTaskCompletionSource),
- cancellationToken).ConfigureAwait(false);
- }
+ using var message = response;
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read);
+ await StreamHelper.CopyToAsync(
+ stream,
+ fileStream,
+ IODefaults.CopyToBufferSize,
+ () => Resolve(openTaskCompletionSource),
+ cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json
index d4341f2e8..a97c2e17a 100644
--- a/Emby.Server.Implementations/Localization/Core/nb.json
+++ b/Emby.Server.Implementations/Localization/Core/nb.json
@@ -71,7 +71,7 @@
"ScheduledTaskFailedWithName": "{0} mislykkes",
"ScheduledTaskStartedWithName": "{0} startet",
"ServerNameNeedsToBeRestarted": "{0} må startes på nytt",
- "Shows": "Programmer",
+ "Shows": "Program",
"Songs": "Sanger",
"StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.",
"SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}",
@@ -88,7 +88,7 @@
"UserOnlineFromDevice": "{0} er tilkoblet fra {1}",
"UserPasswordChangedWithName": "Passordet for {0} er oppdatert",
"UserPolicyUpdatedWithName": "Brukerpolicyen har blitt oppdatert for {0}",
- "UserStartedPlayingItemWithValues": "{0} har startet avspilling {1}",
+ "UserStartedPlayingItemWithValues": "{0} har startet avspilling {1} på {2}",
"UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}",
"ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
"ValueSpecialEpisodeName": "Spesialepisode - {0}",
diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json
index 281cadac5..fb6e81beb 100644
--- a/Emby.Server.Implementations/Localization/Core/nn.json
+++ b/Emby.Server.Implementations/Localization/Core/nn.json
@@ -35,7 +35,7 @@
"AuthenticationSucceededWithUserName": "{0} Har logga inn",
"Artists": "Artistar",
"Application": "Program",
- "AppDeviceValues": "App: {0}, Einheit: {1}",
+ "AppDeviceValues": "App: {0}, Eining: {1}",
"Albums": "Album",
"NotificationOptionServerRestartRequired": "Tenaren krev omstart",
"NotificationOptionPluginUpdateInstalled": "Tilleggsprogram-oppdatering vart installert",
@@ -43,7 +43,7 @@
"NotificationOptionPluginInstalled": "Tilleggsprogram installert",
"NotificationOptionPluginError": "Tilleggsprogram feila",
"NotificationOptionNewLibraryContent": "Nytt innhald er lagt til",
- "NotificationOptionInstallationFailed": "Installasjonen feila",
+ "NotificationOptionInstallationFailed": "Installasjonsfeil",
"NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp",
"NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppa",
"NotificationOptionAudioPlayback": "Lydavspilling påbyrja",
@@ -56,5 +56,62 @@
"MusicVideos": "Musikkvideoar",
"Music": "Musikk",
"Movies": "Filmar",
- "MixedContent": "Blanda innhald"
+ "MixedContent": "Blanda innhald",
+ "Sync": "Synkronisera",
+ "TaskDownloadMissingSubtitlesDescription": "Søk Internettet for manglande undertekstar basert på metadatainnstillingar.",
+ "TaskDownloadMissingSubtitles": "Last ned manglande undertekstar",
+ "TaskRefreshChannelsDescription": "Oppdater internettkanalinformasjon.",
+ "TaskRefreshChannels": "Oppdater kanalar",
+ "TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gamal.",
+ "TaskCleanTranscode": "Reins transkodemappe",
+ "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringar for programtillegg som er sette opp til å oppdaterast automatisk.",
+ "TaskUpdatePlugins": "Oppdaterer programtillegg",
+ "TaskRefreshPeopleDescription": "Oppdaterer metadata for skodespelarar og regissørar i mediebiblioteket ditt.",
+ "TaskRefreshPeople": "Oppdater personar",
+ "TaskCleanLogsDescription": "Slett loggfiler som er meir enn {0} dagar gamle.",
+ "TaskCleanLogs": "Reins loggmappe",
+ "TaskRefreshLibraryDescription": "Skannar mediebiblioteket ditt for nye filer og oppdaterer metadata.",
+ "TaskRefreshLibrary": "Skann mediebibliotek",
+ "TaskRefreshChapterImagesDescription": "Lager miniatyrbilete for videoar som har kapittel.",
+ "TaskRefreshChapterImages": "Trekk ut kapittelbilete",
+ "TaskCleanCacheDescription": "Slettar mellomlagra filer som ikkje lengre trengst av systemet.",
+ "TaskCleanCache": "Rens mappe for hurtiglager",
+ "TasksChannelsCategory": "Internettkanalar",
+ "TasksApplicationCategory": "Applikasjon",
+ "TasksLibraryCategory": "Bibliotek",
+ "TasksMaintenanceCategory": "Vedlikehald",
+ "VersionNumber": "Versjon {0}",
+ "ValueSpecialEpisodeName": "Spesialepisode - {0}",
+ "ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
+ "UserStoppedPlayingItemWithValues": "{0} har fullført avspeling {1} på {2}",
+ "UserStartedPlayingItemWithValues": "{0} spelar {1} på {2}",
+ "UserPolicyUpdatedWithName": "Brukarreglar har blitt oppdatert for {0}",
+ "UserPasswordChangedWithName": "Passordet for {0} er oppdatert",
+ "UserOnlineFromDevice": "{0} er direktekopla frå {1}",
+ "UserOfflineFromDevice": "{0} har kopla frå {1}",
+ "UserLockedOutWithName": "Brukar {0} har blitt utestengd",
+ "UserDownloadingItemWithValues": "{0} lastar ned {1}",
+ "UserDeletedWithName": "Brukar {0} er sletta",
+ "UserCreatedWithName": "Brukar {0} er oppretta",
+ "User": "Brukar",
+ "TvShows": "TV-seriar",
+ "System": "System",
+ "SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}",
+ "StartupEmbyServerIsLoading": "Jellyfintenaren laster. Prøv igjen om litt.",
+ "Songs": "Songar",
+ "Shows": "Program",
+ "ServerNameNeedsToBeRestarted": "{0} må omstartast",
+ "ScheduledTaskStartedWithName": "{0} starta",
+ "ScheduledTaskFailedWithName": "{0} feila",
+ "ProviderValue": "Leverandør: {0}",
+ "PluginUpdatedWithName": "{0} blei oppdatert",
+ "PluginUninstalledWithName": "{0} blei avinstallert",
+ "PluginInstalledWithName": "{0} blei installert",
+ "Plugin": "Programtillegg",
+ "Playlists": "Speleliste",
+ "Photos": "Foto",
+ "NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa",
+ "NotificationOptionVideoPlayback": "Videoavspeling starta",
+ "NotificationOptionUserLockedOut": "Brukar er utestengd",
+ "NotificationOptionTaskFailed": "Planlagt oppgåve feila"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json
index d6be86da3..ed6877f7d 100644
--- a/Emby.Server.Implementations/Localization/Core/ta.json
+++ b/Emby.Server.Implementations/Localization/Core/ta.json
@@ -18,7 +18,7 @@
"MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன",
"MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது",
"MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது",
- "Inherit": "மரபரிமையாகப் பெறு",
+ "Inherit": "மரபுரிமையாகப் பெறு",
"HeaderRecordingGroups": "பதிவு குழுக்கள்",
"HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்",
"Folders": "கோப்புறைகள்",
@@ -31,7 +31,7 @@
"TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு",
"TaskRefreshChannels": "சேனல்களை புதுப்பி",
"TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி",
- "TaskRefreshLibrary": "மீடியா நூலகத்தை ஆராய்",
+ "TaskRefreshLibrary": "ஊடக நூலகத்தை ஆராய்",
"TasksChannelsCategory": "இணைய சேனல்கள்",
"TasksApplicationCategory": "செயலி",
"TasksLibraryCategory": "நூலகம்",
@@ -46,7 +46,7 @@
"Sync": "ஒத்திசைவு",
"StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.",
"Songs": "பாடல்கள்",
- "Shows": "தொடர்கள்",
+ "Shows": "நிகழ்ச்சிகள்",
"ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்",
"ScheduledTaskStartedWithName": "{0} துவங்கியது",
"ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது",
@@ -67,20 +67,20 @@
"NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது",
"NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது",
"NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்",
- "NameSeasonUnknown": "பருவம் அறியப்படாதவை",
+ "NameSeasonUnknown": "அறியப்படாத பருவம்",
"NameSeasonNumber": "பருவம் {0}",
"NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது",
"MusicVideos": "இசைப்படங்கள்",
"Music": "இசை",
"Movies": "திரைப்படங்கள்",
- "Latest": "புதியன",
+ "Latest": "புதியவை",
"LabelRunningTimeValue": "ஓடும் நேரம்: {0}",
"LabelIpAddressValue": "ஐபி முகவரி: {0}",
"ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது",
"ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது",
- "HeaderNextUp": "அடுத்ததாக",
+ "HeaderNextUp": "அடுத்தது",
"HeaderLiveTV": "நேரடித் தொலைக்காட்சி",
- "HeaderFavoriteSongs": "பிடித்த பாட்டுகள்",
+ "HeaderFavoriteSongs": "பிடித்த பாடல்கள்",
"HeaderFavoriteShows": "பிடித்த தொடர்கள்",
"HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்",
"HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
@@ -93,25 +93,25 @@
"Channels": "சேனல்கள்",
"Books": "புத்தகங்கள்",
"AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது",
- "Artists": "கலைஞர்",
+ "Artists": "கலைஞர்கள்",
"Application": "செயலி",
"Albums": "ஆல்பங்கள்",
"NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.",
- "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0 புதுப்பிக்கப்பட்டது",
+ "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0} புதுப்பிக்கப்பட்டது",
"TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.",
"UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
- "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0 } இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
- "TaskDownloadMissingSubtitlesDescription": "மெட்டாடேட்டா உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
+ "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
+ "TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
"TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
- "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட செருகுநிரல்களுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
- "TaskRefreshPeopleDescription": "உங்கள் மீடியா நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மெட்டாடேட்டாவை புதுப்பிக்கும்.",
+ "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
+ "TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.",
"TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
- "TaskCleanLogs": "பதிவு அடைவு சுத்தம் செய்யுங்கள்",
- "TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் மீடியா நூலகத்தை ஸ்கேன் செய்து மீத்தரவை புதுப்பிக்கும்.",
+ "TaskCleanLogs": "பதிவு அடைவை சுத்தம் செய்யுங்கள்",
+ "TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் ஊடக நூலகத்தை ஆராய்ந்து மீத்தரவை புதுப்பிக்கும்.",
"TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.",
"ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது",
"UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்",
"HomeVideos": "முகப்பு வீடியோக்கள்",
- "UserStoppedPlayingItemWithValues": "{2} இல் {1} முடித்துவிட்டது",
+ "UserStoppedPlayingItemWithValues": "{0} {2} இல் {1} முடித்துவிட்டது",
"UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது"
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
index e29fcfb5f..5adcefc1f 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
@@ -5,10 +5,10 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
-using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
@@ -21,10 +21,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
/// Gets or sets the application paths.
/// </summary>
/// <value>The application paths.</value>
- private IApplicationPaths ApplicationPaths { get; set; }
-
+ private readonly IApplicationPaths _applicationPaths;
private readonly ILogger<DeleteCacheFileTask> _logger;
-
private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
@@ -37,20 +35,41 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
IFileSystem fileSystem,
ILocalizationManager localization)
{
- ApplicationPaths = appPaths;
+ _applicationPaths = appPaths;
_logger = logger;
_fileSystem = fileSystem;
_localization = localization;
}
+ /// <inheritdoc />
+ public string Name => _localization.GetLocalizedString("TaskCleanCache");
+
+ /// <inheritdoc />
+ public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
+
+ /// <inheritdoc />
+ public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+
+ /// <inheritdoc />
+ public string Key => "DeleteCacheFiles";
+
+ /// <inheritdoc />
+ public bool IsHidden => false;
+
+ /// <inheritdoc />
+ public bool IsEnabled => true;
+
+ /// <inheritdoc />
+ public bool IsLogged => true;
+
/// <summary>
/// Creates the triggers that define when the task will run.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
- return new[] {
-
+ return new[]
+ {
// Every so often
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
};
@@ -68,7 +87,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
try
{
- DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.CachePath, minDateModified, progress);
+ DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.CachePath, minDateModified, progress);
}
catch (DirectoryNotFoundException)
{
@@ -81,7 +100,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
try
{
- DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, minDateModified, progress);
+ DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.TempDirectory, minDateModified, progress);
}
catch (DirectoryNotFoundException)
{
@@ -91,7 +110,6 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
return Task.CompletedTask;
}
-
/// <summary>
/// Deletes the cache files from directory with a last write time less than a given date.
/// </summary>
@@ -164,26 +182,5 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
_logger.LogError(ex, "Error deleting file {path}", path);
}
}
-
- /// <inheritdoc />
- public string Name => _localization.GetLocalizedString("TaskCleanCache");
-
- /// <inheritdoc />
- public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
-
- /// <inheritdoc />
- public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
-
- /// <inheritdoc />
- public string Key => "DeleteCacheFiles";
-
- /// <inheritdoc />
- public bool IsHidden => false;
-
- /// <inheritdoc />
- public bool IsEnabled => true;
-
- /// <inheritdoc />
- public bool IsLogged => true;
}
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
index 7388086fb..c5af68bce 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
@@ -34,6 +34,27 @@ namespace Emby.Server.Implementations.ScheduledTasks
_localization = localization;
}
+ /// <inheritdoc />
+ public string Name => _localization.GetLocalizedString("TaskUpdatePlugins");
+
+ /// <inheritdoc />
+ public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription");
+
+ /// <inheritdoc />
+ public string Category => _localization.GetLocalizedString("TasksApplicationCategory");
+
+ /// <inheritdoc />
+ public string Key => "PluginUpdates";
+
+ /// <inheritdoc />
+ public bool IsHidden => false;
+
+ /// <inheritdoc />
+ public bool IsEnabled => true;
+
+ /// <inheritdoc />
+ public bool IsLogged => true;
+
/// <summary>
/// Creates the triggers that define when the task will run.
/// </summary>
@@ -98,26 +119,5 @@ namespace Emby.Server.Implementations.ScheduledTasks
progress.Report(100);
}
-
- /// <inheritdoc />
- public string Name => _localization.GetLocalizedString("TaskUpdatePlugins");
-
- /// <inheritdoc />
- public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription");
-
- /// <inheritdoc />
- public string Category => _localization.GetLocalizedString("TasksApplicationCategory");
-
- /// <inheritdoc />
- public string Key => "PluginUpdates";
-
- /// <inheritdoc />
- public bool IsHidden => false;
-
- /// <inheritdoc />
- public bool IsEnabled => true;
-
- /// <inheritdoc />
- public bool IsLogged => true;
}
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
index eb628ec5f..8b67d37d7 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
@@ -11,7 +11,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
public class DailyTrigger : ITaskTrigger
{
/// <summary>
- /// Get the time of day to trigger the task to run.
+ /// Occurs when [triggered].
+ /// </summary>
+ public event EventHandler<EventArgs> Triggered;
+
+ /// <summary>
+ /// Gets or sets the time of day to trigger the task to run.
/// </summary>
/// <value>The time of day.</value>
public TimeSpan TimeOfDay { get; set; }
@@ -70,11 +75,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Occurs when [triggered].
- /// </summary>
- public event EventHandler<EventArgs> Triggered;
-
- /// <summary>
/// Called when [triggered].
/// </summary>
private void OnTriggered()
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs
index 247a6785a..b04fd7c7e 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs
@@ -11,6 +11,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </summary>
public class IntervalTrigger : ITaskTrigger
{
+ private DateTime _lastStartDate;
+
+ /// <summary>
+ /// Occurs when [triggered].
+ /// </summary>
+ public event EventHandler<EventArgs> Triggered;
+
/// <summary>
/// Gets or sets the interval.
/// </summary>
@@ -28,8 +35,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <value>The timer.</value>
private Timer Timer { get; set; }
- private DateTime _lastStartDate;
-
/// <summary>
/// Stars waiting for the trigger action.
/// </summary>
@@ -89,11 +94,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Occurs when [triggered].
- /// </summary>
- public event EventHandler<EventArgs> Triggered;
-
- /// <summary>
/// Called when [triggered].
/// </summary>
private void OnTriggered()
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs
index 96e5d8897..7cd5493da 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs
@@ -12,6 +12,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </summary>
public class StartupTrigger : ITaskTrigger
{
+ /// <summary>
+ /// Occurs when [triggered].
+ /// </summary>
+ public event EventHandler<EventArgs> Triggered;
+
public int DelayMs { get; set; }
/// <summary>
@@ -49,19 +54,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Occurs when [triggered].
- /// </summary>
- public event EventHandler<EventArgs> Triggered;
-
- /// <summary>
/// Called when [triggered].
/// </summary>
private void OnTriggered()
{
- if (Triggered != null)
- {
- Triggered(this, EventArgs.Empty);
- }
+ Triggered?.Invoke(this, EventArgs.Empty);
}
}
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs
index 4f1bf5c19..0c0ebec08 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs
@@ -11,7 +11,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
public class WeeklyTrigger : ITaskTrigger
{
/// <summary>
- /// Get the time of day to trigger the task to run.
+ /// Occurs when [triggered].
+ /// </summary>
+ public event EventHandler<EventArgs> Triggered;
+
+ /// <summary>
+ /// Gets or sets the time of day to trigger the task to run.
/// </summary>
/// <value>The time of day.</value>
public TimeSpan TimeOfDay { get; set; }
@@ -96,19 +101,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Occurs when [triggered].
- /// </summary>
- public event EventHandler<EventArgs> Triggered;
-
- /// <summary>
/// Called when [triggered].
/// </summary>
private void OnTriggered()
{
- if (Triggered != null)
- {
- Triggered(this, EventArgs.Empty);
- }
+ Triggered?.Invoke(this, EventArgs.Empty);
}
}
}
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 4f54c06dd..f121a3493 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Updates
/// </summary>
private readonly ILogger<InstallationManager> _logger;
private readonly IApplicationPaths _appPaths;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IJsonSerializer _jsonSerializer;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
@@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.Updates
ILogger<InstallationManager> logger,
IApplicationHost appHost,
IApplicationPaths appPaths,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
IJsonSerializer jsonSerializer,
IServerConfigurationManager config,
IFileSystem fileSystem,
@@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Updates
_logger = logger;
_applicationHost = appHost;
_appPaths = appPaths;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_jsonSerializer = jsonSerializer;
_config = config;
_fileSystem = fileSystem;
@@ -116,26 +116,18 @@ namespace Emby.Server.Implementations.Updates
{
try
{
- using (var response = await _httpClient.SendAsync(
- new HttpRequestOptions
- {
- Url = manifest,
- CancellationToken = cancellationToken,
- CacheMode = CacheMode.Unconditional,
- CacheLength = TimeSpan.FromMinutes(3)
- },
- HttpMethod.Get).ConfigureAwait(false))
- using (Stream stream = response.Content)
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetAsync(manifest, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+
+ try
{
- try
- {
- return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
- }
- catch (SerializationException ex)
- {
- _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest);
- return Array.Empty<PackageInfo>();
- }
+ return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
+ }
+ catch (SerializationException ex)
+ {
+ _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest);
+ return Array.Empty<PackageInfo>();
}
}
catch (UriFormatException ex)
@@ -360,42 +352,34 @@ namespace Emby.Server.Implementations.Updates
// Always override the passed-in target (which is a file) and figure it out again
string targetDir = Path.Combine(_appPaths.PluginsPath, package.Name);
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetAsync(package.SourceUrl, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+
// CA5351: Do Not Use Broken Cryptographic Algorithms
#pragma warning disable CA5351
- using (var res = await _httpClient.SendAsync(
- new HttpRequestOptions
- {
- Url = package.SourceUrl,
- CancellationToken = cancellationToken,
- // We need it to be buffered for setting the position
- BufferContent = true
- },
- HttpMethod.Get).ConfigureAwait(false))
- using (var stream = res.Content)
- using (var md5 = MD5.Create())
- {
- cancellationToken.ThrowIfCancellationRequested();
+ using var md5 = MD5.Create();
+ cancellationToken.ThrowIfCancellationRequested();
- var hash = Hex.Encode(md5.ComputeHash(stream));
- if (!string.Equals(package.Checksum, hash, StringComparison.OrdinalIgnoreCase))
- {
- _logger.LogError(
- "The checksums didn't match while installing {Package}, expected: {Expected}, got: {Received}",
- package.Name,
- package.Checksum,
- hash);
- throw new InvalidDataException("The checksum of the received data doesn't match.");
- }
-
- if (Directory.Exists(targetDir))
- {
- Directory.Delete(targetDir, true);
- }
+ var hash = Hex.Encode(md5.ComputeHash(stream));
+ if (!string.Equals(package.Checksum, hash, StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.LogError(
+ "The checksums didn't match while installing {Package}, expected: {Expected}, got: {Received}",
+ package.Name,
+ package.Checksum,
+ hash);
+ throw new InvalidDataException("The checksum of the received data doesn't match.");
+ }
- stream.Position = 0;
- _zipClient.ExtractAllFromZip(stream, targetDir, true);
+ if (Directory.Exists(targetDir))
+ {
+ Directory.Delete(targetDir, true);
}
+ stream.Position = 0;
+ _zipClient.ExtractAllFromZip(stream, targetDir, true);
+
#pragma warning restore CA5351
}
diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs
index a34f9eb62..1c1fc71d7 100644
--- a/Jellyfin.Api/BaseJellyfinApiController.cs
+++ b/Jellyfin.Api/BaseJellyfinApiController.cs
@@ -1,4 +1,5 @@
using System.Net.Mime;
+using MediaBrowser.Common.Json;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api
@@ -8,7 +9,10 @@ namespace Jellyfin.Api
/// </summary>
[ApiController]
[Route("[controller]")]
- [Produces(MediaTypeNames.Application.Json)]
+ [Produces(
+ MediaTypeNames.Application.Json,
+ JsonDefaults.CamelCaseMediaType,
+ JsonDefaults.PascalCaseMediaType)]
public class BaseJellyfinApiController : ControllerBase
{
}
diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs
index 33abe3ccd..3f0fc2e91 100644
--- a/Jellyfin.Api/Controllers/DashboardController.cs
+++ b/Jellyfin.Api/Controllers/DashboardController.cs
@@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
namespace Jellyfin.Api.Controllers
{
@@ -26,39 +27,21 @@ namespace Jellyfin.Api.Controllers
{
private readonly ILogger<DashboardController> _logger;
private readonly IServerApplicationHost _appHost;
- private readonly IConfiguration _appConfig;
- private readonly IServerConfigurationManager _serverConfigurationManager;
- private readonly IResourceFileManager _resourceFileManager;
/// <summary>
/// Initializes a new instance of the <see cref="DashboardController"/> class.
/// </summary>
/// <param name="logger">Instance of <see cref="ILogger{DashboardController}"/> interface.</param>
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
- /// <param name="appConfig">Instance of <see cref="IConfiguration"/> interface.</param>
- /// <param name="resourceFileManager">Instance of <see cref="IResourceFileManager"/> interface.</param>
- /// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param>
public DashboardController(
ILogger<DashboardController> logger,
- IServerApplicationHost appHost,
- IConfiguration appConfig,
- IResourceFileManager resourceFileManager,
- IServerConfigurationManager serverConfigurationManager)
+ IServerApplicationHost appHost)
{
_logger = logger;
_appHost = appHost;
- _appConfig = appConfig;
- _resourceFileManager = resourceFileManager;
- _serverConfigurationManager = serverConfigurationManager;
}
/// <summary>
- /// Gets the path of the directory containing the static web interface content, or null if the server is not
- /// hosting the web client.
- /// </summary>
- private string? WebClientUiPath => GetWebClientUiPath(_appConfig, _serverConfigurationManager);
-
- /// <summary>
/// Gets the configuration pages.
/// </summary>
/// <param name="enableInMainMenu">Whether to enable in the main menu.</param>
@@ -169,87 +152,6 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
- /// <summary>
- /// Gets the robots.txt.
- /// </summary>
- /// <response code="200">Robots.txt returned.</response>
- /// <returns>The robots.txt.</returns>
- [HttpGet("robots.txt")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ApiExplorerSettings(IgnoreApi = true)]
- public ActionResult GetRobotsTxt()
- {
- return GetWebClientResource("robots.txt");
- }
-
- /// <summary>
- /// Gets a resource from the web client.
- /// </summary>
- /// <param name="resourceName">The resource name.</param>
- /// <response code="200">Web client returned.</response>
- /// <response code="404">Server does not host a web client.</response>
- /// <returns>The resource.</returns>
- [HttpGet("web/{*resourceName}")]
- [ApiExplorerSettings(IgnoreApi = true)]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult GetWebClientResource([FromRoute] string resourceName)
- {
- if (!_appConfig.HostWebClient() || WebClientUiPath == null)
- {
- return NotFound("Server does not host a web client.");
- }
-
- var path = resourceName;
- var basePath = WebClientUiPath;
-
- var requestPathAndQuery = Request.GetEncodedPathAndQuery();
- // Bounce them to the startup wizard if it hasn't been completed yet
- if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted
- && !requestPathAndQuery.Contains("wizard", StringComparison.OrdinalIgnoreCase)
- && requestPathAndQuery.Contains("index", StringComparison.OrdinalIgnoreCase))
- {
- return Redirect("index.html?start=wizard#!/wizardstart.html");
- }
-
- var stream = new FileStream(_resourceFileManager.GetResourcePath(basePath, path), FileMode.Open, FileAccess.Read);
- return File(stream, MimeTypes.GetMimeType(path));
- }
-
- /// <summary>
- /// Gets the favicon.
- /// </summary>
- /// <response code="200">Favicon.ico returned.</response>
- /// <returns>The favicon.</returns>
- [HttpGet("favicon.ico")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ApiExplorerSettings(IgnoreApi = true)]
- public ActionResult GetFavIcon()
- {
- return GetWebClientResource("favicon.ico");
- }
-
- /// <summary>
- /// Gets the path of the directory containing the static web interface content.
- /// </summary>
- /// <param name="appConfig">The app configuration.</param>
- /// <param name="serverConfigManager">The server configuration manager.</param>
- /// <returns>The directory path, or null if the server is not hosting the web client.</returns>
- public static string? GetWebClientUiPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager)
- {
- if (!appConfig.HostWebClient())
- {
- return null;
- }
-
- if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath))
- {
- return serverConfigManager.Configuration.DashboardSourcePath;
- }
-
- return serverConfigManager.ApplicationPaths.WebPath;
- }
-
private IEnumerable<ConfigurationPageInfo> GetConfigPages(IPlugin plugin)
{
return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1));
diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
index c547d0cde..c3b67eec3 100644
--- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
+++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
@@ -153,7 +153,6 @@ namespace Jellyfin.Api.Controllers
{
var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Parse(key.Substring("landing-".Length)), existingDisplayPreferences.Client);
itemPreferences.ViewType = Enum.Parse<ViewType>(displayPreferences.ViewType);
- _displayPreferencesManager.SaveChanges(itemPreferences);
}
var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Empty, existingDisplayPreferences.Client);
@@ -167,8 +166,7 @@ namespace Jellyfin.Api.Controllers
itemPrefs.ViewType = viewType;
}
- _displayPreferencesManager.SaveChanges(existingDisplayPreferences);
- _displayPreferencesManager.SaveChanges(itemPrefs);
+ _displayPreferencesManager.SaveChanges();
return NoContent();
}
diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs
index ee87d917b..9ebd89819 100644
--- a/Jellyfin.Api/Controllers/DlnaServerController.cs
+++ b/Jellyfin.Api/Controllers/DlnaServerController.cs
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Net.Mime;
using System.Threading.Tasks;
using Emby.Dlna;
using Emby.Dlna.Main;
@@ -17,8 +18,6 @@ namespace Jellyfin.Api.Controllers
[Route("Dlna")]
public class DlnaServerController : BaseJellyfinApiController
{
- private const string XMLContentType = "text/xml; charset=UTF-8";
-
private readonly IDlnaManager _dlnaManager;
private readonly IContentDirectory _contentDirectory;
private readonly IConnectionManager _connectionManager;
@@ -44,7 +43,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the description xml.</returns>
[HttpGet("{serverId}/description")]
[HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
- [Produces(XMLContentType)]
+ [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetDescriptionXml([FromRoute] string serverId)
{
@@ -61,8 +60,9 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Dlna content directory returned.</response>
/// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
[HttpGet("{serverId}/ContentDirectory")]
- [HttpGet("{serverId}/ContentDirectory.xml", Name = "GetContentDirectory_2")]
- [Produces(XMLContentType)]
+ [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")]
+ [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")]
+ [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetContentDirectory([FromRoute] string serverId)
@@ -76,8 +76,9 @@ namespace Jellyfin.Api.Controllers
/// <param name="serverId">Server UUID.</param>
/// <returns>Dlna media receiver registrar xml.</returns>
[HttpGet("{serverId}/MediaReceiverRegistrar")]
- [HttpGet("{serverId}/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_2")]
- [Produces(XMLContentType)]
+ [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
+ [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")]
+ [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetMediaReceiverRegistrar([FromRoute] string serverId)
@@ -91,8 +92,9 @@ namespace Jellyfin.Api.Controllers
/// <param name="serverId">Server UUID.</param>
/// <returns>Dlna media receiver registrar xml.</returns>
[HttpGet("{serverId}/ConnectionManager")]
- [HttpGet("{serverId}/ConnectionManager.xml", Name = "GetConnectionManager_2")]
- [Produces(XMLContentType)]
+ [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")]
+ [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")]
+ [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetConnectionManager([FromRoute] string serverId)
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index b115ac6cd..0c884d58d 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -1354,15 +1354,20 @@ namespace Jellyfin.Api.Controllers
segmentFormat = "mpegts";
}
+ var maxMuxingQueueSize = encodingOptions.MaxMuxingQueueSize > 128
+ ? encodingOptions.MaxMuxingQueueSize.ToString(CultureInfo.InvariantCulture)
+ : "128";
+
return string.Format(
CultureInfo.InvariantCulture,
- "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size 2048 -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
+ "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -individual_header_trailer 0 -hls_segment_type {8} -start_number {9} -hls_segment_filename \"{10}\" -hls_playlist_type vod -hls_list_size 0 -y \"{11}\"",
inputModifier,
_encodingHelper.GetInputArgument(state, encodingOptions),
threads,
mapArgs,
GetVideoArguments(state, encodingOptions, startNumber),
GetAudioArguments(state, encodingOptions),
+ maxMuxingQueueSize,
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
segmentFormat,
startNumberParam,
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 3ccfc826d..eace0f911 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -16,6 +16,7 @@ using Jellyfin.Api.Models.LiveTvDtos;
using Jellyfin.Data.Enums;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -1069,7 +1070,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetSchedulesDirectCountries()
{
- var client = _httpClientFactory.CreateClient();
+ var client = _httpClientFactory.CreateClient(NamedClient.Default);
// https://json.schedulesdirect.org/20141201/available/countries
// Can't dispose the response as it's required up the call chain.
var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries")
diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs
index 30a4f73fc..81aefd15c 100644
--- a/Jellyfin.Api/Controllers/RemoteImageController.cs
+++ b/Jellyfin.Api/Controllers/RemoteImageController.cs
@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -244,7 +245,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>Task.</returns>
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
{
- var httpClient = _httpClientFactory.CreateClient();
+ var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last();
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index f42810c94..83b03f965 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -11,6 +11,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
@@ -233,7 +234,7 @@ namespace Jellyfin.Api.Controllers
.First();
}
- var list = primaryVersion.LinkedAlternateVersions.ToList();
+ var alternateVersionsOfPrimary = primaryVersion.LinkedAlternateVersions.ToList();
foreach (var item in items.Where(i => i.Id != primaryVersion.Id))
{
@@ -241,17 +242,20 @@ namespace Jellyfin.Api.Controllers
await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
- list.Add(new LinkedChild
+ if (!alternateVersionsOfPrimary.Any(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase)))
{
- Path = item.Path,
- ItemId = item.Id
- });
+ alternateVersionsOfPrimary.Add(new LinkedChild
+ {
+ Path = item.Path,
+ ItemId = item.Id
+ });
+ }
foreach (var linkedItem in item.LinkedAlternateVersions)
{
- if (!list.Any(i => string.Equals(i.Path, linkedItem.Path, StringComparison.OrdinalIgnoreCase)))
+ if (!alternateVersionsOfPrimary.Any(i => string.Equals(i.Path, linkedItem.Path, StringComparison.OrdinalIgnoreCase)))
{
- list.Add(linkedItem);
+ alternateVersionsOfPrimary.Add(linkedItem);
}
}
@@ -262,7 +266,7 @@ namespace Jellyfin.Api.Controllers
}
}
- primaryVersion.LinkedAlternateVersions = list.ToArray();
+ primaryVersion.LinkedAlternateVersions = alternateVersionsOfPrimary.ToArray();
await primaryVersion.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -470,7 +474,7 @@ namespace Jellyfin.Api.Controllers
{
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
- var httpClient = _httpClientFactory.CreateClient();
+ var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false);
}
diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs
index eae2be05b..a3f2d88ce 100644
--- a/Jellyfin.Api/Helpers/AudioHelper.cs
+++ b/Jellyfin.Api/Helpers/AudioHelper.cs
@@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
@@ -138,7 +139,7 @@ namespace Jellyfin.Api.Helpers
{
StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);
- var httpClient = _httpClientFactory.CreateClient();
+ var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false);
}
diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
index deb54dbe6..884bfbe44 100644
--- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
+++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
@@ -72,7 +72,7 @@ namespace Jellyfin.Api.Helpers
return new NoContentResult();
}
- return new PhysicalFileResult(path, contentType);
+ return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true };
}
/// <summary>
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 24bc07b66..ca0542b03 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -14,9 +14,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
- <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.6" />
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.5.1" />
</ItemGroup>
diff --git a/Jellyfin.Api/MvcRoutePrefix.cs b/Jellyfin.Api/MvcRoutePrefix.cs
deleted file mode 100644
index e00973094..000000000
--- a/Jellyfin.Api/MvcRoutePrefix.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ApplicationModels;
-
-namespace Jellyfin.Api
-{
- /// <summary>
- /// Route prefixing for ASP.NET MVC.
- /// </summary>
- public static class MvcRoutePrefix
- {
- /// <summary>
- /// Adds route prefixes to the MVC conventions.
- /// </summary>
- /// <param name="opts">The MVC options.</param>
- /// <param name="prefixes">The list of prefixes.</param>
- public static void UseGeneralRoutePrefix(this MvcOptions opts, params string[] prefixes)
- {
- opts.Conventions.Insert(0, new RoutePrefixConvention(prefixes));
- }
-
- private class RoutePrefixConvention : IApplicationModelConvention
- {
- private readonly AttributeRouteModel[] _routePrefixes;
-
- public RoutePrefixConvention(IEnumerable<string> prefixes)
- {
- _routePrefixes = prefixes.Select(p => new AttributeRouteModel(new RouteAttribute(p))).ToArray();
- }
-
- public void Apply(ApplicationModel application)
- {
- foreach (var controller in application.Controllers)
- {
- if (controller.Selectors == null)
- {
- continue;
- }
-
- var newSelectors = new List<SelectorModel>();
- foreach (var selector in controller.Selectors)
- {
- newSelectors.AddRange(_routePrefixes.Select(routePrefix => new SelectorModel(selector)
- {
- AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(routePrefix, selector.AttributeRouteModel)
- }));
- }
-
- controller.Selectors.Clear();
- newSelectors.ForEach(selector => controller.Selectors.Add(selector));
- }
- }
- }
- }
-}
diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs
index 3858916d1..620e82830 100644
--- a/Jellyfin.Data/Entities/ActivityLog.cs
+++ b/Jellyfin.Data/Entities/ActivityLog.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -11,7 +9,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// An entity referencing an activity log entry.
/// </summary>
- public partial class ActivityLog : IHasConcurrencyToken
+ public class ActivityLog : IHasConcurrencyToken
{
/// <summary>
/// Initializes a new instance of the <see cref="ActivityLog"/> class.
@@ -32,13 +30,11 @@ namespace Jellyfin.Data.Entities
throw new ArgumentNullException(nameof(type));
}
- this.Name = name;
- this.Type = type;
- this.UserId = userId;
- this.DateCreated = DateTime.UtcNow;
- this.LogSeverity = LogLevel.Trace;
-
- Init();
+ Name = name;
+ Type = type;
+ UserId = userId;
+ DateCreated = DateTime.UtcNow;
+ LogSeverity = LogLevel.Trace;
}
/// <summary>
@@ -47,38 +43,21 @@ namespace Jellyfin.Data.Entities
/// </summary>
protected ActivityLog()
{
- Init();
- }
-
- /// <summary>
- /// Static create function (for use in LINQ queries, etc.)
- /// </summary>
- /// <param name="name">The name.</param>
- /// <param name="type">The type.</param>
- /// <param name="userId">The user's id.</param>
- /// <returns>The new <see cref="ActivityLog"/> instance.</returns>
- public static ActivityLog Create(string name, string type, Guid userId)
- {
- return new ActivityLog(name, type, userId);
}
- /*************************************************************************
- * Properties
- *************************************************************************/
-
/// <summary>
/// Gets or sets the identity of this instance.
/// This is the key in the backing database.
/// </summary>
- [Key]
- [Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
/// <summary>
/// Gets or sets the name.
- /// Required, Max length = 512.
/// </summary>
+ /// <remarks>
+ /// Required, Max length = 512.
+ /// </remarks>
[Required]
[MaxLength(512)]
[StringLength(512)]
@@ -86,24 +65,30 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Gets or sets the overview.
- /// Max length = 512.
/// </summary>
+ /// <remarks>
+ /// Max length = 512.
+ /// </remarks>
[MaxLength(512)]
[StringLength(512)]
public string Overview { get; set; }
/// <summary>
/// Gets or sets the short overview.
- /// Max length = 512.
/// </summary>
+ /// <remarks>
+ /// Max length = 512.
+ /// </remarks>
[MaxLength(512)]
[StringLength(512)]
public string ShortOverview { get; set; }
/// <summary>
/// Gets or sets the type.
- /// Required, Max length = 256.
/// </summary>
+ /// <remarks>
+ /// Required, Max length = 256.
+ /// </remarks>
[Required]
[MaxLength(256)]
[StringLength(256)]
@@ -111,43 +96,42 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Gets or sets the user id.
- /// Required.
/// </summary>
- [Required]
+ /// <remarks>
+ /// Required.
+ /// </remarks>
public Guid UserId { get; set; }
/// <summary>
/// Gets or sets the item id.
- /// Max length = 256.
/// </summary>
+ /// <remarks>
+ /// Max length = 256.
+ /// </remarks>
[MaxLength(256)]
[StringLength(256)]
public string ItemId { get; set; }
/// <summary>
/// Gets or sets the date created. This should be in UTC.
- /// Required.
/// </summary>
- [Required]
+ /// <remarks>
+ /// Required.
+ /// </remarks>
public DateTime DateCreated { get; set; }
/// <summary>
/// Gets or sets the log severity. Default is <see cref="LogLevel.Trace"/>.
- /// Required.
/// </summary>
- [Required]
+ /// <remarks>
+ /// Required.
+ /// </remarks>
public LogLevel LogSeverity { get; set; }
- /// <summary>
- /// Gets or sets the row version.
- /// Required, ConcurrencyToken.
- /// </summary>
+ /// <inheritdoc />
[ConcurrencyCheck]
- [Required]
public uint RowVersion { get; set; }
- partial void Init();
-
/// <inheritdoc />
public void OnSavingChanges()
{
diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs
index cda83f6bb..701e4df00 100644
--- a/Jellyfin.Data/Entities/DisplayPreferences.cs
+++ b/Jellyfin.Data/Entities/DisplayPreferences.cs
@@ -1,4 +1,6 @@
-using System;
+#pragma warning disable CA2227
+
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs
index ca12ba421..878811e59 100644
--- a/Jellyfin.Data/Entities/Group.cs
+++ b/Jellyfin.Data/Entities/Group.cs
@@ -1,9 +1,8 @@
-#pragma warning disable CS1591
+#pragma warning disable CA2227
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Interfaces;
@@ -13,11 +12,10 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// An entity representing a group.
/// </summary>
- public partial class Group : IHasPermissions, IHasConcurrencyToken
+ public class Group : IHasPermissions, IHasConcurrencyToken
{
/// <summary>
/// Initializes a new instance of the <see cref="Group"/> class.
- /// Public constructor with required data.
/// </summary>
/// <param name="name">The name of the group.</param>
public Group(string name)
@@ -31,33 +29,25 @@ namespace Jellyfin.Data.Entities
Id = Guid.NewGuid();
Permissions = new HashSet<Permission>();
- ProviderMappings = new HashSet<ProviderMapping>();
Preferences = new HashSet<Preference>();
-
- Init();
}
/// <summary>
/// Initializes a new instance of the <see cref="Group"/> class.
- /// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
+ /// <remarks>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </remarks>
protected Group()
{
- Init();
}
- /*************************************************************************
- * Properties
- *************************************************************************/
-
/// <summary>
/// Gets or sets the id of this group.
/// </summary>
/// <remarks>
/// Identity, Indexed, Required.
/// </remarks>
- [Key]
- [Required]
public Guid Id { get; protected set; }
/// <summary>
@@ -71,42 +61,19 @@ namespace Jellyfin.Data.Entities
[StringLength(255)]
public string Name { get; set; }
- /// <summary>
- /// Gets or sets the row version.
- /// </summary>
- /// <remarks>
- /// Required, Concurrency Token.
- /// </remarks>
+ /// <inheritdoc />
[ConcurrencyCheck]
- [Required]
public uint RowVersion { get; set; }
- public void OnSavingChanges()
- {
- RowVersion++;
- }
-
- /*************************************************************************
- * Navigation properties
- *************************************************************************/
-
- [ForeignKey("Permission_GroupPermissions_Id")]
+ /// <summary>
+ /// Gets or sets a collection containing the group's permissions.
+ /// </summary>
public virtual ICollection<Permission> Permissions { get; protected set; }
- [ForeignKey("ProviderMapping_ProviderMappings_Id")]
- public virtual ICollection<ProviderMapping> ProviderMappings { get; protected set; }
-
- [ForeignKey("Preference_Preferences_Id")]
- public virtual ICollection<Preference> Preferences { get; protected set; }
-
/// <summary>
- /// Static create function (for use in LINQ queries, etc.)
+ /// Gets or sets a collection containing the group's preferences.
/// </summary>
- /// <param name="name">The name of this group.</param>
- public static Group Create(string name)
- {
- return new Group(name);
- }
+ public virtual ICollection<Preference> Preferences { get; protected set; }
/// <inheritdoc/>
public bool HasPermission(PermissionKind kind)
@@ -120,6 +87,10 @@ namespace Jellyfin.Data.Entities
Permissions.First(p => p.Kind == kind).Value = value;
}
- partial void Init();
+ /// <inheritdoc />
+ public void OnSavingChanges()
+ {
+ RowVersion++;
+ }
}
}
diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs
index cf0895ad4..ab8452e62 100644
--- a/Jellyfin.Data/Entities/ImageInfo.cs
+++ b/Jellyfin.Data/Entities/ImageInfo.cs
@@ -1,32 +1,65 @@
-#pragma warning disable CS1591
-
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Jellyfin.Data.Entities
{
+ /// <summary>
+ /// An entity representing an image.
+ /// </summary>
public class ImageInfo
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ImageInfo"/> class.
+ /// </summary>
+ /// <param name="path">The path.</param>
public ImageInfo(string path)
{
Path = path;
LastModified = DateTime.UtcNow;
}
- [Key]
- [Required]
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ImageInfo"/> class.
+ /// </summary>
+ /// <remarks>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </remarks>
+ protected ImageInfo()
+ {
+ }
+
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <remarks>
+ /// Identity, Indexed, Required.
+ /// </remarks>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
+ /// <summary>
+ /// Gets or sets the user id.
+ /// </summary>
public Guid? UserId { get; protected set; }
+ /// <summary>
+ /// Gets or sets the path of the image.
+ /// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
[Required]
[MaxLength(512)]
[StringLength(512)]
public string Path { get; set; }
- [Required]
+ /// <summary>
+ /// Gets or sets the date last modified.
+ /// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
public DateTime LastModified { get; set; }
}
}
diff --git a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs
index 023cdc740..d81e4a31c 100644
--- a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs
+++ b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs
@@ -1,12 +1,13 @@
-#pragma warning disable CS1591
-
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Enums;
namespace Jellyfin.Data.Entities
{
+ /// <summary>
+ /// An entity that represents a user's display preferences for a specific item.
+ /// </summary>
public class ItemDisplayPreferences
{
/// <summary>
diff --git a/Jellyfin.Data/Entities/Libraries/Artwork.cs b/Jellyfin.Data/Entities/Libraries/Artwork.cs
index 7be22af25..06cd33330 100644
--- a/Jellyfin.Data/Entities/Libraries/Artwork.cs
+++ b/Jellyfin.Data/Entities/Libraries/Artwork.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
diff --git a/Jellyfin.Data/Entities/Libraries/Book.cs b/Jellyfin.Data/Entities/Libraries/Book.cs
index 8337788dd..2e63f75bd 100644
--- a/Jellyfin.Data/Entities/Libraries/Book.cs
+++ b/Jellyfin.Data/Entities/Libraries/Book.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System.Collections.Generic;
using Jellyfin.Data.Interfaces;
diff --git a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs
index bd716712b..4a3d290f0 100644
--- a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
@@ -8,7 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// An entity containing metadata for a book.
/// </summary>
- public class BookMetadata : Metadata, IHasCompanies
+ public class BookMetadata : ItemMetadata, IHasCompanies
{
/// <summary>
/// Initializes a new instance of the <see cref="BookMetadata"/> class.
diff --git a/Jellyfin.Data/Entities/Libraries/Chapter.cs b/Jellyfin.Data/Entities/Libraries/Chapter.cs
index d9293c3cc..f503de379 100644
--- a/Jellyfin.Data/Entities/Libraries/Chapter.cs
+++ b/Jellyfin.Data/Entities/Libraries/Chapter.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
diff --git a/Jellyfin.Data/Entities/Libraries/Collection.cs b/Jellyfin.Data/Entities/Libraries/Collection.cs
index 2e1bbcfb6..39eded752 100644
--- a/Jellyfin.Data/Entities/Libraries/Collection.cs
+++ b/Jellyfin.Data/Entities/Libraries/Collection.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
diff --git a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
index c9306f630..4467c9bbd 100644
--- a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
+++ b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
@@ -73,7 +73,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Gets or sets the next item in the collection.
/// </summary>
/// <remarks>
- /// TODO check if this properly updated dependant and has the proper principal relationship
+ /// TODO check if this properly updated dependant and has the proper principal relationship.
/// </remarks>
public virtual CollectionItem Next { get; set; }
@@ -81,7 +81,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Gets or sets the previous item in the collection.
/// </summary>
/// <remarks>
- /// TODO check if this properly updated dependant and has the proper principal relationship
+ /// TODO check if this properly updated dependant and has the proper principal relationship.
/// </remarks>
public virtual CollectionItem Previous { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/Company.cs b/Jellyfin.Data/Entities/Libraries/Company.cs
index 02da26bc2..3b6ed3309 100644
--- a/Jellyfin.Data/Entities/Libraries/Company.cs
+++ b/Jellyfin.Data/Entities/Libraries/Company.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
diff --git a/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs b/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs
index 60cc96a34..8aa0486af 100644
--- a/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs
@@ -6,7 +6,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// An entity holding metadata for a <see cref="Company"/>.
/// </summary>
- public class CompanyMetadata : Metadata
+ public class CompanyMetadata : ItemMetadata
{
/// <summary>
/// Initializes a new instance of the <see cref="CompanyMetadata"/> class.
diff --git a/Jellyfin.Data/Entities/Libraries/CustomItem.cs b/Jellyfin.Data/Entities/Libraries/CustomItem.cs
index 6a4f0a537..115489c78 100644
--- a/Jellyfin.Data/Entities/Libraries/CustomItem.cs
+++ b/Jellyfin.Data/Entities/Libraries/CustomItem.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System.Collections.Generic;
using Jellyfin.Data.Interfaces;
diff --git a/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs
index bc1835528..f0daedfbe 100644
--- a/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs
@@ -5,7 +5,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// An entity containing metadata for a custom item.
/// </summary>
- public class CustomItemMetadata : Metadata
+ public class CustomItemMetadata : ItemMetadata
{
/// <summary>
/// Initializes a new instance of the <see cref="CustomItemMetadata"/> class.
diff --git a/Jellyfin.Data/Entities/Libraries/Episode.cs b/Jellyfin.Data/Entities/Libraries/Episode.cs
index 430a11e3c..0bdc2d764 100644
--- a/Jellyfin.Data/Entities/Libraries/Episode.cs
+++ b/Jellyfin.Data/Entities/Libraries/Episode.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.Collections.Generic;
using Jellyfin.Data.Interfaces;
diff --git a/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs b/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs
index 348100cb4..7efb840f0 100644
--- a/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs
@@ -6,7 +6,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// An entity containing metadata for an <see cref="Episode"/>.
/// </summary>
- public class EpisodeMetadata : Metadata
+ public class EpisodeMetadata : ItemMetadata
{
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeMetadata"/> class.
diff --git a/Jellyfin.Data/Entities/Libraries/Genre.cs b/Jellyfin.Data/Entities/Libraries/Genre.cs
index aeedd7bfd..2a2dbd1a5 100644
--- a/Jellyfin.Data/Entities/Libraries/Genre.cs
+++ b/Jellyfin.Data/Entities/Libraries/Genre.cs
@@ -14,8 +14,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Genre"/> class.
/// </summary>
/// <param name="name">The name.</param>
- /// <param name="metadata">The metadata.</param>
- public Genre(string name, Metadata metadata)
+ /// <param name="itemMetadata">The metadata.</param>
+ public Genre(string name, ItemMetadata itemMetadata)
{
if (string.IsNullOrEmpty(name))
{
@@ -24,12 +24,12 @@ namespace Jellyfin.Data.Entities.Libraries
Name = name;
- if (metadata == null)
+ if (itemMetadata == null)
{
- throw new ArgumentNullException(nameof(metadata));
+ throw new ArgumentNullException(nameof(itemMetadata));
}
- metadata.Genres.Add(this);
+ itemMetadata.Genres.Add(this);
}
/// <summary>
diff --git a/Jellyfin.Data/Entities/Libraries/Metadata.cs b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
index 877bb5fbd..1d2dc0f66 100644
--- a/Jellyfin.Data/Entities/Libraries/Metadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@@ -9,14 +11,14 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// An abstract class that holds metadata.
/// </summary>
- public abstract class Metadata : IHasArtwork, IHasConcurrencyToken
+ public abstract class ItemMetadata : IHasArtwork, IHasConcurrencyToken
{
/// <summary>
- /// Initializes a new instance of the <see cref="Metadata"/> class.
+ /// Initializes a new instance of the <see cref="ItemMetadata"/> class.
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
- protected Metadata(string title, string language)
+ protected ItemMetadata(string title, string language)
{
if (string.IsNullOrEmpty(title))
{
@@ -41,12 +43,12 @@ namespace Jellyfin.Data.Entities.Libraries
}
/// <summary>
- /// Initializes a new instance of the <see cref="Metadata"/> class.
+ /// Initializes a new instance of the <see cref="ItemMetadata"/> class.
/// </summary>
/// <remarks>
/// Default constructor. Protected due to being abstract.
/// </remarks>
- protected Metadata()
+ protected ItemMetadata()
{
}
diff --git a/Jellyfin.Data/Entities/Libraries/MediaFile.cs b/Jellyfin.Data/Entities/Libraries/MediaFile.cs
index 8bc649c98..9924d5728 100644
--- a/Jellyfin.Data/Entities/Libraries/MediaFile.cs
+++ b/Jellyfin.Data/Entities/Libraries/MediaFile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs
index 6e6de598e..fcfb35bfa 100644
--- a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs
+++ b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs
@@ -14,8 +14,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
/// </summary>
/// <param name="providerId">The provider id.</param>
- /// <param name="metadata">The metadata entity.</param>
- public MetadataProviderId(string providerId, Metadata metadata)
+ /// <param name="itemMetadata">The metadata entity.</param>
+ public MetadataProviderId(string providerId, ItemMetadata itemMetadata)
{
if (string.IsNullOrEmpty(providerId))
{
@@ -24,12 +24,12 @@ namespace Jellyfin.Data.Entities.Libraries
ProviderId = providerId;
- if (metadata == null)
+ if (itemMetadata == null)
{
- throw new ArgumentNullException(nameof(metadata));
+ throw new ArgumentNullException(nameof(itemMetadata));
}
- metadata.Sources.Add(this);
+ itemMetadata.Sources.Add(this);
}
/// <summary>
diff --git a/Jellyfin.Data/Entities/Libraries/Movie.cs b/Jellyfin.Data/Entities/Libraries/Movie.cs
index 0a8cc83dd..08db904fa 100644
--- a/Jellyfin.Data/Entities/Libraries/Movie.cs
+++ b/Jellyfin.Data/Entities/Libraries/Movie.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System.Collections.Generic;
using Jellyfin.Data.Interfaces;
diff --git a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs
index 31102bf13..aa1501a5c 100644
--- a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -8,7 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// An entity holding the metadata for a movie.
/// </summary>
- public class MovieMetadata : Metadata, IHasCompanies
+ public class MovieMetadata : ItemMetadata, IHasCompanies
{
/// <summary>
/// Initializes a new instance of the <see cref="MovieMetadata"/> class.
diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs
index 2ed1f78c5..06aff6f45 100644
--- a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs
+++ b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System.Collections.Generic;
namespace Jellyfin.Data.Entities.Libraries
diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs
index cc5919bfe..05c0b0374 100644
--- a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@@ -6,7 +8,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// An entity holding the metadata for a music album.
/// </summary>
- public class MusicAlbumMetadata : Metadata
+ public class MusicAlbumMetadata : ItemMetadata
{
/// <summary>
/// Initializes a new instance of the <see cref="MusicAlbumMetadata"/> class.
diff --git a/Jellyfin.Data/Entities/Libraries/Person.cs b/Jellyfin.Data/Entities/Libraries/Person.cs
index 8beb3dd08..af4c87b73 100644
--- a/Jellyfin.Data/Entities/Libraries/Person.cs
+++ b/Jellyfin.Data/Entities/Libraries/Person.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
diff --git a/Jellyfin.Data/Entities/Libraries/PersonRole.cs b/Jellyfin.Data/Entities/Libraries/PersonRole.cs
index 5290228d6..cd38ee83d 100644
--- a/Jellyfin.Data/Entities/Libraries/PersonRole.cs
+++ b/Jellyfin.Data/Entities/Libraries/PersonRole.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@@ -16,17 +18,17 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="PersonRole"/> class.
/// </summary>
/// <param name="type">The role type.</param>
- /// <param name="metadata">The metadata.</param>
- public PersonRole(PersonRoleType type, Metadata metadata)
+ /// <param name="itemMetadata">The metadata.</param>
+ public PersonRole(PersonRoleType type, ItemMetadata itemMetadata)
{
Type = type;
- if (metadata == null)
+ if (itemMetadata == null)
{
- throw new ArgumentNullException(nameof(metadata));
+ throw new ArgumentNullException(nameof(itemMetadata));
}
- metadata.PersonRoles.Add(this);
+ itemMetadata.PersonRoles.Add(this);
Sources = new HashSet<MetadataProviderId>();
}
diff --git a/Jellyfin.Data/Entities/Libraries/Photo.cs b/Jellyfin.Data/Entities/Libraries/Photo.cs
index 44338a4ce..25562ec96 100644
--- a/Jellyfin.Data/Entities/Libraries/Photo.cs
+++ b/Jellyfin.Data/Entities/Libraries/Photo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System.Collections.Generic;
using Jellyfin.Data.Interfaces;
diff --git a/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs b/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs
index 1ef9dd5f9..ffc790b57 100644
--- a/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs
@@ -5,7 +5,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// An entity that holds metadata for a photo.
/// </summary>
- public class PhotoMetadata : Metadata
+ public class PhotoMetadata : ItemMetadata
{
/// <summary>
/// Initializes a new instance of the <see cref="PhotoMetadata"/> class.
diff --git a/Jellyfin.Data/Entities/Libraries/Rating.cs b/Jellyfin.Data/Entities/Libraries/Rating.cs
index ba054a39e..98226cd80 100644
--- a/Jellyfin.Data/Entities/Libraries/Rating.cs
+++ b/Jellyfin.Data/Entities/Libraries/Rating.cs
@@ -14,17 +14,17 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Rating"/> class.
/// </summary>
/// <param name="value">The value.</param>
- /// <param name="metadata">The metadata.</param>
- public Rating(double value, Metadata metadata)
+ /// <param name="itemMetadata">The metadata.</param>
+ public Rating(double value, ItemMetadata itemMetadata)
{
Value = value;
- if (metadata == null)
+ if (itemMetadata == null)
{
- throw new ArgumentNullException(nameof(metadata));
+ throw new ArgumentNullException(nameof(itemMetadata));
}
- metadata.Ratings.Add(this);
+ itemMetadata.Ratings.Add(this);
}
/// <summary>
diff --git a/Jellyfin.Data/Entities/Libraries/Release.cs b/Jellyfin.Data/Entities/Libraries/Release.cs
index 43c7080d7..b633e08fb 100644
--- a/Jellyfin.Data/Entities/Libraries/Release.cs
+++ b/Jellyfin.Data/Entities/Libraries/Release.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
diff --git a/Jellyfin.Data/Entities/Libraries/Season.cs b/Jellyfin.Data/Entities/Libraries/Season.cs
index eef788bad..eb6674dbc 100644
--- a/Jellyfin.Data/Entities/Libraries/Season.cs
+++ b/Jellyfin.Data/Entities/Libraries/Season.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.Collections.Generic;
diff --git a/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs
index eedeb089e..7ce79756b 100644
--- a/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs
@@ -6,7 +6,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// An entity that holds metadata for seasons.
/// </summary>
- public class SeasonMetadata : Metadata
+ public class SeasonMetadata : ItemMetadata
{
/// <summary>
/// Initializes a new instance of the <see cref="SeasonMetadata"/> class.
diff --git a/Jellyfin.Data/Entities/Libraries/Series.cs b/Jellyfin.Data/Entities/Libraries/Series.cs
index e959c1fe0..8c8317d14 100644
--- a/Jellyfin.Data/Entities/Libraries/Series.cs
+++ b/Jellyfin.Data/Entities/Libraries/Series.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.Collections.Generic;
diff --git a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs
index 898f3006d..877dbfc69 100644
--- a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@@ -9,7 +11,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// An entity representing series metadata.
/// </summary>
- public class SeriesMetadata : Metadata, IHasCompanies
+ public class SeriesMetadata : ItemMetadata, IHasCompanies
{
/// <summary>
/// Initializes a new instance of the <see cref="SeriesMetadata"/> class.
diff --git a/Jellyfin.Data/Entities/Libraries/Track.cs b/Jellyfin.Data/Entities/Libraries/Track.cs
index 09ce82a9b..782bfb5ce 100644
--- a/Jellyfin.Data/Entities/Libraries/Track.cs
+++ b/Jellyfin.Data/Entities/Libraries/Track.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
using System;
using System.Collections.Generic;
using Jellyfin.Data.Interfaces;
diff --git a/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs b/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs
index 048068a1a..321f93bf2 100644
--- a/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs
@@ -5,7 +5,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// An entity holding metadata for a track.
/// </summary>
- public class TrackMetadata : Metadata
+ public class TrackMetadata : ItemMetadata
{
/// <summary>
/// Initializes a new instance of the <see cref="TrackMetadata"/> class.
diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs
index c0f67f836..d92e5d9d2 100644
--- a/Jellyfin.Data/Entities/Permission.cs
+++ b/Jellyfin.Data/Entities/Permission.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Enums;
@@ -10,7 +8,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// An entity representing whether the associated user has a specific permission.
/// </summary>
- public partial class Permission : IHasConcurrencyToken
+ public class Permission : IHasConcurrencyToken
{
/// <summary>
/// Initializes a new instance of the <see cref="Permission"/> class.
@@ -22,8 +20,6 @@ namespace Jellyfin.Data.Entities
{
Kind = kind;
Value = value;
-
- Init();
}
/// <summary>
@@ -32,21 +28,14 @@ namespace Jellyfin.Data.Entities
/// </summary>
protected Permission()
{
- Init();
}
- /*************************************************************************
- * Properties
- *************************************************************************/
-
/// <summary>
/// Gets or sets the id of this permission.
/// </summary>
/// <remarks>
/// Identity, Indexed, Required.
/// </remarks>
- [Key]
- [Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
@@ -56,7 +45,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public PermissionKind Kind { get; protected set; }
/// <summary>
@@ -65,36 +53,16 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool Value { get; set; }
- /// <summary>
- /// Gets or sets the row version.
- /// </summary>
- /// <remarks>
- /// Required, ConcurrencyToken.
- /// </remarks>
+ /// <inheritdoc />
[ConcurrencyCheck]
- [Required]
public uint RowVersion { get; set; }
- /// <summary>
- /// Static create function (for use in LINQ queries, etc.)
- /// </summary>
- /// <param name="kind">The permission kind.</param>
- /// <param name="value">The value of this permission.</param>
- /// <returns>The newly created instance.</returns>
- public static Permission Create(PermissionKind kind, bool value)
- {
- return new Permission(kind, value);
- }
-
/// <inheritdoc/>
public void OnSavingChanges()
{
RowVersion++;
}
-
- partial void Init();
}
}
diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs
index 1797f0a40..4efddf2a4 100644
--- a/Jellyfin.Data/Entities/Preference.cs
+++ b/Jellyfin.Data/Entities/Preference.cs
@@ -31,18 +31,12 @@ namespace Jellyfin.Data.Entities
{
}
- /*************************************************************************
- * Properties
- *************************************************************************/
-
/// <summary>
/// Gets or sets the id of this preference.
/// </summary>
/// <remarks>
/// Identity, Indexed, Required.
/// </remarks>
- [Key]
- [Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
@@ -52,7 +46,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public PreferenceKind Kind { get; protected set; }
/// <summary>
@@ -66,27 +59,10 @@ namespace Jellyfin.Data.Entities
[StringLength(65535)]
public string Value { get; set; }
- /// <summary>
- /// Gets or sets the row version.
- /// </summary>
- /// <remarks>
- /// Required, ConcurrencyToken.
- /// </remarks>
+ /// <inheritdoc/>
[ConcurrencyCheck]
- [Required]
public uint RowVersion { get; set; }
- /// <summary>
- /// Static create function (for use in LINQ queries, etc.)
- /// </summary>
- /// <param name="kind">The preference kind.</param>
- /// <param name="value">The value.</param>
- /// <returns>The new instance.</returns>
- public static Preference Create(PreferenceKind kind, string value)
- {
- return new Preference(kind, value);
- }
-
/// <inheritdoc/>
public void OnSavingChanges()
{
diff --git a/Jellyfin.Data/Entities/ProviderMapping.cs b/Jellyfin.Data/Entities/ProviderMapping.cs
deleted file mode 100644
index 44ebfba76..000000000
--- a/Jellyfin.Data/Entities/ProviderMapping.cs
+++ /dev/null
@@ -1,129 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Jellyfin.Data.Entities
-{
- public partial class ProviderMapping
- {
- partial void Init();
-
- /// <summary>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </summary>
- protected ProviderMapping()
- {
- Init();
- }
-
- /// <summary>
- /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
- /// </summary>
- public static ProviderMapping CreateProviderMappingUnsafe()
- {
- return new ProviderMapping();
- }
-
- /// <summary>
- /// Public constructor with required data.
- /// </summary>
- /// <param name="providername"></param>
- /// <param name="providersecrets"></param>
- /// <param name="providerdata"></param>
- /// <param name="_user0"></param>
- /// <param name="_group1"></param>
- public ProviderMapping(string providername, string providersecrets, string providerdata, User _user0, Group _group1)
- {
- if (string.IsNullOrEmpty(providername))
- {
- throw new ArgumentNullException(nameof(providername));
- }
-
- this.ProviderName = providername;
-
- if (string.IsNullOrEmpty(providersecrets))
- {
- throw new ArgumentNullException(nameof(providersecrets));
- }
-
- this.ProviderSecrets = providersecrets;
-
- if (string.IsNullOrEmpty(providerdata))
- {
- throw new ArgumentNullException(nameof(providerdata));
- }
-
- this.ProviderData = providerdata;
-
- Init();
- }
-
- /// <summary>
- /// Static create function (for use in LINQ queries, etc.)
- /// </summary>
- /// <param name="providername"></param>
- /// <param name="providersecrets"></param>
- /// <param name="providerdata"></param>
- /// <param name="_user0"></param>
- /// <param name="_group1"></param>
- public static ProviderMapping Create(string providername, string providersecrets, string providerdata, User _user0, Group _group1)
- {
- return new ProviderMapping(providername, providersecrets, providerdata, _user0, _group1);
- }
-
- /*************************************************************************
- * Properties
- *************************************************************************/
-
- /// <summary>
- /// Identity, Indexed, Required.
- /// </summary>
- [Key]
- [Required]
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public int Id { get; protected set; }
-
- /// <summary>
- /// Required, Max length = 255
- /// </summary>
- [Required]
- [MaxLength(255)]
- [StringLength(255)]
- public string ProviderName { get; set; }
-
- /// <summary>
- /// Required, Max length = 65535
- /// </summary>
- [Required]
- [MaxLength(65535)]
- [StringLength(65535)]
- public string ProviderSecrets { get; set; }
-
- /// <summary>
- /// Required, Max length = 65535
- /// </summary>
- [Required]
- [MaxLength(65535)]
- [StringLength(65535)]
- public string ProviderData { get; set; }
-
- /// <summary>
- /// Required, ConcurrenyToken.
- /// </summary>
- [ConcurrencyCheck]
- [Required]
- public uint RowVersion { get; set; }
-
- public void OnSavingChanges()
- {
- RowVersion++;
- }
-
- /*************************************************************************
- * Navigation properties
- *************************************************************************/
- }
-}
-
diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs
index 7ea1f4498..f7ab57a1b 100644
--- a/Jellyfin.Data/Entities/User.cs
+++ b/Jellyfin.Data/Entities/User.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CA2227
using System;
using System.Collections.Generic;
@@ -15,7 +15,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// An entity representing a user.
/// </summary>
- public partial class User : IHasPermissions, IHasConcurrencyToken
+ public class User : IHasPermissions, IHasConcurrencyToken
{
/// <summary>
/// The values being delimited here are Guids, so commas work as they do not appear in Guids.
@@ -75,7 +75,6 @@ namespace Jellyfin.Data.Entities
AddDefaultPermissions();
AddDefaultPreferences();
- Init();
}
/// <summary>
@@ -84,21 +83,14 @@ namespace Jellyfin.Data.Entities
/// </summary>
protected User()
{
- Init();
}
- /*************************************************************************
- * Properties
- *************************************************************************/
-
/// <summary>
/// Gets or sets the Id of the user.
/// </summary>
/// <remarks>
/// Identity, Indexed, Required.
/// </remarks>
- [Key]
- [Required]
[JsonIgnore]
public Guid Id { get; set; }
@@ -139,7 +131,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool MustUpdatePassword { get; set; }
/// <summary>
@@ -180,7 +171,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public int InvalidLoginAttemptCount { get; set; }
/// <summary>
@@ -204,7 +194,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public SubtitlePlaybackMode SubtitleMode { get; set; }
/// <summary>
@@ -213,7 +202,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool PlayDefaultAudioTrack { get; set; }
/// <summary>
@@ -232,7 +220,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool DisplayMissingEpisodes { get; set; }
/// <summary>
@@ -241,7 +228,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool DisplayCollectionsView { get; set; }
/// <summary>
@@ -250,7 +236,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool EnableLocalPassword { get; set; }
/// <summary>
@@ -259,7 +244,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool HidePlayedInLatest { get; set; }
/// <summary>
@@ -268,7 +252,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool RememberAudioSelections { get; set; }
/// <summary>
@@ -277,7 +260,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool RememberSubtitleSelections { get; set; }
/// <summary>
@@ -286,7 +268,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool EnableNextEpisodeAutoPlay { get; set; }
/// <summary>
@@ -295,7 +276,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool EnableAutoLogin { get; set; }
/// <summary>
@@ -304,7 +284,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public bool EnableUserPreferenceAccess { get; set; }
/// <summary>
@@ -322,7 +301,6 @@ namespace Jellyfin.Data.Entities
/// This is a temporary stopgap for until the library db is migrated.
/// This corresponds to the value of the index of this user in the library db.
/// </summary>
- [Required]
public long InternalId { get; set; }
/// <summary>
@@ -340,7 +318,9 @@ namespace Jellyfin.Data.Entities
[Required]
public virtual DisplayPreferences DisplayPreferences { get; set; }
- [Required]
+ /// <summary>
+ /// Gets or sets the level of sync play permissions this user has.
+ /// </summary>
public SyncPlayAccess SyncPlayAccess { get; set; }
/// <summary>
@@ -350,13 +330,8 @@ namespace Jellyfin.Data.Entities
/// Required, Concurrency Token.
/// </remarks>
[ConcurrencyCheck]
- [Required]
public uint RowVersion { get; set; }
- /*************************************************************************
- * Navigation properties
- *************************************************************************/
-
/// <summary>
/// Gets or sets the list of access schedules this user has.
/// </summary>
@@ -395,18 +370,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("Preference_Preferences_Guid")]
public virtual ICollection<Preference> Preferences { get; protected set; }
- /// <summary>
- /// Static create function (for use in LINQ queries, etc.)
- /// </summary>
- /// <param name="username">The username for the created user.</param>
- /// <param name="authenticationProviderId">The Id of the user's authentication provider.</param>
- /// <param name="passwordResetProviderId">The Id of the user's password reset provider.</param>
- /// <returns>The created instance.</returns>
- public static User Create(string username, string authenticationProviderId, string passwordResetProviderId)
- {
- return new User(username, authenticationProviderId, passwordResetProviderId);
- }
-
/// <inheritdoc/>
public void OnSavingChanges()
{
@@ -519,7 +482,5 @@ namespace Jellyfin.Data.Entities
Preferences.Add(new Preference(val, string.Empty));
}
}
-
- partial void Init();
}
}
diff --git a/Jellyfin.Data/Enums/ArtKind.cs b/Jellyfin.Data/Enums/ArtKind.cs
index 71b4db6f2..f7a73848c 100644
--- a/Jellyfin.Data/Enums/ArtKind.cs
+++ b/Jellyfin.Data/Enums/ArtKind.cs
@@ -1,13 +1,33 @@
-#pragma warning disable CS1591
-
namespace Jellyfin.Data.Enums
{
+ /// <summary>
+ /// An enum representing types of art.
+ /// </summary>
public enum ArtKind
{
- Other,
- Poster,
- Banner,
- Thumbnail,
- Logo
+ /// <summary>
+ /// Another type of art, not covered by the other members.
+ /// </summary>
+ Other = 0,
+
+ /// <summary>
+ /// A poster.
+ /// </summary>
+ Poster = 1,
+
+ /// <summary>
+ /// A banner.
+ /// </summary>
+ Banner = 2,
+
+ /// <summary>
+ /// A thumbnail.
+ /// </summary>
+ Thumbnail = 3,
+
+ /// <summary>
+ /// A logo.
+ /// </summary>
+ Logo = 4
}
}
diff --git a/Jellyfin.Data/Enums/DynamicDayOfWeek.cs b/Jellyfin.Data/Enums/DynamicDayOfWeek.cs
index a33cd9d1c..d3d8dd822 100644
--- a/Jellyfin.Data/Enums/DynamicDayOfWeek.cs
+++ b/Jellyfin.Data/Enums/DynamicDayOfWeek.cs
@@ -1,18 +1,58 @@
-#pragma warning disable CS1591
-
namespace Jellyfin.Data.Enums
{
+ /// <summary>
+ /// An enum that represents a day of the week, weekdays, weekends, or all days.
+ /// </summary>
public enum DynamicDayOfWeek
{
+ /// <summary>
+ /// Sunday.
+ /// </summary>
Sunday = 0,
+
+ /// <summary>
+ /// Monday.
+ /// </summary>
Monday = 1,
+
+ /// <summary>
+ /// Tuesday.
+ /// </summary>
Tuesday = 2,
+
+ /// <summary>
+ /// Wednesday.
+ /// </summary>
Wednesday = 3,
+
+ /// <summary>
+ /// Thursday.
+ /// </summary>
Thursday = 4,
+
+ /// <summary>
+ /// Friday.
+ /// </summary>
Friday = 5,
+
+ /// <summary>
+ /// Saturday.
+ /// </summary>
Saturday = 6,
+
+ /// <summary>
+ /// All days of the week.
+ /// </summary>
Everyday = 7,
+
+ /// <summary>
+ /// A week day, or Monday-Friday.
+ /// </summary>
Weekday = 8,
+
+ /// <summary>
+ /// Saturday and Sunday.
+ /// </summary>
Weekend = 9
}
}
diff --git a/Jellyfin.Data/Enums/IndexingKind.cs b/Jellyfin.Data/Enums/IndexingKind.cs
index fafe47e0c..c0df07714 100644
--- a/Jellyfin.Data/Enums/IndexingKind.cs
+++ b/Jellyfin.Data/Enums/IndexingKind.cs
@@ -1,7 +1,8 @@
-#pragma warning disable CS1591
-
-namespace Jellyfin.Data.Enums
+namespace Jellyfin.Data.Enums
{
+ /// <summary>
+ /// An enum representing a type of indexing in a user's display preferences.
+ /// </summary>
public enum IndexingKind
{
/// <summary>
diff --git a/Jellyfin.Data/Enums/MediaFileKind.cs b/Jellyfin.Data/Enums/MediaFileKind.cs
index b03591fb8..797c26ec2 100644
--- a/Jellyfin.Data/Enums/MediaFileKind.cs
+++ b/Jellyfin.Data/Enums/MediaFileKind.cs
@@ -1,13 +1,33 @@
-#pragma warning disable CS1591
-
namespace Jellyfin.Data.Enums
{
+ /// <summary>
+ /// An enum representing the type of media file.
+ /// </summary>
public enum MediaFileKind
{
- Main,
- Sidecar,
- AdditionalPart,
- AlternativeFormat,
- AdditionalStream
+ /// <summary>
+ /// The main file.
+ /// </summary>
+ Main = 0,
+
+ /// <summary>
+ /// A sidecar file.
+ /// </summary>
+ Sidecar = 1,
+
+ /// <summary>
+ /// An additional part to the main file.
+ /// </summary>
+ AdditionalPart = 2,
+
+ /// <summary>
+ /// An alternative format to the main file.
+ /// </summary>
+ AlternativeFormat = 3,
+
+ /// <summary>
+ /// An additional stream for the main file.
+ /// </summary>
+ AdditionalStream = 4
}
}
diff --git a/Jellyfin.Data/Enums/PersonRoleType.cs b/Jellyfin.Data/Enums/PersonRoleType.cs
index 2d80eaa4c..1e619f5ee 100644
--- a/Jellyfin.Data/Enums/PersonRoleType.cs
+++ b/Jellyfin.Data/Enums/PersonRoleType.cs
@@ -1,20 +1,68 @@
-#pragma warning disable CS1591
-
namespace Jellyfin.Data.Enums
{
+ /// <summary>
+ /// An enum representing a person's role in a specific media item.
+ /// </summary>
public enum PersonRoleType
{
- Other,
- Director,
- Artist,
- OriginalArtist,
- Actor,
- VoiceActor,
- Producer,
- Remixer,
- Conductor,
- Composer,
- Author,
- Editor
+ /// <summary>
+ /// Another role, not covered by the other types.
+ /// </summary>
+ Other = 0,
+
+ /// <summary>
+ /// The director of the media.
+ /// </summary>
+ Director = 1,
+
+ /// <summary>
+ /// An artist.
+ /// </summary>
+ Artist = 2,
+
+ /// <summary>
+ /// The original artist.
+ /// </summary>
+ OriginalArtist = 3,
+
+ /// <summary>
+ /// An actor.
+ /// </summary>
+ Actor = 4,
+
+ /// <summary>
+ /// A voice actor.
+ /// </summary>
+ VoiceActor = 5,
+
+ /// <summary>
+ /// A producer.
+ /// </summary>
+ Producer = 6,
+
+ /// <summary>
+ /// A remixer.
+ /// </summary>
+ Remixer = 7,
+
+ /// <summary>
+ /// A conductor.
+ /// </summary>
+ Conductor = 8,
+
+ /// <summary>
+ /// A composer.
+ /// </summary>
+ Composer = 9,
+
+ /// <summary>
+ /// An author.
+ /// </summary>
+ Author = 10,
+
+ /// <summary>
+ /// An editor.
+ /// </summary>
+ Editor = 11
}
}
diff --git a/Jellyfin.Data/Enums/SubtitlePlaybackMode.cs b/Jellyfin.Data/Enums/SubtitlePlaybackMode.cs
index c8fc21159..ca41300ed 100644
--- a/Jellyfin.Data/Enums/SubtitlePlaybackMode.cs
+++ b/Jellyfin.Data/Enums/SubtitlePlaybackMode.cs
@@ -1,13 +1,33 @@
-#pragma warning disable CS1591
-
-namespace Jellyfin.Data.Enums
+namespace Jellyfin.Data.Enums
{
+ /// <summary>
+ /// An enum representing a subtitle playback mode.
+ /// </summary>
public enum SubtitlePlaybackMode
{
+ /// <summary>
+ /// The default subtitle playback mode.
+ /// </summary>
Default = 0,
+
+ /// <summary>
+ /// Always show subtitles.
+ /// </summary>
Always = 1,
+
+ /// <summary>
+ /// Only show forced subtitles.
+ /// </summary>
OnlyForced = 2,
+
+ /// <summary>
+ /// Don't show subtitles.
+ /// </summary>
None = 3,
+
+ /// <summary>
+ /// Only show subtitles when the current audio stream is in a different language.
+ /// </summary>
Smart = 4
}
}
diff --git a/Jellyfin.Data/Enums/UnratedItem.cs b/Jellyfin.Data/Enums/UnratedItem.cs
index 5259e7739..871794086 100644
--- a/Jellyfin.Data/Enums/UnratedItem.cs
+++ b/Jellyfin.Data/Enums/UnratedItem.cs
@@ -1,17 +1,53 @@
-#pragma warning disable CS1591
-
namespace Jellyfin.Data.Enums
{
+ /// <summary>
+ /// An enum representing an unrated item.
+ /// </summary>
public enum UnratedItem
{
- Movie,
- Trailer,
- Series,
- Music,
- Book,
- LiveTvChannel,
- LiveTvProgram,
- ChannelContent,
- Other
+ /// <summary>
+ /// A movie.
+ /// </summary>
+ Movie = 0,
+
+ /// <summary>
+ /// A trailer.
+ /// </summary>
+ Trailer = 1,
+
+ /// <summary>
+ /// A series.
+ /// </summary>
+ Series = 2,
+
+ /// <summary>
+ /// Music.
+ /// </summary>
+ Music = 3,
+
+ /// <summary>
+ /// A book.
+ /// </summary>
+ Book = 4,
+
+ /// <summary>
+ /// A live TV channel
+ /// </summary>
+ LiveTvChannel = 5,
+
+ /// <summary>
+ /// A live TV program.
+ /// </summary>
+ LiveTvProgram = 6,
+
+ /// <summary>
+ /// Channel content.
+ /// </summary>
+ ChannelContent = 7,
+
+ /// <summary>
+ /// Another type, not covered by the other fields.
+ /// </summary>
+ Other = 8
}
}
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index 1e01521ad..95343f91b 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -4,7 +4,7 @@
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
- <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
@@ -41,8 +41,12 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.6" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.6" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.7" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Compile Include="..\SharedVersion.cs" />
</ItemGroup>
</Project>
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 21748ca19..30ed3e6af 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -24,11 +24,11 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.6">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.6">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
index 7c5c5a3ec..46f1c618f 100644
--- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
+++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
@@ -14,22 +14,21 @@ namespace Jellyfin.Server.Implementations.Users
/// </summary>
public class DisplayPreferencesManager : IDisplayPreferencesManager
{
- private readonly JellyfinDbProvider _dbProvider;
+ private readonly JellyfinDb _dbContext;
/// <summary>
/// Initializes a new instance of the <see cref="DisplayPreferencesManager"/> class.
/// </summary>
- /// <param name="dbProvider">The Jellyfin db provider.</param>
- public DisplayPreferencesManager(JellyfinDbProvider dbProvider)
+ /// <param name="dbContext">The database context.</param>
+ public DisplayPreferencesManager(JellyfinDb dbContext)
{
- _dbProvider = dbProvider;
+ _dbContext = dbContext;
}
/// <inheritdoc />
public DisplayPreferences GetDisplayPreferences(Guid userId, string client)
{
- using var dbContext = _dbProvider.CreateContext();
- var prefs = dbContext.DisplayPreferences
+ var prefs = _dbContext.DisplayPreferences
.Include(pref => pref.HomeSections)
.FirstOrDefault(pref =>
pref.UserId == userId && string.Equals(pref.Client, client));
@@ -37,7 +36,7 @@ namespace Jellyfin.Server.Implementations.Users
if (prefs == null)
{
prefs = new DisplayPreferences(userId, client);
- dbContext.DisplayPreferences.Add(prefs);
+ _dbContext.DisplayPreferences.Add(prefs);
}
return prefs;
@@ -46,14 +45,13 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc />
public ItemDisplayPreferences GetItemDisplayPreferences(Guid userId, Guid itemId, string client)
{
- using var dbContext = _dbProvider.CreateContext();
- var prefs = dbContext.ItemDisplayPreferences
+ var prefs = _dbContext.ItemDisplayPreferences
.FirstOrDefault(pref => pref.UserId == userId && pref.ItemId == itemId && string.Equals(pref.Client, client));
if (prefs == null)
{
prefs = new ItemDisplayPreferences(userId, Guid.Empty, client);
- dbContext.ItemDisplayPreferences.Add(prefs);
+ _dbContext.ItemDisplayPreferences.Add(prefs);
}
return prefs;
@@ -62,27 +60,15 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc />
public IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client)
{
- using var dbContext = _dbProvider.CreateContext();
-
- return dbContext.ItemDisplayPreferences
+ return _dbContext.ItemDisplayPreferences
.Where(prefs => prefs.UserId == userId && prefs.ItemId != Guid.Empty && string.Equals(prefs.Client, client))
.ToList();
}
/// <inheritdoc />
- public void SaveChanges(DisplayPreferences preferences)
- {
- using var dbContext = _dbProvider.CreateContext();
- dbContext.Update(preferences);
- dbContext.SaveChanges();
- }
-
- /// <inheritdoc />
- public void SaveChanges(ItemDisplayPreferences preferences)
+ public void SaveChanges()
{
- using var dbContext = _dbProvider.CreateContext();
- dbContext.Update(preferences);
- dbContext.SaveChanges();
+ _dbContext.SaveChanges();
}
}
}
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index 755844dd9..8d569a779 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Reflection;
using Emby.Drawing;
using Emby.Server.Implementations;
@@ -15,6 +16,7 @@ using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -67,12 +69,8 @@ namespace Jellyfin.Server
Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
}
- // TODO: Set up scoping and use AddDbContextPool,
- // can't register as Transient since tracking transient in GC is funky
- // serviceCollection.AddDbContext<JellyfinDb>(
- // options => options
- // .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
- // ServiceLifetime.Transient);
+ ServiceCollection.AddDbContextPool<JellyfinDb>(
+ options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"));
ServiceCollection.AddEventServices();
ServiceCollection.AddSingleton<IEventManager, EventManager>();
diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
index 71c66a310..c7fbfa4d0 100644
--- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
@@ -1,6 +1,8 @@
+using System.Collections.Generic;
using Jellyfin.Server.Middleware;
using MediaBrowser.Controller.Configuration;
using Microsoft.AspNetCore.Builder;
+using Microsoft.OpenApi.Models;
namespace Jellyfin.Server.Extensions
{
@@ -23,6 +25,7 @@ namespace Jellyfin.Server.Extensions
// specifying the Swagger JSON endpoint.
var baseUrl = serverConfigurationManager.Configuration.BaseUrl.Trim('/');
+ var apiDocBaseUrl = serverConfigurationManager.Configuration.BaseUrl;
if (!string.IsNullOrEmpty(baseUrl))
{
baseUrl += '/';
@@ -32,19 +35,25 @@ namespace Jellyfin.Server.Extensions
.UseSwagger(c =>
{
// Custom path requires {documentName}, SwaggerDoc documentName is 'api-docs'
- c.RouteTemplate = $"/{baseUrl}{{documentName}}/openapi.json";
+ c.RouteTemplate = "{documentName}/openapi.json";
+ c.PreSerializeFilters.Add((swagger, httpReq) =>
+ {
+ swagger.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}{apiDocBaseUrl}" } };
+ });
})
.UseSwaggerUI(c =>
{
c.DocumentTitle = "Jellyfin API";
c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API");
- c.RoutePrefix = $"{baseUrl}api-docs/swagger";
+ c.InjectStylesheet($"/{baseUrl}api-docs/swagger/custom.css");
+ c.RoutePrefix = "api-docs/swagger";
})
.UseReDoc(c =>
{
c.DocumentTitle = "Jellyfin API";
c.SpecUrl($"/{baseUrl}api-docs/openapi.json");
- c.RoutePrefix = $"{baseUrl}api-docs/redoc";
+ c.InjectStylesheet($"/{baseUrl}api-docs/redoc/custom.css");
+ c.RoutePrefix = "api-docs/redoc";
});
}
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 896f5f0b8..8dcce93a4 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
-using Jellyfin.Api;
using Jellyfin.Api.Auth;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Auth.DownloadPolicy;
@@ -134,13 +133,11 @@ namespace Jellyfin.Server.Extensions
/// Extension method for adding the jellyfin API to the service collection.
/// </summary>
/// <param name="serviceCollection">The service collection.</param>
- /// <param name="baseUrl">The base url for the API.</param>
- /// <param name="pluginAssemblies">An IEnumberable containing all plugin assemblies with API controllers.</param>
+ /// <param name="pluginAssemblies">An IEnumerable containing all plugin assemblies with API controllers.</param>
/// /// <param name="corsHosts">The configured cors hosts.</param>
/// <returns>The MVC builder.</returns>
public static IMvcBuilder AddJellyfinApi(
this IServiceCollection serviceCollection,
- string baseUrl,
IEnumerable<Assembly> pluginAssemblies,
string[] corsHosts)
{
@@ -155,7 +152,9 @@ namespace Jellyfin.Server.Extensions
})
.AddMvc(opts =>
{
- opts.UseGeneralRoutePrefix(baseUrl);
+ // Allow requester to change between camelCase and PascalCase
+ opts.RespectBrowserAcceptHeader = true;
+
opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter());
opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter());
diff --git a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs
index 9b347ae2c..8043989b1 100644
--- a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs
+++ b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Formatters
public CamelCaseJsonProfileFormatter() : base(JsonDefaults.GetCamelCaseOptions())
{
SupportedMediaTypes.Clear();
- SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"CamelCase\""));
+ SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.CamelCaseMediaType));
}
}
}
diff --git a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs
index 0024708ba..d0110b125 100644
--- a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs
+++ b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs
@@ -1,3 +1,4 @@
+using System.Net.Mime;
using MediaBrowser.Common.Json;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
@@ -16,8 +17,8 @@ namespace Jellyfin.Server.Formatters
{
SupportedMediaTypes.Clear();
// Add application/json for default formatter
- SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
- SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"PascalCase\""));
+ SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json));
+ SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.PascalCaseMediaType));
}
}
}
diff --git a/Jellyfin.Server/Formatters/XmlOutputFormatter.cs b/Jellyfin.Server/Formatters/XmlOutputFormatter.cs
index 58319657d..01d99d7c8 100644
--- a/Jellyfin.Server/Formatters/XmlOutputFormatter.cs
+++ b/Jellyfin.Server/Formatters/XmlOutputFormatter.cs
@@ -16,8 +16,9 @@ namespace Jellyfin.Server.Formatters
/// </summary>
public XmlOutputFormatter()
{
+ SupportedMediaTypes.Clear();
SupportedMediaTypes.Add(MediaTypeNames.Text.Xml);
- SupportedMediaTypes.Add("text/xml;charset=UTF-8");
+
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 7541707d9..c3bec1c71 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -41,8 +41,10 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
- <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.6" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.7" />
<PackageReference Include="prometheus-net" Version="3.6.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
@@ -63,4 +65,16 @@
<ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
</ItemGroup>
+ <ItemGroup>
+ <None Update="wwwroot\api-docs\redoc\custom.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ <None Update="wwwroot\api-docs\swagger\custom.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ <None Update="wwwroot\api-docs\banner-dark.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
+
</Project>
diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
index 9316737bd..ae3a3a1c5 100644
--- a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
+++ b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
@@ -44,11 +44,7 @@ namespace Jellyfin.Server.Middleware
var localPath = httpContext.Request.Path.ToString();
var baseUrlPrefix = serverConfigurationManager.Configuration.BaseUrl;
- if (string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
- || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
- || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
- || string.IsNullOrEmpty(localPath)
- || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
+ if (!localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
{
// Always redirect back to the default path if the base prefix is invalid or missing
_logger.LogDebug("Normalizing an URL at {LocalPath}", localPath);
diff --git a/Jellyfin.Server/Middleware/CorsPolicyProvider.cs b/Jellyfin.Server/Middleware/CorsPolicyProvider.cs
new file mode 100644
index 000000000..7c2b28ed8
--- /dev/null
+++ b/Jellyfin.Server/Middleware/CorsPolicyProvider.cs
@@ -0,0 +1,7 @@
+namespace Jellyfin.Server.Middleware
+{
+ public class CorsPolicyProvider
+ {
+
+ }
+}
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index b9a90f9db..45959aec2 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -169,7 +169,7 @@ namespace Jellyfin.Server
// If hosting the web client, validate the client content path
if (startupConfig.HostWebClient())
{
- string? webContentPath = DashboardController.GetWebClientUiPath(startupConfig, appHost.ServerConfigurationManager);
+ string? webContentPath = appHost.ServerConfigurationManager.ApplicationPaths.WebPath;
if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0)
{
throw new InvalidOperationException(
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index c3ca86383..5601915a3 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -3,14 +3,18 @@ using System.ComponentModel;
using System.Net.Http.Headers;
using Jellyfin.Api.TypeConverters;
using Jellyfin.Server.Extensions;
+using Jellyfin.Server.Implementations;
using Jellyfin.Server.Middleware;
using Jellyfin.Server.Models;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Prometheus;
@@ -50,7 +54,6 @@ namespace Jellyfin.Server
options.HttpsPort = _serverApplicationHost.HttpsPort;
});
services.AddJellyfinApi(
- _serverConfigurationManager.Configuration.BaseUrl.TrimStart('/'),
_serverApplicationHost.GetApiPluginAssemblies(),
_serverConfigurationManager.Configuration.CorsHosts);
@@ -77,6 +80,9 @@ namespace Jellyfin.Server
c.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue($"({_serverApplicationHost.ApplicationUserAgentAddress})"));
})
.ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler());
+
+ services.AddHealthChecks()
+ .AddDbContextCheck<JellyfinDb>();
}
/// <summary>
@@ -84,54 +90,78 @@ namespace Jellyfin.Server
/// </summary>
/// <param name="app">The application builder.</param>
/// <param name="env">The webhost environment.</param>
+ /// <param name="appConfig">The application config.</param>
public void Configure(
IApplicationBuilder app,
- IWebHostEnvironment env)
+ IWebHostEnvironment env,
+ IConfiguration appConfig)
{
- if (env.IsDevelopment())
+ // Only add base url redirection if a base url is set.
+ if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.BaseUrl))
{
- app.UseDeveloperExceptionPage();
+ app.UseBaseUrlRedirection();
}
- app.UseMiddleware<ExceptionMiddleware>();
+ // Wrap rest of configuration so everything only listens on BaseUrl.
+ app.Map(_serverConfigurationManager.Configuration.BaseUrl, mainApp =>
+ {
+ if (env.IsDevelopment())
+ {
+ mainApp.UseDeveloperExceptionPage();
+ }
- app.UseMiddleware<ResponseTimeMiddleware>();
+ mainApp.UseMiddleware<ExceptionMiddleware>();
- app.UseWebSockets();
+ mainApp.UseMiddleware<ResponseTimeMiddleware>();
- app.UseResponseCompression();
+ mainApp.UseWebSockets();
- app.UseCors(ServerCorsPolicy.DefaultPolicyName);
+ mainApp.UseResponseCompression();
- if (_serverConfigurationManager.Configuration.RequireHttps
- && _serverApplicationHost.ListenWithHttps)
- {
- app.UseHttpsRedirection();
- }
+ mainApp.UseCors(ServerCorsPolicy.DefaultPolicyName);
- app.UseAuthentication();
- app.UseJellyfinApiSwagger(_serverConfigurationManager);
- app.UseRouting();
- app.UseAuthorization();
- if (_serverConfigurationManager.Configuration.EnableMetrics)
- {
- // Must be registered after any middleware that could change HTTP response codes or the data will be bad
- app.UseHttpMetrics();
- }
+ if (_serverConfigurationManager.Configuration.RequireHttps
+ && _serverApplicationHost.ListenWithHttps)
+ {
+ mainApp.UseHttpsRedirection();
+ }
- app.UseLanFiltering();
- app.UseIpBasedAccessValidation();
- app.UseBaseUrlRedirection();
- app.UseWebSocketHandler();
- app.UseServerStartupMessage();
+ mainApp.UseStaticFiles();
+ if (appConfig.HostWebClient())
+ {
+ mainApp.UseStaticFiles(new StaticFileOptions
+ {
+ FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
+ RequestPath = "/web"
+ });
+ }
+
+ mainApp.UseAuthentication();
+ mainApp.UseJellyfinApiSwagger(_serverConfigurationManager);
+ mainApp.UseRouting();
+ mainApp.UseAuthorization();
+
+ mainApp.UseLanFiltering();
+ mainApp.UseIpBasedAccessValidation();
+ mainApp.UseWebSocketHandler();
+ mainApp.UseServerStartupMessage();
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapControllers();
if (_serverConfigurationManager.Configuration.EnableMetrics)
{
- endpoints.MapMetrics(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/') + "/metrics");
+ // Must be registered after any middleware that could change HTTP response codes or the data will be bad
+ mainApp.UseHttpMetrics();
}
+
+ mainApp.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ if (_serverConfigurationManager.Configuration.EnableMetrics)
+ {
+ endpoints.MapMetrics("/metrics");
+ }
+
+ endpoints.MapHealthChecks("/health");
+ });
});
// Add type descriptor for legacy datetime parsing.
diff --git a/Jellyfin.Server/wwwroot/api-docs/banner-dark.svg b/Jellyfin.Server/wwwroot/api-docs/banner-dark.svg
new file mode 100644
index 000000000..b62b7545c
--- /dev/null
+++ b/Jellyfin.Server/wwwroot/api-docs/banner-dark.svg
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+ - Part of the Jellyfin project (https://jellyfin.media)
+ -
+ - All copyright belongs to the Jellyfin contributors; a full list can
+ - be found in the file CONTRIBUTORS.md
+ -
+ - This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
+ - To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/.
+- ***** END LICENSE BLOCK ***** -->
+<svg id="banner-dark" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1536 512">
+ <defs>
+ <linearGradient id="linear-gradient" x1="110.25" y1="213.3" x2="496.14" y2="436.09" gradientUnits="userSpaceOnUse">
+ <stop offset="0" stop-color="#aa5cc3"/>
+ <stop offset="1" stop-color="#00a4dc"/>
+ </linearGradient>
+ </defs>
+ <title>banner-dark</title>
+ <g id="banner-dark">
+ <g id="banner-dark-icon">
+ <path id="inner-shape" d="M261.42,201.62c-20.44,0-86.24,119.29-76.2,139.43s142.48,19.92,152.4,0S281.86,201.63,261.42,201.62Z" fill="url(#linear-gradient)"/>
+ <path id="outer-shape" d="M261.42,23.3C199.83,23.3,1.57,382.73,31.8,443.43s429.34,60,459.24,0S323,23.3,261.42,23.3ZM411.9,390.76c-19.59,39.33-281.08,39.77-300.9,0S221.1,115.48,261.45,115.48,431.49,351.42,411.9,390.76Z" fill="url(#linear-gradient)"/>
+ </g>
+ <g id="jellyfin-light-outlines" style="isolation:isolate" transform="translate(43.8)">
+ <path d="M556.64,350.75a67,67,0,0,1-22.87-27.47,8.91,8.91,0,0,1-1.49-4.75,7.42,7.42,0,0,1,2.83-5.94,9.25,9.25,0,0,1,6.09-2.38c3.16,0,5.94,1.69,8.31,5.05a48.09,48.09,0,0,0,16.34,20.34,40.59,40.59,0,0,0,24,7.58q20.51,0,33.27-12.62t12.77-33.12V159a8.44,8.44,0,0,1,2.67-6.39,9.56,9.56,0,0,1,6.83-2.52,9,9,0,0,1,6.68,2.52,8.7,8.7,0,0,1,2.53,6.39v138.4a64.7,64.7,0,0,1-8.32,32.67,59,59,0,0,1-23,22.72Q608.62,361,589.9,361A57.21,57.21,0,0,1,556.64,350.75Z" fill="#fff"/>
+ <path d="M831.66,279.47a8.77,8.77,0,0,1-6.24,2.53H713.16q0,17.82,7.27,31.92a54.91,54.91,0,0,0,20.79,22.28q13.51,8.18,31.93,8.17a54,54,0,0,0,25.54-5.94,52.7,52.7,0,0,0,18.12-15.15,10,10,0,0,1,6.24-2.67,8.14,8.14,0,0,1,7.72,7.72,8.81,8.81,0,0,1-3,6.24,74.7,74.7,0,0,1-23.91,19A65.56,65.56,0,0,1,773.45,361q-22.87,0-40.4-9.8a69.51,69.51,0,0,1-27.32-27.48q-9.79-17.66-9.8-40.83,0-24.36,9.65-42.62t25.69-27.92a65.2,65.2,0,0,1,34.16-9.65A70,70,0,0,1,798.84,211a65.78,65.78,0,0,1,25.39,24.36q9.81,16,10.1,38A8.07,8.07,0,0,1,831.66,279.47ZM733.5,231.8Q718.8,243.68,714.64,266H815.92v-2.38A46.91,46.91,0,0,0,807,240.27a48.47,48.47,0,0,0-18.56-15.15,54,54,0,0,0-23-5.2Q748.2,219.92,733.5,231.8Z" fill="#fff"/>
+ <path d="M888.24,355.5a8.92,8.92,0,0,1-15.3-6.38v-202a8.91,8.91,0,1,1,17.82,0v202A8.65,8.65,0,0,1,888.24,355.5Z" fill="#fff"/>
+ <path d="M956.55,355.5a8.92,8.92,0,0,1-15.3-6.38v-202a8.91,8.91,0,1,1,17.82,0v202A8.65,8.65,0,0,1,956.55,355.5Z" fill="#fff"/>
+ <path d="M1122.86,206.11a8.7,8.7,0,0,1,2.53,6.39v131q0,23.44-9.21,40.09a61.58,61.58,0,0,1-25.54,25.25q-16.34,8.61-36.83,8.61a96.73,96.73,0,0,1-23.31-2.68,61.72,61.72,0,0,1-18-7.12q-6.24-3.87-6.24-8.62a17.94,17.94,0,0,1,.6-3,8.06,8.06,0,0,1,3-4.45,7.49,7.49,0,0,1,4.45-1.49,7.91,7.91,0,0,1,3.56.89q19,10.39,36.24,10.4,24.65,0,39.06-15.44t14.4-42.18V333.38a54.37,54.37,0,0,1-21.38,20,62.55,62.55,0,0,1-30.3,7.58q-25.83,0-39.2-15.45t-13.37-41.87V212.5a8.91,8.91,0,1,1,17.82,0V301q0,21.39,9.36,32.38t29.25,11a48,48,0,0,0,23.32-6.09,49.88,49.88,0,0,0,17.82-16,37.44,37.44,0,0,0,6.68-21.24V212.5a9,9,0,0,1,15.29-6.39Z" fill="#fff"/>
+ <path d="M1210.18,161.41q-5.21,6.24-5.2,17.23v30.59h33.27a8.19,8.19,0,0,1,5.79,2.38,8.26,8.26,0,0,1,0,11.88,8.22,8.22,0,0,1-5.79,2.37H1205V349.12a8.91,8.91,0,1,1-17.82,0V225.86h-21.68a7.83,7.83,0,0,1-5.94-2.52,8.21,8.21,0,0,1-2.37-5.79,8,8,0,0,1,2.37-6.09,8.33,8.33,0,0,1,5.94-2.23h21.68V178.64q0-18.7,10.84-29t29-10.24a46.1,46.1,0,0,1,15.45,2.52q7.13,2.53,7.12,8.17a8.07,8.07,0,0,1-2.37,5.94,7.37,7.37,0,0,1-5.35,2.37,18.81,18.81,0,0,1-6.53-1.48,42,42,0,0,0-10.4-1.78Q1215.37,155.18,1210.18,161.41ZM1276,180.87c-2.19-1.88-3.27-4.61-3.27-8.17v-3q0-5.34,3.41-8.17t9.36-2.82q11.88,0,11.88,11v3c0,3.56-1,6.29-3.12,8.17s-5.1,2.82-9.06,2.82S1278.14,182.75,1276,180.87Zm15.59,174.63a8.92,8.92,0,0,1-15.3-6.38V212.5a8.91,8.91,0,1,1,17.82,0V349.12A8.65,8.65,0,0,1,1291.56,355.5Z" fill="#fff"/>
+ <path d="M1452.53,218.88q12.92,16.2,12.92,42.92v87.32a8.4,8.4,0,0,1-2.67,6.38,8.8,8.8,0,0,1-6.24,2.53,8.64,8.64,0,0,1-8.91-8.91V262.69q0-19.31-9.65-31.33t-29.85-12a53.28,53.28,0,0,0-42.77,21.83,36.24,36.24,0,0,0-7.13,21.53v86.43a8.91,8.91,0,1,1-17.82,0V216.06a8.91,8.91,0,1,1,17.82,0V232.4q8-12.77,23-21.24A61.84,61.84,0,0,1,1412,202.7Q1439.61,202.7,1452.53,218.88Z" fill="#fff"/>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/Jellyfin.Server/wwwroot/api-docs/redoc/custom.css b/Jellyfin.Server/wwwroot/api-docs/redoc/custom.css
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/Jellyfin.Server/wwwroot/api-docs/redoc/custom.css
diff --git a/Jellyfin.Server/wwwroot/api-docs/swagger/custom.css b/Jellyfin.Server/wwwroot/api-docs/swagger/custom.css
new file mode 100644
index 000000000..acb59888e
--- /dev/null
+++ b/Jellyfin.Server/wwwroot/api-docs/swagger/custom.css
@@ -0,0 +1,15 @@
+/* logo */
+.topbar-wrapper img[alt="Swagger UI"], .topbar-wrapper span {
+ visibility: collapse;
+}
+
+.topbar-wrapper .link:after {
+ content: url(../banner-dark.svg);
+ display: block;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ max-width: 100%;
+ max-height: 100%;
+ width: 150px;
+}
+/* end logo */
diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs
index 4cea61682..57c654667 100644
--- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs
+++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs
@@ -1,5 +1,3 @@
-using MediaBrowser.Model.Configuration;
-
namespace MediaBrowser.Common.Configuration
{
/// <summary>
@@ -17,8 +15,7 @@ namespace MediaBrowser.Common.Configuration
/// Gets the path to the web UI resources folder.
/// </summary>
/// <remarks>
- /// This value is not relevant if the server is configured to not host any static web content. Additionally,
- /// the value for <see cref="ServerConfiguration.DashboardSourcePath"/> takes precedence over this one.
+ /// This value is not relevant if the server is configured to not host any static web content.
/// </remarks>
string WebPath { get; }
diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs
index 5867cd4a0..67f7e8f14 100644
--- a/MediaBrowser.Common/Json/JsonDefaults.cs
+++ b/MediaBrowser.Common/Json/JsonDefaults.cs
@@ -10,6 +10,16 @@ namespace MediaBrowser.Common.Json
public static class JsonDefaults
{
/// <summary>
+ /// Pascal case json profile media type.
+ /// </summary>
+ public const string PascalCaseMediaType = "application/json; profile=\"PascalCase\"";
+
+ /// <summary>
+ /// Camel case json profile media type.
+ /// </summary>
+ public const string CamelCaseMediaType = "application/json; profile=\"CamelCase\"";
+
+ /// <summary>
/// Gets the default <see cref="JsonSerializerOptions" /> options.
/// </summary>
/// <remarks>
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 6e258371c..70dcc2397 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -18,9 +18,9 @@
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.7" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
- <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.6" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
</ItemGroup>
diff --git a/MediaBrowser.Common/Net/CacheMode.cs b/MediaBrowser.Common/Net/CacheMode.cs
deleted file mode 100644
index 78fa3bf9b..000000000
--- a/MediaBrowser.Common/Net/CacheMode.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1602
-
-namespace MediaBrowser.Common.Net
-{
- public enum CacheMode
- {
- None = 0,
- Unconditional = 1
- }
-}
diff --git a/MediaBrowser.Common/Net/CompressionMethods.cs b/MediaBrowser.Common/Net/CompressionMethods.cs
deleted file mode 100644
index 39b72609f..000000000
--- a/MediaBrowser.Common/Net/CompressionMethods.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1602
-
-using System;
-
-namespace MediaBrowser.Common.Net
-{
- [Flags]
- public enum CompressionMethods
- {
- None = 0b00000001,
- Deflate = 0b00000010,
- Gzip = 0b00000100
- }
-}
diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs
deleted file mode 100644
index 347fc9833..000000000
--- a/MediaBrowser.Common/Net/HttpRequestOptions.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using Microsoft.Net.Http.Headers;
-
-namespace MediaBrowser.Common.Net
-{
- /// <summary>
- /// Class HttpRequestOptions.
- /// </summary>
- public class HttpRequestOptions
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="HttpRequestOptions"/> class.
- /// </summary>
- public HttpRequestOptions()
- {
- RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- CacheMode = CacheMode.None;
- DecompressionMethod = CompressionMethods.Deflate;
- }
-
- /// <summary>
- /// Gets or sets the URL.
- /// </summary>
- /// <value>The URL.</value>
- public string Url { get; set; }
-
- public CompressionMethods DecompressionMethod { get; set; }
-
- /// <summary>
- /// Gets or sets the accept header.
- /// </summary>
- /// <value>The accept header.</value>
- public string AcceptHeader
- {
- get => GetHeaderValue(HeaderNames.Accept);
- set => RequestHeaders[HeaderNames.Accept] = value;
- }
-
- /// <summary>
- /// Gets or sets the cancellation token.
- /// </summary>
- /// <value>The cancellation token.</value>
- public CancellationToken CancellationToken { get; set; }
-
- /// <summary>
- /// Gets or sets the user agent.
- /// </summary>
- /// <value>The user agent.</value>
- public string UserAgent
- {
- get => GetHeaderValue(HeaderNames.UserAgent);
- set => RequestHeaders[HeaderNames.UserAgent] = value;
- }
-
- /// <summary>
- /// Gets or sets the referrer.
- /// </summary>
- /// <value>The referrer.</value>
- public string Referer
- {
- get => GetHeaderValue(HeaderNames.Referer);
- set => RequestHeaders[HeaderNames.Referer] = value;
- }
-
- /// <summary>
- /// Gets or sets the host.
- /// </summary>
- /// <value>The host.</value>
- public string Host
- {
- get => GetHeaderValue(HeaderNames.Host);
- set => RequestHeaders[HeaderNames.Host] = value;
- }
-
- public Dictionary<string, string> RequestHeaders { get; private set; }
-
- public string RequestContentType { get; set; }
-
- public string RequestContent { get; set; }
-
- public bool BufferContent { get; set; }
-
- public bool LogErrorResponseBody { get; set; }
-
- public bool EnableKeepAlive { get; set; }
-
- public CacheMode CacheMode { get; set; }
-
- public TimeSpan CacheLength { get; set; }
-
- public bool EnableDefaultUserAgent { get; set; }
-
- private string GetHeaderValue(string name)
- {
- RequestHeaders.TryGetValue(name, out var value);
-
- return value;
- }
- }
-}
diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs
deleted file mode 100644
index d4fee6c78..000000000
--- a/MediaBrowser.Common/Net/HttpResponseInfo.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System;
-using System.IO;
-using System.Net;
-using System.Net.Http.Headers;
-
-namespace MediaBrowser.Common.Net
-{
- /// <summary>
- /// Class HttpResponseInfo.
- /// </summary>
- public sealed class HttpResponseInfo : IDisposable
- {
-#pragma warning disable CS1591
- public HttpResponseInfo()
- {
- }
-
- public HttpResponseInfo(HttpResponseHeaders headers, HttpContentHeaders contentHeader)
- {
- Headers = headers;
- ContentHeaders = contentHeader;
- }
-
-#pragma warning restore CS1591
-
- /// <summary>
- /// Gets or sets the type of the content.
- /// </summary>
- /// <value>The type of the content.</value>
- public string ContentType { get; set; }
-
- /// <summary>
- /// Gets or sets the response URL.
- /// </summary>
- /// <value>The response URL.</value>
- public string ResponseUrl { get; set; }
-
- /// <summary>
- /// Gets or sets the content.
- /// </summary>
- /// <value>The content.</value>
- public Stream Content { get; set; }
-
- /// <summary>
- /// Gets or sets the status code.
- /// </summary>
- /// <value>The status code.</value>
- public HttpStatusCode StatusCode { get; set; }
-
- /// <summary>
- /// Gets or sets the temp file path.
- /// </summary>
- /// <value>The temp file path.</value>
- public string TempFilePath { get; set; }
-
- /// <summary>
- /// Gets or sets the length of the content.
- /// </summary>
- /// <value>The length of the content.</value>
- public long? ContentLength { get; set; }
-
- /// <summary>
- /// Gets or sets the headers.
- /// </summary>
- /// <value>The headers.</value>
- public HttpResponseHeaders Headers { get; set; }
-
- /// <summary>
- /// Gets or sets the content headers.
- /// </summary>
- /// <value>The content headers.</value>
- public HttpContentHeaders ContentHeaders { get; set; }
-
- /// <inheritdoc />
- public void Dispose()
- {
- // backwards compatibility
- }
- }
-}
diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs
deleted file mode 100644
index 534e22edd..000000000
--- a/MediaBrowser.Common/Net/IHttpClient.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System;
-using System.IO;
-using System.Net.Http;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Common.Net
-{
- /// <summary>
- /// Interface IHttpClient.
- /// </summary>
- public interface IHttpClient
- {
- /// <summary>
- /// Gets the response.
- /// </summary>
- /// <param name="options">The options.</param>
- /// <returns>Task{HttpResponseInfo}.</returns>
- Task<HttpResponseInfo> GetResponse(HttpRequestOptions options);
-
- /// <summary>
- /// Gets the specified options.
- /// </summary>
- /// <param name="options">The options.</param>
- /// <returns>Task{Stream}.</returns>
- Task<Stream> Get(HttpRequestOptions options);
-
- /// <summary>
- /// Warning: Deprecated function,
- /// use 'Task{HttpResponseInfo} SendAsync(HttpRequestOptions options, HttpMethod httpMethod);' instead
- /// Sends the asynchronous.
- /// </summary>
- /// <param name="options">The options.</param>
- /// <param name="httpMethod">The HTTP method.</param>
- /// <returns>Task{HttpResponseInfo}.</returns>
- [Obsolete("Use 'Task{HttpResponseInfo} SendAsync(HttpRequestOptions options, HttpMethod httpMethod);' instead")]
- Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod);
-
- /// <summary>
- /// Sends the asynchronous.
- /// </summary>
- /// <param name="options">The options.</param>
- /// <param name="httpMethod">The HTTP method.</param>
- /// <returns>Task{HttpResponseInfo}.</returns>
- Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod);
-
- /// <summary>
- /// Posts the specified options.
- /// </summary>
- /// <param name="options">The options.</param>
- /// <returns>Task{HttpResponseInfo}.</returns>
- Task<HttpResponseInfo> Post(HttpRequestOptions options);
- }
-}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index a5c22e50f..68126bd8a 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -60,8 +60,6 @@ namespace MediaBrowser.Controller.Entities
protected BaseItem()
{
- ThemeSongIds = Array.Empty<Guid>();
- ThemeVideoIds = Array.Empty<Guid>();
Tags = Array.Empty<string>();
Genres = Array.Empty<string>();
Studios = Array.Empty<string>();
@@ -100,12 +98,52 @@ namespace MediaBrowser.Controller.Entities
};
[JsonIgnore]
- public Guid[] ThemeSongIds { get; set; }
+ public Guid[] ThemeSongIds
+ {
+ get
+ {
+ if (_themeSongIds == null)
+ {
+ _themeSongIds = GetExtras()
+ .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong)
+ .Select(song => song.Id)
+ .ToArray();
+ }
+
+ return _themeSongIds;
+ }
+
+ private set
+ {
+ _themeSongIds = value;
+ }
+ }
+
[JsonIgnore]
- public Guid[] ThemeVideoIds { get; set; }
+ public Guid[] ThemeVideoIds
+ {
+ get
+ {
+ if (_themeVideoIds == null)
+ {
+ _themeVideoIds = GetExtras()
+ .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo)
+ .Select(song => song.Id)
+ .ToArray();
+ }
+
+ return _themeVideoIds;
+ }
+
+ private set
+ {
+ _themeVideoIds = value;
+ }
+ }
[JsonIgnore]
public string PreferredMetadataCountryCode { get; set; }
+
[JsonIgnore]
public string PreferredMetadataLanguage { get; set; }
@@ -635,6 +673,9 @@ namespace MediaBrowser.Controller.Entities
}
private string _sortName;
+ private Guid[] _themeSongIds;
+ private Guid[] _themeVideoIds;
+
/// <summary>
/// Gets the name of the sort.
/// </summary>
@@ -1582,7 +1623,8 @@ namespace MediaBrowser.Controller.Entities
await Task.WhenAll(tasks).ConfigureAwait(false);
- item.ThemeVideoIds = newThemeVideoIds;
+ // They are expected to be sorted by SortName
+ item.ThemeVideoIds = newThemeVideos.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
return themeVideosChanged;
}
@@ -1619,7 +1661,8 @@ namespace MediaBrowser.Controller.Entities
await Task.WhenAll(tasks).ConfigureAwait(false);
- item.ThemeSongIds = newThemeSongIds;
+ // They are expected to be sorted by SortName
+ item.ThemeSongIds = newThemeSongs.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
return themeSongsChanged;
}
@@ -2910,12 +2953,12 @@ namespace MediaBrowser.Controller.Entities
public IEnumerable<BaseItem> GetThemeSongs()
{
- return ThemeVideoIds.Select(LibraryManager.GetItemById).Where(i => i.ExtraType.Equals(Model.Entities.ExtraType.ThemeSong)).OrderBy(i => i.SortName);
+ return ThemeSongIds.Select(LibraryManager.GetItemById);
}
public IEnumerable<BaseItem> GetThemeVideos()
{
- return ThemeVideoIds.Select(LibraryManager.GetItemById).Where(i => i.ExtraType.Equals(Model.Entities.ExtraType.ThemeVideo)).OrderBy(i => i.SortName);
+ return ThemeVideoIds.Select(LibraryManager.GetItemById);
}
/// <summary>
diff --git a/MediaBrowser.Controller/IDisplayPreferencesManager.cs b/MediaBrowser.Controller/IDisplayPreferencesManager.cs
index 856b91b5d..b35f83096 100644
--- a/MediaBrowser.Controller/IDisplayPreferencesManager.cs
+++ b/MediaBrowser.Controller/IDisplayPreferencesManager.cs
@@ -35,15 +35,8 @@ namespace MediaBrowser.Controller
IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client);
/// <summary>
- /// Saves changes to the provided display preferences.
+ /// Saves changes made to the database.
/// </summary>
- /// <param name="preferences">The display preferences to save.</param>
- void SaveChanges(DisplayPreferences preferences);
-
- /// <summary>
- /// Saves changes to the provided item display preferences.
- /// </summary>
- /// <param name="preferences">The item display preferences to save.</param>
- void SaveChanges(ItemDisplayPreferences preferences);
+ void SaveChanges();
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
index d7afd2118..44bd38b54 100644
--- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
@@ -62,6 +62,7 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
public bool? HasImage { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is favorite.
/// </summary>
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 3674181b1..9854ec520 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -14,9 +14,9 @@
</PropertyGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.7" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.6" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 550916f82..2c30ca458 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -456,6 +456,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
+ var isNvencHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
@@ -516,6 +517,24 @@ namespace MediaBrowser.Controller.MediaEncoding
}
if (state.IsVideoRequest
+ && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ var isColorDepth10 = IsColorDepth10(state);
+
+ if (isNvencHevcDecoder && isColorDepth10
+ && _mediaEncoder.SupportsHwaccel("opencl")
+ && encodingOptions.EnableTonemapping
+ && !string.IsNullOrEmpty(state.VideoStream.VideoRange)
+ && state.VideoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
+ {
+ arg.Append("-init_hw_device opencl=ocl:")
+ .Append(encodingOptions.OpenclDevice)
+ .Append(' ')
+ .Append("-filter_hw_device ocl ");
+ }
+ }
+
+ if (state.IsVideoRequest
&& string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
{
arg.Append("-hwaccel videotoolbox ");
@@ -1003,11 +1022,33 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
{
param = "-pix_fmt yuv420p " + param;
}
+ if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
+ var videoStream = state.VideoStream;
+ var isColorDepth10 = IsColorDepth10(state);
+
+ if (videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1
+ && isColorDepth10
+ && _mediaEncoder.SupportsHwaccel("opencl")
+ && encodingOptions.EnableTonemapping
+ && !string.IsNullOrEmpty(videoStream.VideoRange)
+ && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
+ {
+ param = "-pix_fmt nv12 " + param;
+ }
+ else
+ {
+ param = "-pix_fmt yuv420p " + param;
+ }
+ }
+
if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
{
param = "-pix_fmt nv21 " + param;
@@ -1611,64 +1652,45 @@ namespace MediaBrowser.Controller.MediaEncoding
var outputSizeParam = ReadOnlySpan<char>.Empty;
var request = state.BaseRequest;
- // Add resolution params, if specified
- if (request.Width.HasValue
- || request.Height.HasValue
- || request.MaxHeight.HasValue
- || request.MaxWidth.HasValue)
+ outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"');
+
+ // All possible beginning of video filters
+ // Don't break the order
+ string[] beginOfOutputSizeParam = new[]
{
- outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"');
+ // for tonemap_opencl
+ "hwupload,tonemap_opencl",
// hwupload=extra_hw_frames=64,vpp_qsv (for overlay_qsv on linux)
- var index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase);
+ "hwupload=extra_hw_frames",
+
+ // vpp_qsv
+ "vpp",
+
+ // hwdownload,format=p010le (hardware decode + software encode for vaapi)
+ "hwdownload",
+
+ // format=nv12|vaapi,hwupload,scale_vaapi
+ "format",
+
+ // bwdif,scale=expr
+ "bwdif",
+
+ // yadif,scale=expr
+ "yadif",
+
+ // scale=expr
+ "scale"
+ };
+
+ var index = -1;
+ foreach (var param in beginOfOutputSizeParam)
+ {
+ index = outputSizeParam.IndexOf(param, StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Slice(index);
- }
- else
- {
- // vpp_qsv
- index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = outputSizeParam.Slice(index);
- }
- else
- {
- // hwdownload,format=p010le (hardware decode + software encode for vaapi)
- index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = outputSizeParam.Slice(index);
- }
- else
- {
- // format=nv12|vaapi,hwupload,scale_vaapi
- index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = outputSizeParam.Slice(index);
- }
- else
- {
- // yadif,scale=expr
- index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = outputSizeParam.Slice(index);
- }
- else
- {
- // scale=expr
- index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = outputSizeParam.Slice(index);
- }
- }
- }
- }
- }
+ break;
}
}
@@ -1747,9 +1769,9 @@ namespace MediaBrowser.Controller.MediaEncoding
*/
if (isLinux)
{
- retStr = !outputSizeParam.IsEmpty ?
- " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" :
- " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
+ retStr = !outputSizeParam.IsEmpty
+ ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\""
+ : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
}
}
@@ -2084,12 +2106,61 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
+ var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ var isColorDepth10 = IsColorDepth10(state);
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ // If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices
+ var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30;
+
+ // Currently only with the use of NVENC decoder can we get a decent performance.
+ // Currently only the HEVC/H265 format is supported.
+ // NVIDIA Pascal and Turing or higher are recommended.
+ if (isNvdecHevcDecoder && isColorDepth10
+ && _mediaEncoder.SupportsHwaccel("opencl")
+ && options.EnableTonemapping
+ && !string.IsNullOrEmpty(videoStream.VideoRange)
+ && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
+ {
+ var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}";
+
+ if (options.TonemappingParam != 0)
+ {
+ parameters += ":param={4}";
+ }
+
+ if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
+ {
+ parameters += ":range={5}";
+ }
+
+ // Upload the HDR10 or HLG data to the OpenCL device,
+ // use tonemap_opencl filter for tone mapping,
+ // and then download the SDR data to memory.
+ filters.Add("hwupload");
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ parameters,
+ options.TonemappingAlgorithm,
+ options.TonemappingDesat,
+ options.TonemappingThreshold,
+ options.TonemappingPeak,
+ options.TonemappingParam,
+ options.TonemappingRange));
+ filters.Add("hwdownload");
+
+ if (hasGraphicalSubs || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)
+ || string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+ {
+ filters.Add("format=nv12");
+ }
+ }
+
// When the input may or may not be hardware VAAPI decodable
if (isVaapiH264Encoder)
{
@@ -2107,7 +2178,6 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder)
{
var codec = videoStream.Codec.ToLowerInvariant();
- var isColorDepth10 = IsColorDepth10(state);
// Assert 10-bit hardware VAAPI decodable
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
@@ -2136,35 +2206,38 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isVaapiH264Encoder)
{
- filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi"));
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "deinterlace_vaapi=rate={0}",
+ doubleRateDeinterlace ? "field" : "frame"));
}
}
// Add software deinterlace filter before scaling filter
- if (state.DeInterlace("h264", true)
- || state.DeInterlace("avc", true)
- || state.DeInterlace("h265", true)
- || state.DeInterlace("hevc", true))
+ if ((state.DeInterlace("h264", true)
+ || state.DeInterlace("avc", true)
+ || state.DeInterlace("h265", true)
+ || state.DeInterlace("hevc", true))
+ && !isVaapiH264Encoder
+ && !isQsvH264Encoder
+ && !isNvdecH264Decoder)
{
- string deintParam;
- var inputFramerate = videoStream?.RealFrameRate;
-
- // If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle
- if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
+ if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase))
{
- deintParam = "yadif=1:-1:0";
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "bwdif={0}:-1:0",
+ doubleRateDeinterlace ? "1" : "0"));
}
else
{
- deintParam = "yadif=0:-1:0";
- }
-
- if (!string.IsNullOrEmpty(deintParam))
- {
- if (!isVaapiH264Encoder && !isQsvH264Encoder && !isNvdecH264Decoder)
- {
- filters.Add(deintParam);
- }
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "yadif={0}:-1:0",
+ doubleRateDeinterlace ? "1" : "0"));
}
}
@@ -2397,6 +2470,11 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.DeInterlace("h264", true))
{
inputModifier += " -deint 1";
+
+ if (!encodingOptions.DeinterlaceDoubleRate || (videoStream?.RealFrameRate ?? 60) > 30)
+ {
+ inputModifier += " -drop_second_field 1";
+ }
}
}
}
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 814edd732..6ead93e09 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -24,6 +24,7 @@
<ItemGroup>
<PackageReference Include="BDInfo" Version="0.7.6.1" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" />
<PackageReference Include="UTF.Unknown" Version="2.3.0" />
</ItemGroup>
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
index b7b23deff..8a7c032c5 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
@@ -283,6 +283,20 @@ namespace MediaBrowser.MediaEncoding.Probing
public IReadOnlyDictionary<string, int> Disposition { get; set; }
/// <summary>
+ /// Gets or sets the color range.
+ /// </summary>
+ /// <value>The color range.</value>
+ [JsonPropertyName("color_range")]
+ public string ColorRange { get; set; }
+
+ /// <summary>
+ /// Gets or sets the color space.
+ /// </summary>
+ /// <value>The color space.</value>
+ [JsonPropertyName("color_space")]
+ public string ColorSpace { get; set; }
+
+ /// <summary>
/// Gets or sets the color transfer.
/// </summary>
/// <value>The color transfer.</value>
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 40a3b43e1..22537a4d9 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -714,6 +714,16 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.RefFrames = streamInfo.Refs;
}
+ if (!string.IsNullOrEmpty(streamInfo.ColorRange))
+ {
+ stream.ColorRange = streamInfo.ColorRange;
+ }
+
+ if (!string.IsNullOrEmpty(streamInfo.ColorSpace))
+ {
+ stream.ColorSpace = streamInfo.ColorSpace;
+ }
+
if (!string.IsNullOrEmpty(streamInfo.ColorTransfer))
{
stream.ColorTransfer = streamInfo.ColorTransfer;
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 6ac5ac2ff..0a9958b9e 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -31,7 +32,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IMediaSourceManager _mediaSourceManager;
/// <summary>
@@ -46,7 +47,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
IApplicationPaths appPaths,
IFileSystem fileSystem,
IMediaEncoder mediaEncoder,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
IMediaSourceManager mediaSourceManager)
{
_libraryManager = libraryManager;
@@ -54,7 +55,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_appPaths = appPaths;
_fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_mediaSourceManager = mediaSourceManager;
}
@@ -750,24 +751,20 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- private Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
+ private async Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
{
switch (protocol)
{
case MediaProtocol.Http:
- var opts = new HttpRequestOptions()
- {
- Url = path,
- CancellationToken = cancellationToken,
-
- // Needed for seeking
- BufferContent = true
- };
-
- return _httpClient.Get(opts);
+ {
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetAsync(path, cancellationToken)
+ .ConfigureAwait(false);
+ return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ }
case MediaProtocol.File:
- return Task.FromResult<Stream>(File.OpenRead(path));
+ return File.OpenRead(path);
default:
throw new ArgumentOutOfRangeException(nameof(protocol));
}
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 9a30f7e9f..2cd637c5b 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Configuration
public double DownMixAudioBoost { get; set; }
+ public int MaxMuxingQueueSize { get; set; }
+
public bool EnableThrottling { get; set; }
public int ThrottleDelaySeconds { get; set; }
@@ -29,12 +31,30 @@ namespace MediaBrowser.Model.Configuration
public string VaapiDevice { get; set; }
+ public string OpenclDevice { get; set; }
+
+ public bool EnableTonemapping { get; set; }
+
+ public string TonemappingAlgorithm { get; set; }
+
+ public string TonemappingRange { get; set; }
+
+ public double TonemappingDesat { get; set; }
+
+ public double TonemappingThreshold { get; set; }
+
+ public double TonemappingPeak { get; set; }
+
+ public double TonemappingParam { get; set; }
+
public int H264Crf { get; set; }
public int H265Crf { get; set; }
public string EncoderPreset { get; set; }
+ public bool DeinterlaceDoubleRate { get; set; }
+
public string DeinterlaceMethod { get; set; }
public bool EnableDecodingColorDepth10Hevc { get; set; }
@@ -50,13 +70,26 @@ namespace MediaBrowser.Model.Configuration
public EncodingOptions()
{
DownMixAudioBoost = 2;
+ MaxMuxingQueueSize = 2048;
EnableThrottling = false;
ThrottleDelaySeconds = 180;
EncodingThreadCount = -1;
- // This is a DRM device that is almost guaranteed to be there on every intel platform, plus it's the default one in ffmpeg if you don't specify anything
+ // This is a DRM device that is almost guaranteed to be there on every intel platform,
+ // plus it's the default one in ffmpeg if you don't specify anything
VaapiDevice = "/dev/dri/renderD128";
+ // This is the OpenCL device that is used for tonemapping.
+ // The left side of the dot is the platform number, and the right side is the device number on the platform.
+ OpenclDevice = "0.0";
+ EnableTonemapping = false;
+ TonemappingAlgorithm = "reinhard";
+ TonemappingRange = "auto";
+ TonemappingDesat = 0;
+ TonemappingThreshold = 0.8;
+ TonemappingPeak = 0;
+ TonemappingParam = 0;
H264Crf = 23;
H265Crf = 28;
+ DeinterlaceDoubleRate = false;
DeinterlaceMethod = "yadif";
EnableDecodingColorDepth10Hevc = true;
EnableDecodingColorDepth10Vp9 = true;
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index c4fb12e76..68dc1cc83 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -167,12 +167,6 @@ namespace MediaBrowser.Model.Configuration
public bool EnableDashboardResponseCaching { get; set; }
/// <summary>
- /// Gets or sets a custom path to serve the dashboard from.
- /// </summary>
- /// <value>The dashboard source path, or null if the default path should be used.</value>
- public string DashboardSourcePath { get; set; }
-
- /// <summary>
/// Gets or sets the image saving convention.
/// </summary>
/// <value>The image saving convention.</value>
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index f9ec0d238..2d37618c2 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -36,6 +36,18 @@ namespace MediaBrowser.Model.Entities
public string Language { get; set; }
/// <summary>
+ /// Gets or sets the color range.
+ /// </summary>
+ /// <value>The color range.</value>
+ public string ColorRange { get; set; }
+
+ /// <summary>
+ /// Gets or sets the color space.
+ /// </summary>
+ /// <value>The color space.</value>
+ public string ColorSpace { get; set; }
+
+ /// <summary>
/// Gets or sets the color transfer.
/// </summary>
/// <value>The color transfer.</value>
@@ -48,12 +60,6 @@ namespace MediaBrowser.Model.Entities
public string ColorPrimaries { get; set; }
/// <summary>
- /// Gets or sets the color space.
- /// </summary>
- /// <value>The color space.</value>
- public string ColorSpace { get; set; }
-
- /// <summary>
/// Gets or sets the comment.
/// </summary>
/// <value>The comment.</value>
diff --git a/MediaBrowser.Model/IO/FileSystemMetadata.cs b/MediaBrowser.Model/IO/FileSystemMetadata.cs
index b23119d08..118c78e80 100644
--- a/MediaBrowser.Model/IO/FileSystemMetadata.cs
+++ b/MediaBrowser.Model/IO/FileSystemMetadata.cs
@@ -56,7 +56,7 @@ namespace MediaBrowser.Model.IO
public DateTime CreationTimeUtc { get; set; }
/// <summary>
- /// Gets a value indicating whether this instance is directory.
+ /// Gets or sets a value indicating whether this instance is directory.
/// </summary>
/// <value><c>true</c> if this instance is directory; otherwise, <c>false</c>.</value>
public bool IsDirectory { get; set; }
diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs
index bba69d4b4..dc6549787 100644
--- a/MediaBrowser.Model/IO/IFileSystem.cs
+++ b/MediaBrowser.Model/IO/IFileSystem.cs
@@ -201,9 +201,9 @@ namespace MediaBrowser.Model.IO
IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false);
void SetHidden(string path, bool isHidden);
- void SetReadOnly(string path, bool readOnly);
+
void SetAttributes(string path, bool isHidden, bool readOnly);
+
List<FileSystemMetadata> GetDrives();
- void SetExecutable(string path);
}
}
diff --git a/MediaBrowser.Model/IO/IShortcutHandler.cs b/MediaBrowser.Model/IO/IShortcutHandler.cs
index 5c663aa0d..14d5c4b62 100644
--- a/MediaBrowser.Model/IO/IShortcutHandler.cs
+++ b/MediaBrowser.Model/IO/IShortcutHandler.cs
@@ -22,7 +22,6 @@ namespace MediaBrowser.Model.IO
/// </summary>
/// <param name="shortcutPath">The shortcut path.</param>
/// <param name="targetPath">The target path.</param>
- /// <returns>System.String.</returns>
void Create(string shortcutPath, string targetPath);
}
}
diff --git a/MediaBrowser.Model/IO/IStreamHelper.cs b/MediaBrowser.Model/IO/IStreamHelper.cs
index af5ba5b17..0e09db16e 100644
--- a/MediaBrowser.Model/IO/IStreamHelper.cs
+++ b/MediaBrowser.Model/IO/IStreamHelper.cs
@@ -13,8 +13,6 @@ namespace MediaBrowser.Model.IO
Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken);
- Task<int> CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken);
-
Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken);
Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken);
diff --git a/MediaBrowser.Model/IO/IZipClient.cs b/MediaBrowser.Model/IO/IZipClient.cs
index 2daa54f22..fca52ebae 100644
--- a/MediaBrowser.Model/IO/IZipClient.cs
+++ b/MediaBrowser.Model/IO/IZipClient.cs
@@ -26,6 +26,7 @@ namespace MediaBrowser.Model.IO
void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles);
void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles);
+
void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName);
/// <summary>
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 4ae38ade9..c0a75009a 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -34,7 +34,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
- <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.7" />
<PackageReference Include="System.Globalization" Version="4.3.0" />
<PackageReference Include="System.Text.Json" Version="5.0.0-preview.8.20407.11" />
</ItemGroup>
diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs
index 771ca84f7..afe7351d3 100644
--- a/MediaBrowser.Model/Net/MimeTypes.cs
+++ b/MediaBrowser.Model/Net/MimeTypes.cs
@@ -125,7 +125,7 @@ namespace MediaBrowser.Model.Net
{ ".wmv", "video/x-ms-wmv" },
// Type audio
- { ".aac", "audio/mp4" },
+ { ".aac", "audio/aac" },
{ ".ac3", "audio/ac3" },
{ ".ape", "audio/x-ape" },
{ ".dsf", "audio/dsf" },
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 155e8cb8a..171b824ca 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -156,7 +156,7 @@ namespace MediaBrowser.Providers.Manager
/// <inheritdoc/>
public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken)
{
- var httpClient = _httpClientFactory.CreateClient();
+ var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
var contentType = response.Content.Headers.ContentType.MediaType;
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 85966b3bf..39f93c479 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -16,9 +16,9 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
- <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.6" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.7" />
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
<PackageReference Include="PlaylistsNET" Version="1.1.2" />
<PackageReference Include="TvDbSharper" Version="3.2.1" />
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
index c9dac9ecd..e3a1decb8 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -96,7 +97,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
/// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- var httpClient = _httpClientFactory.CreateClient();
+ var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return httpClient.GetAsync(url, cancellationToken);
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
index 321144edf..e6d89e688 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
@@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
@@ -173,7 +174,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
Directory.CreateDirectory(Path.GetDirectoryName(path));
- using var response = await _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
index 651266868..54851c4d0 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -137,7 +138,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- var httpClient = _httpClientFactory.CreateClient();
+ var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return httpClient.GetAsync(url, cancellationToken);
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
index 708426500..670c0cd05 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
@@ -154,7 +155,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
- using var response = await _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(path));
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
index 7f10e6922..8414c9328 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
@@ -14,6 +14,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using MediaBrowser.Common;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -765,7 +766,7 @@ namespace MediaBrowser.Providers.Music
_logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
_stopWatchMusicBrainz.Restart();
- response = await _httpClientFactory.CreateClient().SendAsync(options).ConfigureAwait(false);
+ response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options).ConfigureAwait(false);
// We retry a finite number of times, and only whilst MB is indicating 503 (throttling)
}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
index c18725e0a..8b8fea09e 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
@@ -6,6 +6,7 @@ using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -82,7 +83,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
public string Name => "The Open Movie Database";
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
index 102ad82e1..d53eba7e9 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
@@ -9,6 +9,7 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -129,7 +130,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var url = OmdbProvider.GetOmdbUrl(urlQuery);
- using var response = await OmdbProvider.GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
+ using var response = await OmdbProvider.GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var resultList = new List<SearchResult>();
@@ -274,7 +275,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
class SearchResult
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index c45149c3a..32dab60a6 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -10,6 +10,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
@@ -296,7 +297,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
"i={0}&plot=short&tomatoes=true&r=json",
imdbParam));
- using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
+ using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(stream).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(path));
@@ -334,7 +335,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
imdbParam,
seasonId));
- using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
+ using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<SeasonRootObject>(stream).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(path));
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
index 4e7c0e5a6..de2f6875f 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
@@ -6,6 +6,7 @@ using System.Net.Http;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
@@ -116,7 +117,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
index 90436c7c9..c088d8cec 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
@@ -153,6 +154,13 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
item.IndexNumber = Convert.ToInt32(episode.DvdEpisodeNumber ?? episode.AiredEpisodeNumber);
item.ParentIndexNumber = episode.DvdSeason ?? episode.AiredSeason;
}
+ else if (string.Equals(id.SeriesDisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase))
+ {
+ if (episode.AbsoluteNumber.GetValueOrDefault() != 0)
+ {
+ item.IndexNumber = episode.AbsoluteNumber;
+ }
+ }
else if (episode.AiredEpisodeNumber.HasValue)
{
item.IndexNumber = episode.AiredEpisodeNumber;
@@ -244,7 +252,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
public int Order => 0;
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
index 388a4e3e7..dc3c60dee 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -106,7 +107,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
/// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
index ff8c3455f..49576d488 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
@@ -148,7 +149,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
index d287828eb..d96840e51 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -146,7 +147,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
index c6dd8a5f3..ca9b1d738 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
@@ -8,6 +8,7 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -410,7 +411,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
index f43444028..f6592afe4 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
@@ -155,7 +156,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
index 4da2c042f..e627550f1 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
@@ -10,6 +10,7 @@ using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
@@ -271,7 +272,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs
index 60d7a599e..a975fb8f6 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
@@ -204,7 +205,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index d8918bb6b..f8bc19395 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -383,7 +384,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
internal Task<HttpResponseMessage> GetMovieDbResponse(HttpRequestMessage message)
{
message.Headers.UserAgent.ParseAdd(_appHost.ApplicationUserAgent);
- return _httpClientFactory.CreateClient().SendAsync(message);
+ return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(message);
}
/// <inheritdoc />
@@ -392,7 +393,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
/// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
index f31a7faea..f2d2c8120 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
@@ -31,9 +32,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
_httpClientFactory = httpClientFactory;
}
+ public static string ProviderName => TmdbUtils.ProviderName;
+
+ /// <inheritdoc />
public string Name => ProviderName;
- public static string ProviderName => TmdbUtils.ProviderName;
+ /// <inheritdoc />
+ public int Order => 0;
public bool Supports(BaseItem item)
{
@@ -125,11 +130,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return profile.Iso_639_1?.ToString();
}
- public int Order => 0;
-
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
index e9fb5c703..6bf04b81a 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
@@ -32,7 +33,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
{
const string DataFileName = "info.json";
- internal static TmdbPersonProvider Current { get; private set; }
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
@@ -55,6 +56,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
Current = this;
}
+ internal static TmdbPersonProvider Current { get; private set; }
+
public string Name => TmdbUtils.ProviderName;
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
@@ -95,7 +98,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return new List<RemoteSearchResult>();
}
- var url = string.Format(TmdbUtils.BaseTmdbApiUrl + @"3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(searchInfo.Name), TmdbUtils.ApiKey);
+ var url = string.Format(
+ CultureInfo.InvariantCulture,
+ TmdbUtils.BaseTmdbApiUrl + @"3/search/person?api_key={1}&query={0}",
+ WebUtility.UrlEncode(searchInfo.Name),
+ TmdbUtils.ApiKey);
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
foreach (var header in TmdbUtils.AcceptHeaders)
@@ -200,8 +207,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return result;
}
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
/// <summary>
/// Gets the TMDB id.
/// </summary>
@@ -226,7 +231,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return;
}
- var url = string.Format(TmdbUtils.BaseTmdbApiUrl + @"3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids", TmdbUtils.ApiKey, id);
+ var url = string.Format(
+ CultureInfo.InvariantCulture,
+ TmdbUtils.BaseTmdbApiUrl + @"3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids",
+ TmdbUtils.ApiKey,
+ id);
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
foreach (var header in TmdbUtils.AcceptHeaders)
@@ -259,7 +268,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs
index 5705885b4..30b7674e3 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs
@@ -7,6 +7,7 @@ using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
@@ -138,7 +139,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
protected Task<HttpResponseMessage> GetResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
index 787514d05..e7e2fd05b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
@@ -38,7 +39,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
index e59504cc6..73ed13267 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
@@ -9,6 +9,7 @@ using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -124,7 +125,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
private async Task<SeasonResult> GetSeasonInfo(string seriesTmdbId, int season, string preferredMetadataLanguage,
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
index f11eeb15b..125560175 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
@@ -182,7 +183,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
index 0eded3233..aaba6ffc0 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
@@ -10,6 +10,7 @@ using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -551,7 +552,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs
index 10374bde9..25296387b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Providers;
@@ -36,7 +37,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Trailers
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
index 321153c6b..76dc7df7f 100644
--- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
+++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
@@ -8,6 +8,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
@@ -122,7 +123,7 @@ namespace MediaBrowser.Providers.Studios
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- var httpClient = _httpClientFactory.CreateClient();
+ var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return httpClient.GetAsync(url, cancellationToken);
}
@@ -140,7 +141,7 @@ namespace MediaBrowser.Providers.Studios
if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1)
{
- var httpClient = _httpClientFactory.CreateClient();
+ var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
Directory.CreateDirectory(Path.GetDirectoryName(file));
await using var response = await httpClient.GetStreamAsync(url).ConfigureAwait(false);
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index 75587da1f..fd45dca2a 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -66,8 +66,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Data", "Jellyfin.D
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations", "Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj", "{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api.Tests", "tests\MediaBrowser.Api.Tests\MediaBrowser.Api.Tests.csproj", "{7C93C84F-105C-48E5-A878-406FA0A5B296}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -178,10 +176,6 @@ Global
{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.Build.0 = Release|Any CPU
- {7C93C84F-105C-48E5-A878-406FA0A5B296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7C93C84F-105C-48E5-A878-406FA0A5B296}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7C93C84F-105C-48E5-A878-406FA0A5B296}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7C93C84F-105C-48E5-A878-406FA0A5B296}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/debian/rules b/debian/rules
index 2a5d41a69..96541f41b 100755
--- a/debian/rules
+++ b/debian/rules
@@ -40,7 +40,7 @@ override_dh_clistrip:
override_dh_auto_build:
dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
- "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server
+ "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
override_dh_auto_clean:
dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true
diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64
index f9c6a1674..1ac5f76d6 100644
--- a/deployment/Dockerfile.debian.amd64
+++ b/deployment/Dockerfile.debian.amd64
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/c1a30ceb-adc2-4244-b24a-06ca29bb1ee9/6df5d856ff1b3e910d283f89690b7cae/dotnet-sdk-3.1.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64
index 5c08444df..68381e7bf 100644
--- a/deployment/Dockerfile.debian.arm64
+++ b/deployment/Dockerfile.debian.arm64
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/c1a30ceb-adc2-4244-b24a-06ca29bb1ee9/6df5d856ff1b3e910d283f89690b7cae/dotnet-sdk-3.1.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf
index b54fc3f91..ce1b100c1 100644
--- a/deployment/Dockerfile.debian.armhf
+++ b/deployment/Dockerfile.debian.armhf
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/c1a30ceb-adc2-4244-b24a-06ca29bb1ee9/6df5d856ff1b3e910d283f89690b7cae/dotnet-sdk-3.1.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.docker.amd64 b/deployment/Dockerfile.docker.amd64
index 204ded3a4..e04442606 100644
--- a/deployment/Dockerfile.docker.amd64
+++ b/deployment/Dockerfile.docker.amd64
@@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
diff --git a/deployment/Dockerfile.docker.arm64 b/deployment/Dockerfile.docker.arm64
index eedbaac33..a7ac40492 100644
--- a/deployment/Dockerfile.docker.arm64
+++ b/deployment/Dockerfile.docker.arm64
@@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
diff --git a/deployment/Dockerfile.docker.armhf b/deployment/Dockerfile.docker.armhf
index 2a500246b..b5a42f55f 100644
--- a/deployment/Dockerfile.docker.armhf
+++ b/deployment/Dockerfile.docker.armhf
@@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64
index 3a2f67615..b4a3c1b76 100644
--- a/deployment/Dockerfile.linux.amd64
+++ b/deployment/Dockerfile.linux.amd64
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/c1a30ceb-adc2-4244-b24a-06ca29bb1ee9/6df5d856ff1b3e910d283f89690b7cae/dotnet-sdk-3.1.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.macos b/deployment/Dockerfile.macos
index d1c0784ff..7912e018e 100644
--- a/deployment/Dockerfile.macos
+++ b/deployment/Dockerfile.macos
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/c1a30ceb-adc2-4244-b24a-06ca29bb1ee9/6df5d856ff1b3e910d283f89690b7cae/dotnet-sdk-3.1.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable
index 7270188fd..949f1ef8f 100644
--- a/deployment/Dockerfile.portable
+++ b/deployment/Dockerfile.portable
@@ -15,7 +15,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/c1a30ceb-adc2-4244-b24a-06ca29bb1ee9/6df5d856ff1b3e910d283f89690b7cae/dotnet-sdk-3.1.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64
index b7d3b4bde..9518d8493 100644
--- a/deployment/Dockerfile.ubuntu.amd64
+++ b/deployment/Dockerfile.ubuntu.amd64
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/c1a30ceb-adc2-4244-b24a-06ca29bb1ee9/6df5d856ff1b3e910d283f89690b7cae/dotnet-sdk-3.1.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64
index dc90f9fbd..0174f2f2a 100644
--- a/deployment/Dockerfile.ubuntu.arm64
+++ b/deployment/Dockerfile.ubuntu.arm64
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/c1a30ceb-adc2-4244-b24a-06ca29bb1ee9/6df5d856ff1b3e910d283f89690b7cae/dotnet-sdk-3.1.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf
index db98610c9..0e02240c8 100644
--- a/deployment/Dockerfile.ubuntu.armhf
+++ b/deployment/Dockerfile.ubuntu.armhf
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/c1a30ceb-adc2-4244-b24a-06ca29bb1ee9/6df5d856ff1b3e910d283f89690b7cae/dotnet-sdk-3.1.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64
index 95fd10cf4..d1f2f9e48 100644
--- a/deployment/Dockerfile.windows.amd64
+++ b/deployment/Dockerfile.windows.amd64
@@ -15,7 +15,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/c1a30ceb-adc2-4244-b24a-06ca29bb1ee9/6df5d856ff1b3e910d283f89690b7cae/dotnet-sdk-3.1.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/4f9b8a64-5e09-456c-a087-527cfc8b4cd2/15e14ec06eab947432de139f172f7a98/dotnet-sdk-3.1.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/build.linux.amd64 b/deployment/build.linux.amd64
index a7fb0544a..a1e7f661a 100755
--- a/deployment/build.linux.amd64
+++ b/deployment/build.linux.amd64
@@ -16,7 +16,7 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version}
diff --git a/deployment/build.macos b/deployment/build.macos
index d808141ac..6255c80cb 100755
--- a/deployment/build.macos
+++ b/deployment/build.macos
@@ -16,7 +16,7 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version}
diff --git a/deployment/build.portable b/deployment/build.portable
index 24a8cbf32..ea40ade5d 100755
--- a/deployment/build.portable
+++ b/deployment/build.portable
@@ -16,7 +16,7 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version}
diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64
index bd5dc6438..afe490576 100755
--- a/deployment/build.windows.amd64
+++ b/deployment/build.windows.amd64
@@ -23,7 +23,7 @@ fi
output_dir="dist/jellyfin-server_${version}"
# Build binary
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
# Prepare addins
addin_build_dir="$( mktemp -d )"
diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec
index a1e0b5f14..37b573e50 100644
--- a/fedora/jellyfin.spec
+++ b/fedora/jellyfin.spec
@@ -54,7 +54,7 @@ The Jellyfin media server backend.
export DOTNET_CLI_TELEMETRY_OPTOUT=1
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \
- "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server
+ "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
%{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
%{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json
diff --git a/tests/MediaBrowser.Api.Tests/BrandingServiceTests.cs b/tests/Jellyfin.Api.Tests/BrandingServiceTests.cs
index 5d7f7765c..6fc287420 100644
--- a/tests/MediaBrowser.Api.Tests/BrandingServiceTests.cs
+++ b/tests/Jellyfin.Api.Tests/BrandingServiceTests.cs
@@ -3,7 +3,7 @@ using System.Threading.Tasks;
using MediaBrowser.Model.Branding;
using Xunit;
-namespace MediaBrowser.Api.Tests
+namespace Jellyfin.Api.Tests
{
public sealed class BrandingServiceTests : IClassFixture<JellyfinApplicationFactory>
{
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index f77eba376..bcba3a203 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -14,12 +14,13 @@
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.13.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" />
- <PackageReference Include="AutoFixture.Xunit2" Version="4.12.0" />
- <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.6" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.13.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.13.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.7" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
<PackageReference Include="Moq" Version="4.14.5" />
</ItemGroup>
diff --git a/tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
index 2029f88e9..77f1640fa 100644
--- a/tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs
+++ b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
@@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Extensions.Logging;
-namespace MediaBrowser.Api.Tests
+namespace Jellyfin.Api.Tests
{
/// <summary>
/// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests.
diff --git a/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs
new file mode 100644
index 000000000..3a85b5514
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs
@@ -0,0 +1,42 @@
+using System.IO;
+using System.Reflection;
+using System.Text.Json;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Branding;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Jellyfin.Api.Tests
+{
+ public sealed class OpenApiSpecTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+ private readonly ITestOutputHelper _outputHelper;
+
+ public OpenApiSpecTests(JellyfinApplicationFactory factory, ITestOutputHelper outputHelper)
+ {
+ _factory = factory;
+ _outputHelper = outputHelper;
+ }
+
+ [Fact]
+ public async Task GetSpec_ReturnsCorrectResponse()
+ {
+ // Arrange
+ var client = _factory.CreateClient();
+
+ // Act
+ var response = await client.GetAsync("/api-docs/openapi.json");
+
+ // Assert
+ response.EnsureSuccessStatusCode();
+ Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
+
+ // Write out for publishing
+ var responseBody = await response.Content.ReadAsStringAsync();
+ string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json"));
+ _outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath);
+ File.WriteAllText(outputPath, responseBody);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 746474044..e3f87d29b 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -13,9 +13,9 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 1559f70ab..5de02a29b 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -13,9 +13,9 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index e1a089547..3ac60819b 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -19,9 +19,9 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 0e9e91563..37d0a9929 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -13,9 +13,9 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
index 917d8fb3a..ed971eed7 100644
--- a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
@@ -47,6 +47,10 @@ namespace Jellyfin.Naming.Tests.Video
// FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)]
[InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
[InlineData("3 days to kill (2005).mkv", "3 days to kill", 2005)]
+ [InlineData("My Movie 2013.12.09", "My Movie 2013.12.09", null)]
+ [InlineData("My Movie 2013-12-09", "My Movie 2013-12-09", null)]
+ [InlineData("My Movie 20131209", "My Movie 20131209", null)]
+ [InlineData("My Movie 2013-12-09 2013", "My Movie 2013-12-09", 2013)]
public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)
{
input = Path.GetFileName(input);
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index 03187f4b9..d1679c279 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -15,10 +15,11 @@
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.13.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.13.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="Moq" Version="4.14.5" />
<PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
diff --git a/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj b/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj
deleted file mode 100644
index a4ef10648..000000000
--- a/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj
+++ /dev/null
@@ -1,34 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
- <PropertyGroup>
- <TargetFramework>netcoreapp3.1</TargetFramework>
- <IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- </PropertyGroup>
-
- <ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.6" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.3.0" />
- </ItemGroup>
-
- <ItemGroup>
- <ProjectReference Include="..\..\Jellyfin.Server\Jellyfin.Server.csproj" />
- </ItemGroup>
-
- <!-- Code Analyzers -->
- <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
- <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
- <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
- </ItemGroup>
-
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
-</Project>
diff --git a/tests/jellyfin-tests.ruleset b/tests/jellyfin-tests.ruleset
index 5a113e955..e2abaf5bb 100644
--- a/tests/jellyfin-tests.ruleset
+++ b/tests/jellyfin-tests.ruleset
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<RuleSet Name="Rules for MediaBrowser.Api.Tests" Description="Code analysis rules for MediaBrowser.Api.Tests.csproj" ToolsVersion="14.0">
+<RuleSet Name="Rules for Jellyfin.Api.Tests" Description="Code analysis rules for Jellyfin.Api.Tests.csproj" ToolsVersion="14.0">
<!-- Include the solution default RuleSet. The rules in this file will override the defaults. -->
<Include Path="../jellyfin.ruleset" Action="Default" />
@@ -17,6 +17,6 @@
<!-- CA2007: Consider calling ConfigureAwait on the awaited task -->
<Rule Id="CA2007" Action="None" />
<!-- CA2234: Pass system uri objects instead of strings -->
- <Rule Id="CA2234" Action="Info" />
+ <Rule Id="CA2234" Action="Info" />
</Rules>
</RuleSet>
diff --git a/windows/build-jellyfin.ps1 b/windows/build-jellyfin.ps1
index b76a8e0bb..b65e619ee 100644
--- a/windows/build-jellyfin.ps1
+++ b/windows/build-jellyfin.ps1
@@ -40,7 +40,7 @@ function Build-JellyFin {
Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture"
Write-Verbose "InstallLocation: $ResolvedInstallLocation"
Write-Verbose "DotNetVerbosity: $DotNetVerbosity"
- dotnet publish --self-contained -c $BuildType --output $ResolvedInstallLocation -v $DotNetVerbosity -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:DebugType=none --runtime `"$windowsversion-$Architecture`" Jellyfin.Server
+ dotnet publish --self-contained -c $BuildType --output $ResolvedInstallLocation -v $DotNetVerbosity -p:GenerateDocumentationFile=true -p:DebugSymbols=false -p:DebugType=none --runtime `"$windowsversion-$Architecture`" Jellyfin.Server
}
function Install-FFMPEG {