aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dockerignore1
-rw-r--r--.drone.yml103
-rw-r--r--.editorconfig84
-rw-r--r--.gitignore2
-rw-r--r--BDInfo/BDROM.cs4
-rw-r--r--BDInfo/TSPlaylistFile.cs7
-rw-r--r--BDInfo/TSStreamClipFile.cs4
-rw-r--r--CONTRIBUTORS.md4
-rw-r--r--Emby.Dlna/Configuration/DlnaOptions.cs2
-rw-r--r--Emby.Dlna/DlnaManager.cs4
-rw-r--r--Emby.Dlna/Main/DlnaEntryPoint.cs19
-rw-r--r--Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs3
-rw-r--r--Emby.Dlna/PlayTo/Device.cs63
-rw-r--r--Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs4
-rw-r--r--Emby.Notifications/CoreNotificationTypes.cs32
-rw-r--r--Emby.Notifications/Notifications.cs32
-rw-r--r--Emby.Photos/PhotoProvider.cs5
-rw-r--r--Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs109
-rw-r--r--Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs2
-rw-r--r--Emby.Server.Implementations/AppBase/ConfigurationHelper.cs4
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs611
-rw-r--r--Emby.Server.Implementations/Archiving/ZipClient.cs6
-rw-r--r--Emby.Server.Implementations/Channels/ChannelPostScanTask.cs48
-rw-r--r--Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs2
-rw-r--r--Emby.Server.Implementations/Collections/CollectionImageProvider.cs8
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs4
-rw-r--r--Emby.Server.Implementations/ConfigurationOptions.cs12
-rw-r--r--Emby.Server.Implementations/Data/BaseSqliteRepository.cs27
-rw-r--r--Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs11
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs287
-rw-r--r--Emby.Server.Implementations/Devices/DeviceId.cs12
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs12
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs27
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj4
-rw-r--r--Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs2
-rw-r--r--Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs13
-rw-r--r--Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs17
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs94
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpResultFactory.cs34
-rw-r--r--Emby.Server.Implementations/HttpServer/LoggerUtils.cs55
-rw-r--r--Emby.Server.Implementations/HttpServer/StreamWriter.cs8
-rw-r--r--Emby.Server.Implementations/IO/FileRefresher.cs10
-rw-r--r--Emby.Server.Implementations/IO/LibraryMonitor.cs21
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs59
-rw-r--r--Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs14
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs48
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs5
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs3
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs8
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs9
-rw-r--r--Emby.Server.Implementations/Library/Validators/PeopleValidator.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs15
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs13
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs13
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs78
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/kz.csv13
-rw-r--r--Emby.Server.Implementations/Networking/NetworkManager.cs72
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs2
-rw-r--r--Emby.Server.Implementations/Serialization/JsonSerializer.cs21
-rw-r--r--Emby.Server.Implementations/ServerApplicationPaths.cs15
-rw-r--r--Emby.Server.Implementations/Services/ServicePath.cs161
-rw-r--r--Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs52
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs2
-rw-r--r--Jellyfin.Drawing.Skia/SkiaEncoder.cs2
-rw-r--r--Jellyfin.Server/CoreAppHost.cs32
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj7
-rw-r--r--Jellyfin.Server/Program.cs264
-rw-r--r--Jellyfin.Server/SocketSharp/HttpFile.cs4
-rw-r--r--Jellyfin.Server/SocketSharp/HttpPostedFile.cs204
-rw-r--r--Jellyfin.Server/SocketSharp/RequestMono.cs375
-rw-r--r--Jellyfin.Server/SocketSharp/SharpWebSocket.cs51
-rw-r--r--Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs103
-rw-r--r--Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs138
-rw-r--r--Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs88
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs2
-rw-r--r--MediaBrowser.Api/BaseApiService.cs25
-rw-r--r--MediaBrowser.Api/EnvironmentService.cs10
-rw-r--r--MediaBrowser.Api/FilterService.cs3
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs1
-rw-r--r--MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs16
-rw-r--r--MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs2
-rw-r--r--MediaBrowser.Api/System/ActivityLogWebSocketListener.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/ArtistsService.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs4
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs8
-rw-r--r--MediaBrowser.Api/UserLibrary/GenresService.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs55
-rw-r--r--MediaBrowser.Api/UserLibrary/MusicGenresService.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/PersonsService.cs6
-rw-r--r--MediaBrowser.Api/UserLibrary/StudiosService.cs2
-rw-r--r--MediaBrowser.Common/Configuration/IApplicationPaths.cs6
-rw-r--r--MediaBrowser.Common/IApplicationHost.cs28
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj4
-rw-r--r--MediaBrowser.Common/Net/INetworkManager.cs5
-rw-r--r--MediaBrowser.Controller/Dto/DtoOptions.cs14
-rw-r--r--MediaBrowser.Controller/Dto/IDtoService.cs4
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs74
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs24
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs14
-rw-r--r--MediaBrowser.Controller/Library/TVUtils.cs38
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs11
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs14
-rw-r--r--MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs2
-rw-r--r--MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs15
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs2
-rw-r--r--MediaBrowser.Model/IO/IFileSystem.cs2
-rw-r--r--MediaBrowser.Model/Net/IpAddressInfo.cs1
-rw-r--r--MediaBrowser.Model/Serialization/IJsonSerializer.cs8
-rw-r--r--MediaBrowser.Model/System/PublicSystemInfo.cs6
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs9
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs2
-rw-r--r--MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Manager/GenericPriorityQueue.cs402
-rw-r--r--MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs22
-rw-r--r--MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs24
-rw-r--r--MediaBrowser.Providers/Manager/IPriorityQueue.cs56
-rw-r--r--MediaBrowser.Providers/Manager/SimplePriorityQueue.cs247
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj5
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Music/MusicVideoMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/People/TvdbPersonImageProvider.cs173
-rw-r--r--MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Photos/PhotoMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/TV/MissingEpisodeProvider.cs370
-rw-r--r--MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs2
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs19
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs2
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs244
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs146
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs853
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs398
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs304
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs321
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs1591
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs36
-rw-r--r--MediaBrowser.Providers/TV/TvExternalIds.cs4
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs2
-rw-r--r--RSSDP/ISsdpCommunicationsServer.cs6
-rw-r--r--RSSDP/RSSDP.csproj1
-rw-r--r--RSSDP/SsdpCommunicationsServer.cs23
-rw-r--r--RSSDP/SsdpDeviceLocator.cs2
-rw-r--r--RSSDP/SsdpDevicePublisher.cs19
-rw-r--r--RSSDP/SsdpRootDevice.cs10
-rw-r--r--SocketHttpListener/Ext.cs69
-rw-r--r--SocketHttpListener/Net/HttpListener.cs43
-rw-r--r--SocketHttpListener/Net/HttpListenerPrefixCollection.cs79
-rw-r--r--SocketHttpListener/WebSocket.cs294
-rw-r--r--SocketHttpListener/WebSocketFrame.cs47
-rwxr-xr-xdeployment/debian-x64/build.sh7
-rwxr-xr-xdeployment/macos/build.sh (renamed from deployment/osx-x64/build.sh)0
-rwxr-xr-xdeployment/macos/clean.sh (renamed from deployment/debian-x64/clean.sh)0
-rw-r--r--deployment/macos/dependencies.txt (renamed from deployment/debian-x64/dependencies.txt)0
-rwxr-xr-xdeployment/macos/package.sh (renamed from deployment/debian-x64/package.sh)0
-rwxr-xr-xdeployment/osx-x64/clean.sh7
-rwxr-xr-xdeployment/osx-x64/package.sh7
-rwxr-xr-xdeployment/portable/build.sh (renamed from deployment/framework/build.sh)0
-rwxr-xr-xdeployment/portable/clean.sh (renamed from deployment/framework/clean.sh)0
-rwxr-xr-xdeployment/portable/package.sh (renamed from deployment/framework/package.sh)0
-rwxr-xr-xdeployment/ubuntu-x64/build.sh7
-rwxr-xr-xdeployment/ubuntu-x64/clean.sh7
-rw-r--r--deployment/ubuntu-x64/dependencies.txt1
-rwxr-xr-xdeployment/ubuntu-x64/package.sh7
-rw-r--r--deployment/win-generic/dependencies.txt1
-rw-r--r--deployment/windows/build-jellyfin.ps1 (renamed from deployment/win-generic/build-jellyfin.ps1)4
-rw-r--r--deployment/windows/dependencies.txt (renamed from deployment/osx-x64/dependencies.txt)0
-rw-r--r--deployment/windows/install-jellyfin.ps1 (renamed from deployment/win-generic/install-jellyfin.ps1)0
-rw-r--r--deployment/windows/install.bat (renamed from deployment/win-generic/install.bat)0
-rw-r--r--jellyfin.ruleset8
183 files changed, 3113 insertions, 6934 deletions
diff --git a/.dockerignore b/.dockerignore
index 45e543525..ffd6de2d6 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -8,3 +8,4 @@ README.md
deployment/*/dist
deployment/*/pkg-dist
deployment/collect-dist/
+ci/
diff --git a/.drone.yml b/.drone.yml
index 98db4884b..7705f4f93 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -1,12 +1,111 @@
+---
kind: pipeline
-name: build
+name: build-debug
steps:
- name: submodules
image: docker:git
commands:
- git submodule update --init --recursive
+
+- name: build
+ image: microsoft/dotnet:2-sdk
+ commands:
+ - dotnet publish "Jellyfin.Server" --configuration Debug --output "../ci/ci-debug"
+
+---
+kind: pipeline
+name: build-release
+
+steps:
+- name: submodules
+ image: docker:git
+ commands:
+ - git submodule update --init --recursive
+
+- name: build
+ image: microsoft/dotnet:2-sdk
+ commands:
+ - dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release"
+
+---
+
+kind: pipeline
+name: check-abi
+
+steps:
+- name: submodules
+ image: docker:git
+ commands:
+ - git submodule update --init --recursive
+
- name: build
image: microsoft/dotnet:2-sdk
commands:
- - dotnet publish --configuration release --output /release Jellyfin.Server
+ - dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release"
+
+- name: clone-dotnet-compat
+ image: docker:git
+ commands:
+ - git clone --depth 1 https://github.com/EraYaN/dotnet-compatibility ci/dotnet-compatibility
+
+- name: build-dotnet-compat
+ image: microsoft/dotnet:2-sdk
+ commands:
+ - dotnet publish "ci/dotnet-compatibility/CompatibilityCheckerCoreCLI" --configuration Release --output "../../ci-tools"
+
+- name: download-last-nuget-release-common
+ image: plugins/download
+ settings:
+ source: https://www.nuget.org/api/v2/package/Jellyfin.Common
+ destination: ci/Jellyfin.Common.nupkg
+
+- name: download-last-nuget-release-model
+ image: plugins/download
+ settings:
+ source: https://www.nuget.org/api/v2/package/Jellyfin.Model
+ destination: ci/Jellyfin.Model.nupkg
+
+- name: download-last-nuget-release-controller
+ image: plugins/download
+ settings:
+ source: https://www.nuget.org/api/v2/package/Jellyfin.Controller
+ destination: ci/Jellyfin.Controller.nupkg
+
+- name: download-last-nuget-release-naming
+ image: plugins/download
+ settings:
+ source: https://www.nuget.org/api/v2/package/Jellyfin.Naming
+ destination: ci/Jellyfin.Naming.nupkg
+
+- name: extract-downloaded-nuget-packages
+ image: garthk/unzip
+ commands:
+ - unzip -j ci/Jellyfin.Common.nupkg "*.dll" -d ci/nuget-packages
+ - unzip -j ci/Jellyfin.Model.nupkg "*.dll" -d ci/nuget-packages
+ - unzip -j ci/Jellyfin.Controller.nupkg "*.dll" -d ci/nuget-packages
+ - unzip -j ci/Jellyfin.Naming.nupkg "*.dll" -d ci/nuget-packages
+
+- name: run-dotnet-compat-common
+ image: microsoft/dotnet:2-runtime
+ err_ignore: true
+ commands:
+ - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Common.dll ci/ci-release/MediaBrowser.Common.dll
+
+- name: run-dotnet-compat-model
+ image: microsoft/dotnet:2-runtime
+ err_ignore: true
+ commands:
+ - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Model.dll ci/ci-release/MediaBrowser.Model.dll
+
+- name: run-dotnet-compat-controller
+ image: microsoft/dotnet:2-runtime
+ err_ignore: true
+ commands:
+ - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Controller.dll ci/ci-release/MediaBrowser.Controller.dll
+
+- name: run-dotnet-compat-naming
+ image: microsoft/dotnet:2-runtime
+ err_ignore: true
+ commands:
+ - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/Emby.Naming.dll ci/ci-release/Emby.Naming.dll
diff --git a/.editorconfig b/.editorconfig
index b2891188d..dc9aaa3ed 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -15,6 +15,10 @@ insert_final_newline = true
end_of_line = lf
max_line_length = null
+# YAML indentation
+[*.{yml,yaml}]
+indent_size = 2
+
# XML indentation
[*.{csproj,xml}]
indent_size = 2
@@ -55,15 +59,77 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent
###############################
# Naming Conventions #
###############################
-# Style Definitions
-dotnet_naming_style.pascal_case_style.capitalization = pascal_case
-# Use PascalCase for constant fields
-dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
-dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
-dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
-dotnet_naming_symbols.constant_fields.applicable_kinds = field
-dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
-dotnet_naming_symbols.constant_fields.required_modifiers = const
+# Style Definitions (From Roslyn)
+
+# Non-private static fields are PascalCase
+dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
+dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
+
+dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
+dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
+
+dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
+
+# Constants are PascalCase
+dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
+dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
+
+dotnet_naming_symbols.constants.applicable_kinds = field, local
+dotnet_naming_symbols.constants.required_modifiers = const
+
+dotnet_naming_style.constant_style.capitalization = pascal_case
+
+# Static fields are camelCase and start with s_
+dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
+dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
+dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
+
+dotnet_naming_symbols.static_fields.applicable_kinds = field
+dotnet_naming_symbols.static_fields.required_modifiers = static
+
+dotnet_naming_style.static_field_style.capitalization = camel_case
+dotnet_naming_style.static_field_style.required_prefix = _
+
+# Instance fields are camelCase and start with _
+dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
+dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
+dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
+
+dotnet_naming_symbols.instance_fields.applicable_kinds = field
+
+dotnet_naming_style.instance_field_style.capitalization = camel_case
+dotnet_naming_style.instance_field_style.required_prefix = _
+
+# Locals and parameters are camelCase
+dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
+dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
+dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
+
+dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
+
+dotnet_naming_style.camel_case_style.capitalization = camel_case
+
+# Local functions are PascalCase
+dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
+dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
+
+dotnet_naming_symbols.local_functions.applicable_kinds = local_function
+
+dotnet_naming_style.local_function_style.capitalization = pascal_case
+
+# By default, name items with PascalCase
+dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
+dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
+
+dotnet_naming_symbols.all_members.applicable_kinds = *
+
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+
###############################
# C# Coding Conventions #
###############################
diff --git a/.gitignore b/.gitignore
index aef666272..65e47747e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -264,3 +264,5 @@ deployment/**/pkg-dist-tmp/
deployment/collect-dist/
jellyfin_version.ini
+
+ci/
diff --git a/BDInfo/BDROM.cs b/BDInfo/BDROM.cs
index 4360ff1d4..6759ed55a 100644
--- a/BDInfo/BDROM.cs
+++ b/BDInfo/BDROM.cs
@@ -165,7 +165,7 @@ namespace BDInfo
foreach (var file in files)
{
PlaylistFiles.Add(
- file.Name.ToUpper(), new TSPlaylistFile(this, file, _fileSystem));
+ file.Name.ToUpper(), new TSPlaylistFile(this, file));
}
}
@@ -185,7 +185,7 @@ namespace BDInfo
foreach (var file in files)
{
StreamClipFiles.Add(
- file.Name.ToUpper(), new TSStreamClipFile(file, _fileSystem));
+ file.Name.ToUpper(), new TSStreamClipFile(file));
}
}
diff --git a/BDInfo/TSPlaylistFile.cs b/BDInfo/TSPlaylistFile.cs
index 6e91f6e40..1cc629b1d 100644
--- a/BDInfo/TSPlaylistFile.cs
+++ b/BDInfo/TSPlaylistFile.cs
@@ -28,7 +28,6 @@ namespace BDInfo
{
public class TSPlaylistFile
{
- private readonly IFileSystem _fileSystem;
private FileSystemMetadata FileInfo = null;
public string FileType = null;
public bool IsInitialized = false;
@@ -64,21 +63,19 @@ namespace BDInfo
new List<TSGraphicsStream>();
public TSPlaylistFile(BDROM bdrom,
- FileSystemMetadata fileInfo, IFileSystem fileSystem)
+ FileSystemMetadata fileInfo)
{
BDROM = bdrom;
FileInfo = fileInfo;
- _fileSystem = fileSystem;
Name = fileInfo.Name.ToUpper();
}
public TSPlaylistFile(BDROM bdrom,
string name,
- List<TSStreamClip> clips, IFileSystem fileSystem)
+ List<TSStreamClip> clips)
{
BDROM = bdrom;
Name = name;
- _fileSystem = fileSystem;
IsCustom = true;
foreach (var clip in clips)
{
diff --git a/BDInfo/TSStreamClipFile.cs b/BDInfo/TSStreamClipFile.cs
index d840542ba..e1097b23d 100644
--- a/BDInfo/TSStreamClipFile.cs
+++ b/BDInfo/TSStreamClipFile.cs
@@ -28,7 +28,6 @@ namespace BDInfo
{
public class TSStreamClipFile
{
- private readonly IFileSystem _fileSystem;
public FileSystemMetadata FileInfo = null;
public string FileType = null;
public bool IsValid = false;
@@ -37,10 +36,9 @@ namespace BDInfo
public Dictionary<ushort, TSStream> Streams =
new Dictionary<ushort, TSStream>();
- public TSStreamClipFile(FileSystemMetadata fileInfo, IFileSystem fileSystem)
+ public TSStreamClipFile(FileSystemMetadata fileInfo)
{
FileInfo = fileInfo;
- _fileSystem = fileSystem;
Name = fileInfo.Name.ToUpper();
}
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 28690f36f..4b397b328 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -19,6 +19,10 @@
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
- [RazeLighter777](https://github.com/RazeLighter777)
- [WillWill56](https://github.com/WillWill56)
+ - [Liggy](https://github.com/Liggy)
+ - [fruhnow](https://github.com/fruhnow)
+ - [Lynxy](https://github.com/Lynxy)
+ - [fasheng](https://github.com/fasheng)
# Emby Contributors
diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs
index 0ebb490a1..c7cb364a8 100644
--- a/Emby.Dlna/Configuration/DlnaOptions.cs
+++ b/Emby.Dlna/Configuration/DlnaOptions.cs
@@ -7,6 +7,7 @@ namespace Emby.Dlna.Configuration
public bool EnableServer { get; set; }
public bool EnableDebugLog { get; set; }
public bool BlastAliveMessages { get; set; }
+ public bool SendOnlyMatchedHost { get; set; }
public int ClientDiscoveryIntervalSeconds { get; set; }
public int BlastAliveMessageIntervalSeconds { get; set; }
public string DefaultUserId { get; set; }
@@ -16,6 +17,7 @@ namespace Emby.Dlna.Configuration
EnablePlayTo = true;
EnableServer = true;
BlastAliveMessages = true;
+ SendOnlyMatchedHost = true;
ClientDiscoveryIntervalSeconds = 60;
BlastAliveMessageIntervalSeconds = 1800;
}
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index c507b14e9..f53d27451 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -38,7 +38,9 @@ namespace Emby.Dlna
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILoggerFactory loggerFactory,
- IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo)
+ IJsonSerializer jsonSerializer,
+ IServerApplicationHost appHost,
+ IAssemblyInfo assemblyInfo)
{
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index a20006578..5a7c9b617 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -169,9 +169,10 @@ namespace Emby.Dlna.Main
{
if (_communicationsServer == null)
{
- var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
+ var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows ||
+ _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Linux;
- _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
+ _communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
{
IsShared = true
};
@@ -229,7 +230,7 @@ namespace Emby.Dlna.Main
try
{
- _Publisher = new SsdpDevicePublisher(_communicationsServer, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion);
+ _Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion, _config.GetDlnaConfiguration().SendOnlyMatchedHost);
_Publisher.LogFunction = LogMessage;
_Publisher.SupportPnpRootDevice = false;
@@ -251,11 +252,11 @@ namespace Emby.Dlna.Main
foreach (var address in addresses)
{
- // TODO: Remove this condition on platforms that support it
- //if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
- //{
- // continue;
- //}
+ if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
+ {
+ // Not support IPv6 right now
+ continue;
+ }
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
@@ -268,6 +269,8 @@ namespace Emby.Dlna.Main
{
CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
Location = uri, // Must point to the URL that serves your devices UPnP description document.
+ Address = address,
+ SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
FriendlyName = "Jellyfin",
Manufacturer = "Jellyfin",
ModelName = "Jellyfin Server",
diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs
index 6257892b1..ae8175f4a 100644
--- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs
+++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs
@@ -36,7 +36,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar
};
}
- public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory)
+ public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
+ : base(config, logger, xmlReaderSettingsFactory)
{
}
}
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 037cdd8aa..b62c5e1d4 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -4,6 +4,7 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using System.Xml;
using System.Xml.Linq;
using Emby.Dlna.Common;
using Emby.Dlna.Server;
@@ -733,26 +734,21 @@ namespace Emby.Dlna.PlayTo
return (true, null);
}
- XElement uPnpResponse;
+ XElement uPnpResponse = null;
- // Handle different variations sent back by devices
try
{
- uPnpResponse = XElement.Parse(trackString);
+ uPnpResponse = ParseResponse(trackString);
}
- catch (Exception)
+ catch (Exception ex)
{
- // first try to add a root node with a dlna namesapce
- try
- {
- uPnpResponse = XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + trackString + "</data>");
- uPnpResponse = uPnpResponse.Descendants().First();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Unable to parse xml {0}", trackString);
- return (true, null);
- }
+ _logger.LogError(ex, "Uncaught exception while parsing xml");
+ }
+
+ if (uPnpResponse == null)
+ {
+ _logger.LogError("Failed to parse xml: \n {Xml}", trackString);
+ return (true, null);
}
var e = uPnpResponse.Element(uPnpNamespaces.items);
@@ -762,6 +758,43 @@ namespace Emby.Dlna.PlayTo
return (true, uTrack);
}
+ private XElement ParseResponse(string xml)
+ {
+ // Handle different variations sent back by devices
+ try
+ {
+ return XElement.Parse(xml);
+ }
+ catch (XmlException)
+ {
+
+ }
+
+ // first try to add a root node with a dlna namesapce
+ try
+ {
+ return XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + xml + "</data>")
+ .Descendants()
+ .First();
+ }
+ catch (XmlException)
+ {
+
+ }
+
+ // some devices send back invalid xml
+ try
+ {
+ return XElement.Parse(xml.Replace("&", "&amp;"));
+ }
+ catch (XmlException)
+ {
+
+ }
+
+ return null;
+ }
+
private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
{
if (container == null)
diff --git a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs
index a6fc53953..943caa3e6 100644
--- a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs
+++ b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs
@@ -19,7 +19,6 @@ namespace IsoMounter
private readonly IEnvironmentInfo EnvironmentInfo;
private readonly bool ExecutablesAvailable;
- private readonly IFileSystem FileSystem;
private readonly ILogger _logger;
private readonly string MountCommand;
private readonly string MountPointRoot;
@@ -31,11 +30,10 @@ namespace IsoMounter
#region Constructor(s)
- public LinuxIsoManager(ILogger logger, IFileSystem fileSystem, IEnvironmentInfo environment, IProcessFactory processFactory)
+ public LinuxIsoManager(ILogger logger, IEnvironmentInfo environment, IProcessFactory processFactory)
{
EnvironmentInfo = environment;
- FileSystem = fileSystem;
_logger = logger;
ProcessFactory = processFactory;
diff --git a/Emby.Notifications/CoreNotificationTypes.cs b/Emby.Notifications/CoreNotificationTypes.cs
index 8cc14fa01..0f9fc08d9 100644
--- a/Emby.Notifications/CoreNotificationTypes.cs
+++ b/Emby.Notifications/CoreNotificationTypes.cs
@@ -11,101 +11,81 @@ namespace Emby.Notifications
public class CoreNotificationTypes : INotificationTypeFactory
{
private readonly ILocalizationManager _localization;
- private readonly IServerApplicationHost _appHost;
- public CoreNotificationTypes(ILocalizationManager localization, IServerApplicationHost appHost)
+ public CoreNotificationTypes(ILocalizationManager localization)
{
_localization = localization;
- _appHost = appHost;
}
public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
{
- var knownTypes = new List<NotificationTypeInfo>
+ var knownTypes = new NotificationTypeInfo[]
{
new NotificationTypeInfo
{
Type = NotificationType.ApplicationUpdateInstalled.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.InstallationFailed.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.PluginInstalled.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.PluginError.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.PluginUninstalled.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.PluginUpdateInstalled.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.ServerRestartRequired.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.TaskFailed.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.NewLibraryContent.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.AudioPlayback.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.VideoPlayback.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.AudioPlaybackStopped.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.VideoPlaybackStopped.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.CameraImageUploaded.ToString()
},
-
new NotificationTypeInfo
{
Type = NotificationType.UserLockedOut.ToString()
- }
- };
-
- if (!_appHost.CanSelfUpdate)
- {
- knownTypes.Add(new NotificationTypeInfo
+ },
+ new NotificationTypeInfo
{
Type = NotificationType.ApplicationUpdateAvailable.ToString()
- });
- }
+ }
+ };
foreach (var type in knownTypes)
{
diff --git a/Emby.Notifications/Notifications.cs b/Emby.Notifications/Notifications.cs
index d3290479f..ec08fd193 100644
--- a/Emby.Notifications/Notifications.cs
+++ b/Emby.Notifications/Notifications.cs
@@ -5,21 +5,17 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Updates;
using MediaBrowser.Controller;
-using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Notifications;
-using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
namespace Emby.Notifications
@@ -29,43 +25,40 @@ namespace Emby.Notifications
/// </summary>
public class Notifications : IServerEntryPoint
{
- private readonly IInstallationManager _installationManager;
- private readonly IUserManager _userManager;
private readonly ILogger _logger;
- private readonly ITaskManager _taskManager;
private readonly INotificationManager _notificationManager;
private readonly ILibraryManager _libraryManager;
- private readonly ISessionManager _sessionManager;
private readonly IServerApplicationHost _appHost;
private Timer LibraryUpdateTimer { get; set; }
private readonly object _libraryChangedSyncLock = new object();
private readonly IConfigurationManager _config;
- private readonly IDeviceManager _deviceManager;
private readonly ILocalizationManager _localization;
private readonly IActivityManager _activityManager;
private string[] _coreNotificationTypes;
- public Notifications(IInstallationManager installationManager, IActivityManager activityManager, ILocalizationManager localization, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager)
+ public Notifications(
+ IActivityManager activityManager,
+ ILocalizationManager localization,
+ ILogger logger,
+ INotificationManager notificationManager,
+ ILibraryManager libraryManager,
+ IServerApplicationHost appHost,
+ IConfigurationManager config)
{
- _installationManager = installationManager;
- _userManager = userManager;
_logger = logger;
- _taskManager = taskManager;
_notificationManager = notificationManager;
_libraryManager = libraryManager;
- _sessionManager = sessionManager;
_appHost = appHost;
_config = config;
- _deviceManager = deviceManager;
_localization = localization;
_activityManager = activityManager;
- _coreNotificationTypes = new CoreNotificationTypes(localization, appHost).GetNotificationTypes().Select(i => i.Type).ToArray();
+ _coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray();
}
public Task RunAsync()
@@ -124,10 +117,9 @@ namespace Emby.Notifications
return _config.GetConfiguration<NotificationOptions>("notifications");
}
- async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
+ private async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
{
- // This notification is for users who can't auto-update (aka running as service)
- if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate)
+ if (!_appHost.HasUpdateAvailable)
{
return;
}
@@ -145,7 +137,7 @@ namespace Emby.Notifications
}
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
- void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
+ private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
{
if (!FilterItem(e.Item))
{
diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs
index f3457d105..a4179e660 100644
--- a/Emby.Photos/PhotoProvider.cs
+++ b/Emby.Photos/PhotoProvider.cs
@@ -9,7 +9,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
using TagLib;
using TagLib.IFD;
@@ -21,13 +20,11 @@ namespace Emby.Photos
public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
{
private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
private IImageProcessor _imageProcessor;
- public PhotoProvider(ILogger logger, IFileSystem fileSystem, IImageProcessor imageProcessor)
+ public PhotoProvider(ILogger logger, IImageProcessor imageProcessor)
{
_logger = logger;
- _fileSystem = fileSystem;
_imageProcessor = imageProcessor;
}
diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
index 701c04f9e..65cdccfa5 100644
--- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
+++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
@@ -1,3 +1,4 @@
+using System;
using System.IO;
using MediaBrowser.Common.Configuration;
@@ -14,50 +15,44 @@ namespace Emby.Server.Implementations.AppBase
/// </summary>
protected BaseApplicationPaths(
string programDataPath,
- string appFolderPath,
- string logDirectoryPath = null,
- string configurationDirectoryPath = null,
- string cacheDirectoryPath = null)
+ string logDirectoryPath,
+ string configurationDirectoryPath,
+ string cacheDirectoryPath)
{
ProgramDataPath = programDataPath;
- ProgramSystemPath = appFolderPath;
LogDirectoryPath = logDirectoryPath;
ConfigurationDirectoryPath = configurationDirectoryPath;
CachePath = cacheDirectoryPath;
+
+ DataPath = Path.Combine(ProgramDataPath, "data");
}
+ /// <summary>
+ /// Gets the path to the program data folder
+ /// </summary>
+ /// <value>The program data path.</value>
public string ProgramDataPath { get; private set; }
/// <summary>
/// Gets the path to the system folder
/// </summary>
- public string ProgramSystemPath { get; private set; }
+ public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
/// <summary>
- /// The _data directory
- /// </summary>
- private string _dataDirectory;
- /// <summary>
/// Gets the folder path to the data directory
/// </summary>
/// <value>The data directory.</value>
+ private string _dataPath;
public string DataPath
{
- get
- {
- if (_dataDirectory == null)
- {
- _dataDirectory = Path.Combine(ProgramDataPath, "data");
-
- Directory.CreateDirectory(_dataDirectory);
- }
-
- return _dataDirectory;
- }
+ get => _dataPath;
+ private set => _dataPath = Directory.CreateDirectory(value).FullName;
}
- private const string _virtualDataPath = "%AppDataPath%";
- public string VirtualDataPath => _virtualDataPath;
+ /// <summary>
+ /// Gets the magic strings used for virtual path manipulation.
+ /// </summary>
+ public string VirtualDataPath { get; } = "%AppDataPath%";
/// <summary>
/// Gets the image cache path.
@@ -78,60 +73,16 @@ namespace Emby.Server.Implementations.AppBase
public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations");
/// <summary>
- /// Gets the path to where temporary update files will be stored
- /// </summary>
- /// <value>The plugin configurations path.</value>
- public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates");
-
- /// <summary>
- /// The _log directory
- /// </summary>
- private string _logDirectoryPath;
-
- /// <summary>
/// Gets the path to the log directory
/// </summary>
/// <value>The log directory path.</value>
- public string LogDirectoryPath
- {
- get
- {
- if (string.IsNullOrEmpty(_logDirectoryPath))
- {
- _logDirectoryPath = Path.Combine(ProgramDataPath, "logs");
-
- Directory.CreateDirectory(_logDirectoryPath);
- }
-
- return _logDirectoryPath;
- }
- set => _logDirectoryPath = value;
- }
-
- /// <summary>
- /// The _config directory
- /// </summary>
- private string _configurationDirectoryPath;
+ public string LogDirectoryPath { get; private set; }
/// <summary>
/// Gets the path to the application configuration root directory
/// </summary>
/// <value>The configuration directory path.</value>
- public string ConfigurationDirectoryPath
- {
- get
- {
- if (string.IsNullOrEmpty(_configurationDirectoryPath))
- {
- _configurationDirectoryPath = Path.Combine(ProgramDataPath, "config");
-
- Directory.CreateDirectory(_configurationDirectoryPath);
- }
-
- return _configurationDirectoryPath;
- }
- set => _configurationDirectoryPath = value;
- }
+ public string ConfigurationDirectoryPath { get; private set; }
/// <summary>
/// Gets the path to the system configuration file
@@ -140,28 +91,10 @@ namespace Emby.Server.Implementations.AppBase
public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml");
/// <summary>
- /// The _cache directory
- /// </summary>
- private string _cachePath;
- /// <summary>
/// Gets the folder path to the cache directory
/// </summary>
/// <value>The cache directory.</value>
- public string CachePath
- {
- get
- {
- if (string.IsNullOrEmpty(_cachePath))
- {
- _cachePath = Path.Combine(ProgramDataPath, "cache");
-
- Directory.CreateDirectory(_cachePath);
- }
-
- return _cachePath;
- }
- set => _cachePath = value;
- }
+ public string CachePath { get; set; }
/// <summary>
/// Gets the folder path to the temp directory within the cache folder
diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
index 5feac1adf..af60a8dce 100644
--- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
+++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
@@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.AppBase
get
{
// Lazy load
- LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer, FileSystem));
+ LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
return _configuration;
}
protected set
diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
index 3faad76e7..90b97061f 100644
--- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
+++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
@@ -1,7 +1,6 @@
using System;
using System.IO;
using System.Linq;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase
@@ -18,9 +17,8 @@ namespace Emby.Server.Implementations.AppBase
/// <param name="type">The type.</param>
/// <param name="path">The path.</param>
/// <param name="xmlSerializer">The XML serializer.</param>
- /// <param name="fileSystem">The file system</param>
/// <returns>System.Object.</returns>
- public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
+ public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer)
{
object configuration;
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 353824406..b5a64cbdd 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -102,11 +102,13 @@ using MediaBrowser.Model.Xml;
using MediaBrowser.Providers.Chapters;
using MediaBrowser.Providers.Manager;
using MediaBrowser.Providers.Subtitles;
+using MediaBrowser.Providers.TV.TheTVDB;
using MediaBrowser.WebDashboard.Api;
using MediaBrowser.XbmcMetadata.Providers;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.DependencyInjection;
using ServiceStack;
-using ServiceStack.Text.Jsv;
using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
namespace Emby.Server.Implementations
@@ -122,12 +124,6 @@ namespace Emby.Server.Implementations
/// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
public abstract bool CanSelfRestart { get; }
- /// <summary>
- /// Gets or sets a value indicating whether this instance can self update.
- /// </summary>
- /// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
- public virtual bool CanSelfUpdate => false;
-
public virtual bool CanLaunchWebBrowser
{
get
@@ -202,7 +198,7 @@ namespace Emby.Server.Implementations
/// Gets all concrete types.
/// </summary>
/// <value>All concrete types.</value>
- public Tuple<Type, string>[] AllConcreteTypes { get; protected set; }
+ public Type[] AllConcreteTypes { get; protected set; }
/// <summary>
/// The disposable parts
@@ -219,8 +215,6 @@ namespace Emby.Server.Implementations
protected IEnvironmentInfo EnvironmentInfo { get; set; }
- private IBlurayExaminer BlurayExaminer { get; set; }
-
public PackageVersionClass SystemUpdateLevel
{
get
@@ -232,12 +226,7 @@ namespace Emby.Server.Implementations
}
}
- public virtual string OperatingSystemDisplayName => EnvironmentInfo.OperatingSystemName;
-
- /// <summary>
- /// The container
- /// </summary>
- protected readonly SimpleInjector.Container Container = new SimpleInjector.Container();
+ protected IServiceProvider _serviceProvider;
/// <summary>
/// Gets the server configuration manager.
@@ -309,7 +298,6 @@ namespace Emby.Server.Implementations
/// <value>The user data repository.</value>
private IUserDataManager UserDataManager { get; set; }
private IUserRepository UserRepository { get; set; }
- internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
internal SqliteItemRepository ItemRepository { get; set; }
private INotificationManager NotificationManager { get; set; }
@@ -325,6 +313,8 @@ namespace Emby.Server.Implementations
private IMediaSourceManager MediaSourceManager { get; set; }
private IPlaylistManager PlaylistManager { get; set; }
+ private readonly IConfiguration _configuration;
+
/// <summary>
/// Gets or sets the installation manager.
/// </summary>
@@ -363,8 +353,10 @@ namespace Emby.Server.Implementations
IFileSystem fileSystem,
IEnvironmentInfo environmentInfo,
IImageEncoder imageEncoder,
- INetworkManager networkManager)
+ INetworkManager networkManager,
+ IConfiguration configuration)
{
+ _configuration = configuration;
// hack alert, until common can target .net core
BaseExtensions.CryptographyProvider = CryptographyProvider;
@@ -440,7 +432,7 @@ namespace Emby.Server.Implementations
{
if (_deviceId == null)
{
- _deviceId = new DeviceId(ApplicationPaths, LoggerFactory, FileSystemManager);
+ _deviceId = new DeviceId(ApplicationPaths, LoggerFactory);
}
return _deviceId.Value;
@@ -453,138 +445,58 @@ namespace Emby.Server.Implementations
/// <value>The name.</value>
public string Name => ApplicationProductName;
- private static Tuple<Assembly, string> GetAssembly(Type type)
- {
- var assembly = type.GetTypeInfo().Assembly;
-
- return new Tuple<Assembly, string>(assembly, null);
- }
-
- public virtual IStreamHelper CreateStreamHelper()
- {
- return new StreamHelper();
- }
-
/// <summary>
- /// Creates an instance of type and resolves all constructor dependancies
+ /// Creates an instance of type and resolves all constructor dependencies
/// </summary>
/// <param name="type">The type.</param>
/// <returns>System.Object.</returns>
public object CreateInstance(Type type)
- {
- return Container.GetInstance(type);
- }
+ => ActivatorUtilities.CreateInstance(_serviceProvider, type);
+
+ /// <summary>
+ /// Creates an instance of type and resolves all constructor dependencies
+ /// </summary>
+ /// <param name="type">The type.</param>
+ /// <returns>System.Object.</returns>
+ public T CreateInstance<T>()
+ => ActivatorUtilities.CreateInstance<T>(_serviceProvider);
/// <summary>
/// Creates the instance safe.
/// </summary>
/// <param name="typeInfo">The type information.</param>
/// <returns>System.Object.</returns>
- protected object CreateInstanceSafe(Tuple<Type, string> typeInfo)
+ protected object CreateInstanceSafe(Type type)
{
- var type = typeInfo.Item1;
-
try
{
- return Container.GetInstance(type);
+ Logger.LogDebug("Creating instance of {Type}", type);
+ return ActivatorUtilities.CreateInstance(_serviceProvider, type);
}
catch (Exception ex)
{
- Logger.LogError(ex, "Error creating {type}", type.FullName);
- // Don't blow up in release mode
+ Logger.LogError(ex, "Error creating {Type}", type);
return null;
}
}
/// <summary>
- /// Registers the specified obj.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="obj">The obj.</param>
- /// <param name="manageLifetime">if set to <c>true</c> [manage lifetime].</param>
- protected void RegisterSingleInstance<T>(T obj, bool manageLifetime = true)
- where T : class
- {
- Container.RegisterInstance<T>(obj);
-
- if (manageLifetime)
- {
- var disposable = obj as IDisposable;
-
- if (disposable != null)
- {
- DisposableParts.Add(disposable);
- }
- }
- }
-
- /// <summary>
- /// Registers the single instance.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="func">The func.</param>
- protected void RegisterSingleInstance<T>(Func<T> func)
- where T : class
- {
- Container.RegisterSingleton(func);
- }
-
- /// <summary>
/// Resolves this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>``0.</returns>
- public T Resolve<T>()
- {
- return (T)Container.GetRegistration(typeof(T), true).GetInstance();
- }
-
- /// <summary>
- /// Resolves this instance.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <returns>``0.</returns>
- public T TryResolve<T>()
- {
- var result = Container.GetRegistration(typeof(T), false);
-
- if (result == null)
- {
- return default(T);
- }
- return (T)result.GetInstance();
- }
-
- /// <summary>
- /// Loads the assembly.
- /// </summary>
- /// <param name="file">The file.</param>
- /// <returns>Assembly.</returns>
- protected Tuple<Assembly, string> LoadAssembly(string file)
- {
- try
- {
- var assembly = Assembly.LoadFrom(file);
-
- return new Tuple<Assembly, string>(assembly, file);
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error loading assembly {File}", file);
- return null;
- }
- }
+ public T Resolve<T>() => _serviceProvider.GetService<T>();
/// <summary>
/// Gets the export types.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>IEnumerable{Type}.</returns>
- public IEnumerable<Tuple<Type, string>> GetExportTypes<T>()
+ public IEnumerable<Type> GetExportTypes<T>()
{
var currentType = typeof(T);
- return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i.Item1));
+ return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i));
}
/// <summary>
@@ -596,9 +508,10 @@ namespace Emby.Server.Implementations
public IEnumerable<T> GetExports<T>(bool manageLifetime = true)
{
var parts = GetExportTypes<T>()
- .Select(CreateInstanceSafe)
+ .Select(x => CreateInstanceSafe(x))
.Where(i => i != null)
- .Cast<T>();
+ .Cast<T>()
+ .ToList(); // Convert to list so this isn't executed for each iteration
if (manageLifetime)
{
@@ -611,33 +524,6 @@ namespace Emby.Server.Implementations
return parts;
}
- public List<Tuple<T, string>> GetExportsWithInfo<T>(bool manageLifetime = true)
- {
- var parts = GetExportTypes<T>()
- .Select(i =>
- {
- var obj = CreateInstanceSafe(i);
-
- if (obj == null)
- {
- return null;
- }
- return new Tuple<T, string>((T)obj, i.Item2);
- })
- .Where(i => i != null)
- .ToList();
-
- if (manageLifetime)
- {
- lock (DisposableParts)
- {
- DisposableParts.AddRange(parts.Select(i => i.Item1).OfType<IDisposable>());
- }
- }
-
- return parts;
- }
-
/// <summary>
/// Runs the startup tasks.
/// </summary>
@@ -664,16 +550,18 @@ namespace Emby.Server.Implementations
var entryPoints = GetExports<IServerEntryPoint>();
- var now = DateTime.UtcNow;
+ var stopWatch = new Stopwatch();
+ stopWatch.Start();
await Task.WhenAll(StartEntryPoints(entryPoints, true));
- Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:fff} ms", DateTime.Now - now);
+ Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
Logger.LogInformation("Core startup complete");
HttpServer.GlobalResponse = null;
- now = DateTime.UtcNow;
+ stopWatch.Restart();
await Task.WhenAll(StartEntryPoints(entryPoints, false));
- Logger.LogInformation("Executed all post-startup entry points in {Elapsed:fff} ms", DateTime.Now - now);
+ Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
+ stopWatch.Stop();
}
private IEnumerable<Task> StartEntryPoints(IEnumerable<IServerEntryPoint> entryPoints, bool isBeforeStartup)
@@ -691,7 +579,7 @@ namespace Emby.Server.Implementations
}
}
- public async Task Init()
+ public async Task Init(IServiceCollection serviceCollection)
{
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
@@ -721,7 +609,7 @@ namespace Emby.Server.Implementations
SetHttpLimit();
- await RegisterResources();
+ await RegisterResources(serviceCollection);
FindParts();
}
@@ -736,104 +624,105 @@ namespace Emby.Server.Implementations
/// <summary>
/// Registers resources that classes will depend on
/// </summary>
- protected async Task RegisterResources()
+ protected async Task RegisterResources(IServiceCollection serviceCollection)
{
- RegisterSingleInstance(ConfigurationManager);
- RegisterSingleInstance<IApplicationHost>(this);
+ serviceCollection.AddMemoryCache();
- RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
+ serviceCollection.AddSingleton(ConfigurationManager);
+ serviceCollection.AddSingleton<IApplicationHost>(this);
- RegisterSingleInstance(JsonSerializer);
+ serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
- RegisterSingleInstance(LoggerFactory, false);
- RegisterSingleInstance(Logger);
+ serviceCollection.AddSingleton(JsonSerializer);
- RegisterSingleInstance(EnvironmentInfo);
+ serviceCollection.AddSingleton(LoggerFactory);
+ serviceCollection.AddLogging();
+ serviceCollection.AddSingleton(Logger);
- RegisterSingleInstance(FileSystemManager);
+ serviceCollection.AddSingleton(EnvironmentInfo);
+
+ serviceCollection.AddSingleton(FileSystemManager);
+ serviceCollection.AddSingleton<TvDbClientManager>();
HttpClient = CreateHttpClient();
- RegisterSingleInstance(HttpClient);
+ serviceCollection.AddSingleton(HttpClient);
- RegisterSingleInstance(NetworkManager);
+ serviceCollection.AddSingleton(NetworkManager);
IsoManager = new IsoManager();
- RegisterSingleInstance(IsoManager);
+ serviceCollection.AddSingleton(IsoManager);
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager);
- RegisterSingleInstance(TaskManager);
+ serviceCollection.AddSingleton(TaskManager);
- RegisterSingleInstance(XmlSerializer);
+ serviceCollection.AddSingleton(XmlSerializer);
ProcessFactory = new ProcessFactory();
- RegisterSingleInstance(ProcessFactory);
+ serviceCollection.AddSingleton(ProcessFactory);
- var streamHelper = CreateStreamHelper();
- ApplicationHost.StreamHelper = streamHelper;
- RegisterSingleInstance(streamHelper);
+ ApplicationHost.StreamHelper = new StreamHelper();
+ serviceCollection.AddSingleton(StreamHelper);
- RegisterSingleInstance(CryptographyProvider);
+ serviceCollection.AddSingleton(CryptographyProvider);
SocketFactory = new SocketFactory();
- RegisterSingleInstance(SocketFactory);
-
- ZipClient = new ZipClient(FileSystemManager);
- RegisterSingleInstance(ZipClient);
+ serviceCollection.AddSingleton(SocketFactory);
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, ZipClient, PackageRuntime);
- RegisterSingleInstance(InstallationManager);
+ serviceCollection.AddSingleton(InstallationManager);
+
+ ZipClient = new ZipClient();
+ serviceCollection.AddSingleton(ZipClient);
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor());
- RegisterSingleInstance(HttpResultFactory);
+ serviceCollection.AddSingleton(HttpResultFactory);
- RegisterSingleInstance<IServerApplicationHost>(this);
- RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths);
+ serviceCollection.AddSingleton<IServerApplicationHost>(this);
+ serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
- RegisterSingleInstance(ServerConfigurationManager);
+ serviceCollection.AddSingleton(ServerConfigurationManager);
- IAssemblyInfo assemblyInfo = new AssemblyInfo();
- RegisterSingleInstance(assemblyInfo);
+ var assemblyInfo = new AssemblyInfo();
+ serviceCollection.AddSingleton<IAssemblyInfo>(assemblyInfo);
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory);
await LocalizationManager.LoadAll();
- RegisterSingleInstance<ILocalizationManager>(LocalizationManager);
+ serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
- BlurayExaminer = new BdInfoExaminer(FileSystemManager);
- RegisterSingleInstance(BlurayExaminer);
+ serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager));
- RegisterSingleInstance<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
+ serviceCollection.AddSingleton<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager);
- RegisterSingleInstance(UserDataManager);
+ serviceCollection.AddSingleton(UserDataManager);
UserRepository = GetUserRepository();
// This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it
- RegisterSingleInstance(UserRepository);
+ serviceCollection.AddSingleton(UserRepository);
var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager);
- DisplayPreferencesRepository = displayPreferencesRepo;
- RegisterSingleInstance(DisplayPreferencesRepository);
+ serviceCollection.AddSingleton<IDisplayPreferencesRepository>(displayPreferencesRepo);
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo);
- RegisterSingleInstance<IItemRepository>(ItemRepository);
+ serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
AuthenticationRepository = GetAuthenticationRepository();
- RegisterSingleInstance(AuthenticationRepository);
+ serviceCollection.AddSingleton(AuthenticationRepository);
- UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager, CryptographyProvider);
- RegisterSingleInstance(UserManager);
+ UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
+ serviceCollection.AddSingleton(UserManager);
LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager);
- RegisterSingleInstance(LibraryManager);
+ serviceCollection.AddSingleton(LibraryManager);
// TODO wtaylor: investigate use of second music manager
var musicManager = new MusicManager(LibraryManager);
- RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager));
+ serviceCollection.AddSingleton<IMusicManager>(new MusicManager(LibraryManager));
- LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo);
- RegisterSingleInstance(LibraryMonitor);
+ LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo);
+ serviceCollection.AddSingleton(LibraryMonitor);
- RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LoggerFactory, LibraryManager, UserManager));
+ serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager));
CertificateInfo = GetCertificateInfo(true);
Certificate = GetCertificate(CertificateInfo);
@@ -841,88 +730,88 @@ namespace Emby.Server.Implementations
HttpServer = new HttpListenerHost(this,
LoggerFactory,
ServerConfigurationManager,
- "web/index.html",
+ _configuration,
NetworkManager,
JsonSerializer,
- XmlSerializer,
- GetParseFn);
+ XmlSerializer);
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
- RegisterSingleInstance(HttpServer);
+ serviceCollection.AddSingleton(HttpServer);
ImageProcessor = GetImageProcessor();
- RegisterSingleInstance(ImageProcessor);
+ serviceCollection.AddSingleton(ImageProcessor);
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
- RegisterSingleInstance(TVSeriesManager);
+ serviceCollection.AddSingleton(TVSeriesManager);
var encryptionManager = new EncryptionManager();
- RegisterSingleInstance<IEncryptionManager>(encryptionManager);
+ serviceCollection.AddSingleton<IEncryptionManager>(encryptionManager);
- DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager);
- RegisterSingleInstance(DeviceManager);
+ DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager);
+ serviceCollection.AddSingleton(DeviceManager);
MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder);
- RegisterSingleInstance(MediaSourceManager);
+ serviceCollection.AddSingleton(MediaSourceManager);
SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager);
- RegisterSingleInstance(SubtitleManager);
+ serviceCollection.AddSingleton(SubtitleManager);
ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer);
- RegisterSingleInstance(ProviderManager);
+ serviceCollection.AddSingleton(ProviderManager);
- DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager);
- RegisterSingleInstance(DtoService);
+ DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager);
+ serviceCollection.AddSingleton(DtoService);
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
- RegisterSingleInstance(ChannelManager);
+ serviceCollection.AddSingleton(ChannelManager);
SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
- RegisterSingleInstance(SessionManager);
+ serviceCollection.AddSingleton(SessionManager);
- var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo);
- RegisterSingleInstance<IDlnaManager>(dlnaManager);
+ serviceCollection.AddSingleton<IDlnaManager>(
+ new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo));
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
- RegisterSingleInstance(CollectionManager);
+ serviceCollection.AddSingleton(CollectionManager);
PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager);
- RegisterSingleInstance(PlaylistManager);
+ serviceCollection.AddSingleton(PlaylistManager);
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
- RegisterSingleInstance(LiveTvManager);
+ serviceCollection.AddSingleton(LiveTvManager);
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
- RegisterSingleInstance(UserViewManager);
+ serviceCollection.AddSingleton(UserViewManager);
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
- RegisterSingleInstance(NotificationManager);
+ serviceCollection.AddSingleton(NotificationManager);
- RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
+ serviceCollection.AddSingleton<IDeviceDiscovery>(
+ new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
- RegisterSingleInstance(ChapterManager);
+ serviceCollection.AddSingleton(ChapterManager);
- RegisterMediaEncoder(assemblyInfo);
+ RegisterMediaEncoder(serviceCollection);
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
- RegisterSingleInstance(EncodingManager);
+ serviceCollection.AddSingleton(EncodingManager);
var activityLogRepo = GetActivityLogRepository();
- RegisterSingleInstance(activityLogRepo);
- RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
+ serviceCollection.AddSingleton(activityLogRepo);
+ serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
var authContext = new AuthorizationContext(AuthenticationRepository, UserManager);
- RegisterSingleInstance<IAuthorizationContext>(authContext);
- RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
+ serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
+ serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager);
- RegisterSingleInstance(AuthService);
+ serviceCollection.AddSingleton(AuthService);
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
- RegisterSingleInstance(SubtitleEncoder);
+ serviceCollection.AddSingleton(SubtitleEncoder);
- RegisterSingleInstance(CreateResourceFileManager());
+ serviceCollection.AddSingleton(CreateResourceFileManager());
displayPreferencesRepo.Initialize();
@@ -935,6 +824,8 @@ namespace Emby.Server.Implementations
((UserDataManager)UserDataManager).Repository = userDataRepo;
ItemRepository.Initialize(userDataRepo, UserManager);
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
+
+ _serviceProvider = serviceCollection.BuildServiceProvider();
}
protected virtual IBrotliCompressor CreateBrotliCompressor()
@@ -942,11 +833,6 @@ namespace Emby.Server.Implementations
return null;
}
- private static Func<string, object> GetParseFn(Type propertyType)
- {
- return s => JsvReader.GetParseFn(propertyType)(s);
- }
-
public virtual string PackageRuntime => "netcore";
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo)
@@ -1058,7 +944,7 @@ namespace Emby.Server.Implementations
protected virtual FFMpegInfo GetFFMpegInfo()
{
- return new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, GetFfmpegInstallInfo())
+ return new FFMpegLoader(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo())
.GetFFMpegInfo(StartupOptions);
}
@@ -1066,7 +952,7 @@ namespace Emby.Server.Implementations
/// Registers the media encoder.
/// </summary>
/// <returns>Task.</returns>
- private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo)
+ private void RegisterMediaEncoder(IServiceCollection serviceCollection)
{
string encoderPath = null;
string probePath = null;
@@ -1098,7 +984,7 @@ namespace Emby.Server.Implementations
5000);
MediaEncoder = mediaEncoder;
- RegisterSingleInstance(MediaEncoder);
+ serviceCollection.AddSingleton(MediaEncoder);
}
/// <summary>
@@ -1174,7 +1060,10 @@ namespace Emby.Server.Implementations
}
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
- Plugins = GetExportsWithInfo<IPlugin>().Select(LoadPlugin).Where(i => i != null).ToArray();
+ Plugins = GetExports<IPlugin>()
+ .Select(LoadPlugin)
+ .Where(i => i != null)
+ .ToArray();
HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>());
@@ -1208,19 +1097,15 @@ namespace Emby.Server.Implementations
IsoManager.AddParts(GetExports<IIsoMounter>());
}
- private IPlugin LoadPlugin(Tuple<IPlugin, string> info)
+ private IPlugin LoadPlugin(IPlugin plugin)
{
- var plugin = info.Item1;
- var assemblyFilePath = info.Item2;
-
try
{
- var assemblyPlugin = plugin as IPluginAssembly;
-
- if (assemblyPlugin != null)
+ if (plugin is IPluginAssembly assemblyPlugin)
{
var assembly = plugin.GetType().Assembly;
var assemblyName = assembly.GetName();
+ var assemblyFilePath = assembly.Location;
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
@@ -1264,78 +1149,15 @@ namespace Emby.Server.Implementations
{
Logger.LogInformation("Loading assemblies");
- var assemblyInfos = GetComposablePartAssemblies();
-
- foreach (var assemblyInfo in assemblyInfos)
- {
- var assembly = assemblyInfo.Item1;
- var path = assemblyInfo.Item2;
-
- if (path == null)
- {
- Logger.LogInformation("Loading {assemblyName}", assembly.FullName);
- }
- else
+ AllConcreteTypes = GetComposablePartAssemblies()
+ .SelectMany(x => x.ExportedTypes)
+ .Where(type =>
{
- Logger.LogInformation("Loading {assemblyName} from {path}", assembly.FullName, path);
- }
- }
-
- AllConcreteTypes = assemblyInfos
- .SelectMany(GetTypes)
- .Where(info =>
- {
- var t = info.Item1;
- return t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType;
+ return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType;
})
.ToArray();
}
- /// <summary>
- /// Gets a list of types within an assembly
- /// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference
- /// </summary>
- protected List<Tuple<Type, string>> GetTypes(Tuple<Assembly, string> assemblyInfo)
- {
- if (assemblyInfo == null)
- {
- return new List<Tuple<Type, string>>();
- }
-
- var assembly = assemblyInfo.Item1;
-
- try
- {
- // This null checking really shouldn't be needed but adding it due to some
- // unhandled exceptions in mono 5.0 that are a little hard to hunt down
- var types = assembly.GetTypes() ?? new Type[] { };
- return types.Where(t => t != null).Select(i => new Tuple<Type, string>(i, assemblyInfo.Item2)).ToList();
- }
- catch (ReflectionTypeLoadException ex)
- {
- if (ex.LoaderExceptions != null)
- {
- foreach (var loaderException in ex.LoaderExceptions)
- {
- if (loaderException != null)
- {
- Logger.LogError("LoaderException: " + loaderException.Message);
- }
- }
- }
-
- // If it fails we can still get a list of the Types it was able to resolve
- var types = ex.Types ?? new Type[] { };
- return types.Where(t => t != null).Select(i => new Tuple<Type, string>(i, assemblyInfo.Item2)).ToList();
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error loading types from assembly");
-
- return new List<Tuple<Type, string>>();
- }
- }
-
private CertificateInfo CertificateInfo { get; set; }
protected X509Certificate Certificate { get; private set; }
@@ -1546,150 +1368,63 @@ namespace Emby.Server.Implementations
/// Gets the composable part assemblies.
/// </summary>
/// <returns>IEnumerable{Assembly}.</returns>
- protected List<Tuple<Assembly, string>> GetComposablePartAssemblies()
+ protected IEnumerable<Assembly> GetComposablePartAssemblies()
{
- var list = GetPluginAssemblies(ApplicationPaths.PluginsPath);
-
- // Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that
- // This will prevent the .dll file from getting locked, and allow us to replace it when needed
+ if (Directory.Exists(ApplicationPaths.PluginsPath))
+ {
+ foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly))
+ {
+ Logger.LogInformation("Loading assembly {Path}", file);
+ yield return Assembly.LoadFrom(file);
+ }
+ }
// Include composable parts in the Api assembly
- list.Add(GetAssembly(typeof(ApiEntryPoint)));
+ yield return typeof(ApiEntryPoint).Assembly;
// Include composable parts in the Dashboard assembly
- list.Add(GetAssembly(typeof(DashboardService)));
+ yield return typeof(DashboardService).Assembly;
// Include composable parts in the Model assembly
- list.Add(GetAssembly(typeof(SystemInfo)));
+ yield return typeof(SystemInfo).Assembly;
// Include composable parts in the Common assembly
- list.Add(GetAssembly(typeof(IApplicationHost)));
+ yield return typeof(IApplicationHost).Assembly;
// Include composable parts in the Controller assembly
- list.Add(GetAssembly(typeof(IServerApplicationHost)));
+ yield return typeof(IServerApplicationHost).Assembly;
// Include composable parts in the Providers assembly
- list.Add(GetAssembly(typeof(ProviderUtils)));
+ yield return typeof(ProviderUtils).Assembly;
// Include composable parts in the Photos assembly
- list.Add(GetAssembly(typeof(PhotoProvider)));
+ yield return typeof(PhotoProvider).Assembly;
// Emby.Server implementations
- list.Add(GetAssembly(typeof(InstallationManager)));
+ yield return typeof(InstallationManager).Assembly;
// MediaEncoding
- list.Add(GetAssembly(typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder)));
+ yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
// Dlna
- list.Add(GetAssembly(typeof(DlnaEntryPoint)));
+ yield return typeof(DlnaEntryPoint).Assembly;
// Local metadata
- list.Add(GetAssembly(typeof(BoxSetXmlSaver)));
+ yield return typeof(BoxSetXmlSaver).Assembly;
// Notifications
- list.Add(GetAssembly(typeof(NotificationManager)));
+ yield return typeof(NotificationManager).Assembly;
// Xbmc
- list.Add(GetAssembly(typeof(ArtistNfoProvider)));
-
- list.AddRange(GetAssembliesWithPartsInternal().Select(i => new Tuple<Assembly, string>(i, null)));
-
- return list.ToList();
- }
-
- protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal();
+ yield return typeof(ArtistNfoProvider).Assembly;
- private List<Tuple<Assembly, string>> GetPluginAssemblies(string path)
- {
- try
- {
- return FilterAssembliesToLoad(Directory.EnumerateFiles(path, "*.dll", SearchOption.AllDirectories))
- .Select(LoadAssembly)
- .Where(a => a != null)
- .ToList();
- }
- catch (DirectoryNotFoundException)
+ foreach (var i in GetAssembliesWithPartsInternal())
{
- return new List<Tuple<Assembly, string>>();
+ yield return i;
}
}
- private IEnumerable<string> FilterAssembliesToLoad(IEnumerable<string> paths)
- {
-
- var exclude = new[]
- {
- "mbplus.dll",
- "mbintros.dll",
- "embytv.dll",
- "Messenger.dll",
- "Messages.dll",
- "MediaBrowser.Plugins.TvMazeProvider.dll",
- "MBBookshelf.dll",
- "MediaBrowser.Channels.Adult.YouJizz.dll",
- "MediaBrowser.Channels.Vine-co.dll",
- "MediaBrowser.Plugins.Vimeo.dll",
- "MediaBrowser.Channels.Vevo.dll",
- "MediaBrowser.Plugins.Twitch.dll",
- "MediaBrowser.Channels.SvtPlay.dll",
- "MediaBrowser.Plugins.SoundCloud.dll",
- "MediaBrowser.Plugins.SnesBox.dll",
- "MediaBrowser.Plugins.RottenTomatoes.dll",
- "MediaBrowser.Plugins.Revision3.dll",
- "MediaBrowser.Plugins.NesBox.dll",
- "MBChapters.dll",
- "MediaBrowser.Channels.LeagueOfLegends.dll",
- "MediaBrowser.Plugins.ADEProvider.dll",
- "MediaBrowser.Channels.BallStreams.dll",
- "MediaBrowser.Channels.Adult.Beeg.dll",
- "ChannelDownloader.dll",
- "Hamstercat.Emby.EmbyBands.dll",
- "EmbyTV.dll",
- "MediaBrowser.Channels.HitboxTV.dll",
- "MediaBrowser.Channels.HockeyStreams.dll",
- "MediaBrowser.Plugins.ITV.dll",
- "MediaBrowser.Plugins.Lastfm.dll",
- "ServerRestart.dll",
- "MediaBrowser.Plugins.NotifyMyAndroidNotifications.dll",
- "MetadataViewer.dll"
- };
-
- var minRequiredVersions = new Dictionary<string, Version>(StringComparer.OrdinalIgnoreCase)
- {
- { "moviethemesongs.dll", new Version(1, 6) },
- { "themesongs.dll", new Version(1, 2) }
- };
-
- return paths.Where(path =>
- {
- var filename = Path.GetFileName(path);
- if (exclude.Contains(filename ?? string.Empty, StringComparer.OrdinalIgnoreCase))
- {
- return false;
- }
-
- if (minRequiredVersions.TryGetValue(filename, out Version minRequiredVersion))
- {
- try
- {
- var version = Version.Parse(FileVersionInfo.GetVersionInfo(path).FileVersion);
-
- if (version < minRequiredVersion)
- {
- Logger.LogInformation("Not loading {filename} {version} because the minimum supported version is {minRequiredVersion}. Please update to the newer version", filename, version, minRequiredVersion);
- return false;
- }
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error getting version number from {path}", path);
-
- return false;
- }
- }
- return true;
- });
- }
+ protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal();
/// <summary>
/// Gets the system status.
@@ -1718,9 +1453,8 @@ namespace Emby.Server.Implementations
SupportsHttps = SupportsHttps,
HttpsPortNumber = HttpsPort,
OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(),
- OperatingSystemDisplayName = OperatingSystemDisplayName,
+ OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName,
CanSelfRestart = CanSelfRestart,
- CanSelfUpdate = CanSelfUpdate,
CanLaunchWebBrowser = CanLaunchWebBrowser,
WanAddress = wanAddress,
HasUpdateAvailable = HasUpdateAvailable,
@@ -1788,7 +1522,7 @@ namespace Emby.Server.Implementations
public async Task<string> GetWanApiUrl(CancellationToken cancellationToken)
{
- var url = "http://ipv4.icanhazip.com";
+ const string url = "http://ipv4.icanhazip.com";
try
{
using (var response = await HttpClient.Get(new HttpRequestOptions
@@ -1845,7 +1579,7 @@ namespace Emby.Server.Implementations
if (addresses.Count == 0)
{
- addresses.AddRange(NetworkManager.GetLocalIpAddresses());
+ addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
}
var resultList = new List<IpAddressInfo>();
@@ -2020,21 +1754,6 @@ namespace Emby.Server.Implementations
}
/// <summary>
- /// Updates the application.
- /// </summary>
- /// <param name="package">The package that contains the update</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <param name="progress">The progress.</param>
- public async Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress<double> progress)
- {
- await InstallationManager.InstallPackage(package, false, progress, cancellationToken).ConfigureAwait(false);
-
- HasUpdateAvailable = false;
-
- OnApplicationUpdated(package);
- }
-
- /// <summary>
/// This returns localhost in the case of no external dns, and the hostname if the
/// dns is prefixed with a valid Uri prefix.
/// </summary>
diff --git a/Emby.Server.Implementations/Archiving/ZipClient.cs b/Emby.Server.Implementations/Archiving/ZipClient.cs
index 1135cf694..6b0fd2dc6 100644
--- a/Emby.Server.Implementations/Archiving/ZipClient.cs
+++ b/Emby.Server.Implementations/Archiving/ZipClient.cs
@@ -14,11 +14,9 @@ namespace Emby.Server.Implementations.Archiving
/// </summary>
public class ZipClient : IZipClient
{
- private readonly IFileSystem _fileSystem;
-
- public ZipClient(IFileSystem fileSystem)
+ public ZipClient()
{
- _fileSystem = fileSystem;
+
}
/// <summary>
diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
index ad6c537ef..3c7cbb115 100644
--- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
+++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
@@ -35,64 +35,52 @@ namespace Emby.Server.Implementations.Channels
public static string GetUserDistinctValue(User user)
{
var channels = user.Policy.EnabledChannels
- .OrderBy(i => i)
- .ToList();
+ .OrderBy(i => i);
- return string.Join("|", channels.ToArray());
+ return string.Join("|", channels);
}
private void CleanDatabase(CancellationToken cancellationToken)
{
var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds();
- var databaseIds = _libraryManager.GetItemIds(new InternalItemsQuery
+ var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { typeof(Channel).Name }
+ IncludeItemTypes = new[] { typeof(Channel).Name },
+ ExcludeItemIds = installedChannelIds.ToArray()
});
- var invalidIds = databaseIds
- .Except(installedChannelIds)
- .ToList();
-
- foreach (var id in invalidIds)
+ foreach (var channel in uninstalledChannels)
{
cancellationToken.ThrowIfCancellationRequested();
- CleanChannel(id, cancellationToken);
+ CleanChannel((Channel)channel, cancellationToken);
}
}
- private void CleanChannel(Guid id, CancellationToken cancellationToken)
+ private void CleanChannel(Channel channel, CancellationToken cancellationToken)
{
- _logger.LogInformation("Cleaning channel {0} from database", id);
+ _logger.LogInformation("Cleaning channel {0} from database", channel.Id);
// Delete all channel items
- var allIds = _libraryManager.GetItemIds(new InternalItemsQuery
+ var items = _libraryManager.GetItemList(new InternalItemsQuery
{
- ChannelIds = new[] { id }
+ ChannelIds = new[] { channel.Id }
});
- foreach (var deleteId in allIds)
+ foreach (var item in items)
{
cancellationToken.ThrowIfCancellationRequested();
- DeleteItem(deleteId);
- }
-
- // Finally, delete the channel itself
- DeleteItem(id);
- }
+ _libraryManager.DeleteItem(item, new DeleteOptions
+ {
+ DeleteFileLocation = false
- private void DeleteItem(Guid id)
- {
- var item = _libraryManager.GetItemById(id);
-
- if (item == null)
- {
- return;
+ }, false);
}
- _libraryManager.DeleteItem(item, new DeleteOptions
+ // Finally, delete the channel itself
+ _libraryManager.DeleteItem(channel, new DeleteOptions
{
DeleteFileLocation = false
diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
index 844f77a1a..303a8ac7b 100644
--- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
+++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
@@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels
{
- class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
+ public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly IChannelManager _channelManager;
private readonly IUserManager _userManager;
diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
index 6aeadda2f..cdfb5cadf 100644
--- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
+++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
@@ -10,14 +10,18 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Collections
{
public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet>
{
- public CollectionImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
+ public CollectionImageProvider(
+ IFileSystem fileSystem,
+ IProviderManager providerManager,
+ IApplicationPaths applicationPaths,
+ IImageProcessor imageProcessor)
+ : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
}
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 812e48a1f..2b99e0ddf 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -342,14 +342,12 @@ namespace Emby.Server.Implementations.Collections
{
private readonly CollectionManager _collectionManager;
private readonly IServerConfigurationManager _config;
- private readonly IFileSystem _fileSystem;
private ILogger _logger;
- public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger)
+ public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, ILogger logger)
{
_collectionManager = (CollectionManager)collectionManager;
_config = config;
- _fileSystem = fileSystem;
_logger = logger;
}
diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs
new file mode 100644
index 000000000..30bfd8749
--- /dev/null
+++ b/Emby.Server.Implementations/ConfigurationOptions.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+
+namespace Emby.Server.Implementations
+{
+ public static class ConfigurationOptions
+ {
+ public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string>
+ {
+ {"HttpListenerHost:DefaultRedirectPath", "web/index.html"}
+ };
+ }
+}
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
index 0f432c36c..fba81306b 100644
--- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
+++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
@@ -224,7 +224,7 @@ namespace Emby.Server.Implementations.Data
});
}
- db.ExecuteAll(string.Join(";", queries.ToArray()));
+ db.ExecuteAll(string.Join(";", queries));
Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
}
@@ -232,23 +232,6 @@ namespace Emby.Server.Implementations.Data
protected virtual int? CacheSize => null;
- internal static void CheckOk(int rc)
- {
- string msg = "";
-
- if (raw.SQLITE_OK != rc)
- {
- throw CreateException((ErrorCode)rc, msg);
- }
- }
-
- internal static Exception CreateException(ErrorCode rc, string msg)
- {
- var exp = new Exception(msg);
-
- return exp;
- }
-
private bool _disposed;
protected void CheckDisposed()
{
@@ -375,13 +358,6 @@ namespace Emby.Server.Implementations.Data
}
}
- public class DummyToken : IDisposable
- {
- public void Dispose()
- {
- }
- }
-
public static IDisposable Read(this ReaderWriterLockSlim obj)
{
//if (BaseSqliteRepository.ThreadSafeMode > 0)
@@ -390,6 +366,7 @@ namespace Emby.Server.Implementations.Data
//}
return new WriteLockToken(obj);
}
+
public static IDisposable Write(this ReaderWriterLockSlim obj)
{
//if (BaseSqliteRepository.ThreadSafeMode > 0)
diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
index dcfe14943..f7743a3c2 100644
--- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
+++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
@@ -1,11 +1,8 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Data
@@ -13,18 +10,12 @@ namespace Emby.Server.Implementations.Data
public class CleanDatabaseScheduledTask : ILibraryPostScanTask
{
private readonly ILibraryManager _libraryManager;
- private readonly IItemRepository _itemRepo;
private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
- private readonly IApplicationPaths _appPaths;
- public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths)
+ public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger)
{
_libraryManager = libraryManager;
- _itemRepo = itemRepo;
_logger = logger;
- _fileSystem = fileSystem;
- _appPaths = appPaths;
}
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 3014e482d..70e5fa640 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -536,7 +536,7 @@ namespace Emby.Server.Implementations.Data
throw new ArgumentNullException(nameof(item));
}
- SaveItems(new List<BaseItem> { item }, cancellationToken);
+ SaveItems(new [] { item }, cancellationToken);
}
public void SaveImages(BaseItem item)
@@ -576,7 +576,7 @@ namespace Emby.Server.Implementations.Data
/// or
/// cancellationToken
/// </exception>
- public void SaveItems(List<BaseItem> items, CancellationToken cancellationToken)
+ public void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
{
if (items == null)
{
@@ -587,7 +587,7 @@ namespace Emby.Server.Implementations.Data
CheckDisposed();
- var tuples = new List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>>();
+ var tuples = new List<(BaseItem, List<Guid>, BaseItem, string, List<string>)>();
foreach (var item in items)
{
var ancestorIds = item.SupportsAncestors ?
@@ -599,7 +599,7 @@ namespace Emby.Server.Implementations.Data
var userdataKey = item.GetUserDataKeys().FirstOrDefault();
var inheritedTags = item.GetInheritedTags();
- tuples.Add(new Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>(item, ancestorIds, topParent, userdataKey, inheritedTags));
+ tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
}
using (WriteLock.Write())
@@ -615,7 +615,7 @@ namespace Emby.Server.Implementations.Data
}
}
- private void SaveItemsInTranscation(IDatabaseConnection db, List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>> tuples)
+ private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List<Guid>, BaseItem, string, List<string>)> tuples)
{
var statements = PrepareAllSafe(db, new string[]
{
@@ -966,7 +966,7 @@ namespace Emby.Server.Implementations.Data
if (item.ExtraIds.Length > 0)
{
- saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds.ToArray()));
+ saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds));
}
else
{
@@ -1183,9 +1183,9 @@ namespace Emby.Server.Implementations.Data
/// <exception cref="ArgumentException"></exception>
public BaseItem RetrieveItem(Guid id)
{
- if (id.Equals(Guid.Empty))
+ if (id == Guid.Empty)
{
- throw new ArgumentNullException(nameof(id));
+ throw new ArgumentException(nameof(id), "Guid can't be empty");
}
CheckDisposed();
@@ -2079,14 +2079,14 @@ namespace Emby.Server.Implementations.Data
return false;
}
- var sortingFields = query.OrderBy.Select(i => i.Item1);
+ var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.Item1), StringComparer.OrdinalIgnoreCase);
- return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase)
- || sortingFields.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase)
- || sortingFields.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase)
- || sortingFields.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase)
- || sortingFields.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase)
- || sortingFields.Contains(ItemSortBy.SeriesDatePlayed, StringComparer.OrdinalIgnoreCase)
+ return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked)
+ || sortingFields.Contains(ItemSortBy.IsPlayed)
+ || sortingFields.Contains(ItemSortBy.IsUnplayed)
+ || sortingFields.Contains(ItemSortBy.PlayCount)
+ || sortingFields.Contains(ItemSortBy.DatePlayed)
+ || sortingFields.Contains(ItemSortBy.SeriesDatePlayed)
|| query.IsFavoriteOrLiked.HasValue
|| query.IsFavorite.HasValue
|| query.IsResumable.HasValue
@@ -2094,9 +2094,9 @@ namespace Emby.Server.Implementations.Data
|| query.IsLiked.HasValue;
}
- private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields))
+ private readonly ItemFields[] _allFields = Enum.GetNames(typeof(ItemFields))
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
+ .ToArray();
private string[] GetColumnNamesFromField(ItemFields field)
{
@@ -2151,18 +2151,26 @@ namespace Emby.Server.Implementations.Data
}
}
- private bool HasProgramAttributes(InternalItemsQuery query)
+ private static readonly HashSet<string> _programExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
- var excludeParentTypes = new string[]
- {
- "Series",
- "Season",
- "MusicAlbum",
- "MusicArtist",
- "PhotoAlbum"
- };
+ "Series",
+ "Season",
+ "MusicAlbum",
+ "MusicArtist",
+ "PhotoAlbum"
+ };
+
+ private static readonly HashSet<string> _programTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ {
+ "Program",
+ "TvChannel",
+ "LiveTvProgram",
+ "LiveTvTvChannel"
+ };
- if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ private bool HasProgramAttributes(InternalItemsQuery query)
+ {
+ if (_programExcludeParentTypes.Contains(query.ParentType))
{
return false;
}
@@ -2172,29 +2180,18 @@ namespace Emby.Server.Implementations.Data
return true;
}
- var types = new string[]
- {
- "Program",
- "TvChannel",
- "LiveTvProgram",
- "LiveTvTvChannel"
- };
-
- return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+ return query.IncludeItemTypes.Any(x => _programTypes.Contains(x));
}
- private bool HasServiceName(InternalItemsQuery query)
+ private static readonly HashSet<string> _serviceTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
- var excludeParentTypes = new string[]
- {
- "Series",
- "Season",
- "MusicAlbum",
- "MusicArtist",
- "PhotoAlbum"
- };
+ "TvChannel",
+ "LiveTvTvChannel"
+ };
- if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ private bool HasServiceName(InternalItemsQuery query)
+ {
+ if (_programExcludeParentTypes.Contains(query.ParentType))
{
return false;
}
@@ -2204,27 +2201,18 @@ namespace Emby.Server.Implementations.Data
return true;
}
- var types = new string[]
- {
- "TvChannel",
- "LiveTvTvChannel"
- };
-
- return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+ return query.IncludeItemTypes.Any(x => _serviceTypes.Contains(x));
}
- private bool HasStartDate(InternalItemsQuery query)
+ private static readonly HashSet<string> _startDateTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
- var excludeParentTypes = new string[]
- {
- "Series",
- "Season",
- "MusicAlbum",
- "MusicArtist",
- "PhotoAlbum"
- };
+ "Program",
+ "LiveTvProgram"
+ };
- if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ private bool HasStartDate(InternalItemsQuery query)
+ {
+ if (_programExcludeParentTypes.Contains(query.ParentType))
{
return false;
}
@@ -2234,13 +2222,7 @@ namespace Emby.Server.Implementations.Data
return true;
}
- var types = new string[]
- {
- "Program",
- "LiveTvProgram"
- };
-
- return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+ return query.IncludeItemTypes.Any(x => _startDateTypes.Contains(x));
}
private bool HasEpisodeAttributes(InternalItemsQuery query)
@@ -2263,16 +2245,26 @@ namespace Emby.Server.Implementations.Data
return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase);
}
- private bool HasArtistFields(InternalItemsQuery query)
+
+ private static readonly HashSet<string> _artistExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
- var excludeParentTypes = new string[]
- {
- "Series",
- "Season",
- "PhotoAlbum"
- };
+ "Series",
+ "Season",
+ "PhotoAlbum"
+ };
+
+ private static readonly HashSet<string> _artistsTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ {
+ "Audio",
+ "MusicAlbum",
+ "MusicVideo",
+ "AudioBook",
+ "AudioPodcast"
+ };
- if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ private bool HasArtistFields(InternalItemsQuery query)
+ {
+ if (_artistExcludeParentTypes.Contains(query.ParentType))
{
return false;
}
@@ -2282,18 +2274,18 @@ namespace Emby.Server.Implementations.Data
return true;
}
- var types = new string[]
- {
- "Audio",
- "MusicAlbum",
- "MusicVideo",
- "AudioBook",
- "AudioPodcast"
- };
-
- return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+ return query.IncludeItemTypes.Any(x => _artistsTypes.Contains(x));
}
+ private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ {
+ "Audio",
+ "MusicAlbum",
+ "MusicVideo",
+ "AudioBook",
+ "AudioPodcast"
+ };
+
private bool HasSeriesFields(InternalItemsQuery query)
{
if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase))
@@ -2306,26 +2298,18 @@ namespace Emby.Server.Implementations.Data
return true;
}
- var types = new string[]
- {
- "Book",
- "AudioBook",
- "Episode",
- "Season"
- };
-
- return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+ return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x));
}
- private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns)
+ private List<string> GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable<string> startColumns)
{
var list = startColumns.ToList();
- foreach (var field in allFields)
+ foreach (var field in _allFields)
{
if (!HasField(query, field))
{
- foreach (var fieldToRemove in GetColumnNamesFromField(field).ToList())
+ foreach (var fieldToRemove in GetColumnNamesFromField(field))
{
list.Remove(fieldToRemove);
}
@@ -2419,11 +2403,14 @@ namespace Emby.Server.Implementations.Data
list.Add(builder.ToString());
- var excludeIds = query.ExcludeItemIds.ToList();
- excludeIds.Add(item.Id);
- excludeIds.AddRange(item.ExtraIds);
+ var oldLen = query.ExcludeItemIds.Length;
+ var newLen = oldLen + item.ExtraIds.Length + 1;
+ var excludeIds = new Guid[newLen];
+ query.ExcludeItemIds.CopyTo(excludeIds, 0);
+ excludeIds[oldLen] = item.Id;
+ item.ExtraIds.CopyTo(excludeIds, oldLen + 1);
- query.ExcludeItemIds = excludeIds.ToArray();
+ query.ExcludeItemIds = excludeIds;
query.ExcludeProviderIds = item.ProviderIds;
}
@@ -2444,7 +2431,7 @@ namespace Emby.Server.Implementations.Data
list.Add(builder.ToString());
}
- return list.ToArray();
+ return list;
}
private void BindSearchParams(InternalItemsQuery query, IStatement statement)
@@ -2723,18 +2710,17 @@ namespace Emby.Server.Implementations.Data
private void AddItem(List<BaseItem> items, BaseItem newItem)
{
- var providerIds = newItem.ProviderIds.ToList();
-
for (var i = 0; i < items.Count; i++)
{
var item = items[i];
- foreach (var providerId in providerIds)
+ foreach (var providerId in newItem.ProviderIds)
{
if (providerId.Key == MetadataProviders.TmdbCollection.ToString())
{
continue;
}
+
if (item.GetProviderId(providerId.Key) == providerId.Value)
{
if (newItem.SourceType == SourceType.Library)
@@ -2753,15 +2739,15 @@ namespace Emby.Server.Implementations.Data
{
var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds;
- int slowThreshold = 1000;
+ int slowThreshold = 100;
#if DEBUG
- slowThreshold = 250;
+ slowThreshold = 10;
#endif
if (elapsed >= slowThreshold)
{
- Logger.LogWarning("{0} query time (slow): {1}ms. Query: {2}",
+ Logger.LogWarning("{0} query time (slow): {1:g}. Query: {2}",
methodName,
elapsed,
commandText);
@@ -2806,7 +2792,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses);
commandText += whereText
+ GetGroupBy(query)
@@ -2930,25 +2916,31 @@ namespace Emby.Server.Implementations.Data
private string GetOrderByText(InternalItemsQuery query)
{
- var orderBy = query.OrderBy.ToList();
- var enableOrderInversion = false;
-
- if (query.SimilarTo != null && orderBy.Count == 0)
+ if (string.IsNullOrEmpty(query.SearchTerm))
{
- orderBy.Add(new ValueTuple<string, SortOrder>("SimilarityScore", SortOrder.Descending));
- orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending));
- }
+ int oldLen = query.OrderBy.Length;
- if (!string.IsNullOrEmpty(query.SearchTerm))
+ if (query.SimilarTo != null && oldLen == 0)
+ {
+ var arr = new (string, SortOrder)[oldLen + 2];
+ query.OrderBy.CopyTo(arr, 0);
+ arr[oldLen] = ("SimilarityScore", SortOrder.Descending);
+ arr[oldLen + 1] = (ItemSortBy.Random, SortOrder.Ascending);
+ query.OrderBy = arr;
+ }
+ }
+ else
{
- orderBy = new List<(string, SortOrder)>();
- orderBy.Add(new ValueTuple<string, SortOrder>("SearchScore", SortOrder.Descending));
- orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
+ query.OrderBy = new []
+ {
+ ("SearchScore", SortOrder.Descending),
+ (ItemSortBy.SortName, SortOrder.Ascending)
+ };
}
- query.OrderBy = orderBy.ToArray();
+ var orderBy = query.OrderBy;
- if (orderBy.Count == 0)
+ if (orderBy.Length == 0)
{
return string.Empty;
}
@@ -2957,6 +2949,7 @@ namespace Emby.Server.Implementations.Data
{
var columnMap = MapOrderByField(i.Item1, query);
var columnAscending = i.Item2 == SortOrder.Ascending;
+ const bool enableOrderInversion = false;
if (columnMap.Item2 && enableOrderInversion)
{
columnAscending = !columnAscending;
@@ -2968,7 +2961,7 @@ namespace Emby.Server.Implementations.Data
}));
}
- private ValueTuple<string, bool> MapOrderByField(string name, InternalItemsQuery query)
+ private (string, bool) MapOrderByField(string name, InternalItemsQuery query)
{
if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
{
@@ -3218,7 +3211,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses);
commandText += whereText
+ GetGroupBy(query)
@@ -4378,7 +4371,7 @@ namespace Emby.Server.Implementations.Data
}
else if (query.Years.Length > 1)
{
- var val = string.Join(",", query.Years.ToArray());
+ var val = string.Join(",", query.Years);
whereClauses.Add("ProductionYear in (" + val + ")");
}
@@ -4952,7 +4945,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return result;
}
- return new[] { value }.Where(IsValidType);
+ if (IsValidType(value))
+ {
+ return new[] { value };
+ }
+
+ return Array.Empty<string>();
}
public void DeleteItem(Guid id, CancellationToken cancellationToken)
@@ -5215,32 +5213,32 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName);
}
@@ -5317,7 +5315,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
}
- private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
+ private QueryResult<(BaseItem, ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
{
if (query == null)
{
@@ -5335,7 +5333,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
var typeClause = itemValueTypes.Length == 1 ?
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
- ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray()) + ")");
+ ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
InternalItemsQuery typeSubQuery = null;
@@ -5363,11 +5361,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")");
- var typeWhereText = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses);
-
- itemCountColumnQuery += typeWhereText;
+ itemCountColumnQuery += " where " + string.Join(" AND ", whereClauses);
itemCountColumns = new Dictionary<string, string>()
{
@@ -5400,7 +5394,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
IsSeries = query.IsSeries
};
- columns = GetFinalColumnsToSelect(query, columns.ToArray()).ToList();
+ columns = GetFinalColumnsToSelect(query, columns);
var commandText = "select "
+ string.Join(",", columns)
@@ -5492,8 +5486,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
return connection.RunInTransaction(db =>
{
- var list = new List<Tuple<BaseItem, ItemCounts>>();
- var result = new QueryResult<Tuple<BaseItem, ItemCounts>>();
+ var list = new List<(BaseItem, ItemCounts)>();
+ var result = new QueryResult<(BaseItem, ItemCounts)>();
var statements = PrepareAllSafe(db, statementTexts);
@@ -5531,7 +5525,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
var countStartColumn = columns.Count - 1;
- list.Add(new Tuple<BaseItem, ItemCounts>(item, GetItemCounts(row, countStartColumn, typesToCount)));
+ list.Add((item, GetItemCounts(row, countStartColumn, typesToCount)));
}
}
@@ -6198,6 +6192,5 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return item;
}
-
}
}
diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs
index 866bd137f..495c3436a 100644
--- a/Emby.Server.Implementations/Devices/DeviceId.cs
+++ b/Emby.Server.Implementations/Devices/DeviceId.cs
@@ -11,7 +11,6 @@ namespace Emby.Server.Implementations.Devices
{
private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
private readonly object _syncLock = new object();
@@ -86,19 +85,10 @@ namespace Emby.Server.Implementations.Devices
private string _id;
- public DeviceId(
- IApplicationPaths appPaths,
- ILoggerFactory loggerFactory,
- IFileSystem fileSystem)
+ public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
{
- if (fileSystem == null)
- {
- throw new ArgumentNullException(nameof(fileSystem));
- }
-
_appPaths = appPaths;
_logger = loggerFactory.CreateLogger("SystemId");
- _fileSystem = fileSystem;
}
public string Value => _id ?? (_id = GetDeviceId());
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
index ec3649bca..7d6529a67 100644
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ b/Emby.Server.Implementations/Devices/DeviceManager.cs
@@ -34,8 +34,6 @@ namespace Emby.Server.Implementations.Devices
private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IServerConfigurationManager _config;
- private readonly ILogger _logger;
- private readonly INetworkManager _network;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
@@ -55,17 +53,13 @@ namespace Emby.Server.Implementations.Devices
IUserManager userManager,
IFileSystem fileSystem,
ILibraryMonitor libraryMonitor,
- IServerConfigurationManager config,
- ILoggerFactory loggerFactory,
- INetworkManager network)
+ IServerConfigurationManager config)
{
_json = json;
_userManager = userManager;
_fileSystem = fileSystem;
_libraryMonitor = libraryMonitor;
_config = config;
- _logger = loggerFactory.CreateLogger(nameof(DeviceManager));
- _network = network;
_libraryManager = libraryManager;
_localizationManager = localizationManager;
_authRepo = authRepo;
@@ -414,14 +408,12 @@ namespace Emby.Server.Implementations.Devices
{
private readonly DeviceManager _deviceManager;
private readonly IServerConfigurationManager _config;
- private readonly IFileSystem _fileSystem;
private ILogger _logger;
- public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger)
+ public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, ILogger logger)
{
_deviceManager = (DeviceManager)deviceManager;
_config = config;
- _fileSystem = fileSystem;
_logger = logger;
}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 983eb51e6..7b28a22a8 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -5,8 +5,6 @@ using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Controller.Channels;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -21,8 +19,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
@@ -36,13 +32,9 @@ namespace Emby.Server.Implementations.Dto
private readonly IItemRepository _itemRepo;
private readonly IImageProcessor _imageProcessor;
- private readonly IServerConfigurationManager _config;
- private readonly IFileSystem _fileSystem;
private readonly IProviderManager _providerManager;
- private readonly Func<IChannelManager> _channelManagerFactory;
private readonly IApplicationHost _appHost;
- private readonly Func<IDeviceManager> _deviceManager;
private readonly Func<IMediaSourceManager> _mediaSourceManager;
private readonly Func<ILiveTvManager> _livetvManager;
@@ -52,12 +44,8 @@ namespace Emby.Server.Implementations.Dto
IUserDataManager userDataRepository,
IItemRepository itemRepo,
IImageProcessor imageProcessor,
- IServerConfigurationManager config,
- IFileSystem fileSystem,
IProviderManager providerManager,
- Func<IChannelManager> channelManagerFactory,
IApplicationHost appHost,
- Func<IDeviceManager> deviceManager,
Func<IMediaSourceManager> mediaSourceManager,
Func<ILiveTvManager> livetvManager)
{
@@ -66,12 +54,8 @@ namespace Emby.Server.Implementations.Dto
_userDataRepository = userDataRepository;
_itemRepo = itemRepo;
_imageProcessor = imageProcessor;
- _config = config;
- _fileSystem = fileSystem;
_providerManager = providerManager;
- _channelManagerFactory = channelManagerFactory;
_appHost = appHost;
- _deviceManager = deviceManager;
_mediaSourceManager = mediaSourceManager;
_livetvManager = livetvManager;
}
@@ -95,15 +79,8 @@ namespace Emby.Server.Implementations.Dto
return GetBaseItemDto(item, options, user, owner);
}
- public BaseItemDto[] GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
- {
- return GetBaseItemDtos(items, items.Count, options, user, owner);
- }
-
- public BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null)
- {
- return GetBaseItemDtos(items, items.Length, options, user, owner);
- }
+ public BaseItemDto[] GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
+ => GetBaseItemDtos(items, items.Count, options, user, owner);
public BaseItemDto[] GetBaseItemDtos(IEnumerable<BaseItem> items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null)
{
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 8356a9501..bbf165d62 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -22,9 +22,11 @@
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
<PackageReference Include="sharpcompress" Version="0.22.0" />
- <PackageReference Include="SimpleInjector" Version="4.4.2" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" />
<PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
</ItemGroup>
diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
index 774ed09da..a5badacee 100644
--- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
@@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
{
- class UserDataChangeNotifier : IServerEntryPoint
+ public class UserDataChangeNotifier : IServerEntryPoint
{
private readonly ISessionManager _sessionManager;
private readonly ILogger _logger;
diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs
index 6167d1eaa..bbf51dd24 100644
--- a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs
+++ b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs
@@ -3,27 +3,19 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
using MediaBrowser.Model.IO;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.FFMpeg
{
public class FFMpegLoader
{
- private readonly IHttpClient _httpClient;
private readonly IApplicationPaths _appPaths;
- private readonly ILogger _logger;
- private readonly IZipClient _zipClient;
private readonly IFileSystem _fileSystem;
private readonly FFMpegInstallInfo _ffmpegInstallInfo;
- public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo)
+ public FFMpegLoader(IApplicationPaths appPaths, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo)
{
- _logger = logger;
_appPaths = appPaths;
- _httpClient = httpClient;
- _zipClient = zipClient;
_fileSystem = fileSystem;
_ffmpegInstallInfo = ffmpegInstallInfo;
}
@@ -115,8 +107,7 @@ namespace Emby.Server.Implementations.FFMpeg
var encoderFilename = Path.GetFileName(info.EncoderPath);
var probeFilename = Path.GetFileName(info.ProbePath);
- foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath)
- .ToList())
+ foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath))
{
var allFiles = _fileSystem.GetFilePaths(directory, true).ToList();
diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
index 2232b3eeb..2e0728136 100644
--- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
@@ -539,21 +539,10 @@ namespace Emby.Server.Implementations.HttpClientManager
var contentLength = GetContentLength(httpResponse);
- if (contentLength.HasValue)
- {
- using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
- {
- await httpResponse.GetResponseStream().CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
- }
- }
- else
+ using (var stream = httpResponse.GetResponseStream())
+ using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
{
- // We're not able to track progress
- using (var stream = httpResponse.GetResponseStream())
- using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
- {
- await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
- }
+ await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
}
options.Progress.Report(100);
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index 834ffb130..ee746c669 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -19,7 +20,9 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using ServiceStack.Text.Jsv;
namespace Emby.Server.Implementations.HttpServer
{
@@ -53,20 +56,20 @@ namespace Emby.Server.Implementations.HttpServer
IServerApplicationHost applicationHost,
ILoggerFactory loggerFactory,
IServerConfigurationManager config,
- string defaultRedirectPath,
+ IConfiguration configuration,
INetworkManager networkManager,
IJsonSerializer jsonSerializer,
- IXmlSerializer xmlSerializer,
- Func<Type, Func<string, object>> funcParseFn)
+ IXmlSerializer xmlSerializer)
{
_appHost = applicationHost;
_logger = loggerFactory.CreateLogger("HttpServer");
_config = config;
- DefaultRedirectPath = defaultRedirectPath;
+ DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
_networkManager = networkManager;
_jsonSerializer = jsonSerializer;
_xmlSerializer = xmlSerializer;
- _funcParseFn = funcParseFn;
+
+ _funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
Instance = this;
ResponseFilters = Array.Empty<Action<IRequest, IResponse, object>>();
@@ -284,31 +287,6 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- private static readonly string[] _skipLogExtensions =
- {
- ".js",
- ".css",
- ".woff",
- ".woff2",
- ".ttf",
- ".html"
- };
-
- private bool EnableLogging(string url, string localPath)
- {
- var extension = GetExtension(url);
-
- return ((string.IsNullOrEmpty(extension) || !_skipLogExtensions.Contains(extension))
- && (string.IsNullOrEmpty(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1));
- }
-
- private static string GetExtension(string url)
- {
- var parts = url.Split(new[] { '?' }, 2);
-
- return Path.GetExtension(parts[0]);
- }
-
public static string RemoveQueryStringByKey(string url, string key)
{
var uri = new Uri(url);
@@ -446,10 +424,9 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
{
- var date = DateTime.Now;
+ var stopWatch = new Stopwatch();
+ stopWatch.Start();
var httpRes = httpReq.Response;
- bool enableLog = false;
- bool logHeaders = false;
string urlToLog = null;
string remoteIp = httpReq.RemoteIp;
@@ -496,18 +473,8 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
- var operationName = httpReq.OperationName;
-
- enableLog = EnableLogging(urlString, localPath);
- urlToLog = urlString;
- logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1;
-
- if (enableLog)
- {
- urlToLog = GetUrlToLog(urlString);
-
- LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null);
- }
+ urlToLog = GetUrlToLog(urlString);
+ Logger.LogDebug("HTTP {HttpMethod} {Url} UserAgent: {UserAgent} \nHeaders: {@Headers}", urlToLog, httpReq.UserAgent ?? string.Empty, httpReq.HttpMethod, httpReq.Headers);
if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
@@ -515,6 +482,7 @@ namespace Emby.Server.Implementations.HttpServer
RedirectToUrl(httpRes, DefaultRedirectPath);
return;
}
+
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
{
@@ -560,16 +528,19 @@ namespace Emby.Server.Implementations.HttpServer
RedirectToUrl(httpRes, DefaultRedirectPath);
return;
}
+
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
{
RedirectToUrl(httpRes, "../" + DefaultRedirectPath);
return;
}
+
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
{
RedirectToUrl(httpRes, DefaultRedirectPath);
return;
}
+
if (string.IsNullOrEmpty(localPath))
{
RedirectToUrl(httpRes, "/" + DefaultRedirectPath);
@@ -605,33 +576,21 @@ namespace Emby.Server.Implementations.HttpServer
if (handler != null)
{
- await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, operationName, cancellationToken).ConfigureAwait(false);
+ await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, httpReq.OperationName, cancellationToken).ConfigureAwait(false);
}
else
{
await ErrorHandler(new FileNotFoundException(), httpReq, false, false).ConfigureAwait(false);
}
}
- catch (OperationCanceledException ex)
- {
- await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
- }
-
- catch (IOException ex)
- {
- await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
- }
-
- catch (SocketException ex)
+ catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException)
{
await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
}
-
catch (SecurityException ex)
{
await ErrorHandler(ex, httpReq, false, true).ConfigureAwait(false);
}
-
catch (Exception ex)
{
var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase);
@@ -642,13 +601,15 @@ namespace Emby.Server.Implementations.HttpServer
{
httpRes.Close();
- if (enableLog)
+ stopWatch.Stop();
+ var elapsed = stopWatch.Elapsed;
+ if (elapsed.TotalMilliseconds > 500)
{
- var statusCode = httpRes.StatusCode;
-
- var duration = DateTime.Now - date;
-
- LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration, logHeaders ? httpRes.Headers : null);
+ _logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
+ }
+ else
+ {
+ _logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
}
}
}
@@ -661,12 +622,11 @@ namespace Emby.Server.Implementations.HttpServer
var pathParts = pathInfo.TrimStart('/').Split('/');
if (pathParts.Length == 0)
{
- _logger.LogError("Path parts empty for PathInfo: {pathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl);
+ _logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl);
return null;
}
var restPath = ServiceHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out string contentType);
-
if (restPath != null)
{
return new ServiceHandler
diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
index 75ca57ebb..070717d48 100644
--- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null)
{
- var result = new StreamWriter(content, contentType, _logger);
+ var result = new StreamWriter(content, contentType);
if (responseHeaders == null)
{
@@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.HttpServer
content = Array.Empty<byte>();
}
- result = new StreamWriter(content, contentType, contentLength, _logger);
+ result = new StreamWriter(content, contentType, contentLength);
}
else
{
@@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>();
}
- if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
+ if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
{
responseHeaders["Expires"] = "-1";
}
@@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.HttpServer
bytes = Array.Empty<byte>();
}
- result = new StreamWriter(bytes, contentType, contentLength, _logger);
+ result = new StreamWriter(bytes, contentType, contentLength);
}
else
{
@@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>();
}
- if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
+ if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
{
responseHeaders["Expires"] = "-1";
}
@@ -277,9 +277,10 @@ namespace Emby.Server.Implementations.HttpServer
private object ToOptimizedResultInternal<T>(IRequest request, T dto, IDictionary<string, string> responseHeaders = null)
{
- var contentType = request.ResponseContentType;
+ // TODO: @bond use Span and .Equals
+ var contentType = request.ResponseContentType?.Split(';')[0].Trim().ToLowerInvariant();
- switch (GetRealContentType(contentType))
+ switch (contentType)
{
case "application/xml":
case "text/xml":
@@ -333,13 +334,13 @@ namespace Emby.Server.Implementations.HttpServer
if (isHeadRequest)
{
- var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength, _logger);
+ var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength);
AddResponseHeaders(result, responseHeaders);
return result;
}
else
{
- var result = new StreamWriter(content, contentType, contentLength, _logger);
+ var result = new StreamWriter(content, contentType, contentLength);
AddResponseHeaders(result, responseHeaders);
return result;
}
@@ -348,13 +349,19 @@ namespace Emby.Server.Implementations.HttpServer
private byte[] Compress(byte[] bytes, string compressionType)
{
if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase))
+ {
return CompressBrotli(bytes);
+ }
if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase))
+ {
return Deflate(bytes);
+ }
if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase))
+ {
return GZip(bytes);
+ }
throw new NotSupportedException(compressionType);
}
@@ -390,13 +397,6 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- public static string GetRealContentType(string contentType)
- {
- return contentType == null
- ? null
- : contentType.Split(';')[0].ToLowerInvariant().Trim();
- }
-
private static string SerializeToXmlString(object from)
{
using (var ms = new MemoryStream())
@@ -603,7 +603,7 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- var hasHeaders = new StreamWriter(stream, contentType, _logger)
+ var hasHeaders = new StreamWriter(stream, contentType)
{
OnComplete = options.OnComplete,
OnError = options.OnError
diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs
deleted file mode 100644
index d22d9db26..000000000
--- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using System.Globalization;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.HttpServer
-{
- public static class LoggerUtils
- {
- public static void LogRequest(ILogger logger, string url, string method, string userAgent, QueryParamCollection headers)
- {
- if (headers == null)
- {
- logger.LogInformation("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty);
- }
- else
- {
- var headerText = string.Empty;
- var index = 0;
-
- foreach (var i in headers)
- {
- if (index > 0)
- {
- headerText += ", ";
- }
-
- headerText += i.Name + "=" + i.Value;
-
- index++;
- }
-
- logger.LogInformation("HTTP {0} {1}. {2}", method, url, headerText);
- }
- }
-
- /// <summary>
- /// Logs the response.
- /// </summary>
- /// <param name="logger">The logger.</param>
- /// <param name="statusCode">The status code.</param>
- /// <param name="url">The URL.</param>
- /// <param name="endPoint">The end point.</param>
- /// <param name="duration">The duration.</param>
- public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration, QueryParamCollection headers)
- {
- var durationMs = duration.TotalMilliseconds;
- var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms";
-
- //var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray());
- var headerText = string.Empty;
- logger.LogInformation("HTTP Response {0} to {1}. Time: {2}{3}. {4} {5}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url, headerText);
- }
- }
-}
diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs
index 3269d44cf..cb2e3580b 100644
--- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs
@@ -14,8 +14,6 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
public class StreamWriter : IAsyncStreamWriter, IHasHeaders
{
- private ILogger Logger { get; set; }
-
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary>
@@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="logger">The logger.</param>
- public StreamWriter(Stream source, string contentType, ILogger logger)
+ public StreamWriter(Stream source, string contentType)
{
if (string.IsNullOrEmpty(contentType))
{
@@ -53,7 +51,6 @@ namespace Emby.Server.Implementations.HttpServer
}
SourceStream = source;
- Logger = logger;
Headers["Content-Type"] = contentType;
@@ -69,7 +66,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="logger">The logger.</param>
- public StreamWriter(byte[] source, string contentType, int contentLength, ILogger logger)
+ public StreamWriter(byte[] source, string contentType, int contentLength)
{
if (string.IsNullOrEmpty(contentType))
{
@@ -77,7 +74,6 @@ namespace Emby.Server.Implementations.HttpServer
}
SourceBytes = source;
- Logger = logger;
Headers["Content-Type"] = contentType;
diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs
index 3668f6a7a..73242d0ad 100644
--- a/Emby.Server.Implementations/IO/FileRefresher.cs
+++ b/Emby.Server.Implementations/IO/FileRefresher.cs
@@ -17,31 +17,23 @@ namespace Emby.Server.Implementations.IO
public class FileRefresher : IDisposable
{
private ILogger Logger { get; set; }
- private ITaskManager TaskManager { get; set; }
private ILibraryManager LibraryManager { get; set; }
private IServerConfigurationManager ConfigurationManager { get; set; }
- private readonly IFileSystem _fileSystem;
private readonly List<string> _affectedPaths = new List<string>();
private Timer _timer;
private readonly object _timerLock = new object();
public string Path { get; private set; }
public event EventHandler<EventArgs> Completed;
- private readonly IEnvironmentInfo _environmentInfo;
- private readonly ILibraryManager _libraryManager;
- public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, IEnvironmentInfo environmentInfo, ILibraryManager libraryManager1)
+ public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
{
logger.LogDebug("New file refresher created for {0}", path);
Path = path;
- _fileSystem = fileSystem;
ConfigurationManager = configurationManager;
LibraryManager = libraryManager;
- TaskManager = taskManager;
Logger = logger;
- _environmentInfo = environmentInfo;
- _libraryManager = libraryManager1;
AddPath(path);
}
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index 607a4d333..d47342511 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.IO
/// <summary>
/// Any file name ending in any of these will be ignored by the watchers
/// </summary>
- private readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ private static readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"small.jpg",
"albumart.jpg",
@@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.IO
"TempSBE"
};
- private readonly string[] _alwaysIgnoreSubstrings = new string[]
+ private static readonly string[] _alwaysIgnoreSubstrings = new string[]
{
// Synology
"eaDir",
@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.IO
".actors"
};
- private readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ private static readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
// thumbs.db
".db",
@@ -123,12 +123,6 @@ namespace Emby.Server.Implementations.IO
/// <value>The logger.</value>
private ILogger Logger { get; set; }
- /// <summary>
- /// Gets or sets the task manager.
- /// </summary>
- /// <value>The task manager.</value>
- private ITaskManager TaskManager { get; set; }
-
private ILibraryManager LibraryManager { get; set; }
private IServerConfigurationManager ConfigurationManager { get; set; }
@@ -140,19 +134,12 @@ namespace Emby.Server.Implementations.IO
/// </summary>
public LibraryMonitor(
ILoggerFactory loggerFactory,
- ITaskManager taskManager,
ILibraryManager libraryManager,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
IEnvironmentInfo environmentInfo)
{
- if (taskManager == null)
- {
- throw new ArgumentNullException(nameof(taskManager));
- }
-
LibraryManager = libraryManager;
- TaskManager = taskManager;
Logger = loggerFactory.CreateLogger(GetType().Name);
ConfigurationManager = configurationManager;
_fileSystem = fileSystem;
@@ -541,7 +528,7 @@ namespace Emby.Server.Implementations.IO
}
}
- var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _environmentInfo, LibraryManager);
+ var newRefresher = new FileRefresher(path, ConfigurationManager, LibraryManager, Logger);
newRefresher.Completed += NewRefresher_Completed;
_activeRefreshers.Add(newRefresher);
}
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 7c44878ec..a64dfb607 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -4,8 +4,10 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
+using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.IO
@@ -20,61 +22,27 @@ namespace Emby.Server.Implementations.IO
private readonly bool _supportsAsyncFileStreams;
private char[] _invalidFileNameChars;
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
- private bool EnableSeparateFileAndDirectoryQueries;
- private string _tempPath;
+ private readonly string _tempPath;
- private IEnvironmentInfo _environmentInfo;
- private bool _isEnvironmentCaseInsensitive;
-
- private string _defaultDirectory;
+ private readonly IEnvironmentInfo _environmentInfo;
+ private readonly bool _isEnvironmentCaseInsensitive;
public ManagedFileSystem(
ILoggerFactory loggerFactory,
IEnvironmentInfo environmentInfo,
- string defaultDirectory,
- string tempPath,
- bool enableSeparateFileAndDirectoryQueries)
+ IApplicationPaths applicationPaths)
{
Logger = loggerFactory.CreateLogger("FileSystem");
_supportsAsyncFileStreams = true;
- _tempPath = tempPath;
+ _tempPath = applicationPaths.TempDirectory;
_environmentInfo = environmentInfo;
- _defaultDirectory = defaultDirectory;
-
- // On Linux with mono, this needs to be true or symbolic links are ignored
- EnableSeparateFileAndDirectoryQueries = enableSeparateFileAndDirectoryQueries;
SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows);
_isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
}
- public virtual string DefaultDirectory
- {
- get
- {
- var value = _defaultDirectory;
-
- if (!string.IsNullOrEmpty(value))
- {
- try
- {
- if (Directory.Exists(value))
- {
- return value;
- }
- }
- catch
- {
-
- }
- }
-
- return null;
- }
- }
-
public virtual void AddShortcutHandler(IShortcutHandler handler)
{
_shortcutHandlers.Add(handler);
@@ -718,7 +686,7 @@ namespace Emby.Server.Implementations.IO
SetAttributes(path, false, false);
File.Delete(path);
}
-
+
public virtual List<FileSystemMetadata> GetDrives()
{
// Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout
@@ -777,20 +745,15 @@ namespace Emby.Server.Implementations.IO
var directoryInfo = new DirectoryInfo(path);
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
- if (EnableSeparateFileAndDirectoryQueries)
- {
- return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
- .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
- }
-
- return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", searchOption));
+ return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
+ .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
}
private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
{
return infos.Select(GetFileSystemMetadata);
}
-
+
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
{
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index 80f746c7a..c644d13ea 100644
--- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Controller.Entities;
@@ -7,7 +6,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library
{
@@ -16,16 +14,14 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
{
- private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
private bool _ignoreDotPrefix;
/// <summary>
/// Any folder named in this list will be ignored - can be added to at runtime for extensibility
/// </summary>
- public static readonly Dictionary<string, string> IgnoreFolders = new List<string>
+ public static readonly string[] IgnoreFolders =
{
"metadata",
"ps3_update",
@@ -50,13 +46,11 @@ namespace Emby.Server.Implementations.Library
// macos
".AppleDouble"
- }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+ };
- public CoreResolutionIgnoreRule(IFileSystem fileSystem, ILibraryManager libraryManager, ILogger logger)
+ public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
{
- _fileSystem = fileSystem;
_libraryManager = libraryManager;
- _logger = logger;
_ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT;
}
@@ -117,7 +111,7 @@ namespace Emby.Server.Implementations.Library
if (fileInfo.IsDirectory)
{
// Ignore any folders in our list
- if (IgnoreFolders.ContainsKey(filename))
+ if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
{
return true;
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 064006ebd..3c2272b56 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -986,7 +986,7 @@ namespace Emby.Server.Implementations.Library
// Ensure the location is available.
Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
- return new PeopleValidator(this, _logger, ConfigurationManager, _fileSystem).ValidatePeople(cancellationToken, progress);
+ return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress);
}
/// <summary>
@@ -1225,9 +1225,9 @@ namespace Emby.Server.Implementations.Library
/// <exception cref="ArgumentNullException">id</exception>
public BaseItem GetItemById(Guid id)
{
- if (id.Equals(Guid.Empty))
+ if (id == Guid.Empty)
{
- throw new ArgumentNullException(nameof(id));
+ throw new ArgumentException(nameof(id), "Guid can't be empty");
}
if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
@@ -1237,8 +1237,6 @@ namespace Emby.Server.Implementations.Library
item = RetrieveItem(id);
- //_logger.LogDebug("GetitemById {0}", id);
-
if (item != null)
{
RegisterItem(item);
@@ -1333,7 +1331,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetItemIdsList(query);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1344,7 +1342,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetStudios(query);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1355,7 +1353,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetGenres(query);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1366,7 +1364,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetMusicGenres(query);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1377,7 +1375,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetAllArtists(query);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1421,7 +1419,7 @@ namespace Emby.Server.Implementations.Library
}
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1808,18 +1806,16 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task.</returns>
public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
{
- var list = items.ToList();
-
- ItemRepository.SaveItems(list, cancellationToken);
+ ItemRepository.SaveItems(items, cancellationToken);
- foreach (var item in list)
+ foreach (var item in items)
{
RegisterItem(item);
}
if (ItemAdded != null)
{
- foreach (var item in list)
+ foreach (var item in items)
{
// With the live tv guide this just creates too much noise
if (item.SourceType != SourceType.Library)
@@ -1853,7 +1849,7 @@ namespace Emby.Server.Implementations.Library
/// <summary>
/// Updates the item.
/// </summary>
- public void UpdateItems(List<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+ public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{
foreach (var item in items)
{
@@ -1908,7 +1904,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task.</returns>
public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{
- UpdateItems(new List<BaseItem> { item }, parent, updateReason, cancellationToken);
+ UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
}
/// <summary>
@@ -2005,9 +2001,7 @@ namespace Emby.Server.Implementations.Library
.FirstOrDefault();
}
- var options = collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
-
- return options;
+ return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
}
public string GetContentType(BaseItem item)
@@ -2017,11 +2011,13 @@ namespace Emby.Server.Implementations.Library
{
return configuredContentType;
}
+
configuredContentType = GetConfiguredContentType(item, true);
if (!string.IsNullOrEmpty(configuredContentType))
{
return configuredContentType;
}
+
return GetInheritedContentType(item);
}
@@ -2056,6 +2052,7 @@ namespace Emby.Server.Implementations.Library
{
return collectionFolder.CollectionType;
}
+
return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath);
}
@@ -2066,6 +2063,7 @@ namespace Emby.Server.Implementations.Library
{
return nameValuePair.Value;
}
+
return null;
}
@@ -2108,9 +2106,9 @@ namespace Emby.Server.Implementations.Library
string viewType,
string sortName)
{
- var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views");
-
- path = Path.Combine(path, _fileSystem.GetValidFilename(viewType));
+ var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath,
+ "views",
+ _fileSystem.GetValidFilename(viewType));
var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView));
@@ -2543,7 +2541,7 @@ namespace Emby.Server.Implementations.Library
var resolvers = new IItemResolver[]
{
- new GenericVideoResolver<Trailer>(this, _fileSystem)
+ new GenericVideoResolver<Trailer>(this)
};
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index d992f8d03..541b13cbe 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
@@ -18,11 +17,9 @@ namespace Emby.Server.Implementations.Library.Resolvers
where T : Video, new()
{
protected readonly ILibraryManager LibraryManager;
- protected readonly IFileSystem FileSystem;
- protected BaseVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem)
+ protected BaseVideoResolver(ILibraryManager libraryManager)
{
- FileSystem = fileSystem;
LibraryManager = libraryManager;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 472a3f105..848563679 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -548,7 +548,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private IImageProcessor _imageProcessor;
- public MovieResolver(ILibraryManager libraryManager, IFileSystem fileSystem, IImageProcessor imageProcessor) : base(libraryManager, fileSystem)
+ public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
+ : base(libraryManager)
{
_imageProcessor = imageProcessor;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index a3298c580..db270c398 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -7,7 +7,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
@@ -15,13 +14,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
private readonly IImageProcessor _imageProcessor;
private readonly ILibraryManager _libraryManager;
- private readonly IFileSystem _fileSystem;
- public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager, IFileSystem fileSystem)
+ public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{
_imageProcessor = imageProcessor;
_libraryManager = libraryManager;
- _fileSystem = fileSystem;
}
/// <summary>
@@ -113,8 +110,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
return false;
}
- return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'));
+ return imageProcessor.SupportedInputFormats.Contains(Path.GetExtension(path).TrimStart('.'), StringComparer.Ordinal);
}
-
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
index fa8c89e88..7e4b38b4c 100644
--- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
@@ -9,7 +9,7 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
- class SpecialFolderResolver : FolderResolver<Folder>
+ public class SpecialFolderResolver : FolderResolver<Folder>
{
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index fed0904d1..a6d18c9b5 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -3,7 +3,6 @@ using System.Linq;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers.TV
{
@@ -74,7 +73,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
- public EpisodeResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
+ public EpisodeResolver(ILibraryManager libraryManager)
+ : base(libraryManager)
{
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
index 60752a85d..68d5d8b2d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
@@ -1,13 +1,13 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
public class GenericVideoResolver<T> : BaseVideoResolver<T>
where T : Video, new()
{
- public GenericVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
+ public GenericVideoResolver(ILibraryManager libraryManager)
+ : base(libraryManager)
{
}
}
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 3ff84382f..dfef8e997 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -74,7 +74,6 @@ namespace Emby.Server.Implementations.Library
private readonly Func<IDtoService> _dtoServiceFactory;
private readonly IServerApplicationHost _appHost;
private readonly IFileSystem _fileSystem;
- private readonly ICryptoProvider _cryptographyProvider;
private IAuthenticationProvider[] _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider;
@@ -89,8 +88,7 @@ namespace Emby.Server.Implementations.Library
Func<IDtoService> dtoServiceFactory,
IServerApplicationHost appHost,
IJsonSerializer jsonSerializer,
- IFileSystem fileSystem,
- ICryptoProvider cryptographyProvider)
+ IFileSystem fileSystem)
{
_logger = loggerFactory.CreateLogger(nameof(UserManager));
UserRepository = userRepository;
@@ -101,7 +99,6 @@ namespace Emby.Server.Implementations.Library
_appHost = appHost;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
- _cryptographyProvider = cryptographyProvider;
ConfigurationManager = configurationManager;
_users = Array.Empty<User>();
@@ -171,9 +168,9 @@ namespace Emby.Server.Implementations.Library
/// <exception cref="ArgumentNullException"></exception>
public User GetUserById(Guid id)
{
- if (id.Equals(Guid.Empty))
+ if (id == Guid.Empty)
{
- throw new ArgumentNullException(nameof(id));
+ throw new ArgumentException(nameof(id), "Guid can't be empty");
}
return Users.FirstOrDefault(u => u.Id == id);
diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
index 0ea543ba0..7899cf01b 100644
--- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
@@ -24,7 +24,6 @@ namespace Emby.Server.Implementations.Library.Validators
/// </summary>
private readonly ILogger _logger;
- private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
/// <summary>
@@ -32,11 +31,10 @@ namespace Emby.Server.Implementations.Library.Validators
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
- public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem)
+ public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem)
{
_libraryManager = libraryManager;
_logger = logger;
- _config = config;
_fileSystem = fileSystem;
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 84ca130b7..fceb82ba1 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -105,8 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_mediaSourceManager = mediaSourceManager;
_streamHelper = streamHelper;
- _seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
- _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger);
+ _seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
+ _timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger);
_timerProvider.TimerFired += _timerProvider_TimerFired;
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
@@ -1708,7 +1708,7 @@ 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, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _httpClient, _processFactory, _config, _assemblyInfo);
+ return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
}
return new DirectRecorder(_logger, _httpClient, _fileSystem, _streamHelper);
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index eed239514..9a9bae215 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -7,7 +7,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
@@ -17,7 +16,6 @@ using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Reflection;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
@@ -27,7 +25,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
- private readonly IHttpClient _httpClient;
private readonly IMediaEncoder _mediaEncoder;
private readonly IServerApplicationPaths _appPaths;
private bool _hasExited;
@@ -38,19 +35,23 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IJsonSerializer _json;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly IServerConfigurationManager _config;
- private readonly IAssemblyInfo _assemblyInfo;
- public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, IHttpClient httpClient, IProcessFactory processFactory, IServerConfigurationManager config, IAssemblyInfo assemblyInfo)
+ public EncodedRecorder(
+ ILogger logger,
+ IFileSystem fileSystem,
+ IMediaEncoder mediaEncoder,
+ IServerApplicationPaths appPaths,
+ IJsonSerializer json,
+ IProcessFactory processFactory,
+ IServerConfigurationManager config)
{
_logger = logger;
_fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
_json = json;
- _httpClient = httpClient;
_processFactory = processFactory;
_config = config;
- _assemblyInfo = assemblyInfo;
}
private static bool CopySubtitles => false;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index 6b02eaea8..a2ac60b31 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -17,15 +17,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
protected readonly ILogger Logger;
private readonly string _dataPath;
protected readonly Func<T, T, bool> EqualityComparer;
- private readonly IFileSystem _fileSystem;
- public ItemDataProvider(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer)
+ public ItemDataProvider(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer)
{
Logger = logger;
_dataPath = dataPath;
EqualityComparer = equalityComparer;
_jsonSerializer = jsonSerializer;
- _fileSystem = fileSystem;
}
public IReadOnlyList<T> GetAll()
@@ -45,12 +43,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var jsonFile = path + ".json";
- try
+ if (!File.Exists(jsonFile))
{
- return _jsonSerializer.DeserializeFromFile<List<T>>(jsonFile) ?? new List<T>();
+ return new List<T>();
}
- catch (FileNotFoundException)
+
+ try
{
+ return _jsonSerializer.DeserializeFromFile<List<T>>(jsonFile) ?? new List<T>();
}
catch (IOException)
{
@@ -59,6 +59,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Logger.LogError(ex, "Error deserializing {jsonFile}", jsonFile);
}
+
return new List<T>();
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
index d2ad65a1e..520b44404 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
@@ -1,6 +1,5 @@
using System;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
@@ -8,8 +7,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
public class SeriesTimerManager : ItemDataProvider<SeriesTimerInfo>
{
- public SeriesTimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
- : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
+ public SeriesTimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
+ : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 1dcb02f43..3c807a8ea 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -5,7 +5,6 @@ using System.Linq;
using System.Threading;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Events;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
@@ -19,8 +18,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
- public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1)
- : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
+ public TimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1)
+ : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
_logger = logger1;
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index a36302876..f7ef16fb0 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -184,7 +184,7 @@ namespace Emby.Server.Implementations.LiveTv
public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
{
- var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId);
+ var user = query.UserId == Guid.Empty ? null : _userManager.GetUserById(query.UserId);
var topFolder = GetInternalLiveTvFolder(cancellationToken);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 6d1eff187..715f600a1 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -9,7 +9,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
@@ -23,18 +22,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected readonly IServerConfigurationManager Config;
protected readonly ILogger Logger;
protected IJsonSerializer JsonSerializer;
- protected readonly IMediaEncoder MediaEncoder;
protected readonly IFileSystem FileSystem;
private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
- protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem)
+ protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
{
Config = config;
Logger = logger;
JsonSerializer = jsonSerializer;
- MediaEncoder = mediaEncoder;
FileSystem = fileSystem;
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 77b09a83d..24b100edd 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -31,15 +31,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
- private readonly IEnvironmentInfo _environment;
- public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
+ public HdHomerunHost(
+ IServerConfigurationManager config,
+ ILogger logger,
+ IJsonSerializer jsonSerializer,
+ IFileSystem fileSystem,
+ IHttpClient httpClient,
+ IServerApplicationHost appHost,
+ ISocketFactory socketFactory,
+ INetworkManager networkManager)
+ : base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_socketFactory = socketFactory;
_networkManager = networkManager;
- _environment = environment;
}
public string Name => "HD Homerun";
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 638796e2e..fdaaf0bae 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -26,15 +26,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost;
- private readonly IEnvironmentInfo _environment;
private readonly INetworkManager _networkManager;
private readonly IMediaSourceManager _mediaSourceManager;
- public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment, INetworkManager networkManager) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
+ public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, INetworkManager networkManager)
+ : base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
- _environment = environment;
_networkManager = networkManager;
_mediaSourceManager = mediaSourceManager;
}
@@ -52,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
var channelIdPrefix = GetFullChannelIdPrefix(info);
- var result = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
+ var result = await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
return result.Cast<ChannelInfo>().ToList();
}
@@ -115,7 +114,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task Validate(TunerHostInfo info)
{
- using (var stream = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
+ using (var stream = await new M3uParser(Logger, _httpClient, _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 9a01c42d3..ad124bb0f 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -19,14 +19,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public class M3uParser
{
private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost;
- public M3uParser(ILogger logger, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost)
+ public M3uParser(ILogger logger, IHttpClient httpClient, IServerApplicationHost appHost)
{
_logger = logger;
- _fileSystem = fileSystem;
_httpClient = httpClient;
_appHost = appHost;
}
@@ -157,56 +155,56 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null;
string numberString = null;
+ string attributeValue;
+ double doubleValue;
- // Check for channel number with the format from SatIp
- // #EXTINF:0,84. VOX Schweiz
- // #EXTINF:0,84.0 - VOX Schweiz
- if (!string.IsNullOrWhiteSpace(nameInExtInf))
+ if (attributes.TryGetValue("tvg-chno", out attributeValue))
{
- var numberIndex = nameInExtInf.IndexOf(' ');
- if (numberIndex > 0)
+ if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
{
- var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });
-
- if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
- {
- numberString = numberPart;
- }
+ numberString = attributeValue;
}
}
- if (!string.IsNullOrWhiteSpace(numberString))
- {
- numberString = numberString.Trim();
- }
-
if (!IsValidChannelNumber(numberString))
{
- if (attributes.TryGetValue("tvg-id", out string value))
+ if (attributes.TryGetValue("tvg-id", out attributeValue))
{
- if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var doubleValue))
+ if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
{
- numberString = value;
+ numberString = attributeValue;
+ }
+ else if (attributes.TryGetValue("channel-id", out attributeValue))
+ {
+ if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
+ {
+ numberString = attributeValue;
+ }
}
}
- }
- if (!string.IsNullOrWhiteSpace(numberString))
- {
- numberString = numberString.Trim();
- }
-
- if (!IsValidChannelNumber(numberString))
- {
- if (attributes.TryGetValue("channel-id", out string value))
+ if (String.IsNullOrWhiteSpace(numberString))
{
- numberString = value;
+ // Using this as a fallback now as this leads to Problems with channels like "5 USA"
+ // where 5 isnt ment to be the channel number
+ // Check for channel number with the format from SatIp
+ // #EXTINF:0,84. VOX Schweiz
+ // #EXTINF:0,84.0 - VOX Schweiz
+ if (!string.IsNullOrWhiteSpace(nameInExtInf))
+ {
+ var numberIndex = nameInExtInf.IndexOf(' ');
+ if (numberIndex > 0)
+ {
+ var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });
+
+ if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
+ {
+ numberString = numberPart;
+ }
+ }
+ }
}
- }
- if (!string.IsNullOrWhiteSpace(numberString))
- {
- numberString = numberString.Trim();
}
if (!IsValidChannelNumber(numberString))
@@ -214,7 +212,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
numberString = null;
}
- if (string.IsNullOrWhiteSpace(numberString))
+ if (!string.IsNullOrWhiteSpace(numberString))
+ {
+ numberString = numberString.Trim();
+ }
+ else
{
if (string.IsNullOrWhiteSpace(mediaUrl))
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index 4eff9252e..d74cf3be2 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var now = DateTime.UtcNow;
- var _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
+ _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
//OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile;
diff --git a/Emby.Server.Implementations/Localization/Ratings/kz.csv b/Emby.Server.Implementations/Localization/Ratings/kz.csv
index 4441c5650..d546bff53 100644
--- a/Emby.Server.Implementations/Localization/Ratings/kz.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/kz.csv
@@ -1,6 +1,7 @@
-KZ-К,1
-KZ-БА,6
-KZ-Б14,7
-KZ-Е16,8
-KZ-Е18,10
-KZ-НА,15
+KZ-6-,0
+KZ-6+,6
+KZ-12+,12
+KZ-14+,14
+KZ-16+,16
+KZ-18+,18
+KZ-21+,21
diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs
index 60cc9b88e..ace93ebde 100644
--- a/Emby.Server.Implementations/Networking/NetworkManager.cs
+++ b/Emby.Server.Implementations/Networking/NetworkManager.cs
@@ -79,13 +79,13 @@ namespace Emby.Server.Implementations.Networking
private IpAddressInfo[] _localIpAddresses;
private readonly object _localIpAddressSyncLock = new object();
- public IpAddressInfo[] GetLocalIpAddresses()
+ public IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
{
lock (_localIpAddressSyncLock)
{
if (_localIpAddresses == null)
{
- var addresses = GetLocalIpAddressesInternal().Result.Select(ToIpAddressInfo).ToArray();
+ var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).Result.Select(ToIpAddressInfo).ToArray();
_localIpAddresses = addresses;
@@ -95,9 +95,9 @@ namespace Emby.Server.Implementations.Networking
}
}
- private async Task<List<IPAddress>> GetLocalIpAddressesInternal()
+ private async Task<List<IPAddress>> GetLocalIpAddressesInternal(bool ignoreVirtualInterface)
{
- var list = GetIPsDefault()
+ var list = GetIPsDefault(ignoreVirtualInterface)
.ToList();
if (list.Count == 0)
@@ -383,7 +383,7 @@ namespace Emby.Server.Implementations.Networking
return Dns.GetHostAddressesAsync(hostName);
}
- private List<IPAddress> GetIPsDefault()
+ private List<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
{
NetworkInterface[] interfaces;
@@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.Networking
// Try to exclude virtual adapters
// http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms
var addr = ipProperties.GatewayAddresses.FirstOrDefault();
- if (addr == null || string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))
+ if (addr == null || ignoreVirtualInterface && string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))
{
return new List<IPAddress>();
}
@@ -636,6 +636,66 @@ namespace Emby.Server.Implementations.Networking
return false;
}
+ public bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask)
+ {
+ IPAddress network1 = GetNetworkAddress(ToIPAddress(address1), ToIPAddress(subnetMask));
+ IPAddress network2 = GetNetworkAddress(ToIPAddress(address2), ToIPAddress(subnetMask));
+ return network1.Equals(network2);
+ }
+
+ private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
+ {
+ byte[] ipAdressBytes = address.GetAddressBytes();
+ byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
+
+ if (ipAdressBytes.Length != subnetMaskBytes.Length)
+ {
+ throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
+ }
+
+ byte[] broadcastAddress = new byte[ipAdressBytes.Length];
+ for (int i = 0; i < broadcastAddress.Length; i++)
+ {
+ broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
+ }
+ return new IPAddress(broadcastAddress);
+ }
+
+ public IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address)
+ {
+ NetworkInterface[] interfaces;
+ IPAddress ipaddress = ToIPAddress(address);
+
+ try
+ {
+ var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown };
+
+ interfaces = NetworkInterface.GetAllNetworkInterfaces()
+ .Where(i => validStatuses.Contains(i.OperationalStatus))
+ .ToArray();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error in GetAllNetworkInterfaces");
+ return null;
+ }
+
+ foreach (NetworkInterface ni in interfaces)
+ {
+ if (ni.GetIPProperties().GatewayAddresses.FirstOrDefault() != null)
+ {
+ foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
+ {
+ if (ip.Address.Equals(ipaddress) && ip.IPv4Mask != null)
+ {
+ return ToIpAddressInfo(ip.IPv4Mask);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
public static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint)
{
if (endpoint == null)
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
index 81fdb96d2..2f07ff15a 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <summary>
/// Class ChapterImagesTask
/// </summary>
- class ChapterImagesTask : IScheduledTask
+ public class ChapterImagesTask : IScheduledTask
{
/// <summary>
/// The _logger
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
index 98685cebe..ec9466c4a 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
@@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
var dueTime = triggerDate - now;
- logger.LogInformation("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate.ToString(), dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+ logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:g}, which is {DueTime:g} from now.", taskName, triggerDate, dueTime);
Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
}
diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs
index 44898d498..8ae7fd90c 100644
--- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs
+++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs
@@ -42,6 +42,27 @@ namespace Emby.Server.Implementations.Serialization
}
/// <summary>
+ /// Serializes to stream.
+ /// </summary>
+ /// <param name="obj">The obj.</param>
+ /// <param name="stream">The stream.</param>
+ /// <exception cref="ArgumentNullException">obj</exception>
+ public void SerializeToStream<T>(T obj, Stream stream)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+
+ if (stream == null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ ServiceStack.Text.JsonSerializer.SerializeToStream<T>(obj, stream);
+ }
+
+ /// <summary>
/// Serializes to file.
/// </summary>
/// <param name="obj">The obj.</param>
diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs
index 36975df50..05f6469ec 100644
--- a/Emby.Server.Implementations/ServerApplicationPaths.cs
+++ b/Emby.Server.Implementations/ServerApplicationPaths.cs
@@ -15,21 +15,17 @@ namespace Emby.Server.Implementations
/// </summary>
public ServerApplicationPaths(
string programDataPath,
- string appFolderPath,
- string applicationResourcesPath,
- string logDirectoryPath = null,
- string configurationDirectoryPath = null,
- string cacheDirectoryPath = null)
+ string logDirectoryPath,
+ string configurationDirectoryPath,
+ string cacheDirectoryPath)
: base(programDataPath,
- appFolderPath,
logDirectoryPath,
configurationDirectoryPath,
cacheDirectoryPath)
{
- ApplicationResourcesPath = applicationResourcesPath;
}
- public string ApplicationResourcesPath { get; private set; }
+ public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
/// <summary>
/// Gets the path to the base root media directory
@@ -148,7 +144,6 @@ namespace Emby.Server.Implementations
set => _internalMetadataPath = value;
}
- private const string _virtualInternalMetadataPath = "%MetadataPath%";
- public string VirtualInternalMetadataPath => _virtualInternalMetadataPath;
+ public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
}
}
diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs
index f575baca3..ccb28e8df 100644
--- a/Emby.Server.Implementations/Services/ServicePath.cs
+++ b/Emby.Server.Implementations/Services/ServicePath.cs
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Services
private const char ComponentSeperator = '.';
private const string VariablePrefix = "{";
- readonly bool[] componentsWithSeparators;
+ private readonly bool[] componentsWithSeparators;
private readonly string restPath;
public bool IsWildCardPath { get; private set; }
@@ -54,10 +54,6 @@ namespace Emby.Server.Implementations.Services
public string Description { get; private set; }
public bool IsHidden { get; private set; }
- public int Priority { get; set; } //passed back to RouteAttribute
-
- public IEnumerable<string> PathVariables => this.variablesNames.Where(e => !string.IsNullOrWhiteSpace(e));
-
public static string[] GetPathPartsForMatching(string pathInfo)
{
return pathInfo.ToLowerInvariant().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries);
@@ -83,9 +79,12 @@ namespace Emby.Server.Implementations.Services
{
list.Add(hashPrefix + part);
- var subParts = part.Split(ComponentSeperator);
- if (subParts.Length == 1) continue;
+ if (part.IndexOf(ComponentSeperator) == -1)
+ {
+ continue;
+ }
+ var subParts = part.Split(ComponentSeperator);
foreach (var subPart in subParts)
{
list.Add(hashPrefix + subPart);
@@ -114,7 +113,7 @@ namespace Emby.Server.Implementations.Services
{
if (string.IsNullOrEmpty(component)) continue;
- if (StringContains(component, VariablePrefix)
+ if (component.IndexOf(VariablePrefix, StringComparison.OrdinalIgnoreCase) != -1
&& component.IndexOf(ComponentSeperator) != -1)
{
hasSeparators.Add(true);
@@ -165,7 +164,11 @@ namespace Emby.Server.Implementations.Services
for (var i = 0; i < components.Length - 1; i++)
{
- if (!this.isWildcard[i]) continue;
+ if (!this.isWildcard[i])
+ {
+ continue;
+ }
+
if (this.literalsToMatch[i + 1] == null)
{
throw new ArgumentException(
@@ -173,7 +176,7 @@ namespace Emby.Server.Implementations.Services
}
}
- this.wildcardCount = this.isWildcard.Count(x => x);
+ this.wildcardCount = this.isWildcard.Length;
this.IsWildCardPath = this.wildcardCount > 0;
this.FirstMatchHashKey = !this.IsWildCardPath
@@ -181,19 +184,14 @@ namespace Emby.Server.Implementations.Services
: WildCardChar + PathSeperator + firstLiteralMatch;
this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType);
- RegisterCaseInsenstivePropertyNameMappings();
- }
- private void RegisterCaseInsenstivePropertyNameMappings()
- {
- foreach (var propertyInfo in GetSerializableProperties(RequestType))
- {
- var propertyName = propertyInfo.Name;
- propertyNamesMap.Add(propertyName.ToLowerInvariant(), propertyName);
- }
+ _propertyNamesMap = new HashSet<string>(
+ GetSerializableProperties(RequestType).Select(x => x.Name),
+ StringComparer.OrdinalIgnoreCase);
}
- internal static string[] IgnoreAttributesNamed = new[] {
+ internal static string[] IgnoreAttributesNamed = new[]
+ {
"IgnoreDataMemberAttribute",
"JsonIgnoreAttribute"
};
@@ -201,19 +199,12 @@ namespace Emby.Server.Implementations.Services
private static Type excludeType = typeof(Stream);
- internal static List<PropertyInfo> GetSerializableProperties(Type type)
+ internal static IEnumerable<PropertyInfo> GetSerializableProperties(Type type)
{
- var list = new List<PropertyInfo>();
- var props = GetPublicProperties(type);
-
- foreach (var prop in props)
+ foreach (var prop in GetPublicProperties(type))
{
- if (prop.GetMethod == null)
- {
- continue;
- }
-
- if (excludeType == prop.PropertyType)
+ if (prop.GetMethod == null
+ || excludeType == prop.PropertyType)
{
continue;
}
@@ -230,23 +221,21 @@ namespace Emby.Server.Implementations.Services
if (!ignored)
{
- list.Add(prop);
+ yield return prop;
}
}
-
- // else return those properties that are not decorated with IgnoreDataMember
- return list;
}
- private static List<PropertyInfo> GetPublicProperties(Type type)
+ private static IEnumerable<PropertyInfo> GetPublicProperties(Type type)
{
- if (type.GetTypeInfo().IsInterface)
+ if (type.IsInterface)
{
var propertyInfos = new List<PropertyInfo>();
-
- var considered = new List<Type>();
+ var considered = new List<Type>()
+ {
+ type
+ };
var queue = new Queue<Type>();
- considered.Add(type);
queue.Enqueue(type);
while (queue.Count > 0)
@@ -254,15 +243,16 @@ namespace Emby.Server.Implementations.Services
var subType = queue.Dequeue();
foreach (var subInterface in subType.GetTypeInfo().ImplementedInterfaces)
{
- if (considered.Contains(subInterface)) continue;
+ if (considered.Contains(subInterface))
+ {
+ continue;
+ }
considered.Add(subInterface);
queue.Enqueue(subInterface);
}
- var typeProperties = GetTypesPublicProperties(subType);
-
- var newPropertyInfos = typeProperties
+ var newPropertyInfos = GetTypesPublicProperties(subType)
.Where(x => !propertyInfos.Contains(x));
propertyInfos.InsertRange(0, newPropertyInfos);
@@ -271,28 +261,22 @@ namespace Emby.Server.Implementations.Services
return propertyInfos;
}
- var list = new List<PropertyInfo>();
-
- foreach (var t in GetTypesPublicProperties(type))
- {
- if (t.GetIndexParameters().Length == 0)
- {
- list.Add(t);
- }
- }
- return list;
+ return GetTypesPublicProperties(type)
+ .Where(x => x.GetIndexParameters().Length == 0);
}
- private static PropertyInfo[] GetTypesPublicProperties(Type subType)
+ private static IEnumerable<PropertyInfo> GetTypesPublicProperties(Type subType)
{
- var pis = new List<PropertyInfo>();
foreach (var pi in subType.GetRuntimeProperties())
{
var mi = pi.GetMethod ?? pi.SetMethod;
- if (mi != null && mi.IsStatic) continue;
- pis.Add(pi);
+ if (mi != null && mi.IsStatic)
+ {
+ continue;
+ }
+
+ yield return pi;
}
- return pis.ToArray();
}
/// <summary>
@@ -302,7 +286,7 @@ namespace Emby.Server.Implementations.Services
private readonly StringMapTypeDeserializer typeDeserializer;
- private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>();
+ private readonly HashSet<string> _propertyNamesMap;
public int MatchScore(string httpMethod, string[] withPathInfoParts)
{
@@ -312,13 +296,10 @@ namespace Emby.Server.Implementations.Services
return -1;
}
- var score = 0;
-
//Routes with least wildcard matches get the highest score
- score += Math.Max((100 - wildcardMatchCount), 1) * 1000;
-
- //Routes with less variable (and more literal) matches
- score += Math.Max((10 - VariableArgsCount), 1) * 100;
+ var score = Math.Max((100 - wildcardMatchCount), 1) * 1000
+ //Routes with less variable (and more literal) matches
+ + Math.Max((10 - VariableArgsCount), 1) * 100;
//Exact verb match is better than ANY
if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase))
@@ -333,11 +314,6 @@ namespace Emby.Server.Implementations.Services
return score;
}
- private bool StringContains(string str1, string str2)
- {
- return str1.IndexOf(str2, StringComparison.OrdinalIgnoreCase) != -1;
- }
-
/// <summary>
/// For performance withPathInfoParts should already be a lower case string
/// to minimize redundant matching operations.
@@ -374,7 +350,8 @@ namespace Emby.Server.Implementations.Services
if (i < this.TotalComponentsCount - 1)
{
// Continue to consume up until a match with the next literal
- while (pathIx < withPathInfoParts.Length && !LiteralsEqual(withPathInfoParts[pathIx], this.literalsToMatch[i + 1]))
+ while (pathIx < withPathInfoParts.Length
+ && !string.Equals(withPathInfoParts[pathIx], this.literalsToMatch[i + 1], StringComparison.InvariantCultureIgnoreCase))
{
pathIx++;
wildcardMatchCount++;
@@ -403,10 +380,12 @@ namespace Emby.Server.Implementations.Services
continue;
}
- if (withPathInfoParts.Length <= pathIx || !LiteralsEqual(withPathInfoParts[pathIx], literalToMatch))
+ if (withPathInfoParts.Length <= pathIx
+ || !string.Equals(withPathInfoParts[pathIx], literalToMatch, StringComparison.InvariantCultureIgnoreCase))
{
return false;
}
+
pathIx++;
}
}
@@ -414,35 +393,26 @@ namespace Emby.Server.Implementations.Services
return pathIx == withPathInfoParts.Length;
}
- private static bool LiteralsEqual(string str1, string str2)
- {
- // Most cases
- if (string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- // Handle turkish i
- str1 = str1.ToUpperInvariant();
- str2 = str2.ToUpperInvariant();
-
- // Invariant IgnoreCase would probably be better but it's not available in PCL
- return string.Equals(str1, str2, StringComparison.CurrentCultureIgnoreCase);
- }
-
private bool ExplodeComponents(ref string[] withPathInfoParts)
{
var totalComponents = new List<string>();
for (var i = 0; i < withPathInfoParts.Length; i++)
{
var component = withPathInfoParts[i];
- if (string.IsNullOrEmpty(component)) continue;
+ if (string.IsNullOrEmpty(component))
+ {
+ continue;
+ }
if (this.PathComponentsCount != this.TotalComponentsCount
&& this.componentsWithSeparators[i])
{
var subComponents = component.Split(ComponentSeperator);
- if (subComponents.Length < 2) return false;
+ if (subComponents.Length < 2)
+ {
+ return false;
+ }
+
totalComponents.AddRange(subComponents);
}
else
@@ -483,7 +453,7 @@ namespace Emby.Server.Implementations.Services
continue;
}
- if (!this.propertyNamesMap.TryGetValue(variableName.ToLowerInvariant(), out var propertyNameOnRequest))
+ if (!this._propertyNamesMap.Contains(variableName))
{
if (string.Equals("ignore", variableName, StringComparison.OrdinalIgnoreCase))
{
@@ -507,6 +477,7 @@ namespace Emby.Server.Implementations.Services
{
sb.Append(PathSeperatorChar + requestComponents[j]);
}
+
value = sb.ToString();
}
else
@@ -517,13 +488,13 @@ namespace Emby.Server.Implementations.Services
var stopLiteral = i == this.TotalComponentsCount - 1 ? null : this.literalsToMatch[i + 1];
if (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
{
- var sb = new StringBuilder();
- sb.Append(value);
+ var sb = new StringBuilder(value);
pathIx++;
while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
{
sb.Append(PathSeperatorChar + requestComponents[pathIx++]);
}
+
value = sb.ToString();
}
else
@@ -538,7 +509,7 @@ namespace Emby.Server.Implementations.Services
pathIx++;
}
- requestKeyValuesMap[propertyNameOnRequest] = value;
+ requestKeyValuesMap[variableName] = value;
}
if (queryStringAndFormData != null)
diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
index d13935fba..f835aa1b5 100644
--- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
+++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
@@ -11,15 +11,16 @@ namespace Emby.Server.Implementations.Services
{
internal class PropertySerializerEntry
{
- public PropertySerializerEntry(Action<object, object> propertySetFn, Func<string, object> propertyParseStringFn)
+ public PropertySerializerEntry(Action<object, object> propertySetFn, Func<string, object> propertyParseStringFn, Type propertyType)
{
PropertySetFn = propertySetFn;
PropertyParseStringFn = propertyParseStringFn;
+ PropertyType = PropertyType;
}
- public Action<object, object> PropertySetFn;
- public Func<string, object> PropertyParseStringFn;
- public Type PropertyType;
+ public Action<object, object> PropertySetFn { get; private set; }
+ public Func<string, object> PropertyParseStringFn { get; private set; }
+ public Type PropertyType { get; private set; }
}
private readonly Type type;
@@ -29,7 +30,9 @@ namespace Emby.Server.Implementations.Services
public Func<string, object> GetParseFn(Type propertyType)
{
if (propertyType == typeof(string))
+ {
return s => s;
+ }
return _GetParseFn(propertyType);
}
@@ -48,7 +51,7 @@ namespace Emby.Server.Implementations.Services
var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo);
var propertyType = propertyInfo.PropertyType;
var propertyParseStringFn = GetParseFn(propertyType);
- var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn) { PropertyType = propertyType };
+ var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn, propertyType);
propertySetterMap[propertyInfo.Name] = propertySerializer;
}
@@ -56,34 +59,21 @@ namespace Emby.Server.Implementations.Services
public object PopulateFromMap(object instance, IDictionary<string, string> keyValuePairs)
{
- string propertyName = null;
- string propertyTextValue = null;
PropertySerializerEntry propertySerializerEntry = null;
if (instance == null)
+ {
instance = _CreateInstanceFn(type);
+ }
foreach (var pair in keyValuePairs)
{
- propertyName = pair.Key;
- propertyTextValue = pair.Value;
-
- if (string.IsNullOrEmpty(propertyTextValue))
- {
- continue;
- }
+ string propertyName = pair.Key;
+ string propertyTextValue = pair.Value;
- if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry))
- {
- if (propertyName == "v")
- {
- continue;
- }
-
- continue;
- }
-
- if (propertySerializerEntry.PropertySetFn == null)
+ if (string.IsNullOrEmpty(propertyTextValue)
+ || !propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry)
+ || propertySerializerEntry.PropertySetFn == null)
{
continue;
}
@@ -99,6 +89,7 @@ namespace Emby.Server.Implementations.Services
{
continue;
}
+
propertySerializerEntry.PropertySetFn(instance, value);
}
@@ -107,7 +98,11 @@ namespace Emby.Server.Implementations.Services
public static string LeftPart(string strVal, char needle)
{
- if (strVal == null) return null;
+ if (strVal == null)
+ {
+ return null;
+ }
+
var pos = strVal.IndexOf(needle);
return pos == -1
? strVal
@@ -119,7 +114,10 @@ namespace Emby.Server.Implementations.Services
{
public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo)
{
- if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) return null;
+ if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0)
+ {
+ return null;
+ }
var setMethodInfo = propertyInfo.SetMethod;
return (instance, value) => setMethodInfo.Invoke(instance, new[] { value });
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index fa0ab62d3..03e7b2654 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -1090,7 +1090,7 @@ namespace Emby.Server.Implementations.Session
await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false);
}
- private IList<BaseItem> TranslateItemForPlayback(Guid id, User user)
+ private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user)
{
var item = _libraryManager.GetItemById(id);
diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
index 271188314..16507466f 100644
--- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
+++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
- class AiredEpisodeOrderComparer : IBaseItemComparer
+ public class AiredEpisodeOrderComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs
index 942e84704..46e0dd918 100644
--- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs
+++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
- class SeriesSortNameComparer : IBaseItemComparer
+ public class SeriesSortNameComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index dc714ed18..5060476ba 100644
--- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -282,7 +282,7 @@ namespace Jellyfin.Drawing.Skia
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
// decode
- var _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
+ _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
origin = codec.EncodedOrigin;
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index 315e34a04..84d78d3fb 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -5,28 +5,47 @@ using Emby.Server.Implementations.HttpServer;
using Jellyfin.Server.SocketSharp;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server
{
public class CoreAppHost : ApplicationHost
{
- public CoreAppHost(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, StartupOptions options, IFileSystem fileSystem, IEnvironmentInfo environmentInfo, MediaBrowser.Controller.Drawing.IImageEncoder imageEncoder, MediaBrowser.Common.Net.INetworkManager networkManager)
- : base(applicationPaths, loggerFactory, options, fileSystem, environmentInfo, imageEncoder, networkManager)
+ public CoreAppHost(
+ ServerApplicationPaths applicationPaths,
+ ILoggerFactory loggerFactory,
+ StartupOptions options,
+ IFileSystem fileSystem,
+ IEnvironmentInfo environmentInfo,
+ MediaBrowser.Controller.Drawing.IImageEncoder imageEncoder,
+ MediaBrowser.Common.Net.INetworkManager networkManager,
+ IConfiguration configuration)
+ : base(
+ applicationPaths,
+ loggerFactory,
+ options,
+ fileSystem,
+ environmentInfo,
+ imageEncoder,
+ networkManager,
+ configuration)
{
}
public override bool CanSelfRestart => StartupOptions.RestartPath != null;
+ protected override bool SupportsDualModeSockets => true;
+
protected override void RestartInternal() => Program.Restart();
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
- => new[] { typeof(CoreAppHost).Assembly };
+ {
+ yield return typeof(CoreAppHost).Assembly;
+ }
protected override void ShutdownInternal() => Program.Shutdown();
- protected override bool SupportsDualModeSockets => true;
-
protected override IHttpListener CreateHttpListener()
=> new WebSocketSharpListener(
Logger,
@@ -37,7 +56,6 @@ namespace Jellyfin.Server
CryptographyProvider,
SupportsDualModeSockets,
FileSystemManager,
- EnvironmentInfo
- );
+ EnvironmentInfo);
}
}
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index b1515df43..bd670df52 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -5,11 +5,14 @@
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<!-- We need C# 7.1 for async main-->
<LangVersion>latest</LangVersion>
+ <!-- Disable documentation warnings (for now) -->
+ <NoWarn>SA1600;CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>
@@ -20,6 +23,10 @@
<EmbeddedResource Include="Resources/Configuration/*" />
</ItemGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 7826fde35..41ee73a56 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -21,6 +21,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.AspNetCore;
@@ -34,6 +35,7 @@ namespace Jellyfin.Server
private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory();
private static ILogger _logger;
private static bool _restartOnShutdown;
+ private static IConfiguration appConfig;
public static async Task Main(string[] args)
{
@@ -56,13 +58,32 @@ namespace Jellyfin.Server
errs => Task.FromResult(0)).ConfigureAwait(false);
}
+ public static void Shutdown()
+ {
+ if (!_tokenSource.IsCancellationRequested)
+ {
+ _tokenSource.Cancel();
+ }
+ }
+
+ public static void Restart()
+ {
+ _restartOnShutdown = true;
+
+ Shutdown();
+ }
+
private static async Task StartApp(StartupOptions options)
{
ServerApplicationPaths appPaths = CreateApplicationPaths(options);
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
- await CreateLogger(appPaths);
+
+ appConfig = await CreateConfiguration(appPaths).ConfigureAwait(false);
+
+ CreateLogger(appConfig, appPaths);
+
_logger = _loggerFactory.CreateLogger("Main");
AppDomain.CurrentDomain.UnhandledException += (sender, e)
@@ -75,6 +96,7 @@ namespace Jellyfin.Server
{
return; // Already shutting down
}
+
e.Cancel = true;
_logger.LogInformation("Ctrl+C, shutting down");
Environment.ExitCode = 128 + 2;
@@ -88,6 +110,7 @@ namespace Jellyfin.Server
{
return; // Already shutting down
}
+
_logger.LogInformation("Received a SIGTERM signal, shutting down");
Environment.ExitCode = 128 + 15;
Shutdown();
@@ -101,9 +124,9 @@ namespace Jellyfin.Server
SQLitePCL.Batteries_V2.Init();
// Allow all https requests
- ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
+ ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; } );
- var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, null, appPaths.TempDirectory, true);
+ var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, appPaths);
using (var appHost = new CoreAppHost(
appPaths,
@@ -112,20 +135,21 @@ namespace Jellyfin.Server
fileSystem,
environmentInfo,
new NullImageEncoder(),
- new NetworkManager(_loggerFactory, environmentInfo)))
+ new NetworkManager(_loggerFactory, environmentInfo),
+ appConfig))
{
- await appHost.Init();
+ await appHost.Init(new ServiceCollection()).ConfigureAwait(false);
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
- await appHost.RunStartupTasks();
+ await appHost.RunStartupTasks().ConfigureAwait(false);
// TODO: read input for a stop command
try
{
// Block main thread until shutdown
- await Task.Delay(-1, _tokenSource.Token);
+ await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
@@ -139,136 +163,170 @@ namespace Jellyfin.Server
}
}
+ /// <summary>
+ /// Create the data, config and log paths from the variety of inputs(command line args,
+ /// environment variables) or decide on what default to use. For Windows it's %AppPath%
+ /// for everything else the XDG approach is followed:
+ /// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ /// </summary>
+ /// <param name="options">StartupOptions</param>
+ /// <returns>ServerApplicationPaths</returns>
private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options)
{
- string programDataPath = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
- if (string.IsNullOrEmpty(programDataPath))
+ // dataDir
+ // IF --datadir
+ // ELSE IF $JELLYFIN_DATA_PATH
+ // ELSE IF windows, use <%APPDATA%>/jellyfin
+ // ELSE IF $XDG_DATA_HOME then use $XDG_DATA_HOME/jellyfin
+ // ELSE use $HOME/.local/share/jellyfin
+ var dataDir = options.DataDir;
+
+ if (string.IsNullOrEmpty(dataDir))
{
- if (options.DataDir != null)
+ dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
+
+ if (string.IsNullOrEmpty(dataDir))
{
- programDataPath = options.DataDir;
+ // LocalApplicationData follows the XDG spec on unix machines
+ dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "jellyfin");
}
- else
+ }
+
+ Directory.CreateDirectory(dataDir);
+
+ // configDir
+ // IF --configdir
+ // ELSE IF $JELLYFIN_CONFIG_DIR
+ // ELSE IF --datadir, use <datadir>/config (assume portable run)
+ // ELSE IF <datadir>/config exists, use that
+ // ELSE IF windows, use <datadir>/config
+ // ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin
+ // ELSE $HOME/.config/jellyfin
+ var configDir = options.ConfigDir;
+ if (string.IsNullOrEmpty(configDir))
+ {
+ configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
+
+ if (string.IsNullOrEmpty(configDir))
{
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ if (options.DataDir != null || Directory.Exists(Path.Combine(dataDir, "config")) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+ // Hang config folder off already set dataDir
+ configDir = Path.Combine(dataDir, "config");
}
else
{
- // $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
- programDataPath = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
- // If $XDG_DATA_HOME is either not set or empty, $HOME/.local/share should be used.
- if (string.IsNullOrEmpty(programDataPath))
+ // $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
+ configDir = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
+
+ // If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME /.config should be used.
+ if (string.IsNullOrEmpty(configDir))
{
- programDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share");
+ configDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
}
- }
-
- programDataPath = Path.Combine(programDataPath, "jellyfin");
- }
- }
- if (string.IsNullOrEmpty(programDataPath))
- {
- Console.WriteLine("Cannot continue without path to program data folder (try -programdata)");
- Environment.Exit(1);
- }
- else
- {
- Directory.CreateDirectory(programDataPath);
- }
-
- string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
- if (string.IsNullOrEmpty(configDir))
- {
- if (options.ConfigDir != null)
- {
- configDir = options.ConfigDir;
- }
- else
- {
- // Let BaseApplicationPaths set up the default value
- configDir = null;
+ configDir = Path.Combine(configDir, "jellyfin");
+ }
}
}
- if (configDir != null)
- {
- Directory.CreateDirectory(configDir);
- }
+ // cacheDir
+ // IF --cachedir
+ // ELSE IF $JELLYFIN_CACHE_DIR
+ // ELSE IF windows, use <datadir>/cache
+ // ELSE IF XDG_CACHE_HOME, use $XDG_CACHE_HOME/jellyfin
+ // ELSE HOME/.cache/jellyfin
+ var cacheDir = options.CacheDir;
- string cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
if (string.IsNullOrEmpty(cacheDir))
{
- if (options.CacheDir != null)
- {
- cacheDir = options.CacheDir;
- }
- else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
+
+ if (string.IsNullOrEmpty(cacheDir))
{
- // $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
- cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
- // If $XDG_CACHE_HOME is either not set or empty, $HOME/.cache should be used.
- if (string.IsNullOrEmpty(cacheDir))
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
+ // Hang cache folder off already set dataDir
+ cacheDir = Path.Combine(dataDir, "cache");
+ }
+ else
+ {
+ // $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
+ cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
+
+ // If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
+ if (string.IsNullOrEmpty(cacheDir))
+ {
+ cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
+ }
+
+ cacheDir = Path.Combine(cacheDir, "jellyfin");
}
- cacheDir = Path.Combine(cacheDir, "jellyfin");
}
}
- if (cacheDir != null)
- {
- Directory.CreateDirectory(cacheDir);
- }
+ // logDir
+ // IF --logdir
+ // ELSE IF $JELLYFIN_LOG_DIR
+ // ELSE IF --datadir, use <datadir>/log (assume portable run)
+ // ELSE <datadir>/log
+ var logDir = options.LogDir;
- string logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
if (string.IsNullOrEmpty(logDir))
{
- if (options.LogDir != null)
- {
- logDir = options.LogDir;
- }
- else
+ logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
+
+ if (string.IsNullOrEmpty(logDir))
{
- // Let BaseApplicationPaths set up the default value
- logDir = null;
+ // Hang log folder off already set dataDir
+ logDir = Path.Combine(dataDir, "log");
}
}
- if (logDir != null)
+ // Ensure the main folders exist before we continue
+ try
{
Directory.CreateDirectory(logDir);
+ Directory.CreateDirectory(configDir);
+ Directory.CreateDirectory(cacheDir);
+ }
+ catch (IOException ex)
+ {
+ Console.Error.WriteLine("Error whilst attempting to create folder");
+ Console.Error.WriteLine(ex.ToString());
+ Environment.Exit(1);
}
- string appPath = AppContext.BaseDirectory;
-
- return new ServerApplicationPaths(programDataPath, appPath, appPath, logDir, configDir, cacheDir);
+ return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir);
}
- private static async Task CreateLogger(IApplicationPaths appPaths)
+ private static async Task<IConfiguration> CreateConfiguration(IApplicationPaths appPaths)
{
- try
- {
- string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, "logging.json");
+ string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, "logging.json");
- if (!File.Exists(configPath))
+ if (!File.Exists(configPath))
+ {
+ // For some reason the csproj name is used instead of the assembly name
+ using (Stream rscstr = typeof(Program).Assembly
+ .GetManifestResourceStream("Jellyfin.Server.Resources.Configuration.logging.json"))
+ using (Stream fstr = File.Open(configPath, FileMode.CreateNew))
{
- // For some reason the csproj name is used instead of the assembly name
- using (Stream rscstr = typeof(Program).Assembly
- .GetManifestResourceStream("Jellyfin.Server.Resources.Configuration.logging.json"))
- using (Stream fstr = File.Open(configPath, FileMode.CreateNew))
- {
- await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
- }
+ await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
}
- var configuration = new ConfigurationBuilder()
- .SetBasePath(appPaths.ConfigurationDirectoryPath)
- .AddJsonFile("logging.json")
- .AddEnvironmentVariables("JELLYFIN_")
- .Build();
+ }
+ return new ConfigurationBuilder()
+ .SetBasePath(appPaths.ConfigurationDirectoryPath)
+ .AddJsonFile("logging.json")
+ .AddEnvironmentVariables("JELLYFIN_")
+ .AddInMemoryCollection(ConfigurationOptions.Configuration)
+ .Build();
+ }
+
+ private static void CreateLogger(IConfiguration configuration, IApplicationPaths appPaths)
+ {
+ try
+ {
// Serilog.Log is used by SerilogLoggerFactory when no logger is specified
Serilog.Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
@@ -290,7 +348,7 @@ namespace Jellyfin.Server
}
}
- public static IImageEncoder GetImageEncoder(
+ private static IImageEncoder GetImageEncoder(
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILocalizationManager localizationManager)
@@ -331,26 +389,12 @@ namespace Jellyfin.Server
{
return MediaBrowser.Model.System.OperatingSystem.BSD;
}
+
throw new Exception($"Can't resolve OS with description: '{osDescription}'");
}
}
}
- public static void Shutdown()
- {
- if (!_tokenSource.IsCancellationRequested)
- {
- _tokenSource.Cancel();
- }
- }
-
- public static void Restart()
- {
- _restartOnShutdown = true;
-
- Shutdown();
- }
-
private static void StartNewInstance(StartupOptions options)
{
_logger.LogInformation("Starting new instance");
diff --git a/Jellyfin.Server/SocketSharp/HttpFile.cs b/Jellyfin.Server/SocketSharp/HttpFile.cs
index 89c75e536..448b666b6 100644
--- a/Jellyfin.Server/SocketSharp/HttpFile.cs
+++ b/Jellyfin.Server/SocketSharp/HttpFile.cs
@@ -6,9 +6,13 @@ namespace Jellyfin.Server.SocketSharp
public class HttpFile : IHttpFile
{
public string Name { get; set; }
+
public string FileName { get; set; }
+
public long ContentLength { get; set; }
+
public string ContentType { get; set; }
+
public Stream InputStream { get; set; }
}
}
diff --git a/Jellyfin.Server/SocketSharp/HttpPostedFile.cs b/Jellyfin.Server/SocketSharp/HttpPostedFile.cs
new file mode 100644
index 000000000..f38ed848e
--- /dev/null
+++ b/Jellyfin.Server/SocketSharp/HttpPostedFile.cs
@@ -0,0 +1,204 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Services;
+
+public sealed class HttpPostedFile : IDisposable
+{
+ private string _name;
+ private string _contentType;
+ private Stream _stream;
+ private bool _disposed = false;
+
+ internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length)
+ {
+ _name = name;
+ _contentType = content_type;
+ _stream = new ReadSubStream(base_stream, offset, length);
+ }
+
+ public string ContentType => _contentType;
+
+ public int ContentLength => (int)_stream.Length;
+
+ public string FileName => _name;
+
+ public Stream InputStream => _stream;
+
+ /// <summary>
+ /// Releases the unmanaged resources and disposes of the managed resources used.
+ /// </summary>
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _stream.Dispose();
+ _stream = null;
+
+ _name = null;
+ _contentType = null;
+
+ _disposed = true;
+ }
+
+ private class ReadSubStream : Stream
+ {
+ private Stream _stream;
+ private long _offset;
+ private long _end;
+ private long _position;
+
+ public ReadSubStream(Stream s, long offset, long length)
+ {
+ _stream = s;
+ _offset = offset;
+ _end = offset + length;
+ _position = offset;
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public override int Read(byte[] buffer, int dest_offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if (dest_offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0");
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), "< 0");
+ }
+
+ int len = buffer.Length;
+ if (dest_offset > len)
+ {
+ throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset));
+ }
+
+ // reordered to avoid possible integer overflow
+ if (dest_offset > len - count)
+ {
+ throw new ArgumentException("Reading would overrun buffer", nameof(count));
+ }
+
+ if (count > _end - _position)
+ {
+ count = (int)(_end - _position);
+ }
+
+ if (count <= 0)
+ {
+ return 0;
+ }
+
+ _stream.Position = _position;
+ int result = _stream.Read(buffer, dest_offset, count);
+ if (result > 0)
+ {
+ _position += result;
+ }
+ else
+ {
+ _position = _end;
+ }
+
+ return result;
+ }
+
+ public override int ReadByte()
+ {
+ if (_position >= _end)
+ {
+ return -1;
+ }
+
+ _stream.Position = _position;
+ int result = _stream.ReadByte();
+ if (result < 0)
+ {
+ _position = _end;
+ }
+ else
+ {
+ _position++;
+ }
+
+ return result;
+ }
+
+ public override long Seek(long d, SeekOrigin origin)
+ {
+ long real;
+ switch (origin)
+ {
+ case SeekOrigin.Begin:
+ real = _offset + d;
+ break;
+ case SeekOrigin.End:
+ real = _end + d;
+ break;
+ case SeekOrigin.Current:
+ real = _position + d;
+ break;
+ default:
+ throw new ArgumentException("Unknown SeekOrigin value", nameof(origin));
+ }
+
+ long virt = real - _offset;
+ if (virt < 0 || virt > Length)
+ {
+ throw new ArgumentException("Invalid position", nameof(d));
+ }
+
+ _position = _stream.Seek(real, SeekOrigin.Begin);
+ return _position;
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool CanRead => true;
+
+ public override bool CanSeek => true;
+
+ public override bool CanWrite => false;
+
+ public override long Length => _end - _offset;
+
+ public override long Position
+ {
+ get => _position - _offset;
+ set
+ {
+ if (value > Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ _position = Seek(value, SeekOrigin.Begin);
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs
index a8ba4cdb5..8396ad600 100644
--- a/Jellyfin.Server/SocketSharp/RequestMono.cs
+++ b/Jellyfin.Server/SocketSharp/RequestMono.cs
@@ -11,9 +11,9 @@ namespace Jellyfin.Server.SocketSharp
{
public partial class WebSocketSharpRequest : IHttpRequest
{
- internal static string GetParameter(string header, string attr)
+ internal static string GetParameter(ReadOnlySpan<char> header, string attr)
{
- int ap = header.IndexOf(attr);
+ int ap = header.IndexOf(attr, StringComparison.Ordinal);
if (ap == -1)
{
return null;
@@ -31,13 +31,14 @@ namespace Jellyfin.Server.SocketSharp
ending = ' ';
}
- int end = header.IndexOf(ending, ap + 1);
+ var slice = header.Slice(ap + 1);
+ int end = slice.IndexOf(ending);
if (end == -1)
{
- return ending == '"' ? null : header.Substring(ap);
+ return ending == '"' ? null : header.Slice(ap).ToString();
}
- return header.Substring(ap + 1, end - ap - 1);
+ return slice.Slice(0, end - ap - 1).ToString();
}
private async Task LoadMultiPart(WebROCollection form)
@@ -82,9 +83,7 @@ namespace Jellyfin.Server.SocketSharp
}
else
{
- //
// We use a substream, as in 2.x we will support large uploads streamed to disk,
- //
var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
files[e.Name] = sub;
}
@@ -127,8 +126,12 @@ namespace Jellyfin.Server.SocketSharp
public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"];
- protected bool validate_cookies, validate_query_string, validate_form;
- protected bool checked_cookies, checked_query_string, checked_form;
+ protected bool validate_cookies { get; set; }
+ protected bool validate_query_string { get; set; }
+ protected bool validate_form { get; set; }
+ protected bool checked_cookies { get; set; }
+ protected bool checked_query_string { get; set; }
+ protected bool checked_form { get; set; }
private static void ThrowValidationException(string name, string key, string value)
{
@@ -138,8 +141,12 @@ namespace Jellyfin.Server.SocketSharp
v = v.Substring(0, 16) + "...\"";
}
- string msg = string.Format("A potentially dangerous Request.{0} value was " +
- "detected from the client ({1}={2}).", name, key, v);
+ string msg = string.Format(
+ CultureInfo.InvariantCulture,
+ "A potentially dangerous Request.{0} value was detected from the client ({1}={2}).",
+ name,
+ key,
+ v);
throw new Exception(msg);
}
@@ -179,6 +186,7 @@ namespace Jellyfin.Server.SocketSharp
for (int idx = 1; idx < len; idx++)
{
char next = val[idx];
+
// See http://secunia.com/advisories/14325
if (current == '<' || current == '\xff1c')
{
@@ -218,7 +226,7 @@ namespace Jellyfin.Server.SocketSharp
if (starts_with)
{
- return StrUtils.StartsWith(ContentType, ct, true);
+ return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase);
}
return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase);
@@ -256,6 +264,7 @@ namespace Jellyfin.Server.SocketSharp
value.Append((char)c);
}
}
+
if (c == -1)
{
AddRawKeyValue(form, key, value);
@@ -271,6 +280,7 @@ namespace Jellyfin.Server.SocketSharp
key.Append((char)c);
}
}
+
if (c == -1)
{
AddRawKeyValue(form, key, value);
@@ -308,254 +318,54 @@ namespace Jellyfin.Server.SocketSharp
result.Append(key);
result.Append('=');
}
+
result.Append(pair.Value);
}
return result.ToString();
}
}
-
- public sealed class HttpPostedFile
+ private class HttpMultipart
{
- private string name;
- private string content_type;
- private Stream stream;
- private class ReadSubStream : Stream
+ public class Element
{
- private Stream s;
- private long offset;
- private long end;
- private long position;
-
- public ReadSubStream(Stream s, long offset, long length)
- {
- this.s = s;
- this.offset = offset;
- this.end = offset + length;
- position = offset;
- }
-
- public override void Flush()
- {
- }
-
- public override int Read(byte[] buffer, int dest_offset, int count)
- {
- if (buffer == null)
- {
- throw new ArgumentNullException(nameof(buffer));
- }
-
- if (dest_offset < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0");
- }
-
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(count), "< 0");
- }
-
- int len = buffer.Length;
- if (dest_offset > len)
- {
- throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset));
- }
-
- // reordered to avoid possible integer overflow
- if (dest_offset > len - count)
- {
- throw new ArgumentException("Reading would overrun buffer", nameof(count));
- }
-
- if (count > end - position)
- {
- count = (int)(end - position);
- }
-
- if (count <= 0)
- {
- return 0;
- }
-
- s.Position = position;
- int result = s.Read(buffer, dest_offset, count);
- if (result > 0)
- {
- position += result;
- }
- else
- {
- position = end;
- }
-
- return result;
- }
-
- public override int ReadByte()
- {
- if (position >= end)
- {
- return -1;
- }
-
- s.Position = position;
- int result = s.ReadByte();
- if (result < 0)
- {
- position = end;
- }
- else
- {
- position++;
- }
-
- return result;
- }
+ public string ContentType { get; set; }
- public override long Seek(long d, SeekOrigin origin)
- {
- long real;
- switch (origin)
- {
- case SeekOrigin.Begin:
- real = offset + d;
- break;
- case SeekOrigin.End:
- real = end + d;
- break;
- case SeekOrigin.Current:
- real = position + d;
- break;
- default:
- throw new ArgumentException(nameof(origin));
- }
-
- long virt = real - offset;
- if (virt < 0 || virt > Length)
- {
- throw new ArgumentException();
- }
-
- position = s.Seek(real, SeekOrigin.Begin);
- return position;
- }
-
- public override void SetLength(long value)
- {
- throw new NotSupportedException();
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- throw new NotSupportedException();
- }
+ public string Name { get; set; }
- public override bool CanRead => true;
+ public string Filename { get; set; }
- public override bool CanSeek => true;
+ public Encoding Encoding { get; set; }
- public override bool CanWrite => false;
+ public long Start { get; set; }
- public override long Length => end - offset;
+ public long Length { get; set; }
- public override long Position
+ public override string ToString()
{
- get => position - offset;
- set
- {
- if (value > Length)
- {
- throw new ArgumentOutOfRangeException(nameof(value));
- }
-
- position = Seek(value, SeekOrigin.Begin);
- }
+ return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " +
+ Start.ToString(CultureInfo.CurrentCulture) + ", Length " + Length.ToString(CultureInfo.CurrentCulture);
}
}
- internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length)
- {
- this.name = name;
- this.content_type = content_type;
- this.stream = new ReadSubStream(base_stream, offset, length);
- }
-
- public string ContentType => content_type;
+ private const byte LF = (byte)'\n';
- public int ContentLength => (int)stream.Length;
-
- public string FileName => name;
-
- public Stream InputStream => stream;
- }
-
- private class Helpers
- {
- public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
- }
-
- internal static class StrUtils
- {
- public static bool StartsWith(string str1, string str2, bool ignore_case)
- {
- if (string.IsNullOrEmpty(str1))
- {
- return false;
- }
+ private const byte CR = (byte)'\r';
- var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
- return str1.IndexOf(str2, comparison) == 0;
- }
-
- public static bool EndsWith(string str1, string str2, bool ignore_case)
- {
- int l2 = str2.Length;
- if (l2 == 0)
- {
- return true;
- }
-
- int l1 = str1.Length;
- if (l2 > l1)
- {
- return false;
- }
+ private Stream data;
- var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
- return str1.IndexOf(str2, comparison) == str1.Length - str2.Length - 1;
- }
- }
+ private string boundary;
- private class HttpMultipart
- {
+ private byte[] boundaryBytes;
- public class Element
- {
- public string ContentType;
- public string Name;
- public string Filename;
- public Encoding Encoding;
- public long Start;
- public long Length;
+ private byte[] buffer;
- public override string ToString()
- {
- return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " +
- Start.ToString(CultureInfo.CurrentCulture) + ", Length " + Length.ToString(CultureInfo.CurrentCulture);
- }
- }
+ private bool atEof;
- private Stream data;
- private string boundary;
- private byte[] boundary_bytes;
- private byte[] buffer;
- private bool at_eof;
private Encoding encoding;
- private StringBuilder sb;
- private const byte LF = (byte)'\n', CR = (byte)'\r';
+ private StringBuilder sb;
// See RFC 2046
// In the case of multipart entities, in which one or more different
@@ -570,18 +380,48 @@ namespace Jellyfin.Server.SocketSharp
public HttpMultipart(Stream data, string b, Encoding encoding)
{
this.data = data;
- //DB: 30/01/11: cannot set or read the Position in HttpListener in Win.NET
- //var ms = new MemoryStream(32 * 1024);
- //data.CopyTo(ms);
- //this.data = ms;
-
boundary = b;
- boundary_bytes = encoding.GetBytes(b);
- buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--'
+ boundaryBytes = encoding.GetBytes(b);
+ buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--'
this.encoding = encoding;
sb = new StringBuilder();
}
+ public Element ReadNextElement()
+ {
+ if (atEof || ReadBoundary())
+ {
+ return null;
+ }
+
+ var elem = new Element();
+ ReadOnlySpan<char> header;
+ while ((header = ReadHeaders()) != null)
+ {
+ if (header.StartsWith("Content-Disposition:", StringComparison.OrdinalIgnoreCase))
+ {
+ elem.Name = GetContentDispositionAttribute(header, "name");
+ elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
+ }
+ else if (header.StartsWith("Content-Type:", StringComparison.OrdinalIgnoreCase))
+ {
+ elem.ContentType = header.Slice("Content-Type:".Length).Trim().ToString();
+ elem.Encoding = GetEncoding(elem.ContentType);
+ }
+ }
+
+ long start = data.Position;
+ elem.Start = start;
+ long pos = MoveToNextBoundary();
+ if (pos == -1)
+ {
+ return null;
+ }
+
+ elem.Length = pos - start;
+ return elem;
+ }
+
private string ReadLine()
{
// CRLF or LF are ok as line endings.
@@ -600,6 +440,7 @@ namespace Jellyfin.Server.SocketSharp
{
break;
}
+
got_cr = b == CR;
sb.Append((char)b);
}
@@ -612,7 +453,7 @@ namespace Jellyfin.Server.SocketSharp
return sb.ToString();
}
- private static string GetContentDispositionAttribute(string l, string name)
+ private static string GetContentDispositionAttribute(ReadOnlySpan<char> l, string name)
{
int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal);
if (idx < 0)
@@ -621,7 +462,7 @@ namespace Jellyfin.Server.SocketSharp
}
int begin = idx + name.Length + "=\"".Length;
- int end = l.IndexOf('"', begin);
+ int end = l.Slice(begin).IndexOf('"');
if (end < 0)
{
return null;
@@ -632,10 +473,10 @@ namespace Jellyfin.Server.SocketSharp
return string.Empty;
}
- return l.Substring(begin, end - begin);
+ return l.Slice(begin, end - begin).ToString();
}
- private string GetContentDispositionAttributeWithEncoding(string l, string name)
+ private string GetContentDispositionAttributeWithEncoding(ReadOnlySpan<char> l, string name)
{
int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal);
if (idx < 0)
@@ -644,7 +485,7 @@ namespace Jellyfin.Server.SocketSharp
}
int begin = idx + name.Length + "=\"".Length;
- int end = l.IndexOf('"', begin);
+ int end = l.Slice(begin).IndexOf('"');
if (end < 0)
{
return null;
@@ -655,7 +496,7 @@ namespace Jellyfin.Server.SocketSharp
return string.Empty;
}
- string temp = l.Substring(begin, end - begin);
+ ReadOnlySpan<char> temp = l.Slice(begin, end - begin);
byte[] source = new byte[temp.Length];
for (int i = temp.Length - 1; i >= 0; i--)
{
@@ -681,13 +522,14 @@ namespace Jellyfin.Server.SocketSharp
return false;
}
- if (!StrUtils.EndsWith(line, boundary, false))
+ if (!line.EndsWith(boundary, StringComparison.Ordinal))
{
return true;
}
}
catch
{
+
}
return false;
@@ -769,7 +611,7 @@ namespace Jellyfin.Server.SocketSharp
return -1;
}
- if (!CompareBytes(boundary_bytes, buffer))
+ if (!CompareBytes(boundaryBytes, buffer))
{
state = 0;
data.Position = retval + 2;
@@ -785,7 +627,7 @@ namespace Jellyfin.Server.SocketSharp
if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
{
- at_eof = true;
+ atEof = true;
}
else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
{
@@ -800,6 +642,7 @@ namespace Jellyfin.Server.SocketSharp
c = data.ReadByte();
continue;
}
+
data.Position = retval + 2;
if (got_cr)
{
@@ -818,42 +661,6 @@ namespace Jellyfin.Server.SocketSharp
return retval;
}
- public Element ReadNextElement()
- {
- if (at_eof || ReadBoundary())
- {
- return null;
- }
-
- var elem = new Element();
- string header;
- while ((header = ReadHeaders()) != null)
- {
- if (StrUtils.StartsWith(header, "Content-Disposition:", true))
- {
- elem.Name = GetContentDispositionAttribute(header, "name");
- elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
- }
- else if (StrUtils.StartsWith(header, "Content-Type:", true))
- {
- elem.ContentType = header.Substring("Content-Type:".Length).Trim();
- elem.Encoding = GetEncoding(elem.ContentType);
- }
- }
-
- long start = 0;
- start = data.Position;
- elem.Start = start;
- long pos = MoveToNextBoundary();
- if (pos == -1)
- {
- return null;
- }
-
- elem.Length = pos - start;
- return elem;
- }
-
private static string StripPath(string path)
{
if (path == null || path.Length == 0)
diff --git a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs
index f371cb25a..9b0951857 100644
--- a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs
+++ b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs
@@ -24,6 +24,7 @@ namespace Jellyfin.Server.SocketSharp
private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
+ private bool _disposed = false;
public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger)
{
@@ -40,41 +41,35 @@ namespace Jellyfin.Server.SocketSharp
_logger = logger;
WebSocket = socket;
- socket.OnMessage += socket_OnMessage;
- socket.OnClose += socket_OnClose;
- socket.OnError += socket_OnError;
-
- WebSocket.ConnectAsServer();
+ socket.OnMessage += OnSocketMessage;
+ socket.OnClose += OnSocketClose;
+ socket.OnError += OnSocketError;
}
+ public Task ConnectAsServerAsync()
+ => WebSocket.ConnectAsServer();
+
public Task StartReceive()
{
return _taskCompletionSource.Task;
}
- void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e)
+ private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e)
{
_logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty);
- //Closed?.Invoke(this, EventArgs.Empty);
+
+ // Closed?.Invoke(this, EventArgs.Empty);
}
- void socket_OnClose(object sender, SocketHttpListener.CloseEventArgs e)
+ private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e)
{
_taskCompletionSource.TrySetResult(true);
Closed?.Invoke(this, EventArgs.Empty);
}
- void socket_OnMessage(object sender, SocketHttpListener.MessageEventArgs e)
+ private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e)
{
- //if (!string.IsNullOrEmpty(e.Data))
- //{
- // if (OnReceive != null)
- // {
- // OnReceive(e.Data);
- // }
- // return;
- //}
if (OnReceiveBytes != null)
{
OnReceiveBytes(e.RawData);
@@ -117,6 +112,7 @@ namespace Jellyfin.Server.SocketSharp
public void Dispose()
{
Dispose(true);
+ GC.SuppressFinalize(this);
}
/// <summary>
@@ -125,16 +121,23 @@ namespace Jellyfin.Server.SocketSharp
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
+ if (_disposed)
+ {
+ return;
+ }
+
if (dispose)
{
- WebSocket.OnMessage -= socket_OnMessage;
- WebSocket.OnClose -= socket_OnClose;
- WebSocket.OnError -= socket_OnError;
+ WebSocket.OnMessage -= OnSocketMessage;
+ WebSocket.OnClose -= OnSocketClose;
+ WebSocket.OnError -= OnSocketError;
_cancellationTokenSource.Cancel();
- WebSocket.Close();
+ WebSocket.CloseAsync().GetAwaiter().GetResult();
}
+
+ _disposed = true;
}
/// <summary>
@@ -142,11 +145,5 @@ namespace Jellyfin.Server.SocketSharp
/// </summary>
/// <value>The receive action.</value>
public Action<byte[]> OnReceiveBytes { get; set; }
-
- /// <summary>
- /// Gets or sets the on receive.
- /// </summary>
- /// <value>The on receive.</value>
- public Action<string> OnReceive { get; set; }
}
}
diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs
index a44343ab2..693c2328c 100644
--- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs
+++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs
@@ -34,9 +34,16 @@ namespace Jellyfin.Server.SocketSharp
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
private CancellationToken _disposeCancellationToken;
- public WebSocketSharpListener(ILogger logger, X509Certificate certificate, IStreamHelper streamHelper,
- INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider,
- bool enableDualMode, IFileSystem fileSystem, IEnvironmentInfo environment)
+ public WebSocketSharpListener(
+ ILogger logger,
+ X509Certificate certificate,
+ IStreamHelper streamHelper,
+ INetworkManager networkManager,
+ ISocketFactory socketFactory,
+ ICryptoProvider cryptoProvider,
+ bool enableDualMode,
+ IFileSystem fileSystem,
+ IEnvironmentInfo environment)
{
_logger = logger;
_certificate = certificate;
@@ -61,7 +68,9 @@ namespace Jellyfin.Server.SocketSharp
public void Start(IEnumerable<string> urlPrefixes)
{
if (_listener == null)
- _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _streamHelper, _fileSystem, _environment);
+ {
+ _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment);
+ }
_listener.EnableDualMode = _enableDualMode;
@@ -70,28 +79,23 @@ namespace Jellyfin.Server.SocketSharp
_listener.LoadCert(_certificate);
}
- foreach (var prefix in urlPrefixes)
- {
- _logger.LogInformation("Adding HttpListener prefix " + prefix);
- _listener.Prefixes.Add(prefix);
- }
+ _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes);
+ _listener.Prefixes.AddRange(urlPrefixes);
- _listener.OnContext = ProcessContext;
+ _listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false);
_listener.Start();
}
- private void ProcessContext(HttpListenerContext context)
- {
- var _ = Task.Run(async () => await InitTask(context, _disposeCancellationToken));
- }
-
private static void LogRequest(ILogger logger, HttpListenerRequest request)
{
var url = request.Url.ToString();
- logger.LogInformation("{0} {1}. UserAgent: {2}",
- request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
+ logger.LogInformation(
+ "{0} {1}. UserAgent: {2}",
+ request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod,
+ url,
+ request.UserAgent ?? string.Empty);
}
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
@@ -139,10 +143,7 @@ namespace Jellyfin.Server.SocketSharp
Endpoint = endpoint
};
- if (WebSocketConnecting != null)
- {
- WebSocketConnecting(connectingArgs);
- }
+ WebSocketConnecting?.Invoke(connectingArgs);
if (connectingArgs.AllowConnection)
{
@@ -153,6 +154,7 @@ namespace Jellyfin.Server.SocketSharp
if (WebSocketConnected != null)
{
var socket = new SharpWebSocket(webSocketContext.WebSocket, _logger);
+ await socket.ConnectAsServerAsync().ConfigureAwait(false);
WebSocketConnected(new WebSocketConnectEventArgs
{
@@ -162,33 +164,19 @@ namespace Jellyfin.Server.SocketSharp
Endpoint = endpoint
});
- await ReceiveWebSocket(ctx, socket).ConfigureAwait(false);
+ await socket.StartReceive().ConfigureAwait(false);
}
}
else
{
_logger.LogWarning("Web socket connection not allowed");
- ctx.Response.StatusCode = 401;
- ctx.Response.Close();
+ TryClose(ctx, 401);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "AcceptWebSocketAsync error");
- ctx.Response.StatusCode = 500;
- ctx.Response.Close();
- }
- }
-
- private async Task ReceiveWebSocket(HttpListenerContext ctx, SharpWebSocket socket)
- {
- try
- {
- await socket.StartReceive().ConfigureAwait(false);
- }
- finally
- {
- TryClose(ctx, 200);
+ TryClose(ctx, 500);
}
}
@@ -199,10 +187,6 @@ namespace Jellyfin.Server.SocketSharp
ctx.Response.StatusCode = statusCode;
ctx.Response.Close();
}
- catch (ObjectDisposedException)
- {
- //TODO Investigate and properly fix.
- }
catch (Exception ex)
{
_logger.LogError(ex, "Error closing web socket response");
@@ -223,38 +207,39 @@ namespace Jellyfin.Server.SocketSharp
public Task Stop()
{
_disposeCancellationTokenSource.Cancel();
-
- if (_listener != null)
- {
- _listener.Close();
- }
+ _listener?.Close();
return Task.CompletedTask;
}
+ /// <summary>
+ /// Releases the unmanaged resources and disposes of the managed resources used.
+ /// </summary>
public void Dispose()
{
Dispose(true);
+ GC.SuppressFinalize(this);
}
private bool _disposed;
- private readonly object _disposeLock = new object();
+
+ /// <summary>
+ /// Releases the unmanaged resources and disposes of the managed resources used.
+ /// </summary>
+ /// <param name="disposing">Whether or not the managed resources should be disposed</param>
protected virtual void Dispose(bool disposing)
{
- if (_disposed) return;
-
- lock (_disposeLock)
+ if (_disposed)
{
- if (_disposed) return;
-
- if (disposing)
- {
- Stop();
- }
+ return;
+ }
- //release unmanaged resources here...
- _disposed = true;
+ if (disposing)
+ {
+ Stop().GetAwaiter().GetResult();
}
+
+ _disposed = true;
}
}
}
diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs
index ebeb18ea0..069f47f9a 100644
--- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs
+++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Text;
using Emby.Server.Implementations.HttpServer;
@@ -24,31 +25,7 @@ namespace Jellyfin.Server.SocketSharp
this.request = httpContext.Request;
this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
- //HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
- }
-
- private static string GetHandlerPathIfAny(string listenerUrl)
- {
- if (listenerUrl == null)
- {
- return null;
- }
-
- var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase);
- if (pos == -1)
- {
- return null;
- }
-
- var startHostUrl = listenerUrl.Substring(pos + "://".Length);
- var endPos = startHostUrl.IndexOf('/');
- if (endPos == -1)
- {
- return null;
- }
-
- var endHostUrl = startHostUrl.Substring(endPos + 1);
- return string.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/');
+ // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
}
public HttpListenerRequest HttpRequest => request;
@@ -69,27 +46,48 @@ namespace Jellyfin.Server.SocketSharp
public string UserHostAddress => request.UserHostAddress;
- public string XForwardedFor => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
+ public string XForwardedFor
+ => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
- public int? XForwardedPort => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"]);
+ public int? XForwardedPort
+ => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture);
public string XForwardedProtocol => string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"];
public string XRealIp => string.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"];
private string remoteIp;
- public string RemoteIp =>
- remoteIp ??
- (remoteIp = CheckBadChars(XForwardedFor) ??
- NormalizeIp(CheckBadChars(XRealIp) ??
- (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null)));
+ public string RemoteIp
+ {
+ get
+ {
+ if (remoteIp != null)
+ {
+ return remoteIp;
+ }
+
+ var temp = CheckBadChars(XForwardedFor);
+ if (temp.Length != 0)
+ {
+ return remoteIp = temp.ToString();
+ }
+
+ temp = CheckBadChars(XRealIp);
+ if (temp.Length != 0)
+ {
+ return remoteIp = NormalizeIp(temp).ToString();
+ }
+
+ return remoteIp = NormalizeIp(request.RemoteEndPoint?.Address.ToString()).ToString();
+ }
+ }
private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 };
// CheckBadChars - throws on invalid chars to be not found in header name/value
- internal static string CheckBadChars(string name)
+ internal static ReadOnlySpan<char> CheckBadChars(ReadOnlySpan<char> name)
{
- if (name == null || name.Length == 0)
+ if (name.Length == 0)
{
return name;
}
@@ -99,7 +97,7 @@ namespace Jellyfin.Server.SocketSharp
name = name.Trim(HttpTrimCharacters);
// First, check for correctly formed multi-line value
- // Second, check for absenece of CTL characters
+ // Second, check for absence of CTL characters
int crlf = 0;
for (int i = 0; i < name.Length; ++i)
{
@@ -107,6 +105,7 @@ namespace Jellyfin.Server.SocketSharp
switch (crlf)
{
case 0:
+ {
if (c == '\r')
{
crlf = 1;
@@ -119,31 +118,41 @@ namespace Jellyfin.Server.SocketSharp
}
else if (c == 127 || (c < ' ' && c != '\t'))
{
- throw new ArgumentException("net_WebHeaderInvalidControlChars");
+ throw new ArgumentException("net_WebHeaderInvalidControlChars", nameof(name));
}
+
break;
+ }
case 1:
+ {
if (c == '\n')
{
crlf = 2;
break;
}
- throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
+
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name));
+ }
case 2:
+ {
if (c == ' ' || c == '\t')
{
crlf = 0;
break;
}
- throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
+
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name));
+ }
}
}
+
if (crlf != 0)
{
- throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name));
}
+
return name;
}
@@ -156,19 +165,20 @@ namespace Jellyfin.Server.SocketSharp
return true;
}
}
+
return false;
}
- private string NormalizeIp(string ip)
+ private ReadOnlySpan<char> NormalizeIp(ReadOnlySpan<char> ip)
{
- if (!string.IsNullOrWhiteSpace(ip))
+ if (ip.Length != 0 && !ip.IsWhiteSpace())
{
// Handle ipv4 mapped to ipv6
const string srch = "::ffff:";
var index = ip.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
if (index == 0)
{
- ip = ip.Substring(srch.Length);
+ ip = ip.Slice(srch.Length);
}
}
@@ -216,8 +226,15 @@ namespace Jellyfin.Server.SocketSharp
{
foreach (var acceptsType in acceptContentTypes)
{
- var contentType = HttpResultFactory.GetRealContentType(acceptsType);
- acceptsAnything = acceptsAnything || contentType == "*/*";
+ // TODO: @bond move to Span when Span.Split lands
+ // https://github.com/dotnet/corefx/issues/26528
+ var contentType = acceptsType?.Split(';')[0].Trim();
+ acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase);
+
+ if (acceptsAnything)
+ {
+ break;
+ }
}
if (acceptsAnything)
@@ -226,7 +243,7 @@ namespace Jellyfin.Server.SocketSharp
{
return defaultContentType;
}
- else if (serverDefaultContentType != null)
+ else
{
return serverDefaultContentType;
}
@@ -269,11 +286,11 @@ namespace Jellyfin.Server.SocketSharp
private static string GetQueryStringContentType(IRequest httpReq)
{
- var format = httpReq.QueryString["format"];
+ ReadOnlySpan<char> format = httpReq.QueryString["format"];
if (format == null)
{
const int formatMaxLength = 4;
- var pi = httpReq.PathInfo;
+ ReadOnlySpan<char> pi = httpReq.PathInfo;
if (pi == null || pi.Length <= formatMaxLength)
{
return null;
@@ -281,7 +298,7 @@ namespace Jellyfin.Server.SocketSharp
if (pi[0] == '/')
{
- pi = pi.Substring(1);
+ pi = pi.Slice(1);
}
format = LeftPart(pi, '/');
@@ -304,15 +321,15 @@ namespace Jellyfin.Server.SocketSharp
return null;
}
- public static string LeftPart(string strVal, char needle)
+ public static ReadOnlySpan<char> LeftPart(ReadOnlySpan<char> strVal, char needle)
{
if (strVal == null)
{
return null;
}
- var pos = strVal.IndexOf(needle, StringComparison.Ordinal);
- return pos == -1 ? strVal : strVal.Substring(0, pos);
+ var pos = strVal.IndexOf(needle);
+ return pos == -1 ? strVal : strVal.Slice(0, pos);
}
public static string HandlerFactoryPath;
@@ -326,7 +343,7 @@ namespace Jellyfin.Server.SocketSharp
{
var mode = HandlerFactoryPath;
- var pos = request.RawUrl.IndexOf("?", StringComparison.Ordinal);
+ var pos = request.RawUrl.IndexOf('?', StringComparison.Ordinal);
if (pos != -1)
{
var path = request.RawUrl.Substring(0, pos);
@@ -341,8 +358,9 @@ namespace Jellyfin.Server.SocketSharp
}
this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo);
- this.pathInfo = NormalizePathInfo(pathInfo, mode);
+ this.pathInfo = NormalizePathInfo(pathInfo, mode).ToString();
}
+
return this.pathInfo;
}
}
@@ -444,7 +462,7 @@ namespace Jellyfin.Server.SocketSharp
public string ContentType => request.ContentType;
- public Encoding contentEncoding;
+ private Encoding contentEncoding;
public Encoding ContentEncoding
{
get => contentEncoding ?? request.ContentEncoding;
@@ -502,16 +520,20 @@ namespace Jellyfin.Server.SocketSharp
i++;
}
}
+
return httpFiles;
}
}
- public static string NormalizePathInfo(string pathInfo, string handlerPath)
+ public static ReadOnlySpan<char> NormalizePathInfo(string pathInfo, string handlerPath)
{
- var trimmed = pathInfo.TrimStart('/');
- if (handlerPath != null && trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase))
+ if (handlerPath != null)
{
- return trimmed.Substring(handlerPath.Length);
+ var trimmed = pathInfo.AsSpan().TrimStart('/');
+ if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase))
+ {
+ return trimmed.Slice(handlerPath.Length).ToString();
+ }
}
return pathInfo;
diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs
index cabc96b23..cf5aee5d4 100644
--- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs
+++ b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs
@@ -13,12 +13,12 @@ using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
using IRequest = MediaBrowser.Model.Services.IRequest;
-
namespace Jellyfin.Server.SocketSharp
{
public class WebSocketSharpResponse : IHttpResponse
{
private readonly ILogger _logger;
+
private readonly HttpListenerResponse _response;
public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request)
@@ -30,7 +30,9 @@ namespace Jellyfin.Server.SocketSharp
}
public IRequest Request { get; private set; }
+
public Dictionary<string, object> Items { get; private set; }
+
public object OriginalResponse => _response;
public int StatusCode
@@ -51,7 +53,42 @@ namespace Jellyfin.Server.SocketSharp
set => _response.ContentType = value;
}
- //public ICookies Cookies { get; set; }
+ public QueryParamCollection Headers => _response.Headers;
+
+ private static string AsHeaderValue(Cookie cookie)
+ {
+ DateTime defaultExpires = DateTime.MinValue;
+
+ var path = cookie.Expires == defaultExpires
+ ? "/"
+ : cookie.Path ?? "/";
+
+ var sb = new StringBuilder();
+
+ sb.Append($"{cookie.Name}={cookie.Value};path={path}");
+
+ if (cookie.Expires != defaultExpires)
+ {
+ sb.Append($";expires={cookie.Expires:R}");
+ }
+
+ if (!string.IsNullOrEmpty(cookie.Domain))
+ {
+ sb.Append($";domain={cookie.Domain}");
+ }
+
+ if (cookie.Secure)
+ {
+ sb.Append(";Secure");
+ }
+
+ if (cookie.HttpOnly)
+ {
+ sb.Append(";HttpOnly");
+ }
+
+ return sb.ToString();
+ }
public void AddHeader(string name, string value)
{
@@ -64,8 +101,6 @@ namespace Jellyfin.Server.SocketSharp
_response.AddHeader(name, value);
}
- public QueryParamCollection Headers => _response.Headers;
-
public string GetHeader(string name)
{
return _response.Headers[name];
@@ -114,9 +149,9 @@ namespace Jellyfin.Server.SocketSharp
public void SetContentLength(long contentLength)
{
- //you can happily set the Content-Length header in Asp.Net
- //but HttpListener will complain if you do - you have to set ContentLength64 on the response.
- //workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
+ // you can happily set the Content-Length header in Asp.Net
+ // but HttpListener will complain if you do - you have to set ContentLength64 on the response.
+ // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
_response.ContentLength64 = contentLength;
}
@@ -126,45 +161,6 @@ namespace Jellyfin.Server.SocketSharp
_response.Headers.Add("Set-Cookie", cookieStr);
}
- public static string AsHeaderValue(Cookie cookie)
- {
- var defaultExpires = DateTime.MinValue;
-
- var path = cookie.Expires == defaultExpires
- ? "/"
- : cookie.Path ?? "/";
-
- var sb = new StringBuilder();
-
- sb.Append($"{cookie.Name}={cookie.Value};path={path}");
-
- if (cookie.Expires != defaultExpires)
- {
- sb.Append($";expires={cookie.Expires:R}");
- }
-
- if (!string.IsNullOrEmpty(cookie.Domain))
- {
- sb.Append($";domain={cookie.Domain}");
- }
- //else if (restrictAllCookiesToDomain != null)
- //{
- // sb.Append($";domain={restrictAllCookiesToDomain}");
- //}
-
- if (cookie.Secure)
- {
- sb.Append(";Secure");
- }
- if (cookie.HttpOnly)
- {
- sb.Append(";HttpOnly");
- }
-
- return sb.ToString();
- }
-
-
public bool SendChunked
{
get => _response.SendChunked;
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 8dbc26356..ceff6b02e 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -170,7 +170,7 @@ namespace MediaBrowser.Api
/// </summary>
private void DeleteEncodedMediaCache()
{
- var path = _config.ApplicationPaths.TranscodingTempPath;
+ var path = _config.ApplicationPaths.GetTranscodingTempPath();
foreach (var file in _fileSystem.GetFilePaths(path, true))
{
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs
index 451ee72dd..69673a49c 100644
--- a/MediaBrowser.Api/BaseApiService.cs
+++ b/MediaBrowser.Api/BaseApiService.cs
@@ -9,6 +9,7 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api
@@ -118,8 +119,7 @@ namespace MediaBrowser.Api
{
var options = new DtoOptions();
- var hasFields = request as IHasItemFields;
- if (hasFields != null)
+ if (request is IHasItemFields hasFields)
{
options.Fields = hasFields.GetItemFields();
}
@@ -133,9 +133,11 @@ namespace MediaBrowser.Api
client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
{
- var list = options.Fields.ToList();
- list.Add(Model.Querying.ItemFields.RecursiveItemCount);
- options.Fields = list.ToArray();
+ int oldLen = options.Fields.Length;
+ var arr = new ItemFields[oldLen + 1];
+ options.Fields.CopyTo(arr, 0);
+ arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount;
+ options.Fields = arr;
}
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
@@ -146,9 +148,12 @@ namespace MediaBrowser.Api
client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
{
- var list = options.Fields.ToList();
- list.Add(Model.Querying.ItemFields.ChildCount);
- options.Fields = list.ToArray();
+
+ int oldLen = options.Fields.Length;
+ var arr = new ItemFields[oldLen + 1];
+ options.Fields.CopyTo(arr, 0);
+ arr[oldLen] = Model.Querying.ItemFields.ChildCount;
+ options.Fields = arr;
}
}
@@ -167,7 +172,9 @@ namespace MediaBrowser.Api
if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes))
{
- options.ImageTypes = (hasDtoOptions.EnableImageTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray();
+ options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true))
+ .ToArray();
}
}
diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs
index bdd7a8f8f..f4813e713 100644
--- a/MediaBrowser.Api/EnvironmentService.cs
+++ b/MediaBrowser.Api/EnvironmentService.cs
@@ -173,14 +173,8 @@ namespace MediaBrowser.Api
_fileSystem.DeleteFile(file);
}
- public object Get(GetDefaultDirectoryBrowser request)
- {
- var result = new DefaultDirectoryBrowserInfo();
-
- result.Path = _fileSystem.DefaultDirectory;
-
- return ToOptimizedResult(result);
- }
+ public object Get(GetDefaultDirectoryBrowser request) =>
+ ToOptimizedResult(new DefaultDirectoryBrowserInfo {Path = null});
/// <summary>
/// Gets the specified request.
diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs
index 9caf07cea..201efe737 100644
--- a/MediaBrowser.Api/FilterService.cs
+++ b/MediaBrowser.Api/FilterService.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -180,7 +181,7 @@ namespace MediaBrowser.Api
return ToOptimizedResult(filters);
}
- private QueryFiltersLegacy GetFilters(BaseItem[] items)
+ private QueryFiltersLegacy GetFilters(IReadOnlyCollection<BaseItem> items)
{
var result = new QueryFiltersLegacy();
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index 7aeb0e9e8..bf15cc756 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -37,6 +37,7 @@ namespace MediaBrowser.Api.Playback.Progressive
[Route("/Videos/{Id}/stream.mov", "GET")]
[Route("/Videos/{Id}/stream.iso", "GET")]
[Route("/Videos/{Id}/stream.flv", "GET")]
+ [Route("/Videos/{Id}/stream.rm", "GET")]
[Route("/Videos/{Id}/stream", "GET")]
[Route("/Videos/{Id}/stream.ts", "HEAD")]
[Route("/Videos/{Id}/stream.webm", "HEAD")]
diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
index 16b036912..b7e94b73f 100644
--- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
+++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
@@ -197,16 +197,6 @@ namespace MediaBrowser.Api.ScheduledTasks
throw new ResourceNotFoundException("Task not found");
}
- if (string.Equals(task.ScheduledTask.Key, "SystemUpdateTask", StringComparison.OrdinalIgnoreCase))
- {
- // This is a hack for now just to get the update application function to work when auto-update is disabled
- if (!_config.Configuration.EnableAutoUpdate)
- {
- _config.Configuration.EnableAutoUpdate = true;
- _config.SaveConfiguration();
- }
- }
-
TaskManager.Execute(task, new TaskOptions());
}
@@ -238,16 +228,14 @@ namespace MediaBrowser.Api.ScheduledTasks
// https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
var id = GetPathValue(1);
- var task = TaskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, id));
+ var task = TaskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.Ordinal));
if (task == null)
{
throw new ResourceNotFoundException("Task not found");
}
- var triggerInfos = request;
-
- task.Triggers = triggerInfos.ToArray();
+ task.Triggers = request.ToArray();
}
}
}
diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs
index 387ccad25..beb2fb11d 100644
--- a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs
+++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs
@@ -11,7 +11,7 @@ namespace MediaBrowser.Api.Session
/// <summary>
/// Class SessionInfoWebSocketListener
/// </summary>
- class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState>
+ public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState>
{
/// <summary>
/// Gets the name.
diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
index 0df46c399..43f3c5a22 100644
--- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
+++ b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
@@ -11,7 +11,7 @@ namespace MediaBrowser.Api.System
/// <summary>
/// Class SessionInfoWebSocketListener
/// </summary>
- class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<List<ActivityLogEntry>, WebSocketListenerState>
+ public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<List<ActivityLogEntry>, WebSocketListenerState>
{
/// <summary>
/// Gets the name.
diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
index 651da1939..7a8455ff2 100644
--- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
@@ -112,7 +112,7 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedResult(result);
}
- protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+ protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{
if (request is GetAlbumArtists)
{
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
index 471b41127..e3c9ae58e 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
@@ -209,9 +209,9 @@ namespace MediaBrowser.Api.UserLibrary
};
}
- protected virtual QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+ protected virtual QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{
- return new QueryResult<Tuple<BaseItem, ItemCounts>>();
+ return new QueryResult<(BaseItem, ItemCounts)>();
}
private void SetItemCounts(BaseItemDto dto, ItemCounts counts)
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
index 7af50c329..a26f59573 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
@@ -396,14 +396,12 @@ namespace MediaBrowser.Api.UserLibrary
public VideoType[] GetVideoTypes()
{
- var val = VideoTypes;
-
- if (string.IsNullOrEmpty(val))
+ if (string.IsNullOrEmpty(VideoTypes))
{
- return new VideoType[] { };
+ return Array.Empty<VideoType>();
}
- return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
+ return VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
}
/// <summary>
diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs
index baf570d50..0c04d02dd 100644
--- a/MediaBrowser.Api/UserLibrary/GenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/GenresService.cs
@@ -92,7 +92,7 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedResult(result);
}
- protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+ protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{
var viewType = GetParentItemViewType(request);
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index 3ae7da007..3c7ad1d0a 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.Linq;
using MediaBrowser.Controller.Dto;
@@ -12,6 +13,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Services;
+using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.UserLibrary
{
@@ -90,7 +92,7 @@ namespace MediaBrowser.Api.UserLibrary
var options = GetDtoOptions(_authContext, request);
- var ancestorIds = new List<Guid>();
+ var ancestorIds = Array.Empty<Guid>();
var excludeFolderIds = user.Configuration.LatestItemsExcludes;
if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0)
@@ -99,12 +101,12 @@ namespace MediaBrowser.Api.UserLibrary
.Where(i => i is Folder)
.Where(i => !excludeFolderIds.Contains(i.Id.ToString("N")))
.Select(i => i.Id)
- .ToList();
+ .ToArray();
}
var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
{
- OrderBy = new[] { ItemSortBy.DatePlayed }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
+ OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) },
IsResumable = true,
StartIndex = request.StartIndex,
Limit = request.Limit,
@@ -115,7 +117,7 @@ namespace MediaBrowser.Api.UserLibrary
IsVirtualItem = false,
CollapseBoxSetItems = false,
EnableTotalRecordCount = request.EnableTotalRecordCount,
- AncestorIds = ancestorIds.ToArray(),
+ AncestorIds = ancestorIds,
IncludeItemTypes = request.GetIncludeItemTypes(),
ExcludeItemTypes = request.GetExcludeItemTypes(),
SearchTerm = request.SearchTerm
@@ -155,7 +157,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
private QueryResult<BaseItemDto> GetItems(GetItems request)
{
- var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
+ var user = request.UserId == Guid.Empty ? null : _userManager.GetUserById(request.UserId);
var dtoOptions = GetDtoOptions(_authContext, request);
@@ -190,57 +192,54 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
private QueryResult<BaseItem> GetQueryResult(GetItems request, DtoOptions dtoOptions, User user)
{
- if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
{
request.ParentId = null;
}
- else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
+
+ BaseItem item = null;
+
+ if (!string.IsNullOrEmpty(request.ParentId))
{
- request.ParentId = null;
+ item = _libraryManager.GetItemById(request.ParentId);
}
- var item = string.IsNullOrEmpty(request.ParentId) ?
- null :
- _libraryManager.GetItemById(request.ParentId);
-
if (item == null)
{
- item = string.IsNullOrEmpty(request.ParentId) ?
- user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder() :
- _libraryManager.GetItemById(request.ParentId);
+ item = _libraryManager.GetUserRootFolder();
}
- // Default list type = children
-
- var folder = item as Folder;
+ Folder folder = item as Folder;
if (folder == null)
{
- folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
+ folder = _libraryManager.GetUserRootFolder();
}
var hasCollectionType = folder as IHasCollectionType;
- var isPlaylistQuery = (hasCollectionType != null && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase));
-
- if (isPlaylistQuery)
+ if (hasCollectionType != null
+ && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
{
request.Recursive = true;
request.IncludeItemTypes = "Playlist";
}
- if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null)
+ if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id))
{
- return folder.GetItems(GetItemsQuery(request, dtoOptions, user));
+ Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name);
+ return new QueryResult<BaseItem>
+ {
+ Items = Array.Empty<BaseItem>(),
+ TotalRecordCount = 0
+ };
}
- var userRoot = item as UserRootFolder;
-
- if (userRoot == null)
+ if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || !(item is UserRootFolder))
{
return folder.GetItems(GetItemsQuery(request, dtoOptions, user));
}
var itemsArray = folder.GetChildren(user, true).ToArray();
-
return new QueryResult<BaseItem>
{
Items = itemsArray,
diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
index 4fcc3aa53..94f5262b0 100644
--- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
@@ -83,7 +83,7 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedResult(result);
}
- protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+ protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{
return LibraryManager.GetMusicGenres(query);
}
diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs
index d317f9f38..c26457778 100644
--- a/MediaBrowser.Api/UserLibrary/PersonsService.cs
+++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs
@@ -101,7 +101,7 @@ namespace MediaBrowser.Api.UserLibrary
throw new NotImplementedException();
}
- protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+ protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{
var items = LibraryManager.GetPeopleItems(new InternalPeopleQuery
{
@@ -109,10 +109,10 @@ namespace MediaBrowser.Api.UserLibrary
NameContains = query.NameContains ?? query.SearchTerm
});
- return new QueryResult<Tuple<BaseItem, ItemCounts>>
+ return new QueryResult<(BaseItem, ItemCounts)>
{
TotalRecordCount = items.Count,
- Items = items.Take(query.Limit ?? int.MaxValue).Select(i => new Tuple<BaseItem, ItemCounts>(i, new ItemCounts())).ToArray()
+ Items = items.Take(query.Limit ?? int.MaxValue).Select(i => (i as BaseItem, new ItemCounts())).ToArray()
};
}
diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs
index 4e2483a56..890acc931 100644
--- a/MediaBrowser.Api/UserLibrary/StudiosService.cs
+++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs
@@ -91,7 +91,7 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedResult(result);
}
- protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+ protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{
return LibraryManager.GetStudios(query);
}
diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs
index 27092c0e1..cb4e8bf5f 100644
--- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs
+++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs
@@ -42,12 +42,6 @@ namespace MediaBrowser.Common.Configuration
string PluginConfigurationsPath { get; }
/// <summary>
- /// Gets the path to where temporary update files will be stored
- /// </summary>
- /// <value>The plugin configurations path.</value>
- string TempUpdatePath { get; }
-
- /// <summary>
/// Gets the path to the log directory
/// </summary>
/// <value>The log directory path.</value>
diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs
index 59e3c1767..3a4098612 100644
--- a/MediaBrowser.Common/IApplicationHost.cs
+++ b/MediaBrowser.Common/IApplicationHost.cs
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Updates;
+using Microsoft.Extensions.DependencyInjection;
namespace MediaBrowser.Common
{
@@ -14,12 +15,6 @@ namespace MediaBrowser.Common
public interface IApplicationHost
{
/// <summary>
- /// Gets the display name of the operating system.
- /// </summary>
- /// <value>The display name of the operating system.</value>
- string OperatingSystemDisplayName { get; }
-
- /// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
@@ -78,12 +73,6 @@ namespace MediaBrowser.Common
string ApplicationUserAgent { get; }
/// <summary>
- /// Gets or sets a value indicating whether this instance can self update.
- /// </summary>
- /// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
- bool CanSelfUpdate { get; }
-
- /// <summary>
/// Gets the exports.
/// </summary>
/// <typeparam name="T"></typeparam>
@@ -92,12 +81,6 @@ namespace MediaBrowser.Common
IEnumerable<T> GetExports<T>(bool manageLifetime = true);
/// <summary>
- /// Updates the application.
- /// </summary>
- /// <returns>Task.</returns>
- Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress<double> progress);
-
- /// <summary>
/// Resolves this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
@@ -105,13 +88,6 @@ namespace MediaBrowser.Common
T Resolve<T>();
/// <summary>
- /// Resolves this instance.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <returns>``0.</returns>
- T TryResolve<T>();
-
- /// <summary>
/// Shuts down.
/// </summary>
Task Shutdown();
@@ -131,7 +107,7 @@ namespace MediaBrowser.Common
/// <summary>
/// Inits this instance.
/// </summary>
- Task Init();
+ Task Init(IServiceCollection serviceCollection);
/// <summary>
/// Creates the instance.
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 2220d4661..715f4fccd 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -12,6 +12,10 @@
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
+ </ItemGroup>
+
+ <ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs
index 72fb6e2b8..34c6f5866 100644
--- a/MediaBrowser.Common/Net/INetworkManager.cs
+++ b/MediaBrowser.Common/Net/INetworkManager.cs
@@ -53,7 +53,7 @@ namespace MediaBrowser.Common.Net
/// <returns><c>true</c> if [is in local network] [the specified endpoint]; otherwise, <c>false</c>.</returns>
bool IsInLocalNetwork(string endpoint);
- IpAddressInfo[] GetLocalIpAddresses();
+ IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface);
IpAddressInfo ParseIpAddress(string ipAddress);
@@ -62,5 +62,8 @@ namespace MediaBrowser.Common.Net
Task<IpAddressInfo[]> GetHostAddressesAsync(string host);
bool IsAddressInSubnets(string addressString, string[] subnets);
+
+ bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask);
+ IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address);
}
}
diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs
index aa99f6b58..cdaf95f5c 100644
--- a/MediaBrowser.Controller/Dto/DtoOptions.cs
+++ b/MediaBrowser.Controller/Dto/DtoOptions.cs
@@ -36,9 +36,7 @@ namespace MediaBrowser.Controller.Dto
.ToArray();
public bool ContainsField(ItemFields field)
- {
- return AllItemFields.Contains(field);
- }
+ => Fields.Contains(field);
public DtoOptions(bool allFields)
{
@@ -47,15 +45,7 @@ namespace MediaBrowser.Controller.Dto
EnableUserData = true;
AddCurrentProgram = true;
- if (allFields)
- {
- Fields = AllItemFields;
- }
- else
- {
- Fields = new ItemFields[] { };
- }
-
+ Fields = allFields ? AllItemFields : Array.Empty<ItemFields>();
ImageTypes = AllImageTypes;
}
diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs
index df5ec5dd0..4b6fd58fe 100644
--- a/MediaBrowser.Controller/Dto/IDtoService.cs
+++ b/MediaBrowser.Controller/Dto/IDtoService.cs
@@ -57,9 +57,7 @@ namespace MediaBrowser.Controller.Dto
/// <param name="options">The options.</param>
/// <param name="user">The user.</param>
/// <param name="owner">The owner.</param>
- BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null);
-
- BaseItemDto[] GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null);
+ BaseItemDto[] GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null);
/// <summary>
/// Gets the item by name dto.
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 72c4e3573..43fee79a1 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1283,6 +1283,35 @@ namespace MediaBrowser.Controller.Entities
}).OrderBy(i => i.Path).ToArray();
}
+ protected virtual BaseItem[] LoadExtras(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
+ {
+ var files = fileSystemChildren.Where(i => i.IsDirectory)
+ .SelectMany(i => FileSystem.GetFiles(i.FullName));
+
+ return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
+ .OfType<Video>()
+ .Select(item =>
+ {
+ // Try to retrieve it from the db. If we don't find it, use the resolved version
+ var dbItem = LibraryManager.GetItemById(item.Id) as Video;
+
+ if (dbItem != null)
+ {
+ item = dbItem;
+ }
+ else
+ {
+ // item is new
+ item.ExtraType = MediaBrowser.Model.Entities.ExtraType.Clip;
+ }
+
+ return item;
+
+ // Sort them so that the list can be easily compared for changes
+ }).OrderBy(i => i.Path).ToArray();
+ }
+
+
public Task RefreshMetadata(CancellationToken cancellationToken)
{
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)), cancellationToken);
@@ -1371,6 +1400,8 @@ namespace MediaBrowser.Controller.Entities
var themeVideosChanged = false;
+ var extrasChanged = false;
+
var localTrailersChanged = false;
if (IsFileProtocol && SupportsOwnedItems)
@@ -1382,6 +1413,8 @@ namespace MediaBrowser.Controller.Entities
themeSongsChanged = await RefreshThemeSongs(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
themeVideosChanged = await RefreshThemeVideos(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
+
+ extrasChanged = await RefreshExtras(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
}
}
@@ -1392,7 +1425,7 @@ namespace MediaBrowser.Controller.Entities
}
}
- return themeSongsChanged || themeVideosChanged || localTrailersChanged;
+ return themeSongsChanged || themeVideosChanged || extrasChanged || localTrailersChanged;
}
protected virtual FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
@@ -1435,6 +1468,31 @@ namespace MediaBrowser.Controller.Entities
return itemsChanged;
}
+ private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
+ {
+ var newExtras = LoadExtras(fileSystemChildren, options.DirectoryService).Concat(LoadThemeVideos(fileSystemChildren, options.DirectoryService)).Concat(LoadThemeSongs(fileSystemChildren, options.DirectoryService));
+
+ var newExtraIds = newExtras.Select(i => i.Id).ToArray();
+
+ var extrasChanged = !item.ExtraIds.SequenceEqual(newExtraIds);
+
+ if (extrasChanged)
+ {
+ var ownerId = item.Id;
+
+ var tasks = newExtras.Select(i =>
+ {
+ return RefreshMetadataForOwnedItem(i, true, new MetadataRefreshOptions(options), cancellationToken);
+ });
+
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+
+ item.ExtraIds = newExtraIds;
+ }
+
+ return extrasChanged;
+ }
+
private async Task<bool> RefreshThemeVideos(BaseItem item, MetadataRefreshOptions options, IEnumerable<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var newThemeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService);
@@ -2775,17 +2833,17 @@ namespace MediaBrowser.Controller.Entities
public IEnumerable<BaseItem> GetExtras()
{
- return ThemeVideoIds.Select(LibraryManager.GetItemById).Where(i => i.ExtraType.Equals(Model.Entities.ExtraType.ThemeVideo)).OrderBy(i => i.SortName);
+ return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName);
}
- public IEnumerable<BaseItem> GetExtras(ExtraType[] unused)
+ public IEnumerable<BaseItem> GetExtras(ExtraType[] extraTypes)
{
- return GetExtras();
+ return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null && extraTypes.Contains(i.ExtraType.Value)).OrderBy(i => i.SortName);
}
public IEnumerable<BaseItem> GetDisplayExtras()
{
- return GetExtras();
+ return GetExtras(DisplayExtraTypes);
}
public virtual bool IsHD => Height >= 720;
@@ -2798,8 +2856,10 @@ namespace MediaBrowser.Controller.Entities
{
return RunTimeTicks ?? 0;
}
- // what does this do?
- public static ExtraType[] DisplayExtraTypes = new[] { Model.Entities.ExtraType.ThemeSong, Model.Entities.ExtraType.ThemeVideo };
+
+ // Possible types of extra videos
+ public static ExtraType[] DisplayExtraTypes = new[] { Model.Entities.ExtraType.BehindTheScenes, Model.Entities.ExtraType.Clip, Model.Entities.ExtraType.DeletedScene, Model.Entities.ExtraType.Interview, Model.Entities.ExtraType.Sample, Model.Entities.ExtraType.Scene };
+
public virtual bool SupportsExternalTransfer => false;
}
}
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 8bfadbee6..e49ff20ba 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -810,37 +810,19 @@ namespace MediaBrowser.Controller.Entities
{
if (query.ItemIds.Length > 0)
{
- var result = LibraryManager.GetItemsResult(query);
-
- if (query.OrderBy.Length == 0)
- {
- var ids = query.ItemIds.ToList();
-
- // Try to preserve order
- result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id)).ToArray();
- }
- return result;
+ return LibraryManager.GetItemsResult(query);
}
return GetItemsInternal(query);
}
- public BaseItem[] GetItemList(InternalItemsQuery query)
+ public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query)
{
query.EnableTotalRecordCount = false;
if (query.ItemIds.Length > 0)
{
- var result = LibraryManager.GetItemList(query);
-
- if (query.OrderBy.Length == 0)
- {
- var ids = query.ItemIds.ToList();
-
- // Try to preserve order
- return result.OrderBy(i => ids.IndexOf(i.Id)).ToArray();
- }
- return result.ToArray();
+ return LibraryManager.GetItemList(query);
}
return GetItemsInternal(query).Items;
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 60c183d04..511356aa4 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -193,7 +193,7 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Updates the item.
/// </summary>
- void UpdateItems(List<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
+ void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
/// <summary>
@@ -520,12 +520,12 @@ namespace MediaBrowser.Controller.Library
void UpdateMediaPath(string virtualFolderName, MediaPathInfo path);
void RemoveMediaPath(string virtualFolderName, string path);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query);
int GetCount(InternalItemsQuery query);
diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs
index 5b66e7497..fd5fb6748 100644
--- a/MediaBrowser.Controller/Library/TVUtils.cs
+++ b/MediaBrowser.Controller/Library/TVUtils.cs
@@ -8,16 +8,6 @@ namespace MediaBrowser.Controller.Library
public static class TVUtils
{
/// <summary>
- /// The TVDB API key
- /// </summary>
- public static readonly string TvdbApiKey = "72930AE1CB7E2DB3";
- public static readonly string TvdbBaseUrl = "https://www.thetvdb.com/";
- /// <summary>
- /// The banner URL
- /// </summary>
- public static readonly string BannerUrl = TvdbBaseUrl + "banners/";
-
- /// <summary>
/// Gets the air days.
/// </summary>
/// <param name="day">The day.</param>
@@ -28,24 +18,24 @@ namespace MediaBrowser.Controller.Library
{
if (string.Equals(day, "Daily", StringComparison.OrdinalIgnoreCase))
{
- return new DayOfWeek[]
- {
- DayOfWeek.Sunday,
- DayOfWeek.Monday,
- DayOfWeek.Tuesday,
- DayOfWeek.Wednesday,
- DayOfWeek.Thursday,
- DayOfWeek.Friday,
- DayOfWeek.Saturday
- };
+ return new[]
+ {
+ DayOfWeek.Sunday,
+ DayOfWeek.Monday,
+ DayOfWeek.Tuesday,
+ DayOfWeek.Wednesday,
+ DayOfWeek.Thursday,
+ DayOfWeek.Friday,
+ DayOfWeek.Saturday
+ };
}
if (Enum.TryParse(day, true, out DayOfWeek value))
{
- return new DayOfWeek[]
- {
- value
- };
+ return new[]
+ {
+ value
+ };
}
return new DayOfWeek[] { };
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index f5f147db1..e378c2b89 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -1904,7 +1904,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
flags.Add("+ignidx");
}
- if (state.GenPtsInput)
+ if (state.GenPtsInput || string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
flags.Add("+genpts");
}
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index b812a8ddc..46593fb2f 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -32,16 +32,17 @@ namespace MediaBrowser.Controller.MediaEncoding
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
+ // If ffmpeg process is closed, the state is disposed, so don't write to target in that case
+ if (!target.CanWrite)
+ {
+ break;
+ }
+
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
await target.FlushAsync().ConfigureAwait(false);
}
}
}
- catch (ObjectDisposedException)
- {
- //TODO Investigate and properly fix.
- // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
- }
catch (Exception ex)
{
_logger.LogError(ex, "Error reading ffmpeg log");
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index 5156fce11..47e0f3453 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Persistence
/// </summary>
/// <param name="items">The items.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- void SaveItems(List<BaseItem> items, CancellationToken cancellationToken);
+ void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken);
void SaveImages(BaseItem item);
@@ -141,12 +141,12 @@ namespace MediaBrowser.Controller.Persistence
int GetCount(InternalItemsQuery query);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
- QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query);
List<string> GetMusicGenreNames();
List<string> GetStudioNames();
diff --git a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
index 4180a4f15..442a18cb9 100644
--- a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
@@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Providers
{
- class PlaylistXmlProvider : BaseXmlProvider<Playlist>
+ public class PlaylistXmlProvider : BaseXmlProvider<Playlist>
{
private readonly ILogger _logger;
private readonly IProviderManager _providerManager;
diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
index ce4ef1cfe..6a1a0f090 100644
--- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
@@ -7,20 +7,6 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
public class BaseApplicationConfiguration
{
- // TODO: @bond Remove?
- /// <summary>
- /// Gets or sets a value indicating whether [enable debug level logging].
- /// </summary>
- /// <value><c>true</c> if [enable debug level logging]; otherwise, <c>false</c>.</value>
- public bool EnableDebugLevelLogging { get; set; }
-
- /// <summary>
- /// Enable automatically and silently updating of the application
- /// </summary>
- /// <value><c>true</c> if [enable auto update]; otherwise, <c>false</c>.</value>
- public bool EnableAutoUpdate { get; set; }
-
- // TODO: @bond Remove?
/// <summary>
/// The number of days we should retain log files
/// </summary>
@@ -44,7 +30,6 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
public BaseApplicationConfiguration()
{
- EnableAutoUpdate = true;
LogFileRetentionDays = 3;
}
}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index ed5800329..0ba36b4b9 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -178,6 +178,7 @@ namespace MediaBrowser.Model.Configuration
public string[] LocalNetworkSubnets { get; set; }
public string[] LocalNetworkAddresses { get; set; }
public string[] CodecsUsed { get; set; }
+ public bool IgnoreVirtualInterfaces { get; set; }
public bool EnableExternalContentInSuggestions { get; set; }
public bool RequireHttps { get; set; }
public bool IsBehindProxy { get; set; }
@@ -205,6 +206,7 @@ namespace MediaBrowser.Model.Configuration
CodecsUsed = Array.Empty<string>();
ImageExtractionTimeoutMs = 0;
PathSubstitutions = Array.Empty<PathSubstitution>();
+ IgnoreVirtualInterfaces = false;
EnableSimpleArtistDetection = true;
DisplaySpecialsWithinSeasons = true;
diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs
index 6c9b2bd88..e0771245f 100644
--- a/MediaBrowser.Model/IO/IFileSystem.cs
+++ b/MediaBrowser.Model/IO/IFileSystem.cs
@@ -113,8 +113,6 @@ namespace MediaBrowser.Model.IO
Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share,
FileOpenOptions fileOpenOptions);
- string DefaultDirectory { get; }
-
/// <summary>
/// Swaps the files.
/// </summary>
diff --git a/MediaBrowser.Model/Net/IpAddressInfo.cs b/MediaBrowser.Model/Net/IpAddressInfo.cs
index 7a278d4d4..87fa55bca 100644
--- a/MediaBrowser.Model/Net/IpAddressInfo.cs
+++ b/MediaBrowser.Model/Net/IpAddressInfo.cs
@@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Net
public static IpAddressInfo IPv6Loopback = new IpAddressInfo("::1", IpAddressFamily.InterNetworkV6);
public string Address { get; set; }
+ public IpAddressInfo SubnetMask { get; set; }
public IpAddressFamily AddressFamily { get; set; }
public IpAddressInfo(string address, IpAddressFamily addressFamily)
diff --git a/MediaBrowser.Model/Serialization/IJsonSerializer.cs b/MediaBrowser.Model/Serialization/IJsonSerializer.cs
index ae0cf6f36..18f51f652 100644
--- a/MediaBrowser.Model/Serialization/IJsonSerializer.cs
+++ b/MediaBrowser.Model/Serialization/IJsonSerializer.cs
@@ -15,6 +15,14 @@ namespace MediaBrowser.Model.Serialization
void SerializeToStream(object obj, Stream stream);
/// <summary>
+ /// Serializes to stream.
+ /// </summary>
+ /// <param name="obj">The obj.</param>
+ /// <param name="stream">The stream.</param>
+ /// <exception cref="ArgumentNullException">obj</exception>
+ void SerializeToStream<T>(T obj, Stream stream);
+
+ /// <summary>
/// Serializes to file.
/// </summary>
/// <param name="obj">The obj.</param>
diff --git a/MediaBrowser.Model/System/PublicSystemInfo.cs b/MediaBrowser.Model/System/PublicSystemInfo.cs
index accdc9e60..d97eda352 100644
--- a/MediaBrowser.Model/System/PublicSystemInfo.cs
+++ b/MediaBrowser.Model/System/PublicSystemInfo.cs
@@ -24,12 +24,12 @@ namespace MediaBrowser.Model.System
/// Gets or sets the server version.
/// </summary>
/// <value>The version.</value>
- public string Version { get; set; }
+ public string Version { get; set; }
/// <summary>
- /// Gets or sets the operating sytem.
+ /// Gets or sets the operating system.
/// </summary>
- /// <value>The operating sytem.</value>
+ /// <value>The operating system.</value>
public string OperatingSystem { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index 26f735330..581a1069c 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -1,3 +1,4 @@
+using System;
using System.Runtime.InteropServices;
using MediaBrowser.Model.Updates;
@@ -59,12 +60,6 @@ namespace MediaBrowser.Model.System
/// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
public bool CanSelfRestart { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether this instance can self update.
- /// </summary>
- /// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
- public bool CanSelfUpdate { get; set; }
-
public bool CanLaunchWebBrowser { get; set; }
/// <summary>
@@ -136,7 +131,7 @@ namespace MediaBrowser.Model.System
/// </summary>
public SystemInfo()
{
- CompletedInstallations = new InstallationInfo[] { };
+ CompletedInstallations = Array.Empty<InstallationInfo>();
}
}
}
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index 23805b79f..27ce23778 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -77,7 +77,7 @@ namespace MediaBrowser.Model.Users
public UserPolicy()
{
- EnableContentDeletion = true;
+ EnableContentDeletion = false;
EnableContentDeletionFromFolders = Array.Empty<string>();
EnableSyncTranscoding = true;
diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs
index c6c1a2a94..4d12b2f4a 100644
--- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs
+++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs
@@ -14,7 +14,7 @@ using MediaBrowser.Providers.Movies;
namespace MediaBrowser.Providers.BoxSets
{
- class MovieDbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
+ public class MovieDbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClient _httpClient;
diff --git a/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs b/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs
deleted file mode 100644
index 10ff2515c..000000000
--- a/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs
+++ /dev/null
@@ -1,402 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-
-//TODO Fix namespace or replace
-namespace Priority_Queue
-{
- /// <summary>
- /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
- /// A copy of StablePriorityQueue which also has generic priority-type
- /// </summary>
- /// <typeparam name="TItem">The values in the queue. Must extend the GenericPriorityQueue class</typeparam>
- /// <typeparam name="TPriority">The priority-type. Must extend IComparable&lt;TPriority&gt;</typeparam>
- public sealed class GenericPriorityQueue<TItem, TPriority> : IFixedSizePriorityQueue<TItem, TPriority>
- where TItem : GenericPriorityQueueNode<TPriority>
- where TPriority : IComparable<TPriority>
- {
- private int _numNodes;
- private TItem[] _nodes;
- private long _numNodesEverEnqueued;
-
- /// <summary>
- /// Instantiate a new Priority Queue
- /// </summary>
- /// <param name="maxNodes">The max nodes ever allowed to be enqueued (going over this will cause undefined behavior)</param>
- public GenericPriorityQueue(int maxNodes)
- {
-#if DEBUG
- if (maxNodes <= 0)
- {
- throw new InvalidOperationException("New queue size cannot be smaller than 1");
- }
-#endif
-
- _numNodes = 0;
- _nodes = new TItem[maxNodes + 1];
- _numNodesEverEnqueued = 0;
- }
-
- /// <summary>
- /// Returns the number of nodes in the queue.
- /// O(1)
- /// </summary>
- public int Count => _numNodes;
-
- /// <summary>
- /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize),
- /// attempting to enqueue another item will cause undefined behavior. O(1)
- /// </summary>
- public int MaxSize => _nodes.Length - 1;
-
- /// <summary>
- /// Removes every node from the queue.
- /// O(n) (So, don't do this often!)
- /// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Clear()
- {
- Array.Clear(_nodes, 1, _numNodes);
- _numNodes = 0;
- }
-
- /// <summary>
- /// Returns (in O(1)!) whether the given node is in the queue. O(1)
- /// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool Contains(TItem node)
- {
-#if DEBUG
- if (node == null)
- {
- throw new ArgumentNullException(nameof(node));
- }
- if (node.QueueIndex < 0 || node.QueueIndex >= _nodes.Length)
- {
- throw new InvalidOperationException("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?");
- }
-#endif
-
- return (_nodes[node.QueueIndex] == node);
- }
-
- /// <summary>
- /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out.
- /// If the queue is full, the result is undefined.
- /// If the node is already enqueued, the result is undefined.
- /// O(log n)
- /// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Enqueue(TItem node, TPriority priority)
- {
-#if DEBUG
- if (node == null)
- {
- throw new ArgumentNullException(nameof(node));
- }
- if (_numNodes >= _nodes.Length - 1)
- {
- throw new InvalidOperationException("Queue is full - node cannot be added: " + node);
- }
- if (Contains(node))
- {
- throw new InvalidOperationException("Node is already enqueued: " + node);
- }
-#endif
-
- node.Priority = priority;
- _numNodes++;
- _nodes[_numNodes] = node;
- node.QueueIndex = _numNodes;
- node.InsertionIndex = _numNodesEverEnqueued++;
- CascadeUp(_nodes[_numNodes]);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void Swap(TItem node1, TItem node2)
- {
- //Swap the nodes
- _nodes[node1.QueueIndex] = node2;
- _nodes[node2.QueueIndex] = node1;
-
- //Swap their indicies
- int temp = node1.QueueIndex;
- node1.QueueIndex = node2.QueueIndex;
- node2.QueueIndex = temp;
- }
-
- //Performance appears to be slightly better when this is NOT inlined o_O
- private void CascadeUp(TItem node)
- {
- //aka Heapify-up
- int parent = node.QueueIndex / 2;
- while (parent >= 1)
- {
- var parentNode = _nodes[parent];
- if (HasHigherPriority(parentNode, node))
- break;
-
- //Node has lower priority value, so move it up the heap
- Swap(node, parentNode); //For some reason, this is faster with Swap() rather than (less..?) individual operations, like in CascadeDown()
-
- parent = node.QueueIndex / 2;
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void CascadeDown(TItem node)
- {
- //aka Heapify-down
- TItem newParent;
- int finalQueueIndex = node.QueueIndex;
- while (true)
- {
- newParent = node;
- int childLeftIndex = 2 * finalQueueIndex;
-
- //Check if the left-child is higher-priority than the current node
- if (childLeftIndex > _numNodes)
- {
- //This could be placed outside the loop, but then we'd have to check newParent != node twice
- node.QueueIndex = finalQueueIndex;
- _nodes[finalQueueIndex] = node;
- break;
- }
-
- var childLeft = _nodes[childLeftIndex];
- if (HasHigherPriority(childLeft, newParent))
- {
- newParent = childLeft;
- }
-
- //Check if the right-child is higher-priority than either the current node or the left child
- int childRightIndex = childLeftIndex + 1;
- if (childRightIndex <= _numNodes)
- {
- var childRight = _nodes[childRightIndex];
- if (HasHigherPriority(childRight, newParent))
- {
- newParent = childRight;
- }
- }
-
- //If either of the children has higher (smaller) priority, swap and continue cascading
- if (newParent != node)
- {
- //Move new parent to its new index. node will be moved once, at the end
- //Doing it this way is one less assignment operation than calling Swap()
- _nodes[finalQueueIndex] = newParent;
-
- int temp = newParent.QueueIndex;
- newParent.QueueIndex = finalQueueIndex;
- finalQueueIndex = temp;
- }
- else
- {
- //See note above
- node.QueueIndex = finalQueueIndex;
- _nodes[finalQueueIndex] = node;
- break;
- }
- }
- }
-
- /// <summary>
- /// Returns true if 'higher' has higher priority than 'lower', false otherwise.
- /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false
- /// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool HasHigherPriority(TItem higher, TItem lower)
- {
- var cmp = higher.Priority.CompareTo(lower.Priority);
- return (cmp < 0 || (cmp == 0 && higher.InsertionIndex < lower.InsertionIndex));
- }
-
- /// <summary>
- /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it.
- /// If queue is empty, result is undefined
- /// O(log n)
- /// </summary>
- public bool TryDequeue(out TItem item)
- {
- if (_numNodes <= 0)
- {
- item = default(TItem);
- return false;
- }
-
-#if DEBUG
-
- if (!IsValidQueue())
- {
- throw new InvalidOperationException("Queue has been corrupted (Did you update a node priority manually instead of calling UpdatePriority()?" +
- "Or add the same node to two different queues?)");
- }
-#endif
-
- var returnMe = _nodes[1];
- Remove(returnMe);
- item = returnMe;
- return true;
- }
-
- /// <summary>
- /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain.
- /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior
- /// O(n)
- /// </summary>
- public void Resize(int maxNodes)
- {
-#if DEBUG
- if (maxNodes <= 0)
- {
- throw new InvalidOperationException("Queue size cannot be smaller than 1");
- }
-
- if (maxNodes < _numNodes)
- {
- throw new InvalidOperationException("Called Resize(" + maxNodes + "), but current queue contains " + _numNodes + " nodes");
- }
-#endif
-
- TItem[] newArray = new TItem[maxNodes + 1];
- int highestIndexToCopy = Math.Min(maxNodes, _numNodes);
- for (int i = 1; i <= highestIndexToCopy; i++)
- {
- newArray[i] = _nodes[i];
- }
- _nodes = newArray;
- }
-
- /// <summary>
- /// Returns the head of the queue, without removing it (use Dequeue() for that).
- /// If the queue is empty, behavior is undefined.
- /// O(1)
- /// </summary>
- public TItem First
- {
- get
- {
-#if DEBUG
- if (_numNodes <= 0)
- {
- throw new InvalidOperationException("Cannot call .First on an empty queue");
- }
-#endif
-
- return _nodes[1];
- }
- }
-
- /// <summary>
- /// This method must be called on a node every time its priority changes while it is in the queue.
- /// <b>Forgetting to call this method will result in a corrupted queue!</b>
- /// Calling this method on a node not in the queue results in undefined behavior
- /// O(log n)
- /// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void UpdatePriority(TItem node, TPriority priority)
- {
-#if DEBUG
- if (node == null)
- {
- throw new ArgumentNullException(nameof(node));
- }
- if (!Contains(node))
- {
- throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + node);
- }
-#endif
-
- node.Priority = priority;
- OnNodeUpdated(node);
- }
-
- private void OnNodeUpdated(TItem node)
- {
- //Bubble the updated node up or down as appropriate
- int parentIndex = node.QueueIndex / 2;
- var parentNode = _nodes[parentIndex];
-
- if (parentIndex > 0 && HasHigherPriority(node, parentNode))
- {
- CascadeUp(node);
- }
- else
- {
- //Note that CascadeDown will be called if parentNode == node (that is, node is the root)
- CascadeDown(node);
- }
- }
-
- /// <summary>
- /// Removes a node from the queue. The node does not need to be the head of the queue.
- /// If the node is not in the queue, the result is undefined. If unsure, check Contains() first
- /// O(log n)
- /// </summary>
- public void Remove(TItem node)
- {
-#if DEBUG
- if (node == null)
- {
- throw new ArgumentNullException(nameof(node));
- }
- if (!Contains(node))
- {
- throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + node);
- }
-#endif
-
- //If the node is already the last node, we can remove it immediately
- if (node.QueueIndex == _numNodes)
- {
- _nodes[_numNodes] = null;
- _numNodes--;
- return;
- }
-
- //Swap the node with the last node
- var formerLastNode = _nodes[_numNodes];
- Swap(node, formerLastNode);
- _nodes[_numNodes] = null;
- _numNodes--;
-
- //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate
- OnNodeUpdated(formerLastNode);
- }
-
- public IEnumerator<TItem> GetEnumerator()
- {
- for (int i = 1; i <= _numNodes; i++)
- yield return _nodes[i];
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- /// <summary>
- /// <b>Should not be called in production code.</b>
- /// Checks to make sure the queue is still in a valid state. Used for testing/debugging the queue.
- /// </summary>
- public bool IsValidQueue()
- {
- for (int i = 1; i < _nodes.Length; i++)
- {
- if (_nodes[i] != null)
- {
- int childLeftIndex = 2 * i;
- if (childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i]))
- return false;
-
- int childRightIndex = childLeftIndex + 1;
- if (childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i]))
- return false;
- }
- }
- return true;
- }
- }
-}
diff --git a/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs b/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs
deleted file mode 100644
index b45ae0fd8..000000000
--- a/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace Priority_Queue
-{
- /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
- public class GenericPriorityQueueNode<TPriority>
- {
- /// <summary>
- /// The Priority to insert this node at. Must be set BEFORE adding a node to the queue (ideally just once, in the node's constructor).
- /// Should not be manually edited once the node has been enqueued - use queue.UpdatePriority() instead
- /// </summary>
- public TPriority Priority { get; protected internal set; }
-
- /// <summary>
- /// Represents the current position in the queue
- /// </summary>
- public int QueueIndex { get; internal set; }
-
- /// <summary>
- /// Represents the order the node was inserted in
- /// </summary>
- public long InsertionIndex { get; internal set; }
- }
-}
diff --git a/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs b/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs
deleted file mode 100644
index 509d98e42..000000000
--- a/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-
-namespace Priority_Queue
-{
- /// <summary>
- /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
- /// A helper-interface only needed to make writing unit tests a bit easier (hence the 'internal' access modifier)
- /// </summary>
- internal interface IFixedSizePriorityQueue<TItem, in TPriority> : IPriorityQueue<TItem, TPriority>
- where TPriority : IComparable<TPriority>
- {
- /// <summary>
- /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain.
- /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior
- /// </summary>
- void Resize(int maxNodes);
-
- /// <summary>
- /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize),
- /// attempting to enqueue another item will cause undefined behavior.
- /// </summary>
- int MaxSize { get; }
- }
-}
diff --git a/MediaBrowser.Providers/Manager/IPriorityQueue.cs b/MediaBrowser.Providers/Manager/IPriorityQueue.cs
deleted file mode 100644
index dc319a7f8..000000000
--- a/MediaBrowser.Providers/Manager/IPriorityQueue.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Priority_Queue
-{
- /// <summary>
- /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
- /// The IPriorityQueue interface. This is mainly here for purists, and in case I decide to add more implementations later.
- /// For speed purposes, it is actually recommended that you *don't* access the priority queue through this interface, since the JIT can
- /// (theoretically?) optimize method calls from concrete-types slightly better.
- /// </summary>
- public interface IPriorityQueue<TItem, in TPriority> : IEnumerable<TItem>
- where TPriority : IComparable<TPriority>
- {
- /// <summary>
- /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out.
- /// See implementation for how duplicates are handled.
- /// </summary>
- void Enqueue(TItem node, TPriority priority);
-
- /// <summary>
- /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it.
- /// </summary>
- bool TryDequeue(out TItem item);
-
- /// <summary>
- /// Removes every node from the queue.
- /// </summary>
- void Clear();
-
- /// <summary>
- /// Returns whether the given node is in the queue.
- /// </summary>
- bool Contains(TItem node);
-
- /// <summary>
- /// Removes a node from the queue. The node does not need to be the head of the queue.
- /// </summary>
- void Remove(TItem node);
-
- /// <summary>
- /// Call this method to change the priority of a node.
- /// </summary>
- void UpdatePriority(TItem node, TPriority priority);
-
- /// <summary>
- /// Returns the head of the queue, without removing it (use Dequeue() for that).
- /// </summary>
- TItem First { get; }
-
- /// <summary>
- /// Returns the number of nodes in the queue.
- /// </summary>
- int Count { get; }
- }
-}
diff --git a/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs b/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs
deleted file mode 100644
index d064312cf..000000000
--- a/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs
+++ /dev/null
@@ -1,247 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-
-namespace Priority_Queue
-{
- /// <summary>
- /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
- /// A simplified priority queue implementation. Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than
- /// FastPriorityQueue
- /// </summary>
- /// <typeparam name="TItem">The type to enqueue</typeparam>
- /// <typeparam name="TPriority">The priority-type to use for nodes. Must extend IComparable&lt;TPriority&gt;</typeparam>
- public class SimplePriorityQueue<TItem, TPriority> : IPriorityQueue<TItem, TPriority>
- where TPriority : IComparable<TPriority>
- {
- private class SimpleNode : GenericPriorityQueueNode<TPriority>
- {
- public TItem Data { get; private set; }
-
- public SimpleNode(TItem data)
- {
- Data = data;
- }
- }
-
- private const int INITIAL_QUEUE_SIZE = 10;
- private readonly GenericPriorityQueue<SimpleNode, TPriority> _queue;
-
- public SimplePriorityQueue()
- {
- _queue = new GenericPriorityQueue<SimpleNode, TPriority>(INITIAL_QUEUE_SIZE);
- }
-
- /// <summary>
- /// Given an item of type T, returns the exist SimpleNode in the queue
- /// </summary>
- private SimpleNode GetExistingNode(TItem item)
- {
- var comparer = EqualityComparer<TItem>.Default;
- foreach (var node in _queue)
- {
- if (comparer.Equals(node.Data, item))
- {
- return node;
- }
- }
- throw new InvalidOperationException("Item cannot be found in queue: " + item);
- }
-
- /// <summary>
- /// Returns the number of nodes in the queue.
- /// O(1)
- /// </summary>
- public int Count
- {
- get
- {
- lock (_queue)
- {
- return _queue.Count;
- }
- }
- }
-
-
- /// <summary>
- /// Returns the head of the queue, without removing it (use Dequeue() for that).
- /// Throws an exception when the queue is empty.
- /// O(1)
- /// </summary>
- public TItem First
- {
- get
- {
- lock (_queue)
- {
- if (_queue.Count <= 0)
- {
- throw new InvalidOperationException("Cannot call .First on an empty queue");
- }
-
- SimpleNode first = _queue.First;
- return (first != null ? first.Data : default(TItem));
- }
- }
- }
-
- /// <summary>
- /// Removes every node from the queue.
- /// O(n)
- /// </summary>
- public void Clear()
- {
- lock (_queue)
- {
- _queue.Clear();
- }
- }
-
- /// <summary>
- /// Returns whether the given item is in the queue.
- /// O(n)
- /// </summary>
- public bool Contains(TItem item)
- {
- lock (_queue)
- {
- var comparer = EqualityComparer<TItem>.Default;
- foreach (var node in _queue)
- {
- if (comparer.Equals(node.Data, item))
- {
- return true;
- }
- }
- return false;
- }
- }
-
- /// <summary>
- /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it.
- /// If queue is empty, throws an exception
- /// O(log n)
- /// </summary>
- public bool TryDequeue(out TItem item)
- {
- lock (_queue)
- {
- if (_queue.Count <= 0)
- {
- item = default(TItem);
- return false;
- }
-
- if (_queue.TryDequeue(out SimpleNode node))
- {
- item = node.Data;
- return true;
- }
-
- item = default(TItem);
- return false;
- }
- }
-
- /// <summary>
- /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out.
- /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'.
- /// Duplicates are allowed.
- /// O(log n)
- /// </summary>
- public void Enqueue(TItem item, TPriority priority)
- {
- lock (_queue)
- {
- var node = new SimpleNode(item);
- if (_queue.Count == _queue.MaxSize)
- {
- _queue.Resize(_queue.MaxSize * 2 + 1);
- }
- _queue.Enqueue(node, priority);
- }
- }
-
- /// <summary>
- /// Removes an item from the queue. The item does not need to be the head of the queue.
- /// If the item is not in the queue, an exception is thrown. If unsure, check Contains() first.
- /// If multiple copies of the item are enqueued, only the first one is removed.
- /// O(n)
- /// </summary>
- public void Remove(TItem item)
- {
- lock (_queue)
- {
- try
- {
- _queue.Remove(GetExistingNode(item));
- }
- catch (InvalidOperationException ex)
- {
- throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item, ex);
- }
- }
- }
-
- /// <summary>
- /// Call this method to change the priority of an item.
- /// Calling this method on a item not in the queue will throw an exception.
- /// If the item is enqueued multiple times, only the first one will be updated.
- /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
- /// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
- /// O(n)
- /// </summary>
- public void UpdatePriority(TItem item, TPriority priority)
- {
- lock (_queue)
- {
- try
- {
- SimpleNode updateMe = GetExistingNode(item);
- _queue.UpdatePriority(updateMe, priority);
- }
- catch (InvalidOperationException ex)
- {
- throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + item, ex);
- }
- }
- }
-
- public IEnumerator<TItem> GetEnumerator()
- {
- var queueData = new List<TItem>();
- lock (_queue)
- {
- //Copy to a separate list because we don't want to 'yield return' inside a lock
- foreach (var node in _queue)
- {
- queueData.Add(node.Data);
- }
- }
-
- return queueData.GetEnumerator();
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- public bool IsValidQueue()
- {
- lock (_queue)
- {
- return _queue.IsValidQueue();
- }
- }
- }
-
- /// <summary>
- /// A simplified priority queue implementation. Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than
- /// FastPriorityQueue
- /// This class is kept here for backwards compatibility. It's recommended you use Simple
- /// </summary>
- /// <typeparam name="TItem">The type to enqueue</typeparam>
- public class SimplePriorityQueue<TItem> : SimplePriorityQueue<TItem, float> { }
-}
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index e6ef889c3..52a52efdc 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@@ -11,7 +11,10 @@
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.2.0" />
+ <PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
<PackageReference Include="PlaylistsNET" Version="1.0.2" />
+ <PackageReference Include="TvDbSharper" Version="2.0.0" />
</ItemGroup>
<PropertyGroup>
diff --git a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs
index b9c5d7ce5..20b53d58a 100644
--- a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs
@@ -16,7 +16,7 @@ using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Movies
{
- class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
+ public class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
diff --git a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
index 8b01ff342..93412306f 100644
--- a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
+++ b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
@@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Music
{
- class MusicVideoMetadataService : MetadataService<MusicVideo, MusicVideoInfo>
+ public class MusicVideoMetadataService : MetadataService<MusicVideo, MusicVideoInfo>
{
protected override void MergeData(MetadataResult<MusicVideo> source, MetadataResult<MusicVideo> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{
diff --git a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs
index 181e88820..8c8b99e89 100644
--- a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs
@@ -1,42 +1,35 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Xml;
-using MediaBrowser.Providers.TV;
using MediaBrowser.Providers.TV.TheTVDB;
+using Microsoft.Extensions.Logging;
+using TvDbSharper;
namespace MediaBrowser.Providers.People
{
public class TvdbPersonImageProvider : IRemoteImageProvider, IHasOrder
{
- private readonly IServerConfigurationManager _config;
- private readonly ILibraryManager _libraryManager;
private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
- private readonly IXmlReaderSettingsFactory _xmlSettings;
+ private readonly ILogger _logger;
+ private readonly ILibraryManager _libraryManager;
+ private readonly TvDbClientManager _tvDbClientManager;
- public TvdbPersonImageProvider(IServerConfigurationManager config, ILibraryManager libraryManager, IHttpClient httpClient, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlSettings)
+ public TvdbPersonImageProvider(ILibraryManager libraryManager, IHttpClient httpClient, ILogger<TvdbPersonImageProvider> logger, TvDbClientManager tvDbClientManager)
{
- _config = config;
_libraryManager = libraryManager;
_httpClient = httpClient;
- _fileSystem = fileSystem;
- _xmlSettings = xmlSettings;
+ _logger = logger;
+ _tvDbClientManager = tvDbClientManager;
}
public string Name => ProviderName;
@@ -56,7 +49,7 @@ namespace MediaBrowser.Providers.People
};
}
- public Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
var seriesWithPerson = _libraryManager.GetItemList(new InternalItemsQuery
{
@@ -71,152 +64,44 @@ namespace MediaBrowser.Providers.People
.Where(i => TvdbSeriesProvider.IsValidSeries(i.ProviderIds))
.ToList();
- var infos = seriesWithPerson.Select(i => GetImageFromSeriesData(i, item.Name, cancellationToken))
+ var infos = (await Task.WhenAll(seriesWithPerson.Select(async i =>
+ await GetImageFromSeriesData(i, item.Name, cancellationToken).ConfigureAwait(false)))
+ .ConfigureAwait(false))
.Where(i => i != null)
.Take(1);
- return Task.FromResult(infos);
+ return infos;
}
- private RemoteImageInfo GetImageFromSeriesData(Series series, string personName, CancellationToken cancellationToken)
+ private async Task<RemoteImageInfo> GetImageFromSeriesData(Series series, string personName, CancellationToken cancellationToken)
{
- var tvdbPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, series.ProviderIds);
-
- var actorXmlPath = Path.Combine(tvdbPath, "actors.xml");
+ var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProviders.Tvdb));
try
{
- return GetImageInfo(actorXmlPath, personName, cancellationToken);
- }
- catch (FileNotFoundException)
- {
- return null;
- }
- catch (IOException)
- {
- return null;
- }
- }
-
- private RemoteImageInfo GetImageInfo(string xmlFile, string personName, CancellationToken cancellationToken)
- {
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- using (var fileStream = _fileSystem.GetFileStream(xmlFile, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
- {
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
- {
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Actor":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
- using (var subtree = reader.ReadSubtree())
- {
- var info = FetchImageInfoFromActorNode(personName, subtree);
-
- if (info != null)
- {
- return info;
- }
- }
- break;
- }
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
- }
- }
-
- return null;
- }
-
- /// <summary>
- /// Fetches the data from actor node.
- /// </summary>
- /// <param name="personName">Name of the person.</param>
- /// <param name="reader">The reader.</param>
- /// <returns>System.String.</returns>
- private RemoteImageInfo FetchImageInfoFromActorNode(string personName, XmlReader reader)
- {
- string name = null;
- string image = null;
-
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Name":
- {
- name = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
- break;
- }
-
- case "Image":
- {
- image = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
+ var actorsResult = await _tvDbClientManager
+ .GetActorsAsync(tvdbId, series.GetPreferredMetadataLanguage(), cancellationToken)
+ .ConfigureAwait(false);
+ var actor = actorsResult.Data.FirstOrDefault(a =>
+ string.Equals(a.Name, personName, StringComparison.OrdinalIgnoreCase) &&
+ !string.IsNullOrEmpty(a.Image));
+ if (actor == null)
{
- reader.Read();
+ return null;
}
- }
- if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(image) &&
- string.Equals(name, personName, StringComparison.OrdinalIgnoreCase))
- {
return new RemoteImageInfo
{
- Url = TVUtils.BannerUrl + image,
+ Url = TvdbUtils.BannerUrl + actor.Image,
Type = ImageType.Primary,
ProviderName = Name
-
};
}
-
- return null;
+ catch (TvDbServerException e)
+ {
+ _logger.LogError(e, "Failed to retrieve actor {ActorName} from series {SeriesTvdbId}", personName, tvdbId);
+ return null;
+ }
}
public int Order => 1;
diff --git a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
index fd969c7c2..993581cca 100644
--- a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
@@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Photos
{
- class PhotoAlbumMetadataService : MetadataService<PhotoAlbum, ItemLookupInfo>
+ public class PhotoAlbumMetadataService : MetadataService<PhotoAlbum, ItemLookupInfo>
{
protected override void MergeData(MetadataResult<PhotoAlbum> source, MetadataResult<PhotoAlbum> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{
diff --git a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
index a430e1041..b739c5765 100644
--- a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
+++ b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
@@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Photos
{
- class PhotoMetadataService : MetadataService<Photo, ItemLookupInfo>
+ public class PhotoMetadataService : MetadataService<Photo, ItemLookupInfo>
{
protected override void MergeData(MetadataResult<Photo> source, MetadataResult<Photo> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{
diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
index b28d2a548..30ce5c64c 100644
--- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
+++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
@@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Playlists
{
- class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo>
+ public class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo>
{
protected override IList<BaseItem> GetChildrenForMetadataUpdates(Playlist item)
{
diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
index 25ad36620..0a2975e0f 100644
--- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
@@ -15,7 +15,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Xml;
using MediaBrowser.Providers.TV.TheTVDB;
using Microsoft.Extensions.Logging;
@@ -28,77 +27,58 @@ namespace MediaBrowser.Providers.TV
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
private readonly IFileSystem _fileSystem;
+ private readonly TvDbClientManager _tvDbClientManager;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private readonly IXmlReaderSettingsFactory _xmlSettings;
+ private const double UnairedEpisodeThresholdDays = 2;
- public MissingEpisodeProvider(ILogger logger, IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlSettings)
+ public MissingEpisodeProvider(
+ ILogger logger,
+ IServerConfigurationManager config,
+ ILibraryManager libraryManager,
+ ILocalizationManager localization,
+ IFileSystem fileSystem,
+ TvDbClientManager tvDbClientManager)
{
_logger = logger;
_config = config;
_libraryManager = libraryManager;
_localization = localization;
_fileSystem = fileSystem;
- _xmlSettings = xmlSettings;
+ _tvDbClientManager = tvDbClientManager;
}
public async Task<bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken)
{
var tvdbId = series.GetProviderId(MetadataProviders.Tvdb);
-
- // Todo: Support series by imdb id
- var seriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- seriesProviderIds[MetadataProviders.Tvdb.ToString()] = tvdbId;
-
- var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);
-
- // Doesn't have required provider id's
- if (string.IsNullOrWhiteSpace(seriesDataPath))
- {
- return false;
- }
-
- // Check this in order to avoid logging an exception due to directory not existing
- if (!Directory.Exists(seriesDataPath))
+ if (string.IsNullOrEmpty(tvdbId))
{
return false;
}
- var episodeFiles = _fileSystem.GetFilePaths(seriesDataPath)
- .Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
- .Select(Path.GetFileNameWithoutExtension)
- .Where(i => i.StartsWith("episode-", StringComparison.OrdinalIgnoreCase))
- .ToList();
+ var episodes = await _tvDbClientManager.GetAllEpisodesAsync(Convert.ToInt32(tvdbId), series.GetPreferredMetadataLanguage(), cancellationToken);
- var episodeLookup = episodeFiles
+ var episodeLookup = episodes
.Select(i =>
{
- var parts = i.Split('-');
-
- if (parts.Length == 3)
- {
- if (int.TryParse(parts[1], NumberStyles.Integer, _usCulture, out var seasonNumber))
- {
- if (int.TryParse(parts[2], NumberStyles.Integer, _usCulture, out var episodeNumber))
- {
- return new ValueTuple<int, int>(seasonNumber, episodeNumber);
- }
- }
- }
-
- return new ValueTuple<int, int>(-1, -1);
+ DateTime.TryParse(i.FirstAired, out var firstAired);
+ var seasonNumber = i.AiredSeason.GetValueOrDefault(-1);
+ var episodeNumber = i.AiredEpisodeNumber.GetValueOrDefault(-1);
+ return (seasonNumber: seasonNumber, episodeNumber: episodeNumber, firstAired: firstAired);
})
- .Where(i => i.Item1 != -1 && i.Item2 != -1)
+ .Where(i => i.seasonNumber != -1 && i.episodeNumber != -1)
+ .OrderBy(i => i.seasonNumber)
+ .ThenBy(i => i.episodeNumber)
.ToList();
var allRecursiveChildren = series.GetRecursiveChildren();
- var hasBadData = HasInvalidContent(series, allRecursiveChildren);
+ var hasBadData = HasInvalidContent(allRecursiveChildren);
// Be conservative here to avoid creating missing episodes for ones they already have
var addMissingEpisodes = !hasBadData && _libraryManager.GetLibraryOptions(series).ImportMissingEpisodes;
- var anySeasonsRemoved = RemoveObsoleteOrMissingSeasons(series, allRecursiveChildren, episodeLookup);
+ var anySeasonsRemoved = RemoveObsoleteOrMissingSeasons(allRecursiveChildren, episodeLookup);
if (anySeasonsRemoved)
{
@@ -106,7 +86,7 @@ namespace MediaBrowser.Providers.TV
allRecursiveChildren = series.GetRecursiveChildren();
}
- var anyEpisodesRemoved = RemoveObsoleteOrMissingEpisodes(series, allRecursiveChildren, episodeLookup, addMissingEpisodes);
+ var anyEpisodesRemoved = RemoveObsoleteOrMissingEpisodes(allRecursiveChildren, episodeLookup, addMissingEpisodes);
if (anyEpisodesRemoved)
{
@@ -118,7 +98,7 @@ namespace MediaBrowser.Providers.TV
if (addNewItems && series.IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(series), TvdbSeriesProvider.Current.Name))
{
- hasNewEpisodes = await AddMissingEpisodes(series, allRecursiveChildren, addMissingEpisodes, seriesDataPath, episodeLookup, cancellationToken)
+ hasNewEpisodes = await AddMissingEpisodes(series, allRecursiveChildren, addMissingEpisodes, episodeLookup, cancellationToken)
.ConfigureAwait(false);
}
@@ -134,7 +114,7 @@ namespace MediaBrowser.Providers.TV
/// Returns true if a series has any seasons or episodes without season or episode numbers
/// If this data is missing no virtual items will be added in order to prevent possible duplicates
/// </summary>
- private bool HasInvalidContent(Series series, IList<BaseItem> allItems)
+ private bool HasInvalidContent(IList<BaseItem> allItems)
{
return allItems.OfType<Season>().Any(i => !i.IndexNumber.HasValue) ||
allItems.OfType<Episode>().Any(i =>
@@ -149,43 +129,24 @@ namespace MediaBrowser.Providers.TV
});
}
- private const double UnairedEpisodeThresholdDays = 2;
-
- /// <summary>
- /// Adds the missing episodes.
- /// </summary>
- /// <param name="series">The series.</param>
- /// <returns>Task.</returns>
- private async Task<bool> AddMissingEpisodes(Series series,
- IList<BaseItem> allItems,
+ private async Task<bool> AddMissingEpisodes(
+ Series series,
+ IEnumerable<BaseItem> allItems,
bool addMissingEpisodes,
- string seriesDataPath,
- IEnumerable<ValueTuple<int, int>> episodeLookup,
+ IReadOnlyCollection<(int seasonNumber, int episodenumber, DateTime firstAired)> episodeLookup,
CancellationToken cancellationToken)
{
- var existingEpisodes = allItems.OfType<Episode>()
- .ToList();
+ var existingEpisodes = allItems.OfType<Episode>().ToList();
- var lookup = episodeLookup as IList<ValueTuple<int, int>> ?? episodeLookup.ToList();
-
- var seasonCounts = (from e in lookup
- group e by e.Item1 into g
- select g)
- .ToDictionary(g => g.Key, g => g.Count());
+ var seasonCounts = episodeLookup.GroupBy(e => e.seasonNumber).ToDictionary(g => g.Key, g => g.Count());
var hasChanges = false;
- foreach (var tuple in lookup)
+ foreach (var tuple in episodeLookup)
{
- if (tuple.Item1 <= 0)
- {
- // Ignore season zeros
- continue;
- }
-
- if (tuple.Item2 <= 0)
+ if (tuple.seasonNumber <= 0 || tuple.episodenumber <= 0)
{
- // Ignore episode zeros
+ // Ignore episode/season zeros
continue;
}
@@ -196,33 +157,15 @@ namespace MediaBrowser.Providers.TV
continue;
}
- var airDate = GetAirDate(seriesDataPath, tuple.Item1, tuple.Item2);
-
- if (!airDate.HasValue)
- {
- continue;
- }
-
- var now = DateTime.UtcNow;
+ var airDate = tuple.firstAired;
- now = now.AddDays(0 - UnairedEpisodeThresholdDays);
-
- if (airDate.Value < now)
- {
- if (addMissingEpisodes)
- {
- // tvdb has a lot of nearly blank episodes
- _logger.LogInformation("Creating virtual missing episode {0} {1}x{2}", series.Name, tuple.Item1, tuple.Item2);
- await AddEpisode(series, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false);
+ var now = DateTime.UtcNow.AddDays(-UnairedEpisodeThresholdDays);
- hasChanges = true;
- }
- }
- else if (airDate.Value > now)
+ if (airDate < now && addMissingEpisodes || airDate > now)
{
// tvdb has a lot of nearly blank episodes
- _logger.LogInformation("Creating virtual unaired episode {0} {1}x{2}", series.Name, tuple.Item1, tuple.Item2);
- await AddEpisode(series, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false);
+ _logger.LogInformation("Creating virtual missing/unaired episode {0} {1}x{2}", series.Name, tuple.seasonNumber, tuple.episodenumber);
+ await AddEpisode(series, tuple.seasonNumber, tuple.episodenumber, cancellationToken).ConfigureAwait(false);
hasChanges = true;
}
@@ -234,59 +177,58 @@ namespace MediaBrowser.Providers.TV
/// <summary>
/// Removes the virtual entry after a corresponding physical version has been added
/// </summary>
- private bool RemoveObsoleteOrMissingEpisodes(Series series,
- IList<BaseItem> allRecursiveChildren,
- IEnumerable<ValueTuple<int, int>> episodeLookup,
+ private bool RemoveObsoleteOrMissingEpisodes(
+ IEnumerable<BaseItem> allRecursiveChildren,
+ IEnumerable<(int seasonNumber, int episodeNumber, DateTime firstAired)> episodeLookup,
bool allowMissingEpisodes)
{
- var existingEpisodes = allRecursiveChildren.OfType<Episode>()
- .ToList();
-
- var physicalEpisodes = existingEpisodes
- .Where(i => i.LocationType != LocationType.Virtual)
- .ToList();
+ var existingEpisodes = allRecursiveChildren.OfType<Episode>();
- var virtualEpisodes = existingEpisodes
- .Where(i => i.LocationType == LocationType.Virtual)
- .ToList();
+ var physicalEpisodes = new List<Episode>();
+ var virtualEpisodes = new List<Episode>();
+ foreach (var episode in existingEpisodes)
+ {
+ if (episode.LocationType == LocationType.Virtual)
+ {
+ virtualEpisodes.Add(episode);
+ }
+ else
+ {
+ physicalEpisodes.Add(episode);
+ }
+ }
var episodesToRemove = virtualEpisodes
.Where(i =>
{
- if (i.IndexNumber.HasValue && i.ParentIndexNumber.HasValue)
+ if (!i.IndexNumber.HasValue || !i.ParentIndexNumber.HasValue)
{
- var seasonNumber = i.ParentIndexNumber.Value;
- var episodeNumber = i.IndexNumber.Value;
-
- // If there's a physical episode with the same season and episode number, delete it
- if (physicalEpisodes.Any(p =>
- p.ParentIndexNumber.HasValue && (p.ParentIndexNumber.Value) == seasonNumber &&
- p.ContainsEpisodeNumber(episodeNumber)))
- {
- return true;
- }
+ return true;
+ }
- // If the episode no longer exists in the remote lookup, delete it
- if (!episodeLookup.Any(e => e.Item1 == seasonNumber && e.Item2 == episodeNumber))
- {
- return true;
- }
+ var seasonNumber = i.ParentIndexNumber.Value;
+ var episodeNumber = i.IndexNumber.Value;
- if (!allowMissingEpisodes && i.IsMissingEpisode)
- {
- // If it's missing, but not unaired, remove it
- if (!i.PremiereDate.HasValue || i.PremiereDate.Value.ToLocalTime().Date.AddDays(UnairedEpisodeThresholdDays) < DateTime.Now.Date)
- {
- return true;
- }
- }
+ // If there's a physical episode with the same season and episode number, delete it
+ if (physicalEpisodes.Any(p =>
+ p.ParentIndexNumber.HasValue && p.ParentIndexNumber.Value == seasonNumber &&
+ p.ContainsEpisodeNumber(episodeNumber)))
+ {
+ return true;
+ }
- return false;
+ // If the episode no longer exists in the remote lookup, delete it
+ if (!episodeLookup.Any(e => e.seasonNumber == seasonNumber && e.episodeNumber == episodeNumber))
+ {
+ return true;
}
- return true;
- })
- .ToList();
+ // If it's missing, but not unaired, remove it
+ return !allowMissingEpisodes && i.IsMissingEpisode &&
+ (!i.PremiereDate.HasValue ||
+ i.PremiereDate.Value.ToLocalTime().Date.AddDays(UnairedEpisodeThresholdDays) <
+ DateTime.Now.Date);
+ });
var hasChanges = false;
@@ -295,7 +237,6 @@ namespace MediaBrowser.Providers.TV
_libraryManager.DeleteItem(episodeToRemove, new DeleteOptions
{
DeleteFileLocation = true
-
}, false);
hasChanges = true;
@@ -307,22 +248,27 @@ namespace MediaBrowser.Providers.TV
/// <summary>
/// Removes the obsolete or missing seasons.
/// </summary>
- /// <param name="series">The series.</param>
+ /// <param name="allRecursiveChildren"></param>
/// <param name="episodeLookup">The episode lookup.</param>
/// <returns>Task{System.Boolean}.</returns>
- private bool RemoveObsoleteOrMissingSeasons(Series series,
- IList<BaseItem> allRecursiveChildren,
- IEnumerable<ValueTuple<int, int>> episodeLookup)
+ private bool RemoveObsoleteOrMissingSeasons(IList<BaseItem> allRecursiveChildren,
+ IEnumerable<(int seasonNumber, int episodeNumber, DateTime firstAired)> episodeLookup)
{
var existingSeasons = allRecursiveChildren.OfType<Season>().ToList();
- var physicalSeasons = existingSeasons
- .Where(i => i.LocationType != LocationType.Virtual)
- .ToList();
-
- var virtualSeasons = existingSeasons
- .Where(i => i.LocationType == LocationType.Virtual)
- .ToList();
+ var physicalSeasons = new List<Season>();
+ var virtualSeasons = new List<Season>();
+ foreach (var season in existingSeasons)
+ {
+ if (season.LocationType == LocationType.Virtual)
+ {
+ virtualSeasons.Add(season);
+ }
+ else
+ {
+ physicalSeasons.Add(season);
+ }
+ }
var allEpisodes = allRecursiveChildren.OfType<Episode>().ToList();
@@ -334,28 +280,19 @@ namespace MediaBrowser.Providers.TV
var seasonNumber = i.IndexNumber.Value;
// If there's a physical season with the same number, delete it
- if (physicalSeasons.Any(p => p.IndexNumber.HasValue && (p.IndexNumber.Value) == seasonNumber && string.Equals(p.Series.PresentationUniqueKey, i.Series.PresentationUniqueKey, StringComparison.Ordinal)))
+ if (physicalSeasons.Any(p => p.IndexNumber.HasValue && p.IndexNumber.Value == seasonNumber && string.Equals(p.Series.PresentationUniqueKey, i.Series.PresentationUniqueKey, StringComparison.Ordinal)))
{
return true;
}
// If the season no longer exists in the remote lookup, delete it, but only if an existing episode doesn't require it
- if (episodeLookup.All(e => e.Item1 != seasonNumber))
- {
- if (allEpisodes.All(s => s.ParentIndexNumber != seasonNumber || s.IsInSeasonFolder))
- {
- return true;
- }
- }
-
- return false;
+ return episodeLookup.All(e => e.seasonNumber != seasonNumber) && allEpisodes.All(s => s.ParentIndexNumber != seasonNumber || s.IsInSeasonFolder);
}
// Season does not have a number
// Remove if there are no episodes directly in series without a season number
return allEpisodes.All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder);
- })
- .ToList();
+ });
var hasChanges = false;
@@ -392,21 +329,19 @@ namespace MediaBrowser.Providers.TV
season = await provider.AddSeason(series, seasonNumber, true, cancellationToken).ConfigureAwait(false);
}
- var name = string.Format("Episode {0}", episodeNumber.ToString(_usCulture));
+ var name = $"Episode {episodeNumber.ToString(_usCulture)}";
var episode = new Episode
{
Name = name,
IndexNumber = episodeNumber,
ParentIndexNumber = seasonNumber,
- Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)),
+ Id = _libraryManager.GetNewItemId(series.Id + seasonNumber.ToString(_usCulture) + name, typeof(Episode)),
IsVirtualItem = true,
- SeasonId = season == null ? Guid.Empty : season.Id,
+ SeasonId = season?.Id ?? Guid.Empty,
SeriesId = series.Id
};
- episode.SetParent(season);
-
season.AddChild(episode, cancellationToken);
await episode.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false);
@@ -417,25 +352,31 @@ namespace MediaBrowser.Providers.TV
/// </summary>
/// <param name="existingEpisodes">The existing episodes.</param>
/// <param name="seasonCounts"></param>
- /// <param name="tuple">The tuple.</param>
+ /// <param name="episodeTuple"></param>
/// <returns>Episode.</returns>
- private Episode GetExistingEpisode(IList<Episode> existingEpisodes, Dictionary<int, int> seasonCounts, ValueTuple<int, int> tuple)
+ private Episode GetExistingEpisode(IList<Episode> existingEpisodes, IReadOnlyDictionary<int, int> seasonCounts, (int seasonNumber, int episodeNumber, DateTime firstAired) episodeTuple)
{
- var s = tuple.Item1;
- var e = tuple.Item2;
+ var seasonNumber = episodeTuple.seasonNumber;
+ var episodeNumber = episodeTuple.episodeNumber;
while (true)
{
- var episode = GetExistingEpisode(existingEpisodes, s, e);
+ var episode = GetExistingEpisode(existingEpisodes, seasonNumber, episodeNumber);
if (episode != null)
+ {
return episode;
+ }
- s--;
+ seasonNumber--;
- if (seasonCounts.ContainsKey(s))
- e += seasonCounts[s];
+ if (seasonCounts.ContainsKey(seasonNumber))
+ {
+ episodeNumber += seasonCounts[seasonNumber];
+ }
else
+ {
break;
+ }
}
return null;
@@ -446,88 +387,5 @@ namespace MediaBrowser.Providers.TV
return existingEpisodes
.FirstOrDefault(i => i.ParentIndexNumber == season && i.ContainsEpisodeNumber(episode));
}
-
- /// <summary>
- /// Gets the air date.
- /// </summary>
- /// <param name="seriesDataPath">The series data path.</param>
- /// <param name="seasonNumber">The season number.</param>
- /// <param name="episodeNumber">The episode number.</param>
- /// <returns>System.Nullable{DateTime}.</returns>
- private DateTime? GetAirDate(string seriesDataPath, int seasonNumber, int episodeNumber)
- {
- // First open up the tvdb xml file and make sure it has valid data
- var filename = string.Format("episode-{0}-{1}.xml", seasonNumber.ToString(_usCulture), episodeNumber.ToString(_usCulture));
-
- var xmlPath = Path.Combine(seriesDataPath, filename);
-
- DateTime? airDate = null;
-
- using (var fileStream = _fileSystem.GetFileStream(xmlPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
- {
- // It appears the best way to filter out invalid entries is to only include those with valid air dates
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
- {
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "EpisodeName":
- {
- var val = reader.ReadElementContentAsString();
- if (string.IsNullOrWhiteSpace(val))
- {
- // Not valid, ignore these
- return null;
- }
- break;
- }
- case "FirstAired":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (DateTime.TryParse(val, out var date))
- {
- airDate = date.ToUniversalTime();
- }
- }
-
- break;
- }
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
- }
- }
-
- return airDate;
- }
}
}
diff --git a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs
index d0749405b..dee3030af 100644
--- a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs
@@ -16,7 +16,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV.Omdb
{
- class OmdbEpisodeProvider :
+ public class OmdbEpisodeProvider :
IRemoteMetadataProvider<Episode, EpisodeInfo>,
IHasOrder
{
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
index 5f4f39d45..afbd838e4 100644
--- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -10,6 +10,7 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Xml;
using MediaBrowser.Providers.Manager;
+using MediaBrowser.Providers.TV.TheTVDB;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV
@@ -18,11 +19,24 @@ namespace MediaBrowser.Providers.TV
{
private readonly ILocalizationManager _localization;
private readonly IXmlReaderSettingsFactory _xmlSettings;
+ private readonly TvDbClientManager _tvDbClientManager;
- public SeriesMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager, ILocalizationManager localization, IXmlReaderSettingsFactory xmlSettings) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ public SeriesMetadataService(
+ IServerConfigurationManager serverConfigurationManager,
+ ILogger logger,
+ IProviderManager providerManager,
+ IFileSystem fileSystem,
+ IUserDataManager userDataManager,
+ ILibraryManager libraryManager,
+ ILocalizationManager localization,
+ IXmlReaderSettingsFactory xmlSettings,
+ TvDbClientManager tvDbClientManager
+ )
+ : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
_localization = localization;
_xmlSettings = xmlSettings;
+ _tvDbClientManager = tvDbClientManager;
}
protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
@@ -32,12 +46,13 @@ namespace MediaBrowser.Providers.TV
var seasonProvider = new DummySeasonProvider(ServerConfigurationManager, Logger, _localization, LibraryManager, FileSystem);
await seasonProvider.Run(item, cancellationToken).ConfigureAwait(false);
+ // TODO why does it not register this itself omg
var provider = new MissingEpisodeProvider(Logger,
ServerConfigurationManager,
LibraryManager,
_localization,
FileSystem,
- _xmlSettings);
+ _tvDbClientManager);
try
{
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
index 44590515e..3d7745085 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
@@ -20,7 +20,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV.TheMovieDb
{
- class MovieDbEpisodeProvider :
+ public class MovieDbEpisodeProvider :
MovieDbProviderBase,
IRemoteMetadataProvider<Episode, EpisodeInfo>,
IHasOrder
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs b/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs
new file mode 100644
index 000000000..efb8a0fe8
--- /dev/null
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs
@@ -0,0 +1,244 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using Microsoft.Extensions.Caching.Memory;
+using TvDbSharper;
+using TvDbSharper.Dto;
+
+namespace MediaBrowser.Providers.TV.TheTVDB
+{
+ public class TvDbClientManager
+ {
+ private readonly SemaphoreSlim _cacheWriteLock = new SemaphoreSlim(1, 1);
+ private readonly IMemoryCache _cache;
+ private readonly TvDbClient _tvDbClient;
+ private DateTime _tokenCreatedAt;
+ private const string DefaultLanguage = "en";
+
+ public TvDbClientManager(IMemoryCache memoryCache)
+ {
+ _cache = memoryCache;
+ _tvDbClient = new TvDbClient();
+ _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey);
+ _tokenCreatedAt = DateTime.Now;
+ }
+
+ public TvDbClient TvDbClient
+ {
+ get
+ {
+ // Refresh if necessary
+ if (_tokenCreatedAt > DateTime.Now.Subtract(TimeSpan.FromHours(20)))
+ {
+ try
+ {
+ _tvDbClient.Authentication.RefreshTokenAsync();
+ }
+ catch
+ {
+ _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey);
+ }
+
+ _tokenCreatedAt = DateTime.Now;
+ }
+
+ return _tvDbClient;
+ }
+ }
+
+ public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByNameAsync(string name, string language,
+ CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey("series", name, language);
+ return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken));
+ }
+
+ public Task<TvDbResponse<Series>> GetSeriesByIdAsync(int tvdbId, string language,
+ CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey("series", tvdbId, language);
+ return TryGetValue(cacheKey, language,() => TvDbClient.Series.GetAsync(tvdbId, cancellationToken));
+ }
+
+ public Task<TvDbResponse<EpisodeRecord>> GetEpisodesAsync(int episodeTvdbId, string language,
+ CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey("episode", episodeTvdbId, language);
+ return TryGetValue(cacheKey, language,() => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken));
+ }
+
+ public async Task<List<EpisodeRecord>> GetAllEpisodesAsync(int tvdbId, string language,
+ CancellationToken cancellationToken)
+ {
+ // Traverse all episode pages and join them together
+ var episodes = new List<EpisodeRecord>();
+ var episodePage = await GetEpisodesPageAsync(tvdbId, new EpisodeQuery(), language, cancellationToken)
+ .ConfigureAwait(false);
+ episodes.AddRange(episodePage.Data);
+ if (!episodePage.Links.Next.HasValue || !episodePage.Links.Last.HasValue)
+ {
+ return episodes;
+ }
+
+ int next = episodePage.Links.Next.Value;
+ int last = episodePage.Links.Last.Value;
+
+ for (var page = next; page <= last; ++page)
+ {
+ episodePage = await GetEpisodesPageAsync(tvdbId, page, new EpisodeQuery(), language, cancellationToken)
+ .ConfigureAwait(false);
+ episodes.AddRange(episodePage.Data);
+ }
+
+ return episodes;
+ }
+
+ public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByImdbIdAsync(string imdbId, string language,
+ CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey("series", imdbId, language);
+ return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken));
+ }
+
+ public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByZap2ItIdAsync(string zap2ItId, string language,
+ CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey("series", zap2ItId, language);
+ return TryGetValue( cacheKey, language,() => TvDbClient.Search.SearchSeriesByZap2ItIdAsync(zap2ItId, cancellationToken));
+ }
+ public Task<TvDbResponse<Actor[]>> GetActorsAsync(int tvdbId, string language,
+ CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey("actors", tvdbId, language);
+ return TryGetValue(cacheKey, language,() => TvDbClient.Series.GetActorsAsync(tvdbId, cancellationToken));
+ }
+
+ public Task<TvDbResponse<Image[]>> GetImagesAsync(int tvdbId, ImagesQuery imageQuery, string language,
+ CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey("images", tvdbId, language, imageQuery);
+ return TryGetValue(cacheKey, language,() => TvDbClient.Series.GetImagesAsync(tvdbId, imageQuery, cancellationToken));
+ }
+
+ public Task<TvDbResponse<Language[]>> GetLanguagesAsync(CancellationToken cancellationToken)
+ {
+ return TryGetValue("languages", null,() => TvDbClient.Languages.GetAllAsync(cancellationToken));
+ }
+
+ public Task<TvDbResponse<EpisodesSummary>> GetSeriesEpisodeSummaryAsync(int tvdbId, string language,
+ CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey("seriesepisodesummary", tvdbId, language);
+ return TryGetValue(cacheKey, language,
+ () => TvDbClient.Series.GetEpisodesSummaryAsync(tvdbId, cancellationToken));
+ }
+
+ public Task<TvDbResponse<EpisodeRecord[]>> GetEpisodesPageAsync(int tvdbId, int page, EpisodeQuery episodeQuery,
+ string language, CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey(language, tvdbId, episodeQuery);
+
+ return TryGetValue(cacheKey, language,
+ () => TvDbClient.Series.GetEpisodesAsync(tvdbId, page, episodeQuery, cancellationToken));
+ }
+
+ public Task<string> GetEpisodeTvdbId(EpisodeInfo searchInfo, string language,
+ CancellationToken cancellationToken)
+ {
+ searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(),
+ out var seriesTvdbId);
+
+ var episodeQuery = new EpisodeQuery();
+
+ // Prefer SxE over premiere date as it is more robust
+ if (searchInfo.IndexNumber.HasValue && searchInfo.ParentIndexNumber.HasValue)
+ {
+ episodeQuery.AiredEpisode = searchInfo.IndexNumber.Value;
+ episodeQuery.AiredSeason = searchInfo.ParentIndexNumber.Value;
+ }
+ else if (searchInfo.PremiereDate.HasValue)
+ {
+ // tvdb expects yyyy-mm-dd format
+ episodeQuery.FirstAired = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd");
+ }
+
+ return GetEpisodeTvdbId(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken);
+ }
+
+ public async Task<string> GetEpisodeTvdbId(int seriesTvdbId, EpisodeQuery episodeQuery,
+ string language,
+ CancellationToken cancellationToken)
+ {
+ var episodePage =
+ await GetEpisodesPageAsync(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken)
+ .ConfigureAwait(false);
+ return episodePage.Data.FirstOrDefault()?.Id.ToString();
+ }
+
+ public Task<TvDbResponse<EpisodeRecord[]>> GetEpisodesPageAsync(int tvdbId, EpisodeQuery episodeQuery,
+ string language, CancellationToken cancellationToken)
+ {
+ return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken);
+ }
+
+ private async Task<T> TryGetValue<T>(string key, string language, Func<Task<T>> resultFactory)
+ {
+ if (_cache.TryGetValue(key, out T cachedValue))
+ {
+ return cachedValue;
+ }
+
+ await _cacheWriteLock.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ if (_cache.TryGetValue(key, out cachedValue))
+ {
+ return cachedValue;
+ }
+
+ _tvDbClient.AcceptedLanguage = TvdbUtils.NormalizeLanguage(language) ?? DefaultLanguage;
+ var result = await resultFactory.Invoke().ConfigureAwait(false);
+ _cache.Set(key, result, TimeSpan.FromHours(1));
+ return result;
+ }
+ finally
+ {
+ _cacheWriteLock.Release();
+ }
+ }
+
+ private static string GenerateKey(params object[] objects)
+ {
+ var key = string.Empty;
+
+ foreach (var obj in objects)
+ {
+ var objType = obj.GetType();
+ if (objType.IsPrimitive || objType == typeof(string))
+ {
+ key += obj + ";";
+ }
+ else
+ {
+ foreach (PropertyInfo propertyInfo in objType.GetProperties())
+ {
+ var currentValue = propertyInfo.GetValue(obj, null);
+ if (currentValue == null)
+ {
+ continue;
+ }
+
+ key += propertyInfo.Name + "=" + currentValue + ";";
+ }
+ }
+ }
+
+ return key;
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
index 102a3d4ec..c04e98e64 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
@@ -1,33 +1,30 @@
+using System;
using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
+using Microsoft.Extensions.Logging;
+using TvDbSharper;
+using TvDbSharper.Dto;
namespace MediaBrowser.Providers.TV.TheTVDB
{
public class TvdbEpisodeImageProvider : IRemoteImageProvider
{
- private readonly IServerConfigurationManager _config;
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
+ private readonly ILogger _logger;
+ private readonly TvDbClientManager _tvDbClientManager;
- public TvdbEpisodeImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
+ public TvdbEpisodeImageProvider(IHttpClient httpClient, ILogger<TvdbEpisodeImageProvider> logger, TvDbClientManager tvDbClientManager)
{
- _config = config;
_httpClient = httpClient;
- _fileSystem = fileSystem;
+ _logger = logger;
+ _tvDbClientManager = tvDbClientManager;
}
public string Name => "TheTVDB";
@@ -45,113 +42,70 @@ namespace MediaBrowser.Providers.TV.TheTVDB
};
}
- public Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
var episode = (Episode)item;
var series = episode.Series;
-
+ var imageResult = new List<RemoteImageInfo>();
+ var language = item.GetPreferredMetadataLanguage();
if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
{
- // Process images
- var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, series.ProviderIds);
-
- var nodes = TvdbEpisodeProvider.Current.GetEpisodeXmlNodes(seriesDataPath, episode.GetLookupInfo());
-
- var result = nodes.Select(i => GetImageInfo(i, cancellationToken))
- .Where(i => i != null)
- .ToList();
+ var episodeTvdbId = episode.GetProviderId(MetadataProviders.Tvdb);
- return Task.FromResult<IEnumerable<RemoteImageInfo>>(result);
- }
-
- return Task.FromResult<IEnumerable<RemoteImageInfo>>(new RemoteImageInfo[] { });
- }
-
- private RemoteImageInfo GetImageInfo(XmlReader reader, CancellationToken cancellationToken)
- {
- var height = 225;
- var width = 400;
- var url = string.Empty;
-
- // Use XmlReader for best performance
- using (reader)
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ // Process images
+ try
{
- if (reader.NodeType == XmlNodeType.Element)
+ if (string.IsNullOrEmpty(episodeTvdbId))
{
- cancellationToken.ThrowIfCancellationRequested();
-
- switch (reader.Name)
+ var episodeInfo = new EpisodeInfo
{
- case "thumb_width":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- width = rval;
- }
- }
- break;
- }
-
- case "thumb_height":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- height = rval;
- }
- }
- break;
- }
-
- case "filename":
- {
- var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- url = TVUtils.BannerUrl + val;
- }
- break;
- }
- default:
- {
- reader.Skip();
- break;
- }
+ IndexNumber = episode.IndexNumber.Value,
+ ParentIndexNumber = episode.ParentIndexNumber.Value,
+ SeriesProviderIds = series.ProviderIds
+ };
+ episodeTvdbId = await _tvDbClientManager
+ .GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false);
+ if (string.IsNullOrEmpty(episodeTvdbId))
+ {
+ _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
+ episodeInfo.ParentIndexNumber, episodeInfo.IndexNumber, series.GetProviderId(MetadataProviders.Tvdb));
+ return imageResult;
}
}
- else
+
+ var episodeResult =
+ await _tvDbClientManager
+ .GetEpisodesAsync(Convert.ToInt32(episodeTvdbId), language, cancellationToken)
+ .ConfigureAwait(false);
+
+ var image = GetImageInfo(episodeResult.Data);
+ if (image != null)
{
- reader.Read();
+ imageResult.Add(image);
}
}
+ catch (TvDbServerException e)
+ {
+ _logger.LogError(e, "Failed to retrieve episode images for {TvDbId}", episodeTvdbId);
+ }
}
- if (string.IsNullOrEmpty(url))
+ return imageResult;
+ }
+
+ private RemoteImageInfo GetImageInfo(EpisodeRecord episode)
+ {
+ if (string.IsNullOrEmpty(episode.Filename))
{
return null;
}
return new RemoteImageInfo
{
- Width = width,
- Height = height,
+ Width = Convert.ToInt32(episode.ThumbWidth),
+ Height = Convert.ToInt32(episode.ThumbHeight),
ProviderName = Name,
- Url = url,
+ Url = TvdbUtils.BannerUrl + episode.Filename,
Type = ImageType.Primary
};
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
index be137e879..b256f2667 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
@@ -1,22 +1,16 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
+using TvDbSharper;
+using TvDbSharper.Dto;
namespace MediaBrowser.Providers.TV.TheTVDB
{
@@ -24,44 +18,52 @@ namespace MediaBrowser.Providers.TV.TheTVDB
/// <summary>
/// Class RemoteEpisodeProvider
/// </summary>
- class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>
+ class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
{
- private static readonly string FullIdKey = MetadataProviders.Tvdb + "-Full";
-
- internal static TvdbEpisodeProvider Current;
- private readonly IFileSystem _fileSystem;
- private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
private readonly ILogger _logger;
- private readonly IXmlReaderSettingsFactory _xmlSettings;
+ private readonly TvDbClientManager _tvDbClientManager;
- public TvdbEpisodeProvider(IFileSystem fileSystem, IServerConfigurationManager config, IHttpClient httpClient, ILogger logger, IXmlReaderSettingsFactory xmlSettings)
+ public TvdbEpisodeProvider(IHttpClient httpClient, ILogger<TvdbEpisodeProvider> logger, TvDbClientManager tvDbClientManager)
{
- _fileSystem = fileSystem;
- _config = config;
_httpClient = httpClient;
_logger = logger;
- _xmlSettings = xmlSettings;
- Current = this;
+ _tvDbClientManager = tvDbClientManager;
}
- public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
{
var list = new List<RemoteSearchResult>();
// The search query must either provide an episode number or date
- if (!searchInfo.IndexNumber.HasValue && !searchInfo.PremiereDate.HasValue)
+ if (!searchInfo.IndexNumber.HasValue || !searchInfo.PremiereDate.HasValue)
{
- return Task.FromResult((IEnumerable<RemoteSearchResult>)list);
+ return list;
}
if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds))
{
- var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, searchInfo.SeriesProviderIds);
-
try
{
- var metadataResult = FetchEpisodeData(searchInfo, seriesDataPath, cancellationToken);
+ var episodeTvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb);
+ if (string.IsNullOrEmpty(episodeTvdbId))
+ {
+ searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(),
+ out var seriesTvdbId);
+ episodeTvdbId = await _tvDbClientManager
+ .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
+ .ConfigureAwait(false);
+ if (string.IsNullOrEmpty(episodeTvdbId))
+ {
+ _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
+ searchInfo.ParentIndexNumber, searchInfo.IndexNumber, seriesTvdbId);
+ return list;
+ }
+ }
+
+ var episodeResult = await _tvDbClientManager.GetEpisodesAsync(Convert.ToInt32(episodeTvdbId),
+ searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ var metadataResult = MapEpisodeToResult(searchInfo, episodeResult.Data);
if (metadataResult.HasMetadata)
{
@@ -80,689 +82,117 @@ namespace MediaBrowser.Providers.TV.TheTVDB
});
}
}
- catch (FileNotFoundException)
- {
- // Don't fail the provider because this will just keep on going and going.
- }
- catch (IOException)
+ catch (TvDbServerException e)
{
- // Don't fail the provider because this will just keep on going and going.
+ _logger.LogError(e, "Failed to retrieve episode with id {TvDbId}", searchInfo.IndexNumber);
}
}
- return Task.FromResult((IEnumerable<RemoteSearchResult>)list);
+ return list;
}
public string Name => "TheTVDB";
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
{
- var result = new MetadataResult<Episode>();
- result.QueriedById = true;
+ var result = new MetadataResult<Episode>
+ {
+ QueriedById = true
+ };
if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) &&
(searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue))
{
- var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(searchInfo.SeriesProviderIds, null, null, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
-
- if (string.IsNullOrEmpty(seriesDataPath))
- {
- return result;
- }
-
+ var tvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb);
try
{
- result = FetchEpisodeData(searchInfo, seriesDataPath, cancellationToken);
- }
- catch (FileNotFoundException)
- {
- // Don't fail the provider because this will just keep on going and going.
+ if (string.IsNullOrEmpty(tvdbId))
+ {
+ tvdbId = await _tvDbClientManager
+ .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
+ .ConfigureAwait(false);
+ if (string.IsNullOrEmpty(tvdbId))
+ {
+ _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
+ searchInfo.ParentIndexNumber, searchInfo.IndexNumber, tvdbId);
+ return result;
+ }
+ }
+
+ var episodeResult = await _tvDbClientManager.GetEpisodesAsync(
+ Convert.ToInt32(tvdbId), searchInfo.MetadataLanguage,
+ cancellationToken).ConfigureAwait(false);
+
+ result = MapEpisodeToResult(searchInfo, episodeResult.Data);
}
- catch (IOException)
+ catch (TvDbServerException e)
{
- // Don't fail the provider because this will just keep on going and going.
+ _logger.LogError(e, "Failed to retrieve episode with id {TvDbId}", tvdbId);
}
}
else
{
- _logger.LogDebug("No series identity found for {0}", searchInfo.Name);
+ _logger.LogDebug("No series identity found for {EpisodeName}", searchInfo.Name);
}
return result;
}
- /// <summary>
- /// Gets the episode XML files.
- /// </summary>
- /// <param name="seriesDataPath">The series data path.</param>
- /// <param name="searchInfo">The search information.</param>
- /// <returns>List{FileInfo}.</returns>
- internal List<XmlReader> GetEpisodeXmlNodes(string seriesDataPath, EpisodeInfo searchInfo)
- {
- var seriesXmlPath = TvdbSeriesProvider.Current.GetSeriesXmlPath(searchInfo.SeriesProviderIds, searchInfo.MetadataLanguage);
-
- try
- {
- return GetXmlNodes(seriesXmlPath, searchInfo);
- }
- catch (FileNotFoundException)
- {
- return new List<XmlReader>();
- }
- catch (IOException)
- {
- return new List<XmlReader>();
- }
- }
-
- /// <summary>
- /// Fetches the episode data.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <param name="seriesDataPath">The series data path.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{System.Boolean}.</returns>
- private MetadataResult<Episode> FetchEpisodeData(EpisodeInfo id, string seriesDataPath, CancellationToken cancellationToken)
+ private static MetadataResult<Episode> MapEpisodeToResult(EpisodeInfo id, EpisodeRecord episode)
{
- var result = new MetadataResult<Episode>()
+ var result = new MetadataResult<Episode>
{
+ HasMetadata = true,
Item = new Episode
{
IndexNumber = id.IndexNumber,
ParentIndexNumber = id.ParentIndexNumber,
- IndexNumberEnd = id.IndexNumberEnd
- }
- };
-
- var xmlNodes = GetEpisodeXmlNodes(seriesDataPath, id);
-
- if (xmlNodes.Count > 0)
- {
- FetchMainEpisodeInfo(result, xmlNodes[0], id.SeriesDisplayOrder, cancellationToken);
-
- result.HasMetadata = true;
- }
-
- foreach (var node in xmlNodes.Skip(1))
- {
- FetchAdditionalPartInfo(result, node, cancellationToken);
- }
-
- return result;
- }
-
- private List<XmlReader> GetXmlNodes(string xmlFile, EpisodeInfo searchInfo)
- {
- var list = new List<XmlReader>();
-
- if (searchInfo.IndexNumber.HasValue)
- {
- var files = GetEpisodeXmlFiles(searchInfo.SeriesDisplayOrder, searchInfo.ParentIndexNumber, searchInfo.IndexNumber, searchInfo.IndexNumberEnd, Path.GetDirectoryName(xmlFile));
-
- list = files.Select(GetXmlReader).ToList();
- }
-
- if (list.Count == 0 && searchInfo.PremiereDate.HasValue)
- {
- list = GetXmlNodesByPremiereDate(xmlFile, searchInfo.PremiereDate.Value);
- }
-
- return list;
- }
-
- private string GetEpisodeFileName(string seriesDisplayOrder, int? seasonNumber, int? episodeNumber)
- {
- if (string.Equals(seriesDisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase))
- {
- return string.Format("episode-abs-{0}.xml", episodeNumber);
- }
- else if (string.Equals(seriesDisplayOrder, "dvd", StringComparison.OrdinalIgnoreCase))
- {
- return string.Format("episode-dvd-{0}-{1}.xml", seasonNumber.Value, episodeNumber);
- }
- else
- {
- return string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber);
- }
- }
-
- private FileSystemMetadata GetEpisodeFileInfoWithFallback(string seriesDataPath, string seriesDisplayOrder, int? seasonNumber, int? episodeNumber)
- {
- var file = Path.Combine(seriesDataPath, GetEpisodeFileName(seriesDisplayOrder, seasonNumber, episodeNumber));
- var fileInfo = _fileSystem.GetFileInfo(file);
-
- if (fileInfo.Exists)
- {
- return fileInfo;
- }
-
- if (!seasonNumber.HasValue)
- {
- return fileInfo;
- }
-
- // revert to aired order
- if (string.Equals(seriesDisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase) || string.Equals(seriesDisplayOrder, "dvd", StringComparison.OrdinalIgnoreCase))
- {
- file = Path.Combine(seriesDataPath, GetEpisodeFileName(null, seasonNumber, episodeNumber));
- return _fileSystem.GetFileInfo(file);
- }
-
- return fileInfo;
- }
-
- private List<FileSystemMetadata> GetEpisodeXmlFiles(string seriesDisplayOrder, int? seasonNumber, int? episodeNumber, int? endingEpisodeNumber, string seriesDataPath)
- {
- var files = new List<FileSystemMetadata>();
-
- if (episodeNumber == null)
- {
- return files;
- }
-
- if (!seasonNumber.HasValue)
- {
- seriesDisplayOrder = "absolute";
- }
-
- var fileInfo = GetEpisodeFileInfoWithFallback(seriesDataPath, seriesDisplayOrder, seasonNumber, episodeNumber);
-
- if (fileInfo.Exists)
- {
- files.Add(fileInfo);
- }
-
- var end = endingEpisodeNumber ?? episodeNumber;
- episodeNumber++;
-
- while (episodeNumber <= end)
- {
- fileInfo = GetEpisodeFileInfoWithFallback(seriesDataPath, seriesDisplayOrder, seasonNumber, episodeNumber);
-
- if (fileInfo.Exists)
- {
- files.Add(fileInfo);
- }
- else
- {
- break;
- }
-
- episodeNumber++;
- }
-
- return files;
- }
-
- private XmlReader GetXmlReader(FileSystemMetadata xmlFile)
- {
- return GetXmlReader(File.ReadAllText(xmlFile.FullName, Encoding.UTF8));
- }
-
- private XmlReader GetXmlReader(string xml)
- {
- var streamReader = new StringReader(xml);
-
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- return XmlReader.Create(streamReader, settings);
- }
-
- private List<XmlReader> GetXmlNodesByPremiereDate(string xmlFile, DateTime premiereDate)
- {
- var list = new List<XmlReader>();
-
- using (var fileStream = _fileSystem.GetFileStream(xmlFile, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
- {
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
- {
- // Use XmlReader for best performance
-
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
+ IndexNumberEnd = id.IndexNumberEnd,
+ AirsBeforeEpisodeNumber = episode.AirsBeforeEpisode,
+ AirsAfterSeasonNumber = episode.AirsAfterSeason,
+ AirsBeforeSeasonNumber = episode.AirsBeforeSeason,
+ Name = episode.EpisodeName,
+ Overview = episode.Overview,
+ CommunityRating = (float?)episode.SiteRating,
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Episode":
- {
- var outerXml = reader.ReadOuterXml();
-
- var airDate = GetEpisodeAirDate(outerXml);
-
- if (airDate.HasValue && premiereDate.Date == airDate.Value.Date)
- {
- list.Add(GetXmlReader(outerXml));
- return list;
- }
-
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
}
- }
-
- return list;
- }
-
- private DateTime? GetEpisodeAirDate(string xml)
- {
- using (var streamReader = new StringReader(xml))
- {
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "FirstAired":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (DateTime.TryParse(val, out var date))
- {
- date = date.ToUniversalTime();
-
- return date;
- }
- }
-
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
- }
- return null;
- }
-
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+ };
+ result.ResetPeople();
- private void FetchMainEpisodeInfo(MetadataResult<Episode> result, XmlReader reader, string seriesOrder, CancellationToken cancellationToken)
- {
var item = result.Item;
+ item.SetProviderId(MetadataProviders.Tvdb, episode.Id.ToString());
+ item.SetProviderId(MetadataProviders.Imdb, episode.ImdbId);
- int? episodeNumber = null;
- int? seasonNumber = null;
- int? combinedEpisodeNumber = null;
- int? combinedSeasonNumber = null;
-
- // Use XmlReader for best performance
- using (reader)
+ if (string.Equals(id.SeriesDisplayOrder, "dvd", StringComparison.OrdinalIgnoreCase))
{
- result.ResetPeople();
-
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "id":
- {
- var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.SetProviderId(MetadataProviders.Tvdb, val);
- }
- break;
- }
-
- case "IMDB_ID":
- {
- var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.SetProviderId(MetadataProviders.Imdb, val);
- }
- break;
- }
-
- case "EpisodeNumber":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- episodeNumber = rval;
- }
- }
-
- break;
- }
-
- case "SeasonNumber":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- seasonNumber = rval;
- }
- }
-
- break;
- }
-
- case "Combined_episodenumber":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (float.TryParse(val, NumberStyles.Any, _usCulture, out var num))
- {
- combinedEpisodeNumber = Convert.ToInt32(num);
- }
- }
-
- break;
- }
-
- case "Combined_season":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (float.TryParse(val, NumberStyles.Any, _usCulture, out var num))
- {
- combinedSeasonNumber = Convert.ToInt32(num);
- }
- }
-
- break;
- }
-
- case "airsbefore_episode":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- item.AirsBeforeEpisodeNumber = rval;
- }
- }
-
- break;
- }
-
- case "airsafter_season":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- item.AirsAfterSeasonNumber = rval;
- }
- }
-
- break;
- }
-
- case "airsbefore_season":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- item.AirsBeforeSeasonNumber = rval;
- }
- }
-
- break;
- }
-
- case "EpisodeName":
- {
- var val = reader.ReadElementContentAsString();
- if (!item.LockedFields.Contains(MetadataFields.Name))
- {
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.Name = val;
- }
- }
- break;
- }
-
- case "Overview":
- {
- var val = reader.ReadElementContentAsString();
- if (!item.LockedFields.Contains(MetadataFields.Overview))
- {
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.Overview = val;
- }
- }
- break;
- }
- case "Rating":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // float.TryParse is local aware, so it can be probamatic, force us culture
- if (float.TryParse(val, NumberStyles.AllowDecimalPoint, _usCulture, out var rval))
- {
- item.CommunityRating = rval;
- }
- }
- break;
- }
- case "RatingCount":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- //item.VoteCount = rval;
- }
- }
-
- break;
- }
-
- case "FirstAired":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (DateTime.TryParse(val, out var date))
- {
- date = date.ToUniversalTime();
-
- item.PremiereDate = date;
- item.ProductionYear = date.Year;
- }
- }
-
- break;
- }
-
- case "Director":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (!item.LockedFields.Contains(MetadataFields.Cast))
- {
- AddPeople(result, val, PersonType.Director);
- }
- }
-
- break;
- }
- case "GuestStars":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (!item.LockedFields.Contains(MetadataFields.Cast))
- {
- AddGuestStars(result, val);
- }
- }
-
- break;
- }
- case "Writer":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (!item.LockedFields.Contains(MetadataFields.Cast))
- {
- //AddPeople(result, val, PersonType.Writer);
- }
- }
-
- break;
- }
- case "Language":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- result.ResultLanguage = val;
- }
-
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
+ item.IndexNumber = Convert.ToInt32(episode.DvdEpisodeNumber ?? episode.AiredEpisodeNumber);
+ item.ParentIndexNumber = episode.DvdSeason ?? episode.AiredSeason;
}
-
- if (string.Equals(seriesOrder, "dvd", StringComparison.OrdinalIgnoreCase))
+ else if (episode.AiredEpisodeNumber.HasValue)
{
- episodeNumber = combinedEpisodeNumber ?? episodeNumber;
- seasonNumber = combinedSeasonNumber ?? seasonNumber;
+ item.IndexNumber = episode.AiredEpisodeNumber;
}
-
- if (episodeNumber.HasValue)
+ else if (episode.AiredSeason.HasValue)
{
- item.IndexNumber = episodeNumber;
+ item.ParentIndexNumber = episode.AiredSeason;
}
- if (seasonNumber.HasValue)
+ if (DateTime.TryParse(episode.FirstAired, out var date))
{
- item.ParentIndexNumber = seasonNumber;
+ // dates from tvdb are UTC but without offset or Z
+ item.PremiereDate = date;
+ item.ProductionYear = date.Year;
}
- }
- private void AddPeople<T>(MetadataResult<T> result, string val, string personType)
- {
- // Sometimes tvdb actors have leading spaces
- foreach (var person in val.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .Select(str => new PersonInfo { Type = personType, Name = str.Trim() }))
+ foreach (var director in episode.Directors)
{
- result.AddPerson(person);
+ result.AddPerson(new PersonInfo
+ {
+ Name = director,
+ Type = PersonType.Director
+ });
}
- }
-
- private void AddGuestStars<T>(MetadataResult<T> result, string val)
- where T : BaseItem
- {
- // example:
- // <GuestStars>|Mark C. Thomas| Dennis Kiefer| David Nelson (David)| Angela Nicholas| Tzi Ma| Kevin P. Kearns (Pasco)|</GuestStars>
- var persons = val.Split('|')
- .Select(i => i.Trim())
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .ToList();
-
- foreach (var person in persons)
+ foreach (var person in episode.GuestStars)
{
var index = person.IndexOf('(');
string role = null;
@@ -782,106 +212,17 @@ namespace MediaBrowser.Providers.TV.TheTVDB
Role = role
});
}
- }
-
- private void FetchAdditionalPartInfo(MetadataResult<Episode> result, XmlReader reader, CancellationToken cancellationToken)
- {
- var item = result.Item;
-
- // Use XmlReader for best performance
- using (reader)
+ foreach (var writer in episode.Writers)
{
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ result.AddPerson(new PersonInfo
{
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "EpisodeName":
- {
- var val = reader.ReadElementContentAsString();
- if (!item.LockedFields.Contains(MetadataFields.Name))
- {
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.Name += ", " + val;
- }
- }
- break;
- }
-
- case "Overview":
- {
- var val = reader.ReadElementContentAsString();
- if (!item.LockedFields.Contains(MetadataFields.Overview))
- {
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.Overview += Environment.NewLine + Environment.NewLine + val;
- }
- }
- break;
- }
- case "Director":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (!item.LockedFields.Contains(MetadataFields.Cast))
- {
- AddPeople(result, val, PersonType.Director);
- }
- }
-
- break;
- }
- case "GuestStars":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (!item.LockedFields.Contains(MetadataFields.Cast))
- {
- AddGuestStars(result, val);
- }
- }
-
- break;
- }
- case "Writer":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (!item.LockedFields.Contains(MetadataFields.Cast))
- {
- //AddPeople(result, val, PersonType.Writer);
- }
- }
-
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
+ Name = writer,
+ Type = PersonType.Writer
+ });
}
+
+ result.ResultLanguage = episode.Language.EpisodeName;
+ return result;
}
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs
deleted file mode 100644
index d45696057..000000000
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs
+++ /dev/null
@@ -1,398 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Xml;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Providers.TV.TheTVDB
-{
- /// <summary>
- /// Class TvdbPrescanTask
- /// </summary>
- public class TvdbPrescanTask : ILibraryPostScanTask
- {
- public const string TvdbBaseUrl = "https://thetvdb.com/";
-
- /// <summary>
- /// The server time URL
- /// </summary>
- private const string ServerTimeUrl = TvdbBaseUrl + "api/Updates.php?type=none";
-
- /// <summary>
- /// The updates URL
- /// </summary>
- private const string UpdatesUrl = TvdbBaseUrl + "api/Updates.php?type=all&time={0}";
-
- /// <summary>
- /// The _HTTP client
- /// </summary>
- private readonly IHttpClient _httpClient;
- /// <summary>
- /// The _logger
- /// </summary>
- private readonly ILogger _logger;
- /// <summary>
- /// The _config
- /// </summary>
- private readonly IServerConfigurationManager _config;
- private readonly IFileSystem _fileSystem;
- private readonly ILibraryManager _libraryManager;
- private readonly IXmlReaderSettingsFactory _xmlSettings;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="TvdbPrescanTask"/> class.
- /// </summary>
- /// <param name="logger">The logger.</param>
- /// <param name="httpClient">The HTTP client.</param>
- /// <param name="config">The config.</param>
- public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IFileSystem fileSystem, ILibraryManager libraryManager, IXmlReaderSettingsFactory xmlSettings)
- {
- _logger = logger;
- _httpClient = httpClient;
- _config = config;
- _fileSystem = fileSystem;
- _libraryManager = libraryManager;
- _xmlSettings = xmlSettings;
- }
-
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- /// <summary>
- /// Runs the specified progress.
- /// </summary>
- /// <param name="progress">The progress.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
- {
- var path = TvdbSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
-
- Directory.CreateDirectory(path);
-
- var timestampFile = Path.Combine(path, "time.txt");
-
- var timestampFileInfo = _fileSystem.GetFileInfo(timestampFile);
-
- // Don't check for tvdb updates anymore frequently than 24 hours
- if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1)
- {
- return;
- }
-
- // Find out the last time we queried tvdb for updates
- var lastUpdateTime = timestampFileInfo.Exists ? File.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
-
- string newUpdateTime;
-
- var existingDirectories = _fileSystem.GetDirectoryPaths(path)
- .Select(Path.GetFileName)
- .ToList();
-
- var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
- {
- IncludeItemTypes = new[] { typeof(Series).Name },
- Recursive = true,
- GroupByPresentationUniqueKey = false,
- DtoOptions = new DtoOptions(false)
- {
- EnableImages = false
- }
-
- }).Cast<Series>()
- .ToList();
-
- var seriesIdsInLibrary = seriesList
- .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
- .Select(i => i.GetProviderId(MetadataProviders.Tvdb))
- .ToList();
-
- var missingSeries = seriesIdsInLibrary.Except(existingDirectories, StringComparer.OrdinalIgnoreCase)
- .ToList();
-
- var enableInternetProviders = seriesList.Count == 0 ? false : seriesList[0].IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(seriesList[0]), TvdbSeriesProvider.Current.Name);
- if (!enableInternetProviders)
- {
- progress.Report(100);
- return;
- }
-
- // If this is our first time, update all series
- if (string.IsNullOrEmpty(lastUpdateTime))
- {
- // First get tvdb server time
- using (var response = await _httpClient.SendAsync(new HttpRequestOptions
- {
- Url = ServerTimeUrl,
- CancellationToken = cancellationToken,
- EnableHttpCompression = true,
- BufferContent = false
-
- }, "GET").ConfigureAwait(false))
- {
- // First get tvdb server time
- using (var stream = response.Content)
- {
- newUpdateTime = GetUpdateTime(stream);
- }
- }
-
- existingDirectories.AddRange(missingSeries);
-
- await UpdateSeries(existingDirectories, path, null, progress, cancellationToken).ConfigureAwait(false);
- }
- else
- {
- var seriesToUpdate = await GetSeriesIdsToUpdate(existingDirectories, lastUpdateTime, cancellationToken).ConfigureAwait(false);
-
- newUpdateTime = seriesToUpdate.Item2;
-
- long.TryParse(lastUpdateTime, NumberStyles.Any, UsCulture, out var lastUpdateValue);
-
- var nullableUpdateValue = lastUpdateValue == 0 ? (long?)null : lastUpdateValue;
-
- var listToUpdate = seriesToUpdate.Item1.ToList();
- listToUpdate.AddRange(missingSeries);
-
- await UpdateSeries(listToUpdate, path, nullableUpdateValue, progress, cancellationToken).ConfigureAwait(false);
- }
-
- File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);
- progress.Report(100);
- }
-
- /// <summary>
- /// Gets the update time.
- /// </summary>
- /// <param name="response">The response.</param>
- /// <returns>System.String.</returns>
- private string GetUpdateTime(Stream response)
- {
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- using (var streamReader = new StreamReader(response, Encoding.UTF8))
- {
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Time":
- {
- return (reader.ReadElementContentAsString() ?? string.Empty).Trim();
- }
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
- }
-
- return null;
- }
-
- /// <summary>
- /// Gets the series ids to update.
- /// </summary>
- /// <param name="existingSeriesIds">The existing series ids.</param>
- /// <param name="lastUpdateTime">The last update time.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IEnumerable{System.String}}.</returns>
- private async Task<Tuple<IEnumerable<string>, string>> GetSeriesIdsToUpdate(IEnumerable<string> existingSeriesIds, string lastUpdateTime, CancellationToken cancellationToken)
- {
- // First get last time
- using (var response = await _httpClient.SendAsync(new HttpRequestOptions
- {
- Url = string.Format(UpdatesUrl, lastUpdateTime),
- CancellationToken = cancellationToken,
- EnableHttpCompression = true,
- BufferContent = false
-
- }, "GET").ConfigureAwait(false))
- {
- using (var stream = response.Content)
- {
- var data = GetUpdatedSeriesIdList(stream);
-
- var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
-
- var seriesList = data.Item1
- .Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i));
-
- return new Tuple<IEnumerable<string>, string>(seriesList, data.Item2);
- }
- }
- }
-
- private Tuple<List<string>, string> GetUpdatedSeriesIdList(Stream stream)
- {
- string updateTime = null;
- var idList = new List<string>();
-
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- using (var streamReader = new StreamReader(stream, Encoding.UTF8))
- {
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Time":
- {
- updateTime = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
- break;
- }
- case "Series":
- {
- var id = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
- idList.Add(id);
- break;
- }
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
- }
-
- return new Tuple<List<string>, string>(idList, updateTime);
- }
-
- /// <summary>
- /// Updates the series.
- /// </summary>
- /// <param name="seriesIds">The series ids.</param>
- /// <param name="seriesDataPath">The series data path.</param>
- /// <param name="lastTvDbUpdateTime">The last tv db update time.</param>
- /// <param name="progress">The progress.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private async Task UpdateSeries(List<string> seriesIds, string seriesDataPath, long? lastTvDbUpdateTime, IProgress<double> progress, CancellationToken cancellationToken)
- {
- var numComplete = 0;
-
- var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
- {
- IncludeItemTypes = new[] { typeof(Series).Name },
- Recursive = true,
- GroupByPresentationUniqueKey = false,
- DtoOptions = new DtoOptions(false)
- {
- EnableImages = false
- }
-
- }).Cast<Series>();
-
- // Gather all series into a lookup by tvdb id
- var allSeries = seriesList
- .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
- .ToLookup(i => i.GetProviderId(MetadataProviders.Tvdb));
-
- foreach (var seriesId in seriesIds)
- {
- // Find the preferred language(s) for the movie in the library
- var languages = allSeries[seriesId]
- .Select(i => i.GetPreferredMetadataLanguage())
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .ToList();
-
- foreach (var language in languages)
- {
- try
- {
- await UpdateSeries(seriesId, seriesDataPath, lastTvDbUpdateTime, language, cancellationToken).ConfigureAwait(false);
- }
- catch (HttpException ex)
- {
- _logger.LogError(ex, "Error updating tvdb series id {ID}, language {Language}", seriesId, language);
-
- // Already logged at lower levels, but don't fail the whole operation, unless timed out
- // We have to fail this to make it run again otherwise new episode data could potentially be missing
- if (ex.IsTimedOut)
- {
- throw;
- }
- }
- }
-
- numComplete++;
- double percent = numComplete;
- percent /= seriesIds.Count;
- percent *= 100;
-
- progress.Report(percent);
- }
- }
-
- /// <summary>
- /// Updates the series.
- /// </summary>
- /// <param name="id">The id.</param>
- /// <param name="seriesDataPath">The series data path.</param>
- /// <param name="lastTvDbUpdateTime">The last tv db update time.</param>
- /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private Task UpdateSeries(string id, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
- {
- _logger.LogInformation("Updating series from tvdb " + id + ", language " + preferredMetadataLanguage);
-
- seriesDataPath = Path.Combine(seriesDataPath, id);
-
- Directory.CreateDirectory(seriesDataPath);
-
- return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), null, null, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken);
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs
index 01ede44bb..94ca603f2 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs
@@ -1,41 +1,32 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
using System.Linq;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Xml;
+using Microsoft.Extensions.Logging;
+using TvDbSharper;
+using TvDbSharper.Dto;
+using RatingType = MediaBrowser.Model.Dto.RatingType;
namespace MediaBrowser.Providers.TV.TheTVDB
{
public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
{
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
- private readonly IXmlReaderSettingsFactory _xmlSettings;
+ private readonly ILogger _logger;
+ private readonly TvDbClientManager _tvDbClientManager;
- public TvdbSeasonImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlSettings)
+ public TvdbSeasonImageProvider(IHttpClient httpClient, ILogger<TvdbSeasonImageProvider> logger, TvDbClientManager tvDbClientManager)
{
- _config = config;
_httpClient = httpClient;
- _fileSystem = fileSystem;
- _xmlSettings = xmlSettings;
+ _logger = logger;
+ _tvDbClientManager = tvDbClientManager;
}
public string Name => ProviderName;
@@ -62,91 +53,66 @@ namespace MediaBrowser.Providers.TV.TheTVDB
var season = (Season)item;
var series = season.Series;
- if (series != null && season.IndexNumber.HasValue && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
+ if (series == null || !season.IndexNumber.HasValue || !TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
{
- var seriesProviderIds = series.ProviderIds;
- var seasonNumber = season.IndexNumber.Value;
+ return new RemoteImageInfo[] { };
+ }
- var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.Name, series.ProductionYear, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
+ var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProviders.Tvdb));
+ var seasonNumber = season.IndexNumber.Value;
+ var language = item.GetPreferredMetadataLanguage();
+ var remoteImages = new List<RemoteImageInfo>();
- if (!string.IsNullOrEmpty(seriesDataPath))
+ var keyTypes = new[] { KeyType.Season, KeyType.Seasonwide, KeyType.Fanart };
+ foreach (var keyType in keyTypes)
+ {
+ var imageQuery = new ImagesQuery
{
- var path = Path.Combine(seriesDataPath, "banners.xml");
-
- try
- {
- return GetImages(path, item.GetPreferredMetadataLanguage(), seasonNumber, _xmlSettings, _fileSystem, cancellationToken);
- }
- catch (FileNotFoundException)
- {
- // No tvdb data yet. Don't blow up
- }
- catch (IOException)
- {
- // No tvdb data yet. Don't blow up
- }
+ KeyType = keyType,
+ SubKey = seasonNumber.ToString()
+ };
+ try
+ {
+ var imageResults = await _tvDbClientManager
+ .GetImagesAsync(tvdbId, imageQuery, language, cancellationToken).ConfigureAwait(false);
+ remoteImages.AddRange(GetImages(imageResults.Data, language));
+ }
+ catch (TvDbServerException)
+ {
+ _logger.LogDebug("No images of type {KeyType} found for series {TvdbId}", keyType, tvdbId);
}
}
- return new RemoteImageInfo[] { };
+ return remoteImages;
}
- internal static IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, int seasonNumber, IXmlReaderSettingsFactory xmlReaderSettingsFactory, IFileSystem fileSystem, CancellationToken cancellationToken)
+ private IEnumerable<RemoteImageInfo> GetImages(Image[] images, string preferredLanguage)
{
- var settings = xmlReaderSettingsFactory.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
var list = new List<RemoteImageInfo>();
-
- using (var fileStream = fileSystem.GetFileStream(xmlPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ var languages = _tvDbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data;
+ foreach (Image image in images)
{
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
+ var imageInfo = new RemoteImageInfo
{
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- cancellationToken.ThrowIfCancellationRequested();
+ RatingType = RatingType.Score,
+ CommunityRating = (double?)image.RatingsInfo.Average,
+ VoteCount = image.RatingsInfo.Count,
+ Url = TvdbUtils.BannerUrl + image.FileName,
+ ProviderName = ProviderName,
+ Language = languages.FirstOrDefault(lang => lang.Id == image.LanguageId)?.Abbreviation,
+ ThumbnailUrl = TvdbUtils.BannerUrl + image.Thumbnail
+ };
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Banner":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
- using (var subtree = reader.ReadSubtree())
- {
- AddImage(subtree, list, seasonNumber);
- }
- break;
- }
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
+ var resolution = image.Resolution.Split('x');
+ if (resolution.Length == 2)
+ {
+ imageInfo.Width = Convert.ToInt32(resolution[0]);
+ imageInfo.Height = Convert.ToInt32(resolution[1]);
}
- }
+ imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType);
+ list.Add(imageInfo);
+ }
var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i =>
@@ -155,6 +121,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
{
return 3;
}
+
if (!isLanguageEn)
{
if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
@@ -162,177 +129,18 @@ namespace MediaBrowser.Providers.TV.TheTVDB
return 2;
}
}
+
if (string.IsNullOrEmpty(i.Language))
{
return isLanguageEn ? 3 : 2;
}
+
return 0;
})
.ThenByDescending(i => i.CommunityRating ?? 0)
.ThenByDescending(i => i.VoteCount ?? 0);
}
- private static void AddImage(XmlReader reader, List<RemoteImageInfo> images, int seasonNumber)
- {
- reader.MoveToContent();
-
- string bannerType = null;
- string bannerType2 = null;
- string url = null;
- int? bannerSeason = null;
- int? width = null;
- int? height = null;
- string language = null;
- double? rating = null;
- int? voteCount = null;
- string thumbnailUrl = null;
-
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Rating":
- {
- var val = reader.ReadElementContentAsString() ?? string.Empty;
-
- if (double.TryParse(val, NumberStyles.Any, UsCulture, out var rval))
- {
- rating = rval;
- }
-
- break;
- }
-
- case "RatingCount":
- {
- var val = reader.ReadElementContentAsString() ?? string.Empty;
-
- if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval))
- {
- voteCount = rval;
- }
-
- break;
- }
-
- case "Language":
- {
- language = reader.ReadElementContentAsString() ?? string.Empty;
- break;
- }
-
- case "ThumbnailPath":
- {
- thumbnailUrl = reader.ReadElementContentAsString() ?? string.Empty;
- break;
- }
-
- case "BannerType":
- {
- bannerType = reader.ReadElementContentAsString() ?? string.Empty;
- break;
- }
-
- case "BannerType2":
- {
- bannerType2 = reader.ReadElementContentAsString() ?? string.Empty;
-
- // Sometimes the resolution is stuffed in here
- var resolutionParts = bannerType2.Split('x');
-
- if (resolutionParts.Length == 2)
- {
- if (int.TryParse(resolutionParts[0], NumberStyles.Integer, UsCulture, out var rval))
- {
- width = rval;
- }
-
- if (int.TryParse(resolutionParts[1], NumberStyles.Integer, UsCulture, out rval))
- {
- height = rval;
- }
-
- }
-
- break;
- }
-
- case "BannerPath":
- {
- url = reader.ReadElementContentAsString() ?? string.Empty;
- break;
- }
-
- case "Season":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- bannerSeason = int.Parse(val);
- }
- break;
- }
-
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- if (!string.IsNullOrEmpty(url) && bannerSeason.HasValue && bannerSeason.Value == seasonNumber)
- {
- var imageInfo = new RemoteImageInfo
- {
- RatingType = RatingType.Score,
- CommunityRating = rating,
- VoteCount = voteCount,
- Url = TVUtils.BannerUrl + url,
- ProviderName = ProviderName,
- Language = language,
- Width = width,
- Height = height
- };
-
- if (!string.IsNullOrEmpty(thumbnailUrl))
- {
- imageInfo.ThumbnailUrl = TVUtils.BannerUrl + thumbnailUrl;
- }
-
- if (string.Equals(bannerType, "season", StringComparison.OrdinalIgnoreCase))
- {
- if (string.Equals(bannerType2, "season", StringComparison.OrdinalIgnoreCase))
- {
- imageInfo.Type = ImageType.Primary;
- images.Add(imageInfo);
- }
- else if (string.Equals(bannerType2, "seasonwide", StringComparison.OrdinalIgnoreCase))
- {
- imageInfo.Type = ImageType.Banner;
- images.Add(imageInfo);
- }
- }
- else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))
- {
- imageInfo.Type = ImageType.Backdrop;
- images.Add(imageInfo);
- }
- }
-
- }
-
public int Order => 0;
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs
index 2b4337ed1..365f49fb7 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs
@@ -1,40 +1,32 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
using System.Linq;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Xml;
+using Microsoft.Extensions.Logging;
+using TvDbSharper;
+using TvDbSharper.Dto;
+using RatingType = MediaBrowser.Model.Dto.RatingType;
+using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace MediaBrowser.Providers.TV.TheTVDB
{
public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
{
- private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private readonly IFileSystem _fileSystem;
- private readonly IXmlReaderSettingsFactory _xmlReaderSettingsFactory;
+ private readonly ILogger _logger;
+ private readonly TvDbClientManager _tvDbClientManager;
- public TvdbSeriesImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
+ public TvdbSeriesImageProvider(IHttpClient httpClient, ILogger<TvdbSeriesImageProvider> logger, TvDbClientManager tvDbClientManager)
{
- _config = config;
_httpClient = httpClient;
- _fileSystem = fileSystem;
- _xmlReaderSettingsFactory = xmlReaderSettingsFactory;
+ _logger = logger;
+ _tvDbClientManager = tvDbClientManager;
}
public string Name => ProviderName;
@@ -58,273 +50,92 @@ namespace MediaBrowser.Providers.TV.TheTVDB
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
- if (TvdbSeriesProvider.IsValidSeries(item.ProviderIds))
+ if (!TvdbSeriesProvider.IsValidSeries(item.ProviderIds))
{
- var language = item.GetPreferredMetadataLanguage();
-
- var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(item.ProviderIds, item.Name, item.ProductionYear, language, cancellationToken).ConfigureAwait(false);
+ return Array.Empty<RemoteImageInfo>();
+ }
- if (string.IsNullOrEmpty(seriesDataPath))
+ var language = item.GetPreferredMetadataLanguage();
+ var remoteImages = new List<RemoteImageInfo>();
+ var keyTypes = new[] { KeyType.Poster, KeyType.Series, KeyType.Fanart };
+ var tvdbId = Convert.ToInt32(item.GetProviderId(MetadataProviders.Tvdb));
+ foreach (KeyType keyType in keyTypes)
+ {
+ var imageQuery = new ImagesQuery
{
- return new RemoteImageInfo[] { };
- }
-
- var path = Path.Combine(seriesDataPath, "banners.xml");
-
+ KeyType = keyType
+ };
try
{
- return GetImages(path, language, cancellationToken);
- }
- catch (FileNotFoundException)
- {
- // No tvdb data yet. Don't blow up
+ var imageResults =
+ await _tvDbClientManager.GetImagesAsync(tvdbId, imageQuery, language, cancellationToken)
+ .ConfigureAwait(false);
+
+ remoteImages.AddRange(GetImages(imageResults.Data, language));
}
- catch (IOException)
+ catch (TvDbServerException)
{
- // No tvdb data yet. Don't blow up
+ _logger.LogDebug("No images of type {KeyType} exist for series {TvDbId}", keyType,
+ tvdbId);
}
}
-
- return new RemoteImageInfo[] { };
+ return remoteImages;
}
- private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, CancellationToken cancellationToken)
+ private IEnumerable<RemoteImageInfo> GetImages(Image[] images, string preferredLanguage)
{
- var settings = _xmlReaderSettingsFactory.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
var list = new List<RemoteImageInfo>();
+ var languages = _tvDbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data;
- using (var fileStream = _fileSystem.GetFileStream(xmlPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ foreach (Image image in images)
{
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
+ var imageInfo = new RemoteImageInfo
{
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- cancellationToken.ThrowIfCancellationRequested();
+ RatingType = RatingType.Score,
+ CommunityRating = (double?)image.RatingsInfo.Average,
+ VoteCount = image.RatingsInfo.Count,
+ Url = TvdbUtils.BannerUrl + image.FileName,
+ ProviderName = Name,
+ Language = languages.FirstOrDefault(lang => lang.Id == image.LanguageId)?.Abbreviation,
+ ThumbnailUrl = TvdbUtils.BannerUrl + image.Thumbnail
+ };
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Banner":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
- using (var subtree = reader.ReadSubtree())
- {
- AddImage(subtree, list);
- }
- break;
- }
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
+ var resolution = image.Resolution.Split('x');
+ if (resolution.Length == 2)
+ {
+ imageInfo.Width = Convert.ToInt32(resolution[0]);
+ imageInfo.Height = Convert.ToInt32(resolution[1]);
}
- }
+ imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType);
+ list.Add(imageInfo);
+ }
var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i =>
- {
- if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (!isLanguageEn)
{
- if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
{
- return 2;
+ return 3;
}
- }
- if (string.IsNullOrEmpty(i.Language))
- {
- return isLanguageEn ? 3 : 2;
- }
- return 0;
- })
- .ThenByDescending(i => i.CommunityRating ?? 0)
- .ThenByDescending(i => i.VoteCount ?? 0);
- }
-
- private void AddImage(XmlReader reader, List<RemoteImageInfo> images)
- {
- reader.MoveToContent();
- string bannerType = null;
- string url = null;
- int? bannerSeason = null;
- int? width = null;
- int? height = null;
- string language = null;
- double? rating = null;
- int? voteCount = null;
- string thumbnailUrl = null;
-
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
+ if (!isLanguageEn)
{
- case "Rating":
- {
- var val = reader.ReadElementContentAsString() ?? string.Empty;
-
- if (double.TryParse(val, NumberStyles.Any, _usCulture, out var rval))
- {
- rating = rval;
- }
-
- break;
- }
-
- case "RatingCount":
- {
- var val = reader.ReadElementContentAsString() ?? string.Empty;
-
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- voteCount = rval;
- }
-
- break;
- }
-
- case "Language":
- {
- language = reader.ReadElementContentAsString() ?? string.Empty;
- break;
- }
-
- case "ThumbnailPath":
- {
- thumbnailUrl = reader.ReadElementContentAsString() ?? string.Empty;
- break;
- }
-
- case "BannerType":
- {
- bannerType = reader.ReadElementContentAsString() ?? string.Empty;
-
- break;
- }
-
- case "BannerPath":
- {
- url = reader.ReadElementContentAsString() ?? string.Empty;
- break;
- }
-
- case "BannerType2":
- {
- var bannerType2 = reader.ReadElementContentAsString() ?? string.Empty;
-
- // Sometimes the resolution is stuffed in here
- var resolutionParts = bannerType2.Split('x');
-
- if (resolutionParts.Length == 2)
- {
- if (int.TryParse(resolutionParts[0], NumberStyles.Integer, _usCulture, out var rval))
- {
- width = rval;
- }
-
- if (int.TryParse(resolutionParts[1], NumberStyles.Integer, _usCulture, out rval))
- {
- height = rval;
- }
-
- }
-
- break;
- }
-
- case "Season":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- bannerSeason = int.Parse(val);
- }
- break;
- }
-
-
- default:
- reader.Skip();
- break;
+ if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
+ {
+ return 2;
+ }
}
- }
- else
- {
- reader.Read();
- }
- }
- if (!string.IsNullOrEmpty(url) && !bannerSeason.HasValue)
- {
- var imageInfo = new RemoteImageInfo
- {
- RatingType = RatingType.Score,
- CommunityRating = rating,
- VoteCount = voteCount,
- Url = TVUtils.BannerUrl + url,
- ProviderName = Name,
- Language = language,
- Width = width,
- Height = height
- };
-
- if (!string.IsNullOrEmpty(thumbnailUrl))
- {
- imageInfo.ThumbnailUrl = TVUtils.BannerUrl + thumbnailUrl;
- }
-
- if (string.Equals(bannerType, "poster", StringComparison.OrdinalIgnoreCase))
- {
- imageInfo.Type = ImageType.Primary;
- images.Add(imageInfo);
- }
- else if (string.Equals(bannerType, "series", StringComparison.OrdinalIgnoreCase))
- {
- imageInfo.Type = ImageType.Banner;
- images.Add(imageInfo);
- }
- else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))
- {
- imageInfo.Type = ImageType.Backdrop;
- images.Add(imageInfo);
- }
- }
+ if (string.IsNullOrEmpty(i.Language))
+ {
+ return isLanguageEn ? 3 : 2;
+ }
+ return 0;
+ })
+ .ThenByDescending(i => i.CommunityRating ?? 0)
+ .ThenByDescending(i => i.VoteCount ?? 0);
}
public int Order => 0;
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
index 52e60a8ed..9c24e4c98 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
@@ -1,72 +1,42 @@
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.IO;
using System.Linq;
-using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
+using TvDbSharper;
+using TvDbSharper.Dto;
+using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace MediaBrowser.Providers.TV.TheTVDB
{
public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
{
internal static TvdbSeriesProvider Current { get; private set; }
- private readonly IZipClient _zipClient;
private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
- private readonly IXmlReaderSettingsFactory _xmlSettings;
- private readonly IServerConfigurationManager _config;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
+ private readonly TvDbClientManager _tvDbClientManager;
- public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager, IXmlReaderSettingsFactory xmlSettings, ILocalizationManager localizationManager)
+ public TvdbSeriesProvider(IHttpClient httpClient, ILogger<TvdbSeriesProvider> logger, ILibraryManager libraryManager, ILocalizationManager localizationManager, TvDbClientManager tvDbClientManager)
{
- _zipClient = zipClient;
_httpClient = httpClient;
- _fileSystem = fileSystem;
- _config = config;
_logger = logger;
_libraryManager = libraryManager;
- _xmlSettings = xmlSettings;
_localizationManager = localizationManager;
Current = this;
- }
-
- public const string TvdbBaseUrl = "https://www.thetvdb.com/";
-
- private const string SeriesSearchUrl = TvdbBaseUrl + "api/GetSeries.php?seriesname={0}&language={1}";
- private const string SeriesGetZip = TvdbBaseUrl + "api/{0}/series/{1}/all/{2}.zip";
- private const string GetSeriesByImdbId = TvdbBaseUrl + "api/GetSeriesByRemoteID.php?imdbid={0}&language={1}";
- private const string GetSeriesByZap2ItId = TvdbBaseUrl + "api/GetSeriesByRemoteID.php?zap2it={0}&language={1}";
-
- private string NormalizeLanguage(string language)
- {
- if (string.IsNullOrWhiteSpace(language))
- {
- return language;
- }
-
- // pt-br is just pt to tvdb
- return language.Split('-')[0].ToLowerInvariant();
+ _tvDbClientManager = tvDbClientManager;
}
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
@@ -96,8 +66,10 @@ namespace MediaBrowser.Providers.TV.TheTVDB
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo itemId, CancellationToken cancellationToken)
{
- var result = new MetadataResult<Series>();
- result.QueriedById = true;
+ var result = new MetadataResult<Series>
+ {
+ QueriedById = true
+ };
if (!IsValidSeries(itemId.ProviderIds))
{
@@ -109,428 +81,99 @@ namespace MediaBrowser.Providers.TV.TheTVDB
if (IsValidSeries(itemId.ProviderIds))
{
- var seriesDataPath = await EnsureSeriesInfo(itemId.ProviderIds, itemId.Name, itemId.Year, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false);
-
- if (string.IsNullOrEmpty(seriesDataPath))
- {
- return result;
- }
-
result.Item = new Series();
result.HasMetadata = true;
- FetchSeriesData(result, itemId.MetadataLanguage, itemId.ProviderIds, cancellationToken);
+ await FetchSeriesData(result, itemId.MetadataLanguage, itemId.ProviderIds, cancellationToken)
+ .ConfigureAwait(false);
}
return result;
}
- /// <summary>
- /// Fetches the series data.
- /// </summary>
- /// <param name="result">The result.</param>
- /// <param name="metadataLanguage">The metadata language.</param>
- /// <param name="seriesProviderIds">The series provider ids.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{System.Boolean}.</returns>
- private void FetchSeriesData(MetadataResult<Series> result, string metadataLanguage, Dictionary<string, string> seriesProviderIds, CancellationToken cancellationToken)
+ private async Task FetchSeriesData(MetadataResult<Series> result, string metadataLanguage, Dictionary<string, string> seriesProviderIds, CancellationToken cancellationToken)
{
var series = result.Item;
- if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out string id) && !string.IsNullOrEmpty(id))
+ if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out var tvdbId) && !string.IsNullOrEmpty(tvdbId))
{
- series.SetProviderId(MetadataProviders.Tvdb, id);
+ series.SetProviderId(MetadataProviders.Tvdb, tvdbId);
}
- if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out id) && !string.IsNullOrEmpty(id))
+ if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out var imdbId) && !string.IsNullOrEmpty(imdbId))
{
- series.SetProviderId(MetadataProviders.Imdb, id);
+ series.SetProviderId(MetadataProviders.Imdb, imdbId);
+ tvdbId = await GetSeriesByRemoteId(imdbId, MetadataProviders.Imdb.ToString(), metadataLanguage,
+ cancellationToken).ConfigureAwait(false);
}
- if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out id) && !string.IsNullOrEmpty(id))
+ if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out var zap2It) && !string.IsNullOrEmpty(zap2It))
{
- series.SetProviderId(MetadataProviders.Zap2It, id);
+ series.SetProviderId(MetadataProviders.Zap2It, zap2It);
+ tvdbId = await GetSeriesByRemoteId(zap2It, MetadataProviders.Zap2It.ToString(), metadataLanguage,
+ cancellationToken).ConfigureAwait(false);
}
- var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);
-
- var seriesXmlPath = GetSeriesXmlPath(seriesProviderIds, metadataLanguage);
- var actorsXmlPath = Path.Combine(seriesDataPath, "actors.xml");
-
- FetchSeriesInfo(result, seriesXmlPath, cancellationToken);
-
- cancellationToken.ThrowIfCancellationRequested();
-
- result.ResetPeople();
-
- FetchActors(result, actorsXmlPath);
- }
-
- /// <summary>
- /// Downloads the series zip.
- /// </summary>
- internal async Task DownloadSeriesZip(string seriesId, string idType, string seriesName, int? seriesYear, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
- {
try
{
- await DownloadSeriesZip(seriesId, idType, seriesName, seriesYear, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
- return;
- }
- catch (HttpException ex)
- {
- if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
- {
- throw;
- }
- }
-
- if (!string.Equals(preferredMetadataLanguage, "en", StringComparison.OrdinalIgnoreCase))
- {
- await DownloadSeriesZip(seriesId, idType, seriesName, seriesYear, seriesDataPath, lastTvDbUpdateTime, "en", preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
- }
- }
-
- private async Task DownloadSeriesZip(string seriesId, string idType, string seriesName, int? seriesYear, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, string saveAsMetadataLanguage, CancellationToken cancellationToken)
- {
- if (string.IsNullOrWhiteSpace(seriesId))
- {
- throw new ArgumentNullException(nameof(seriesId));
- }
-
- if (!string.Equals(idType, "tvdb", StringComparison.OrdinalIgnoreCase))
- {
- seriesId = await GetSeriesByRemoteId(seriesId, idType, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
+ var seriesResult =
+ await _tvDbClientManager
+ .GetSeriesByIdAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken)
+ .ConfigureAwait(false);
+ MapSeriesToResult(result, seriesResult.Data, metadataLanguage);
}
-
- // If searching by remote id came up empty, then do a regular search
- if (string.IsNullOrWhiteSpace(seriesId) && !string.IsNullOrWhiteSpace(seriesName))
+ catch (TvDbServerException e)
{
- var searchInfo = new SeriesInfo
- {
- Name = seriesName,
- Year = seriesYear,
- MetadataLanguage = preferredMetadataLanguage
- };
- var results = await GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
- var result = results.FirstOrDefault();
- if (result != null)
- {
- seriesId = result.GetProviderId(MetadataProviders.Tvdb);
- }
- }
-
- if (string.IsNullOrWhiteSpace(seriesId))
- {
- throw new ArgumentNullException(nameof(seriesId));
+ _logger.LogError(e, "Failed to retrieve series with id {TvdbId}", tvdbId);
+ return;
}
- var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, NormalizeLanguage(preferredMetadataLanguage));
-
- using (var response = await _httpClient.SendAsync(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- BufferContent = false
-
- }, "GET").ConfigureAwait(false))
- {
- using (var zipStream = response.Content)
- {
- // Delete existing files
- DeleteXmlFiles(seriesDataPath);
-
- // Copy to memory stream because we need a seekable stream
- using (var ms = new MemoryStream())
- {
- await zipStream.CopyToAsync(ms).ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
- ms.Position = 0;
- _zipClient.ExtractAllFromZip(ms, seriesDataPath, true);
- }
- }
- }
+ result.ResetPeople();
- // Sanitize all files, except for extracted episode files
- foreach (var file in _fileSystem.GetFilePaths(seriesDataPath, true).ToList()
- .Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
- .Where(i => !Path.GetFileName(i).StartsWith("episode-", StringComparison.OrdinalIgnoreCase)))
+ try
{
- await SanitizeXmlFile(file).ConfigureAwait(false);
+ var actorsResult = await _tvDbClientManager
+ .GetActorsAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken).ConfigureAwait(false);
+ MapActorsToResult(result, actorsResult.Data);
}
-
- var downloadLangaugeXmlFile = Path.Combine(seriesDataPath, NormalizeLanguage(preferredMetadataLanguage) + ".xml");
- var saveAsLanguageXmlFile = Path.Combine(seriesDataPath, saveAsMetadataLanguage + ".xml");
-
- if (!string.Equals(downloadLangaugeXmlFile, saveAsLanguageXmlFile, StringComparison.OrdinalIgnoreCase))
+ catch (TvDbServerException e)
{
- File.Copy(downloadLangaugeXmlFile, saveAsLanguageXmlFile, true);
+ _logger.LogError(e, "Failed to retrieve actors for series {TvdbId}", tvdbId);
}
-
- await ExtractEpisodes(seriesDataPath, downloadLangaugeXmlFile, lastTvDbUpdateTime).ConfigureAwait(false);
}
private async Task<string> GetSeriesByRemoteId(string id, string idType, string language, CancellationToken cancellationToken)
{
- string url;
- if (string.Equals(idType, MetadataProviders.Zap2It.ToString(), StringComparison.OrdinalIgnoreCase))
- {
- url = string.Format(GetSeriesByZap2ItId, id, NormalizeLanguage(language));
- }
- else
- {
- url = string.Format(GetSeriesByImdbId, id, NormalizeLanguage(language));
- }
-
- using (var response = await _httpClient.SendAsync(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- BufferContent = false
-
- }, "GET").ConfigureAwait(false))
- {
- using (var result = response.Content)
- {
- return FindSeriesId(result);
- }
- }
- }
-
- private string FindSeriesId(Stream stream)
- {
- using (var streamReader = new StreamReader(stream, Encoding.UTF8))
- {
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Series":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
- using (var subtree = reader.ReadSubtree())
- {
- return FindSeriesId(subtree);
- }
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
- }
-
- return null;
- }
-
- private string FindSeriesId(XmlReader reader)
- {
- reader.MoveToContent();
- reader.Read();
+ TvDbResponse<SeriesSearchResult[]> result = null;
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ try
{
- if (reader.NodeType == XmlNodeType.Element)
+ if (string.Equals(idType, MetadataProviders.Zap2It.ToString(), StringComparison.OrdinalIgnoreCase))
{
- switch (reader.Name)
- {
- case "seriesid":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- return val;
- }
-
- return null;
- }
-
- default:
- reader.Skip();
- break;
- }
+ result = await _tvDbClientManager.GetSeriesByZap2ItIdAsync(id, language, cancellationToken)
+ .ConfigureAwait(false);
}
else
{
- reader.Read();
+ result = await _tvDbClientManager.GetSeriesByImdbIdAsync(id, language, cancellationToken)
+ .ConfigureAwait(false);
}
}
-
- return null;
- }
-
- internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds)
- {
- if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out string id))
+ catch (TvDbServerException e)
{
- // This check should ideally never be necessary but we're seeing some cases of this and haven't tracked them down yet.
- if (!string.IsNullOrWhiteSpace(id))
- {
- return true;
- }
+ _logger.LogError(e, "Failed to retrieve series with remote id {RemoteId}", id);
}
- if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out id))
- {
- // This check should ideally never be necessary but we're seeing some cases of this and haven't tracked them down yet.
- if (!string.IsNullOrWhiteSpace(id))
- {
- return true;
- }
- }
-
- if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out id))
- {
- // This check should ideally never be necessary but we're seeing some cases of this and haven't tracked them down yet.
- if (!string.IsNullOrWhiteSpace(id))
- {
- return true;
- }
- }
- return false;
- }
-
- private SemaphoreSlim _ensureSemaphore = new SemaphoreSlim(1, 1);
- internal async Task<string> EnsureSeriesInfo(Dictionary<string, string> seriesProviderIds, string seriesName, int? seriesYear, string preferredMetadataLanguage, CancellationToken cancellationToken)
- {
- await _ensureSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out string seriesId) && !string.IsNullOrWhiteSpace(seriesId))
- {
- var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);
-
- // Only download if not already there
- // The post-scan task will take care of updates so we don't need to re-download here
- if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage))
- {
- await DownloadSeriesZip(seriesId, MetadataProviders.Tvdb.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
- }
-
- return seriesDataPath;
- }
-
- if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out seriesId) && !string.IsNullOrWhiteSpace(seriesId))
- {
- var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);
-
- // Only download if not already there
- // The post-scan task will take care of updates so we don't need to re-download here
- if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage))
- {
- try
- {
- await DownloadSeriesZip(seriesId, MetadataProviders.Imdb.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
- }
- catch (ArgumentNullException)
- {
- // Unable to determine tvdb id based on imdb id
- return null;
- }
- }
-
- return seriesDataPath;
- }
-
- if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out seriesId) && !string.IsNullOrWhiteSpace(seriesId))
- {
- var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);
-
- // Only download if not already there
- // The post-scan task will take care of updates so we don't need to re-download here
- if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage))
- {
- try
- {
- await DownloadSeriesZip(seriesId, MetadataProviders.Zap2It.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
- }
- catch (ArgumentNullException)
- {
- // Unable to determine tvdb id based on Zap2It id
- return null;
- }
- }
-
- return seriesDataPath;
- }
-
- return null;
- }
- finally
- {
- _ensureSemaphore.Release();
- }
+ return result?.Data.First().Id.ToString();
}
- private bool IsCacheValid(string seriesDataPath, string preferredMetadataLanguage)
+ internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds)
{
- try
- {
- var files = _fileSystem.GetFiles(seriesDataPath, new[] { ".xml" }, true, false)
- .ToList();
-
- var seriesXmlFilename = preferredMetadataLanguage + ".xml";
-
- const int cacheHours = 12;
-
- var seriesFile = files.FirstOrDefault(i => string.Equals(seriesXmlFilename, i.Name, StringComparison.OrdinalIgnoreCase));
- // No need to check age if automatic updates are enabled
- if (seriesFile == null || !seriesFile.Exists || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(seriesFile)).TotalHours > cacheHours)
- {
- return false;
- }
-
- var actorsXml = files.FirstOrDefault(i => string.Equals("actors.xml", i.Name, StringComparison.OrdinalIgnoreCase));
- // No need to check age if automatic updates are enabled
- if (actorsXml == null || !actorsXml.Exists || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(actorsXml)).TotalHours > cacheHours)
- {
- return false;
- }
-
- var bannersXml = files.FirstOrDefault(i => string.Equals("banners.xml", i.Name, StringComparison.OrdinalIgnoreCase));
- // No need to check age if automatic updates are enabled
- if (bannersXml == null || !bannersXml.Exists || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(bannersXml)).TotalHours > cacheHours)
- {
- return false;
- }
- return true;
- }
- catch (FileNotFoundException)
- {
- return false;
- }
- catch (IOException)
- {
- return false;
- }
+ return seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out _) ||
+ seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out _) ||
+ seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out _);
}
/// <summary>
@@ -543,7 +186,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
/// <returns>Task{System.String}.</returns>
private async Task<IEnumerable<RemoteSearchResult>> FindSeries(string name, int? year, string language, CancellationToken cancellationToken)
{
- var results = (await FindSeriesInternal(name, language, cancellationToken).ConfigureAwait(false));
+ var results = await FindSeriesInternal(name, language, cancellationToken).ConfigureAwait(false);
if (results.Count == 0)
{
@@ -552,7 +195,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
if (!string.IsNullOrWhiteSpace(nameWithoutYear) && !string.Equals(nameWithoutYear, name, StringComparison.OrdinalIgnoreCase))
{
- results = (await FindSeriesInternal(nameWithoutYear, language, cancellationToken).ConfigureAwait(false));
+ results = await FindSeriesInternal(nameWithoutYear, language, cancellationToken).ConfigureAwait(false);
}
}
@@ -570,194 +213,59 @@ namespace MediaBrowser.Providers.TV.TheTVDB
private async Task<List<RemoteSearchResult>> FindSeriesInternal(string name, string language, CancellationToken cancellationToken)
{
- var url = string.Format(SeriesSearchUrl, WebUtility.UrlEncode(name), NormalizeLanguage(language));
-
var comparableName = GetComparableName(name);
-
var list = new List<Tuple<List<string>, RemoteSearchResult>>();
-
- using (var response = await _httpClient.SendAsync(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- BufferContent = false
-
- }, "GET").ConfigureAwait(false))
+ TvDbResponse<SeriesSearchResult[]> result;
+ try
{
- using (var stream = response.Content)
- {
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- using (var streamReader = new StreamReader(stream, Encoding.UTF8))
- {
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Series":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
- using (var subtree = reader.ReadSubtree())
- {
- var searchResultInfo = GetSeriesSearchResultFromSubTree(subtree);
- if (searchResultInfo != null)
- {
- searchResultInfo.Item2.SearchProviderName = Name;
- list.Add(searchResultInfo);
- }
- }
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
- }
- }
+ result = await _tvDbClientManager.GetSeriesByNameAsync(comparableName, language, cancellationToken)
+ .ConfigureAwait(false);
}
-
- return list
- .OrderBy(i => i.Item1.Contains(comparableName, StringComparer.OrdinalIgnoreCase) ? 0 : 1)
- .ThenBy(i => list.IndexOf(i))
- .Select(i => i.Item2)
- .ToList();
- }
-
- private Tuple<List<string>, RemoteSearchResult> GetSeriesSearchResultFromSubTree(XmlReader reader)
- {
- var searchResult = new RemoteSearchResult
+ catch (TvDbServerException e)
{
- SearchProviderName = Name
- };
-
- var tvdbTitles = new List<string>();
- string seriesId = null;
-
- reader.MoveToContent();
- reader.Read();
+ _logger.LogError(e, "No series results found for {Name}", comparableName);
+ return new List<RemoteSearchResult>();
+ }
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ foreach (var seriesSearchResult in result.Data)
{
- if (reader.NodeType == XmlNodeType.Element)
+ var tvdbTitles = new List<string>
{
- switch (reader.Name)
- {
- case "SeriesName":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- tvdbTitles.Add(GetComparableName(val));
- }
- break;
- }
-
- case "AliasNames":
- {
- var val = reader.ReadElementContentAsString();
-
- var alias = (val ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).Select(GetComparableName);
- tvdbTitles.AddRange(alias);
- break;
- }
-
- case "IMDB_ID":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- searchResult.SetProviderId(MetadataProviders.Imdb, val);
- }
- break;
- }
-
- case "banner":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- searchResult.ImageUrl = TVUtils.BannerUrl + val;
- }
- break;
- }
-
- case "FirstAired":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (DateTime.TryParse(val, out var date))
- {
- searchResult.ProductionYear = date.Year;
- }
- }
- break;
- }
-
- case "id":
- case "seriesid":
- {
- var val = reader.ReadElementContentAsString();
+ GetComparableName(seriesSearchResult.SeriesName)
+ };
+ tvdbTitles.AddRange(seriesSearchResult.Aliases.Select(GetComparableName));
- if (!string.IsNullOrWhiteSpace(val))
- {
- seriesId = val;
- }
- break;
- }
+ DateTime.TryParse(seriesSearchResult.FirstAired, out var firstAired);
+ var remoteSearchResult = new RemoteSearchResult
+ {
+ Name = tvdbTitles.FirstOrDefault(),
+ ProductionYear = firstAired.Year,
+ SearchProviderName = Name,
+ ImageUrl = TvdbUtils.BannerUrl + seriesSearchResult.Banner
- default:
- reader.Skip();
- break;
- }
+ };
+ try
+ {
+ var seriesSesult =
+ await _tvDbClientManager.GetSeriesByIdAsync(seriesSearchResult.Id, language, cancellationToken)
+ .ConfigureAwait(false);
+ remoteSearchResult.SetProviderId(MetadataProviders.Imdb, seriesSesult.Data.ImdbId);
+ remoteSearchResult.SetProviderId(MetadataProviders.Zap2It, seriesSesult.Data.Zap2itId);
}
- else
+ catch (TvDbServerException e)
{
- reader.Read();
+ _logger.LogError(e, "Unable to retrieve series with id {TvdbId}", seriesSearchResult.Id);
}
- }
- if (tvdbTitles.Count == 0)
- {
- return null;
+ remoteSearchResult.SetProviderId(MetadataProviders.Tvdb, seriesSearchResult.Id.ToString());
+ list.Add(new Tuple<List<string>, RemoteSearchResult>(tvdbTitles, remoteSearchResult));
}
- searchResult.Name = tvdbTitles.FirstOrDefault();
- searchResult.SetProviderId(MetadataProviders.Tvdb, seriesId);
-
- return new Tuple<List<string>, RemoteSearchResult>(tvdbTitles, searchResult);
+ return list
+ .OrderBy(i => i.Item1.Contains(comparableName, StringComparer.OrdinalIgnoreCase) ? 0 : 1)
+ .ThenBy(i => list.IndexOf(i))
+ .Select(i => i.Item2)
+ .ToList();
}
/// <summary>
@@ -767,7 +275,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
/// <summary>
/// The spacers
/// </summary>
- const string spacers = "/,.:;\\(){}[]+-_=–*"; // (there are not actually two - in the they are different char codes)
+ const string spacers = "/,.:;\\(){}[]+-_=–*"; // (there are two types of dashes, short and long)
/// <summary>
/// Gets the name of the comparable.
@@ -781,7 +289,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
var sb = new StringBuilder();
foreach (var c in name)
{
- if ((int)c >= 0x2B0 && (int)c <= 0x0333)
+ if (c >= 0x2B0 && c <= 0x0333)
{
// skip char modifier and diacritics
}
@@ -817,895 +325,83 @@ namespace MediaBrowser.Providers.TV.TheTVDB
return name.Trim();
}
- private void FetchSeriesInfo(MetadataResult<Series> result, string seriesXmlPath, CancellationToken cancellationToken)
+ private void MapSeriesToResult(MetadataResult<Series> result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage)
{
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- var episiodeAirDates = new List<DateTime>();
+ Series series = result.Item;
+ series.SetProviderId(MetadataProviders.Tvdb, tvdbSeries.Id.ToString());
+ series.Name = tvdbSeries.SeriesName;
+ series.Overview = (tvdbSeries.Overview ?? string.Empty).Trim();
+ result.ResultLanguage = metadataLanguage;
+ series.AirDays = TVUtils.GetAirDays(tvdbSeries.AirsDayOfWeek);
+ series.AirTime = tvdbSeries.AirsTime;
- using (var fileStream = _fileSystem.GetFileStream(seriesXmlPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ series.CommunityRating = (float?)tvdbSeries.SiteRating;
+ series.SetProviderId(MetadataProviders.Imdb, tvdbSeries.ImdbId);
+ series.SetProviderId(MetadataProviders.Zap2It, tvdbSeries.Zap2itId);
+ if (Enum.TryParse(tvdbSeries.Status, true, out SeriesStatus seriesStatus))
{
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
- {
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Series":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
- using (var subtree = reader.ReadSubtree())
- {
- FetchDataFromSeriesNode(result, subtree, cancellationToken);
- }
- break;
- }
-
- case "Episode":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
- using (var subtree = reader.ReadSubtree())
- {
- var date = GetFirstAiredDateFromEpisodeNode(subtree, cancellationToken);
-
- if (date.HasValue)
- {
- episiodeAirDates.Add(date.Value);
- }
- }
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
- }
- }
-
- if (result.Item.Status.HasValue && result.Item.Status.Value == SeriesStatus.Ended && episiodeAirDates.Count > 0)
- {
- result.Item.EndDate = episiodeAirDates.Max();
- }
- }
-
- private DateTime? GetFirstAiredDateFromEpisodeNode(XmlReader reader, CancellationToken cancellationToken)
- {
- DateTime? airDate = null;
- int? seasonNumber = null;
-
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "FirstAired":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (DateTime.TryParse(val, out var date))
- {
- airDate = date.ToUniversalTime();
- }
- }
-
- break;
- }
-
- case "SeasonNumber":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- seasonNumber = rval;
- }
- }
-
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- if (seasonNumber.HasValue && seasonNumber.Value != 0)
- {
- return airDate;
- }
-
- return null;
- }
-
- /// <summary>
- /// Fetches the actors.
- /// </summary>
- /// <param name="result">The result.</param>
- /// <param name="actorsXmlPath">The actors XML path.</param>
- private void FetchActors(MetadataResult<Series> result, string actorsXmlPath)
- {
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- using (var fileStream = _fileSystem.GetFileStream(actorsXmlPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
- {
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
- {
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Actor":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
- using (var subtree = reader.ReadSubtree())
- {
- FetchDataFromActorNode(result, subtree);
- }
- break;
- }
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
- }
- }
- }
-
- /// <summary>
- /// Fetches the data from actor node.
- /// </summary>
- /// <param name="result">The result.</param>
- /// <param name="reader">The reader.</param>
- private void FetchDataFromActorNode(MetadataResult<Series> result, XmlReader reader)
- {
- reader.MoveToContent();
-
- var personInfo = new PersonInfo();
-
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Name":
- {
- personInfo.Name = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
- break;
- }
-
- case "Role":
- {
- personInfo.Role = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
- break;
- }
-
- case "id":
- {
- reader.Skip();
- break;
- }
-
- case "Image":
- {
- var url = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
- if (!string.IsNullOrWhiteSpace(url))
- {
- personInfo.ImageUrl = TVUtils.BannerUrl + url;
- }
- break;
- }
-
- case "SortOrder":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- personInfo.SortOrder = rval;
- }
- }
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- personInfo.Type = PersonType.Actor;
-
- if (!string.IsNullOrWhiteSpace(personInfo.Name))
- {
- result.AddPerson(personInfo);
- }
- }
-
- private void FetchDataFromSeriesNode(MetadataResult<Series> result, XmlReader reader, CancellationToken cancellationToken)
- {
- Series item = result.Item;
-
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "id":
- {
- item.SetProviderId(MetadataProviders.Tvdb.ToString(), (reader.ReadElementContentAsString() ?? string.Empty).Trim());
- break;
- }
-
- case "SeriesName":
- {
- item.Name = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
- break;
- }
-
- case "Overview":
- {
- item.Overview = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
- break;
- }
-
- case "Language":
- {
- result.ResultLanguage = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
- break;
- }
-
- case "Airs_DayOfWeek":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.AirDays = TVUtils.GetAirDays(val);
- }
- break;
- }
-
- case "Airs_Time":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.AirTime = val;
- }
- break;
- }
-
- case "ContentRating":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.OfficialRating = val;
- }
- break;
- }
-
- case "Rating":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // float.TryParse is local aware, so it can be probamatic, force us culture
- if (float.TryParse(val, NumberStyles.AllowDecimalPoint, _usCulture, out var rval))
- {
- item.CommunityRating = rval;
- }
- }
- break;
- }
- case "RatingCount":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- //item.VoteCount = rval;
- }
- }
-
- break;
- }
-
- case "IMDB_ID":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.SetProviderId(MetadataProviders.Imdb, val);
- }
-
- break;
- }
-
- case "zap2it_id":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.SetProviderId(MetadataProviders.Zap2It, val);
- }
-
- break;
- }
-
- case "Status":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (Enum.TryParse(val, true, out SeriesStatus seriesStatus))
- item.Status = seriesStatus;
- }
-
- break;
- }
-
- case "FirstAired":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (DateTime.TryParse(val, out var date))
- {
- date = date.ToUniversalTime();
-
- item.PremiereDate = date;
- item.ProductionYear = date.Year;
- }
- }
-
- break;
- }
-
- case "Runtime":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- // int.TryParse is local aware, so it can be probamatic, force us culture
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
- {
- item.RunTimeTicks = TimeSpan.FromMinutes(rval).Ticks;
- }
- }
-
- break;
- }
-
- case "Genre":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- var vals = val
- .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
- .Select(i => i.Trim())
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .ToList();
-
- if (vals.Count > 0)
- {
- item.Genres = Array.Empty<string>();
-
- foreach (var genre in vals)
- {
- item.AddGenre(genre);
- }
- }
- }
-
- break;
- }
-
- case "Network":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- var vals = val
- .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
- .Select(i => i.Trim())
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .ToList();
-
- if (vals.Count > 0)
- {
- item.SetStudios(vals);
- }
- }
-
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
-
- /// <summary>
- /// Extracts info for each episode into invididual xml files so that they can be easily accessed without having to step through the entire series xml
- /// </summary>
- /// <param name="seriesDataPath">The series data path.</param>
- /// <param name="xmlFile">The XML file.</param>
- /// <param name="lastTvDbUpdateTime">The last tv db update time.</param>
- /// <returns>Task.</returns>
- private async Task ExtractEpisodes(string seriesDataPath, string xmlFile, long? lastTvDbUpdateTime)
- {
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- using (var fileStream = _fileSystem.GetFileStream(xmlFile, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
- {
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
- {
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Episode":
- {
- var outerXml = reader.ReadOuterXml();
-
- await SaveEpsiodeXml(seriesDataPath, outerXml, lastTvDbUpdateTime).ConfigureAwait(false);
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
- }
- }
- }
-
- private async Task SaveEpsiodeXml(string seriesDataPath, string xml, long? lastTvDbUpdateTime)
- {
- var settings = _xmlSettings.Create(false);
-
- settings.CheckCharacters = false;
- settings.IgnoreProcessingInstructions = true;
- settings.IgnoreComments = true;
-
- var seasonNumber = -1;
- var episodeNumber = -1;
- var absoluteNumber = -1;
- var lastUpdateString = string.Empty;
-
- var dvdSeasonNumber = -1;
- var dvdEpisodeNumber = -1.0;
-
- using (var streamReader = new StringReader(xml))
- {
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "lastupdated":
- {
- lastUpdateString = reader.ReadElementContentAsString();
- break;
- }
-
- case "EpisodeNumber":
- {
- var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var num))
- {
- episodeNumber = num;
- }
- }
- break;
- }
-
- case "Combined_episodenumber":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (float.TryParse(val, NumberStyles.Any, _usCulture, out var num))
- {
- dvdEpisodeNumber = num;
- }
- }
-
- break;
- }
-
- case "Combined_season":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (float.TryParse(val, NumberStyles.Any, _usCulture, out var num))
- {
- dvdSeasonNumber = Convert.ToInt32(num);
- }
- }
-
- break;
- }
-
- case "absolute_number":
- {
- var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var num))
- {
- absoluteNumber = num;
- }
- }
- break;
- }
-
- case "SeasonNumber":
- {
- var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var num))
- {
- seasonNumber = num;
- }
- }
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
+ series.Status = seriesStatus;
}
- var hasEpisodeChanged = true;
- if (!string.IsNullOrWhiteSpace(lastUpdateString) && lastTvDbUpdateTime.HasValue)
+ if (DateTime.TryParse(tvdbSeries.FirstAired, out var date))
{
- if (long.TryParse(lastUpdateString, NumberStyles.Any, _usCulture, out var num))
- {
- hasEpisodeChanged = num >= lastTvDbUpdateTime.Value;
- }
+ // dates from tvdb are UTC but without offset or Z
+ series.PremiereDate = date;
+ series.ProductionYear = date.Year;
}
- var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber, episodeNumber));
-
- // Only save the file if not already there, or if the episode has changed
- if (hasEpisodeChanged || !File.Exists(file))
+ series.RunTimeTicks = TimeSpan.FromMinutes(Convert.ToDouble(tvdbSeries.Runtime)).Ticks;
+ foreach (var genre in tvdbSeries.Genre)
{
- using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true))
- {
- using (var writer = XmlWriter.Create(fileStream, new XmlWriterSettings
- {
- Encoding = Encoding.UTF8,
- Async = true
- }))
- {
- await writer.WriteRawAsync(xml).ConfigureAwait(false);
- }
- }
+ series.AddGenre(genre);
}
- if (absoluteNumber != -1)
- {
- file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", absoluteNumber));
+ series.AddStudio(tvdbSeries.Network);
- // Only save the file if not already there, or if the episode has changed
- if (hasEpisodeChanged || !File.Exists(file))
- {
- using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true))
- {
- using (var writer = XmlWriter.Create(fileStream, new XmlWriterSettings
- {
- Encoding = Encoding.UTF8,
- Async = true
- }))
- {
- await writer.WriteRawAsync(xml).ConfigureAwait(false);
- }
- }
- }
- }
-
- if (dvdSeasonNumber != -1 && dvdEpisodeNumber != -1 && (dvdSeasonNumber != seasonNumber || dvdEpisodeNumber != episodeNumber))
+ if (result.Item.Status.HasValue && result.Item.Status.Value == SeriesStatus.Ended)
{
- file = Path.Combine(seriesDataPath, string.Format("episode-dvd-{0}-{1}.xml", dvdSeasonNumber, dvdEpisodeNumber));
-
- // Only save the file if not already there, or if the episode has changed
- if (hasEpisodeChanged || !File.Exists(file))
+ try
{
- using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true))
+ var episodeSummary = _tvDbClientManager
+ .GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).Result.Data;
+ var maxSeasonNumber = episodeSummary.AiredSeasons.Select(s => Convert.ToInt32(s)).Max();
+ var episodeQuery = new EpisodeQuery
{
- using (var writer = XmlWriter.Create(fileStream, new XmlWriterSettings
- {
- Encoding = Encoding.UTF8,
- Async = true
- }))
+ AiredSeason = maxSeasonNumber
+ };
+ var episodesPage =
+ _tvDbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).Result.Data;
+ result.Item.EndDate = episodesPage.Select(e =>
{
- await writer.WriteRawAsync(xml).ConfigureAwait(false);
- }
- }
+ DateTime.TryParse(e.FirstAired, out var firstAired);
+ return firstAired;
+ }).Max();
}
- }
- }
-
- /// <summary>
- /// Gets the series data path.
- /// </summary>
- /// <param name="appPaths">The app paths.</param>
- /// <param name="seriesProviderIds">The series provider ids.</param>
- /// <returns>System.String.</returns>
- internal static string GetSeriesDataPath(IApplicationPaths appPaths, Dictionary<string, string> seriesProviderIds)
- {
- if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out string seriesId) && !string.IsNullOrEmpty(seriesId))
- {
- var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
-
- return seriesDataPath;
- }
-
- if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out seriesId) && !string.IsNullOrEmpty(seriesId))
- {
- var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
-
- return seriesDataPath;
- }
-
- if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out seriesId) && !string.IsNullOrEmpty(seriesId))
- {
- var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
-
- return seriesDataPath;
- }
-
- return null;
- }
-
- public string GetSeriesXmlPath(Dictionary<string, string> seriesProviderIds, string language)
- {
- var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);
-
- var seriesXmlFilename = language.ToLowerInvariant() + ".xml";
-
- return Path.Combine(seriesDataPath, seriesXmlFilename);
- }
-
- /// <summary>
- /// Gets the series data path.
- /// </summary>
- /// <param name="appPaths">The app paths.</param>
- /// <returns>System.String.</returns>
- internal static string GetSeriesDataPath(IApplicationPaths appPaths)
- {
- var dataPath = Path.Combine(appPaths.CachePath, "tvdb");
-
- return dataPath;
- }
-
- private void DeleteXmlFiles(string path)
- {
- try
- {
- foreach (var file in _fileSystem.GetFilePaths(path, true)
- .ToList())
+ catch (TvDbServerException e)
{
- _fileSystem.DeleteFile(file);
+ _logger.LogError(e, "Failed to find series end date for series {TvdbId}", tvdbSeries.Id);
}
}
- catch (IOException)
- {
- // No biggie
- }
}
- /// <summary>
- /// Sanitizes the XML file.
- /// </summary>
- /// <param name="file">The file.</param>
- /// <returns>Task.</returns>
- private async Task SanitizeXmlFile(string file)
+ private static void MapActorsToResult(MetadataResult<Series> result, IEnumerable<Actor> actors)
{
- string validXml;
-
- using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true))
- {
- using (var reader = new StreamReader(fileStream))
- {
- var xml = await reader.ReadToEndAsync().ConfigureAwait(false);
-
- validXml = StripInvalidXmlCharacters(xml);
- }
- }
-
- using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
+ foreach (Actor actor in actors)
{
- using (var writer = new StreamWriter(fileStream))
+ var personInfo = new PersonInfo
{
- await writer.WriteAsync(validXml).ConfigureAwait(false);
- }
- }
- }
-
- /// <summary>
- /// Strips the invalid XML characters.
- /// </summary>
- /// <param name="inString">The in string.</param>
- /// <returns>System.String.</returns>
- public static string StripInvalidXmlCharacters(string inString)
- {
- if (inString == null) return null;
-
- var sbOutput = new StringBuilder();
- char ch;
+ Type = PersonType.Actor,
+ Name = (actor.Name ?? string.Empty).Trim(),
+ Role = actor.Role,
+ ImageUrl = TvdbUtils.BannerUrl + actor.Image,
+ SortOrder = actor.SortOrder
+ };
- for (int i = 0; i < inString.Length; i++)
- {
- ch = inString[i];
- if ((ch >= 0x0020 && ch <= 0xD7FF) ||
- (ch >= 0xE000 && ch <= 0xFFFD) ||
- ch == 0x0009 ||
- ch == 0x000A ||
- ch == 0x000D)
+ if (!string.IsNullOrWhiteSpace(personInfo.Name))
{
- sbOutput.Append(ch);
+ result.AddPerson(personInfo);
}
}
- return sbOutput.ToString();
}
public string Name => "TheTVDB";
@@ -1717,7 +413,8 @@ namespace MediaBrowser.Providers.TV.TheTVDB
return;
}
- var srch = await FindSeries(info.Name, info.Year, info.MetadataLanguage, CancellationToken.None).ConfigureAwait(false);
+ var srch = await FindSeries(info.Name, info.Year, info.MetadataLanguage, CancellationToken.None)
+ .ConfigureAwait(false);
var entry = srch.FirstOrDefault();
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs
new file mode 100644
index 000000000..112cbf800
--- /dev/null
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs
@@ -0,0 +1,36 @@
+using System;
+using System.ComponentModel;
+using MediaBrowser.Model.Entities;
+namespace MediaBrowser.Providers.TV.TheTVDB
+{
+ public static class TvdbUtils
+ {
+ public const string TvdbApiKey = "OG4V3YJ3FAP7FP2K";
+ public const string TvdbBaseUrl = "https://www.thetvdb.com/";
+ public const string BannerUrl = TvdbBaseUrl + "banners/";
+
+ public static ImageType GetImageTypeFromKeyType(string keyType)
+ {
+ switch (keyType.ToLowerInvariant())
+ {
+ case "poster":
+ case "season": return ImageType.Primary;
+ case "series":
+ case "seasonwide": return ImageType.Banner;
+ case "fanart": return ImageType.Backdrop;
+ default: throw new ArgumentException($"Invalid or unknown keytype: {keyType}", nameof(keyType));
+ }
+ }
+
+ public static string NormalizeLanguage(string language)
+ {
+ if (string.IsNullOrWhiteSpace(language))
+ {
+ return null;
+ }
+
+ // pt-br is just pt to tvdb
+ return language.Split('-')[0].ToLowerInvariant();
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs
index 5c246e300..3f889fbbe 100644
--- a/MediaBrowser.Providers/TV/TvExternalIds.cs
+++ b/MediaBrowser.Providers/TV/TvExternalIds.cs
@@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.TV
public string Key => MetadataProviders.Tvdb.ToString();
- public string UrlFormatString => TvdbPrescanTask.TvdbBaseUrl + "?tab=series&id={0}";
+ public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}";
public bool Supports(IHasProviderIds item)
{
@@ -53,7 +53,7 @@ namespace MediaBrowser.Providers.TV
public string Key => MetadataProviders.Tvdb.ToString();
- public string UrlFormatString => TvdbPrescanTask.TvdbBaseUrl + "?tab=episode&id={0}";
+ public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}";
public bool Supports(IHasProviderIds item)
{
diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
index 21ee8f92f..a3e48d30d 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
@@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Parsers
{
- class MovieNfoParser : BaseNfoParser<Video>
+ public class MovieNfoParser : BaseNfoParser<Video>
{
protected override bool SupportsUrlAfterClosingXmlTag => true;
diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs
index ef75f997f..c99d684a1 100644
--- a/RSSDP/ISsdpCommunicationsServer.cs
+++ b/RSSDP/ISsdpCommunicationsServer.cs
@@ -45,8 +45,8 @@ namespace Rssdp.Infrastructure
/// <summary>
/// Sends a message to the SSDP multicast address and port.
/// </summary>
- Task SendMulticastMessage(string message, CancellationToken cancellationToken);
- Task SendMulticastMessage(string message, int sendCount, CancellationToken cancellationToken);
+ Task SendMulticastMessage(string message, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken);
+ Task SendMulticastMessage(string message, int sendCount, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken);
#endregion
@@ -63,4 +63,4 @@ namespace Rssdp.Infrastructure
#endregion
}
-} \ No newline at end of file
+}
diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj
index f06d4687b..456a93aa8 100644
--- a/RSSDP/RSSDP.csproj
+++ b/RSSDP/RSSDP.csproj
@@ -3,6 +3,7 @@
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
+ <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
</ItemGroup>
<PropertyGroup>
diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs
index 04e76ef59..d9a4b6ac0 100644
--- a/RSSDP/SsdpCommunicationsServer.cs
+++ b/RSSDP/SsdpCommunicationsServer.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
using MediaBrowser.Model.Net;
+using MediaBrowser.Controller.Configuration;
namespace Rssdp.Infrastructure
{
@@ -45,6 +46,7 @@ namespace Rssdp.Infrastructure
private readonly ILogger _logger;
private ISocketFactory _SocketFactory;
private readonly INetworkManager _networkManager;
+ private readonly IServerConfigurationManager _config;
private int _LocalPort;
private int _MulticastTtl;
@@ -74,9 +76,11 @@ namespace Rssdp.Infrastructure
/// Minimum constructor.
/// </summary>
/// <exception cref="ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception>
- public SsdpCommunicationsServer(ISocketFactory socketFactory, INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
+ public SsdpCommunicationsServer(IServerConfigurationManager config, ISocketFactory socketFactory,
+ INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
: this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding)
{
+ _config = config;
}
/// <summary>
@@ -236,15 +240,15 @@ namespace Rssdp.Infrastructure
}
}
- public Task SendMulticastMessage(string message, CancellationToken cancellationToken)
+ public Task SendMulticastMessage(string message, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken)
{
- return SendMulticastMessage(message, SsdpConstants.UdpResendCount, cancellationToken);
+ return SendMulticastMessage(message, SsdpConstants.UdpResendCount, fromLocalIpAddress, cancellationToken);
}
/// <summary>
/// Sends a message to the SSDP multicast address and port.
/// </summary>
- public async Task SendMulticastMessage(string message, int sendCount, CancellationToken cancellationToken)
+ public async Task SendMulticastMessage(string message, int sendCount, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken)
{
if (message == null) throw new ArgumentNullException(nameof(message));
@@ -264,7 +268,7 @@ namespace Rssdp.Infrastructure
IpAddress = new IpAddressInfo(SsdpConstants.MulticastLocalAdminAddress, IpAddressFamily.InterNetwork),
Port = SsdpConstants.MulticastPort
- }, cancellationToken).ConfigureAwait(false);
+ }, fromLocalIpAddress, cancellationToken).ConfigureAwait(false);
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
@@ -332,14 +336,15 @@ namespace Rssdp.Infrastructure
#region Private Methods
- private Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination, CancellationToken cancellationToken)
+ private Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken)
{
var sockets = _sendSockets;
if (sockets != null)
{
sockets = sockets.ToList();
- var tasks = sockets.Select(s => SendFromSocket(s, messageData, destination, cancellationToken));
+ var tasks = sockets.Where(s => (fromLocalIpAddress == null || fromLocalIpAddress.Equals(s.LocalIPAddress)))
+ .Select(s => SendFromSocket(s, messageData, destination, cancellationToken));
return Task.WhenAll(tasks);
}
@@ -363,11 +368,11 @@ namespace Rssdp.Infrastructure
if (_enableMultiSocketBinding)
{
- foreach (var address in _networkManager.GetLocalIpAddresses())
+ foreach (var address in _networkManager.GetLocalIpAddresses(_config.Configuration.IgnoreVirtualInterfaces))
{
if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
{
- // Not supported ?
+ // Not support IPv6 right now
continue;
}
diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs
index 128bdfcbb..e17e14c1a 100644
--- a/RSSDP/SsdpDeviceLocator.cs
+++ b/RSSDP/SsdpDeviceLocator.cs
@@ -354,7 +354,7 @@ namespace Rssdp.Infrastructure
var message = BuildMessage(header, values);
- return _CommunicationsServer.SendMulticastMessage(message, cancellationToken);
+ return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken);
}
private void ProcessSearchResponseMessage(HttpResponseMessage message, IpAddressInfo localIpAddress)
diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs
index ce64ba117..921f33c21 100644
--- a/RSSDP/SsdpDevicePublisher.cs
+++ b/RSSDP/SsdpDevicePublisher.cs
@@ -7,6 +7,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
+using MediaBrowser.Common.Net;
using Rssdp;
namespace Rssdp.Infrastructure
@@ -16,10 +17,12 @@ namespace Rssdp.Infrastructure
/// </summary>
public class SsdpDevicePublisher : DisposableManagedObjectBase, ISsdpDevicePublisher
{
+ private readonly INetworkManager _networkManager;
private ISsdpCommunicationsServer _CommsServer;
private string _OSName;
private string _OSVersion;
+ private bool _sendOnlyMatchedHost;
private bool _SupportPnpRootDevice;
@@ -37,9 +40,11 @@ namespace Rssdp.Infrastructure
/// <summary>
/// Default constructor.
/// </summary>
- public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, string osName, string osVersion)
+ public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, INetworkManager networkManager,
+ string osName, string osVersion, bool sendOnlyMatchedHost)
{
if (communicationsServer == null) throw new ArgumentNullException(nameof(communicationsServer));
+ if (networkManager == null) throw new ArgumentNullException(nameof(networkManager));
if (osName == null) throw new ArgumentNullException(nameof(osName));
if (osName.Length == 0) throw new ArgumentException("osName cannot be an empty string.", nameof(osName));
if (osVersion == null) throw new ArgumentNullException(nameof(osVersion));
@@ -51,10 +56,12 @@ namespace Rssdp.Infrastructure
_RecentSearchRequests = new Dictionary<string, SearchRequest>(StringComparer.OrdinalIgnoreCase);
_Random = new Random();
+ _networkManager = networkManager;
_CommsServer = communicationsServer;
_CommsServer.RequestReceived += CommsServer_RequestReceived;
_OSName = osName;
_OSVersion = osVersion;
+ _sendOnlyMatchedHost = sendOnlyMatchedHost;
_CommsServer.BeginListeningForBroadcasts();
}
@@ -250,7 +257,11 @@ namespace Rssdp.Infrastructure
foreach (var device in deviceList)
{
- SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken);
+ if (!_sendOnlyMatchedHost ||
+ _networkManager.IsInSameSubnet(device.ToRootDevice().Address, remoteEndPoint.IpAddress, device.ToRootDevice().SubnetMask))
+ {
+ SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken);
+ }
}
}
else
@@ -427,7 +438,7 @@ namespace Rssdp.Infrastructure
var message = BuildMessage(header, values);
- _CommsServer.SendMulticastMessage(message, cancellationToken);
+ _CommsServer.SendMulticastMessage(message, _sendOnlyMatchedHost ? rootDevice.Address : null, cancellationToken);
//WriteTrace(String.Format("Sent alive notification"), device);
}
@@ -472,7 +483,7 @@ namespace Rssdp.Infrastructure
var sendCount = IsDisposed ? 1 : 3;
WriteTrace(String.Format("Sent byebye notification"), device);
- return _CommsServer.SendMulticastMessage(message, sendCount, cancellationToken);
+ return _CommsServer.SendMulticastMessage(message, sendCount, _sendOnlyMatchedHost ? device.ToRootDevice().Address : null, cancellationToken);
}
private void DisposeRebroadcastTimer()
diff --git a/RSSDP/SsdpRootDevice.cs b/RSSDP/SsdpRootDevice.cs
index a2b0f60f5..d918b9040 100644
--- a/RSSDP/SsdpRootDevice.cs
+++ b/RSSDP/SsdpRootDevice.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Text;
using System.Xml;
using Rssdp.Infrastructure;
+using MediaBrowser.Model.Net;
namespace Rssdp
{
@@ -52,6 +53,15 @@ namespace Rssdp
/// </summary>
public Uri Location { get; set; }
+ /// <summary>
+ /// Gets or sets the Address used to check if the received message from same interface with this device/tree. Required.
+ /// </summary>
+ public IpAddressInfo Address { get; set; }
+
+ /// <summary>
+ /// Gets or sets the SubnetMask used to check if the received message from same interface with this device/tree. Required.
+ /// </summary>
+ public IpAddressInfo SubnetMask { get; set; }
/// <summary>
/// The base URL to use for all relative url's provided in other propertise (and those of child devices). Optional.
diff --git a/SocketHttpListener/Ext.cs b/SocketHttpListener/Ext.cs
index a02b48061..2b3c67071 100644
--- a/SocketHttpListener/Ext.cs
+++ b/SocketHttpListener/Ext.cs
@@ -74,18 +74,20 @@ namespace SocketHttpListener
}
}
- private static byte[] readBytes(this Stream stream, byte[] buffer, int offset, int length)
+ private static async Task<byte[]> ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length)
{
- var len = stream.Read(buffer, offset, length);
+ var len = await stream.ReadAsync(buffer, offset, length).ConfigureAwait(false);
if (len < 1)
return buffer.SubArray(0, offset);
var tmp = 0;
while (len < length)
{
- tmp = stream.Read(buffer, offset + len, length - len);
+ tmp = await stream.ReadAsync(buffer, offset + len, length - len).ConfigureAwait(false);
if (tmp < 1)
+ {
break;
+ }
len += tmp;
}
@@ -95,10 +97,9 @@ namespace SocketHttpListener
: buffer;
}
- private static bool readBytes(
- this Stream stream, byte[] buffer, int offset, int length, Stream dest)
+ private static async Task<bool> ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length, Stream dest)
{
- var bytes = stream.readBytes(buffer, offset, length);
+ var bytes = await stream.ReadBytesAsync(buffer, offset, length).ConfigureAwait(false);
var len = bytes.Length;
dest.Write(bytes, 0, len);
@@ -109,16 +110,16 @@ namespace SocketHttpListener
#region Internal Methods
- internal static byte[] Append(this ushort code, string reason)
+ internal static async Task<byte[]> AppendAsync(this ushort code, string reason)
{
using (var buffer = new MemoryStream())
{
var tmp = code.ToByteArrayInternally(ByteOrder.Big);
- buffer.Write(tmp, 0, 2);
+ await buffer.WriteAsync(tmp, 0, 2).ConfigureAwait(false);
if (reason != null && reason.Length > 0)
{
tmp = Encoding.UTF8.GetBytes(reason);
- buffer.Write(tmp, 0, tmp.Length);
+ await buffer.WriteAsync(tmp, 0, tmp.Length).ConfigureAwait(false);
}
return buffer.ToArray();
@@ -331,12 +332,10 @@ namespace SocketHttpListener
: string.Format("\"{0}\"", value.Replace("\"", "\\\""));
}
- internal static byte[] ReadBytes(this Stream stream, int length)
- {
- return stream.readBytes(new byte[length], 0, length);
- }
+ internal static Task<byte[]> ReadBytesAsync(this Stream stream, int length)
+ => stream.ReadBytesAsync(new byte[length], 0, length);
- internal static byte[] ReadBytes(this Stream stream, long length, int bufferLength)
+ internal static async Task<byte[]> ReadBytesAsync(this Stream stream, long length, int bufferLength)
{
using (var result = new MemoryStream())
{
@@ -347,7 +346,7 @@ namespace SocketHttpListener
var end = false;
for (long i = 0; i < count; i++)
{
- if (!stream.readBytes(buffer, 0, bufferLength, result))
+ if (!await stream.ReadBytesAsync(buffer, 0, bufferLength, result).ConfigureAwait(false))
{
end = true;
break;
@@ -355,26 +354,14 @@ namespace SocketHttpListener
}
if (!end && rem > 0)
- stream.readBytes(new byte[rem], 0, rem, result);
+ {
+ await stream.ReadBytesAsync(new byte[rem], 0, rem, result).ConfigureAwait(false);
+ }
return result.ToArray();
}
}
- internal static async Task<byte[]> ReadBytesAsync(this Stream stream, int length)
- {
- var buffer = new byte[length];
-
- var len = await stream.ReadAsync(buffer, 0, length).ConfigureAwait(false);
- var bytes = len < 1
- ? new byte[0]
- : len < length
- ? stream.readBytes(buffer, len, length - len)
- : buffer;
-
- return bytes;
- }
-
internal static string RemovePrefix(this string value, params string[] prefixes)
{
var i = 0;
@@ -493,19 +480,16 @@ namespace SocketHttpListener
return string.Format("{0}; {1}", m, parameters.ToString("; "));
}
- internal static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
- {
- return new List<TSource>(source);
- }
-
internal static ushort ToUInt16(this byte[] src, ByteOrder srcOrder)
{
- return BitConverter.ToUInt16(src.ToHostOrder(srcOrder), 0);
+ src.ToHostOrder(srcOrder);
+ return BitConverter.ToUInt16(src, 0);
}
internal static ulong ToUInt64(this byte[] src, ByteOrder srcOrder)
{
- return BitConverter.ToUInt64(src.ToHostOrder(srcOrder), 0);
+ src.ToHostOrder(srcOrder);
+ return BitConverter.ToUInt64(src, 0);
}
internal static string TrimEndSlash(this string value)
@@ -852,14 +836,17 @@ namespace SocketHttpListener
/// <exception cref="ArgumentNullException">
/// <paramref name="src"/> is <see langword="null"/>.
/// </exception>
- public static byte[] ToHostOrder(this byte[] src, ByteOrder srcOrder)
+ public static void ToHostOrder(this byte[] src, ByteOrder srcOrder)
{
if (src == null)
+ {
throw new ArgumentNullException(nameof(src));
+ }
- return src.Length > 1 && !srcOrder.IsHostOrder()
- ? src.Reverse()
- : src;
+ if (src.Length > 1 && !srcOrder.IsHostOrder())
+ {
+ Array.Reverse(src);
+ }
}
/// <summary>
diff --git a/SocketHttpListener/Net/HttpListener.cs b/SocketHttpListener/Net/HttpListener.cs
index b80180679..f17036a21 100644
--- a/SocketHttpListener/Net/HttpListener.cs
+++ b/SocketHttpListener/Net/HttpListener.cs
@@ -3,7 +3,6 @@ using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography.X509Certificates;
-using MediaBrowser.Common.Net;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
@@ -18,47 +17,55 @@ namespace SocketHttpListener.Net
internal ISocketFactory SocketFactory { get; private set; }
internal IFileSystem FileSystem { get; private set; }
internal IStreamHelper StreamHelper { get; private set; }
- internal INetworkManager NetworkManager { get; private set; }
internal IEnvironmentInfo EnvironmentInfo { get; private set; }
public bool EnableDualMode { get; set; }
- AuthenticationSchemes auth_schemes;
- HttpListenerPrefixCollection prefixes;
- AuthenticationSchemeSelector auth_selector;
- string realm;
- bool unsafe_ntlm_auth;
- bool listening;
- bool disposed;
+ private AuthenticationSchemes auth_schemes;
+ private HttpListenerPrefixCollection prefixes;
+ private AuthenticationSchemeSelector auth_selector;
+ private string realm;
+ private bool unsafe_ntlm_auth;
+ private bool listening;
+ private bool disposed;
- Dictionary<HttpListenerContext, HttpListenerContext> registry; // Dictionary<HttpListenerContext,HttpListenerContext>
- Dictionary<HttpConnection, HttpConnection> connections;
+ private Dictionary<HttpListenerContext, HttpListenerContext> registry;
+ private Dictionary<HttpConnection, HttpConnection> connections;
private ILogger _logger;
private X509Certificate _certificate;
public Action<HttpListenerContext> OnContext { get; set; }
- public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory,
- INetworkManager networkManager, IStreamHelper streamHelper, IFileSystem fileSystem,
+ public HttpListener(
+ ILogger logger,
+ ICryptoProvider cryptoProvider,
+ ISocketFactory socketFactory,
+ IStreamHelper streamHelper,
+ IFileSystem fileSystem,
IEnvironmentInfo environmentInfo)
{
_logger = logger;
CryptoProvider = cryptoProvider;
SocketFactory = socketFactory;
- NetworkManager = networkManager;
StreamHelper = streamHelper;
FileSystem = fileSystem;
EnvironmentInfo = environmentInfo;
+
prefixes = new HttpListenerPrefixCollection(logger, this);
registry = new Dictionary<HttpListenerContext, HttpListenerContext>();
connections = new Dictionary<HttpConnection, HttpConnection>();
auth_schemes = AuthenticationSchemes.Anonymous;
}
- public HttpListener(ILogger logger, X509Certificate certificate, ICryptoProvider cryptoProvider,
- ISocketFactory socketFactory, INetworkManager networkManager, IStreamHelper streamHelper,
- IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
- : this(logger, cryptoProvider, socketFactory, networkManager, streamHelper, fileSystem, environmentInfo)
+ public HttpListener(
+ ILogger logger,
+ X509Certificate certificate,
+ ICryptoProvider cryptoProvider,
+ ISocketFactory socketFactory,
+ IStreamHelper streamHelper,
+ IFileSystem fileSystem,
+ IEnvironmentInfo environmentInfo)
+ : this(logger, cryptoProvider, socketFactory, streamHelper, fileSystem, environmentInfo)
{
_certificate = certificate;
}
diff --git a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs b/SocketHttpListener/Net/HttpListenerPrefixCollection.cs
index 97dc6797c..400a1adb6 100644
--- a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs
+++ b/SocketHttpListener/Net/HttpListenerPrefixCollection.cs
@@ -7,18 +7,18 @@ namespace SocketHttpListener.Net
{
public class HttpListenerPrefixCollection : ICollection<string>, IEnumerable<string>, IEnumerable
{
- List<string> prefixes = new List<string>();
- HttpListener listener;
+ private List<string> _prefixes = new List<string>();
+ private HttpListener _listener;
private ILogger _logger;
internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener)
{
_logger = logger;
- this.listener = listener;
+ _listener = listener;
}
- public int Count => prefixes.Count;
+ public int Count => _prefixes.Count;
public bool IsReadOnly => false;
@@ -26,61 +26,90 @@ namespace SocketHttpListener.Net
public void Add(string uriPrefix)
{
- listener.CheckDisposed();
+ _listener.CheckDisposed();
//ListenerPrefix.CheckUri(uriPrefix);
- if (prefixes.Contains(uriPrefix))
+ if (_prefixes.Contains(uriPrefix))
+ {
return;
+ }
- prefixes.Add(uriPrefix);
- if (listener.IsListening)
- HttpEndPointManager.AddPrefix(_logger, uriPrefix, listener);
+ _prefixes.Add(uriPrefix);
+ if (_listener.IsListening)
+ {
+ HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener);
+ }
+ }
+
+ public void AddRange(IEnumerable<string> uriPrefixes)
+ {
+ _listener.CheckDisposed();
+
+ foreach (var uriPrefix in uriPrefixes)
+ {
+ if (_prefixes.Contains(uriPrefix))
+ {
+ continue;
+ }
+
+ _prefixes.Add(uriPrefix);
+ if (_listener.IsListening)
+ {
+ HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener);
+ }
+ }
}
public void Clear()
{
- listener.CheckDisposed();
- prefixes.Clear();
- if (listener.IsListening)
- HttpEndPointManager.RemoveListener(_logger, listener);
+ _listener.CheckDisposed();
+ _prefixes.Clear();
+ if (_listener.IsListening)
+ {
+ HttpEndPointManager.RemoveListener(_logger, _listener);
+ }
}
public bool Contains(string uriPrefix)
{
- listener.CheckDisposed();
- return prefixes.Contains(uriPrefix);
+ _listener.CheckDisposed();
+ return _prefixes.Contains(uriPrefix);
}
public void CopyTo(string[] array, int offset)
{
- listener.CheckDisposed();
- prefixes.CopyTo(array, offset);
+ _listener.CheckDisposed();
+ _prefixes.CopyTo(array, offset);
}
public void CopyTo(Array array, int offset)
{
- listener.CheckDisposed();
- ((ICollection)prefixes).CopyTo(array, offset);
+ _listener.CheckDisposed();
+ ((ICollection)_prefixes).CopyTo(array, offset);
}
public IEnumerator<string> GetEnumerator()
{
- return prefixes.GetEnumerator();
+ return _prefixes.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
- return prefixes.GetEnumerator();
+ return _prefixes.GetEnumerator();
}
public bool Remove(string uriPrefix)
{
- listener.CheckDisposed();
+ _listener.CheckDisposed();
if (uriPrefix == null)
+ {
throw new ArgumentNullException(nameof(uriPrefix));
+ }
- bool result = prefixes.Remove(uriPrefix);
- if (result && listener.IsListening)
- HttpEndPointManager.RemovePrefix(_logger, uriPrefix, listener);
+ bool result = _prefixes.Remove(uriPrefix);
+ if (result && _listener.IsListening)
+ {
+ HttpEndPointManager.RemovePrefix(_logger, uriPrefix, _listener);
+ }
return result;
}
diff --git a/SocketHttpListener/WebSocket.cs b/SocketHttpListener/WebSocket.cs
index 128bc8b97..0dcb6a64b 100644
--- a/SocketHttpListener/WebSocket.cs
+++ b/SocketHttpListener/WebSocket.cs
@@ -30,9 +30,9 @@ namespace SocketHttpListener
private CookieCollection _cookies;
private AutoResetEvent _exitReceiving;
private object _forConn;
- private object _forEvent;
+ private readonly SemaphoreSlim _forEvent = new SemaphoreSlim(1, 1);
private object _forMessageEventQueue;
- private object _forSend;
+ private readonly SemaphoreSlim _forSend = new SemaphoreSlim(1, 1);
private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private Queue<MessageEventArgs> _messageEventQueue;
private string _protocol;
@@ -109,12 +109,15 @@ namespace SocketHttpListener
#region Private Methods
- private void close(CloseStatusCode code, string reason, bool wait)
+ private async Task CloseAsync(CloseStatusCode code, string reason, bool wait)
{
- close(new PayloadData(((ushort)code).Append(reason)), !code.IsReserved(), wait);
+ await CloseAsync(new PayloadData(
+ await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)),
+ !code.IsReserved(),
+ wait).ConfigureAwait(false);
}
- private void close(PayloadData payload, bool send, bool wait)
+ private async Task CloseAsync(PayloadData payload, bool send, bool wait)
{
lock (_forConn)
{
@@ -126,11 +129,12 @@ namespace SocketHttpListener
_readyState = WebSocketState.CloseSent;
}
- var e = new CloseEventArgs(payload);
- e.WasClean =
- closeHandshake(
+ var e = new CloseEventArgs(payload)
+ {
+ WasClean = await CloseHandshakeAsync(
send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null,
- wait ? 1000 : 0);
+ wait ? 1000 : 0).ConfigureAwait(false)
+ };
_readyState = WebSocketState.Closed;
try
@@ -143,9 +147,9 @@ namespace SocketHttpListener
}
}
- private bool closeHandshake(byte[] frameAsBytes, int millisecondsTimeout)
+ private async Task<bool> CloseHandshakeAsync(byte[] frameAsBytes, int millisecondsTimeout)
{
- var sent = frameAsBytes != null && writeBytes(frameAsBytes);
+ var sent = frameAsBytes != null && await WriteBytesAsync(frameAsBytes).ConfigureAwait(false);
var received =
millisecondsTimeout == 0 ||
(sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout));
@@ -189,11 +193,11 @@ namespace SocketHttpListener
_context = null;
}
- private bool concatenateFragmentsInto(Stream dest)
+ private async Task<bool> ConcatenateFragmentsIntoAsync(Stream dest)
{
while (true)
{
- var frame = WebSocketFrame.Read(_stream, true);
+ var frame = await WebSocketFrame.ReadAsync(_stream, true).ConfigureAwait(false);
if (frame.IsFinal)
{
/* FINAL */
@@ -221,7 +225,7 @@ namespace SocketHttpListener
// CLOSE
if (frame.IsClose)
- return processCloseFrame(frame);
+ return await ProcessCloseFrameAsync(frame).ConfigureAwait(false);
}
else
{
@@ -236,10 +240,10 @@ namespace SocketHttpListener
}
// ?
- return processUnsupportedFrame(
+ return await ProcessUnsupportedFrameAsync(
frame,
CloseStatusCode.IncorrectData,
- "An incorrect data has been received while receiving fragmented data.");
+ "An incorrect data has been received while receiving fragmented data.").ConfigureAwait(false);
}
return true;
@@ -299,44 +303,42 @@ namespace SocketHttpListener
_compression = CompressionMethod.None;
_cookies = new CookieCollection();
_forConn = new object();
- _forEvent = new object();
- _forSend = new object();
_messageEventQueue = new Queue<MessageEventArgs>();
_forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;
_readyState = WebSocketState.Connecting;
}
- private void open()
+ private async Task OpenAsync()
{
try
{
startReceiving();
- lock (_forEvent)
- {
- try
- {
- if (OnOpen != null)
- {
- OnOpen(this, EventArgs.Empty);
- }
- }
- catch (Exception ex)
- {
- processException(ex, "An exception has occurred while OnOpen.");
- }
- }
}
catch (Exception ex)
{
- processException(ex, "An exception has occurred while opening.");
+ await ProcessExceptionAsync(ex, "An exception has occurred while opening.").ConfigureAwait(false);
+ }
+
+ await _forEvent.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ OnOpen?.Invoke(this, EventArgs.Empty);
+ }
+ catch (Exception ex)
+ {
+ await ProcessExceptionAsync(ex, "An exception has occurred while OnOpen.").ConfigureAwait(false);
+ }
+ finally
+ {
+ _forEvent.Release();
}
}
- private bool processCloseFrame(WebSocketFrame frame)
+ private async Task<bool> ProcessCloseFrameAsync(WebSocketFrame frame)
{
var payload = frame.PayloadData;
- close(payload, !payload.ContainsReservedCloseStatusCode, false);
+ await CloseAsync(payload, !payload.ContainsReservedCloseStatusCode, false).ConfigureAwait(false);
return false;
}
@@ -352,7 +354,7 @@ namespace SocketHttpListener
return true;
}
- private void processException(Exception exception, string message)
+ private async Task ProcessExceptionAsync(Exception exception, string message)
{
var code = CloseStatusCode.Abnormal;
var reason = message;
@@ -365,25 +367,31 @@ namespace SocketHttpListener
error(message ?? code.GetMessage(), exception);
if (_readyState == WebSocketState.Connecting)
- Close(HttpStatusCode.BadRequest);
+ {
+ await CloseAsync(HttpStatusCode.BadRequest).ConfigureAwait(false);
+ }
else
- close(code, reason ?? code.GetMessage(), false);
+ {
+ await CloseAsync(code, reason ?? code.GetMessage(), false).ConfigureAwait(false);
+ }
}
- private bool processFragmentedFrame(WebSocketFrame frame)
+ private Task<bool> ProcessFragmentedFrameAsync(WebSocketFrame frame)
{
return frame.IsContinuation // Not first fragment
- ? true
- : processFragments(frame);
+ ? Task.FromResult(true)
+ : ProcessFragmentsAsync(frame);
}
- private bool processFragments(WebSocketFrame first)
+ private async Task<bool> ProcessFragmentsAsync(WebSocketFrame first)
{
using (var buff = new MemoryStream())
{
buff.WriteBytes(first.PayloadData.ApplicationData);
- if (!concatenateFragmentsInto(buff))
+ if (!await ConcatenateFragmentsIntoAsync(buff).ConfigureAwait(false))
+ {
return false;
+ }
byte[] data;
if (_compression != CompressionMethod.None)
@@ -412,36 +420,38 @@ namespace SocketHttpListener
return true;
}
- private bool processUnsupportedFrame(WebSocketFrame frame, CloseStatusCode code, string reason)
+ private async Task<bool> ProcessUnsupportedFrameAsync(WebSocketFrame frame, CloseStatusCode code, string reason)
{
- processException(new WebSocketException(code, reason), null);
+ await ProcessExceptionAsync(new WebSocketException(code, reason), null).ConfigureAwait(false);
return false;
}
- private bool processWebSocketFrame(WebSocketFrame frame)
+ private Task<bool> ProcessWebSocketFrameAsync(WebSocketFrame frame)
{
+ // TODO: @bond change to if/else chain
return frame.IsCompressed && _compression == CompressionMethod.None
- ? processUnsupportedFrame(
+ ? ProcessUnsupportedFrameAsync(
frame,
CloseStatusCode.IncorrectData,
"A compressed data has been received without available decompression method.")
: frame.IsFragmented
- ? processFragmentedFrame(frame)
+ ? ProcessFragmentedFrameAsync(frame)
: frame.IsData
- ? processDataFrame(frame)
+ ? Task.FromResult(processDataFrame(frame))
: frame.IsPing
- ? processPingFrame(frame)
+ ? Task.FromResult(processPingFrame(frame))
: frame.IsPong
- ? processPongFrame(frame)
+ ? Task.FromResult(processPongFrame(frame))
: frame.IsClose
- ? processCloseFrame(frame)
- : processUnsupportedFrame(frame, CloseStatusCode.PolicyViolation, null);
+ ? ProcessCloseFrameAsync(frame)
+ : ProcessUnsupportedFrameAsync(frame, CloseStatusCode.PolicyViolation, null);
}
- private bool send(Opcode opcode, Stream stream)
+ private async Task<bool> SendAsync(Opcode opcode, Stream stream)
{
- lock (_forSend)
+ await _forSend.WaitAsync().ConfigureAwait(false);
+ try
{
var src = stream;
var compressed = false;
@@ -454,7 +464,7 @@ namespace SocketHttpListener
compressed = true;
}
- sent = send(opcode, Mask.Unmask, stream, compressed);
+ sent = await SendAsync(opcode, Mask.Unmask, stream, compressed).ConfigureAwait(false);
if (!sent)
error("Sending a data has been interrupted.");
}
@@ -472,16 +482,20 @@ namespace SocketHttpListener
return sent;
}
+ finally
+ {
+ _forSend.Release();
+ }
}
- private bool send(Opcode opcode, Mask mask, Stream stream, bool compressed)
+ private async Task<bool> SendAsync(Opcode opcode, Mask mask, Stream stream, bool compressed)
{
var len = stream.Length;
/* Not fragmented */
if (len == 0)
- return send(Fin.Final, opcode, mask, new byte[0], compressed);
+ return await SendAsync(Fin.Final, opcode, mask, new byte[0], compressed).ConfigureAwait(false);
var quo = len / FragmentLength;
var rem = (int)(len % FragmentLength);
@@ -490,26 +504,26 @@ namespace SocketHttpListener
if (quo == 0)
{
buff = new byte[rem];
- return stream.Read(buff, 0, rem) == rem &&
- send(Fin.Final, opcode, mask, buff, compressed);
+ return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem &&
+ await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false);
}
buff = new byte[FragmentLength];
if (quo == 1 && rem == 0)
- return stream.Read(buff, 0, FragmentLength) == FragmentLength &&
- send(Fin.Final, opcode, mask, buff, compressed);
+ return await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) == FragmentLength &&
+ await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false);
/* Send fragmented */
// Begin
- if (stream.Read(buff, 0, FragmentLength) != FragmentLength ||
- !send(Fin.More, opcode, mask, buff, compressed))
+ if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength ||
+ !await SendAsync(Fin.More, opcode, mask, buff, compressed).ConfigureAwait(false))
return false;
var n = rem == 0 ? quo - 2 : quo - 1;
for (long i = 0; i < n; i++)
- if (stream.Read(buff, 0, FragmentLength) != FragmentLength ||
- !send(Fin.More, Opcode.Cont, mask, buff, compressed))
+ if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength ||
+ !await SendAsync(Fin.More, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false))
return false;
// End
@@ -518,98 +532,88 @@ namespace SocketHttpListener
else
buff = new byte[rem];
- return stream.Read(buff, 0, rem) == rem &&
- send(Fin.Final, Opcode.Cont, mask, buff, compressed);
+ return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem &&
+ await SendAsync(Fin.Final, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false);
}
- private bool send(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed)
+ private Task<bool> SendAsync(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed)
{
lock (_forConn)
{
if (_readyState != WebSocketState.Open)
{
- return false;
+ return Task.FromResult(false);
}
- return writeBytes(
+ return WriteBytesAsync(
WebSocketFrame.CreateWebSocketFrame(fin, opcode, mask, data, compressed).ToByteArray());
}
}
- private Task sendAsync(Opcode opcode, Stream stream)
- {
- var completionSource = new TaskCompletionSource<bool>();
- Task.Run(() =>
- {
- try
- {
- send(opcode, stream);
- completionSource.TrySetResult(true);
- }
- catch (Exception ex)
- {
- completionSource.TrySetException(ex);
- }
- });
- return completionSource.Task;
- }
-
// As server
- private bool sendHttpResponse(HttpResponse response)
- {
- return writeBytes(response.ToByteArray());
- }
+ private Task<bool> SendHttpResponseAsync(HttpResponse response)
+ => WriteBytesAsync(response.ToByteArray());
private void startReceiving()
{
if (_messageEventQueue.Count > 0)
+ {
_messageEventQueue.Clear();
+ }
_exitReceiving = new AutoResetEvent(false);
_receivePong = new AutoResetEvent(false);
Action receive = null;
- receive = () => WebSocketFrame.ReadAsync(
- _stream,
- true,
- frame =>
- {
- if (processWebSocketFrame(frame) && _readyState != WebSocketState.Closed)
- {
- receive();
-
- if (!frame.IsData)
- return;
-
- lock (_forEvent)
- {
- try
- {
- var e = dequeueFromMessageEventQueue();
- if (e != null && _readyState == WebSocketState.Open)
- OnMessage.Emit(this, e);
- }
- catch (Exception ex)
- {
- processException(ex, "An exception has occurred while OnMessage.");
- }
- }
- }
- else if (_exitReceiving != null)
- {
- _exitReceiving.Set();
- }
- },
- ex => processException(ex, "An exception has occurred while receiving a message."));
+ receive = async () => await WebSocketFrame.ReadAsync(
+ _stream,
+ true,
+ async frame =>
+ {
+ if (await ProcessWebSocketFrameAsync(frame).ConfigureAwait(false) && _readyState != WebSocketState.Closed)
+ {
+ receive();
+
+ if (!frame.IsData)
+ {
+ return;
+ }
+
+ await _forEvent.WaitAsync().ConfigureAwait(false);
+
+ try
+ {
+ var e = dequeueFromMessageEventQueue();
+ if (e != null && _readyState == WebSocketState.Open)
+ {
+ OnMessage.Emit(this, e);
+ }
+ }
+ catch (Exception ex)
+ {
+ await ProcessExceptionAsync(ex, "An exception has occurred while OnMessage.").ConfigureAwait(false);
+ }
+ finally
+ {
+ _forEvent.Release();
+ }
+
+ }
+ else if (_exitReceiving != null)
+ {
+ _exitReceiving.Set();
+ }
+ },
+ async ex => await ProcessExceptionAsync(ex, "An exception has occurred while receiving a message.")).ConfigureAwait(false);
receive();
}
- private bool writeBytes(byte[] data)
+ private async Task<bool> WriteBytesAsync(byte[] data)
{
try
{
- _stream.Write(data, 0, data.Length);
+ await _stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
return true;
}
catch (Exception)
@@ -623,10 +627,10 @@ namespace SocketHttpListener
#region Internal Methods
// As server
- internal void Close(HttpResponse response)
+ internal async Task CloseAsync(HttpResponse response)
{
_readyState = WebSocketState.CloseSent;
- sendHttpResponse(response);
+ await SendHttpResponseAsync(response).ConfigureAwait(false);
closeServerResources();
@@ -634,22 +638,20 @@ namespace SocketHttpListener
}
// As server
- internal void Close(HttpStatusCode code)
- {
- Close(createHandshakeCloseResponse(code));
- }
+ internal Task CloseAsync(HttpStatusCode code)
+ => CloseAsync(createHandshakeCloseResponse(code));
// As server
- public void ConnectAsServer()
+ public async Task ConnectAsServer()
{
try
{
_readyState = WebSocketState.Open;
- open();
+ await OpenAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
- processException(ex, "An exception has occurred while connecting.");
+ await ProcessExceptionAsync(ex, "An exception has occurred while connecting.").ConfigureAwait(false);
}
}
@@ -660,18 +662,18 @@ namespace SocketHttpListener
/// <summary>
/// Closes the WebSocket connection, and releases all associated resources.
/// </summary>
- public void Close()
+ public Task CloseAsync()
{
var msg = _readyState.CheckIfClosable();
if (msg != null)
{
error(msg);
- return;
+ return Task.CompletedTask;
}
var send = _readyState == WebSocketState.Open;
- close(new PayloadData(), send, send);
+ return CloseAsync(new PayloadData(), send, send);
}
/// <summary>
@@ -689,11 +691,11 @@ namespace SocketHttpListener
/// <param name="reason">
/// A <see cref="string"/> that represents the reason for the close.
/// </param>
- public void Close(CloseStatusCode code, string reason)
+ public async Task CloseAsync(CloseStatusCode code, string reason)
{
byte[] data = null;
var msg = _readyState.CheckIfClosable() ??
- (data = ((ushort)code).Append(reason)).CheckIfValidControlData("reason");
+ (data = await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)).CheckIfValidControlData("reason");
if (msg != null)
{
@@ -703,7 +705,7 @@ namespace SocketHttpListener
}
var send = _readyState == WebSocketState.Open && !code.IsReserved();
- close(new PayloadData(data), send, send);
+ await CloseAsync(new PayloadData(data), send, send).ConfigureAwait(false);
}
/// <summary>
@@ -728,7 +730,7 @@ namespace SocketHttpListener
throw new Exception(msg);
}
- return sendAsync(Opcode.Binary, new MemoryStream(data));
+ return SendAsync(Opcode.Binary, new MemoryStream(data));
}
/// <summary>
@@ -753,7 +755,7 @@ namespace SocketHttpListener
throw new Exception(msg);
}
- return sendAsync(Opcode.Text, new MemoryStream(Encoding.UTF8.GetBytes(data)));
+ return SendAsync(Opcode.Text, new MemoryStream(Encoding.UTF8.GetBytes(data)));
}
#endregion
@@ -768,7 +770,7 @@ namespace SocketHttpListener
/// </remarks>
void IDisposable.Dispose()
{
- Close(CloseStatusCode.Away, null);
+ CloseAsync(CloseStatusCode.Away, null).GetAwaiter().GetResult();
}
#endregion
diff --git a/SocketHttpListener/WebSocketFrame.cs b/SocketHttpListener/WebSocketFrame.cs
index 74ed23c45..8ec64026b 100644
--- a/SocketHttpListener/WebSocketFrame.cs
+++ b/SocketHttpListener/WebSocketFrame.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
+using System.Threading.Tasks;
namespace SocketHttpListener
{
@@ -177,7 +178,7 @@ namespace SocketHttpListener
return opcode == Opcode.Text || opcode == Opcode.Binary;
}
- private static WebSocketFrame read(byte[] header, Stream stream, bool unmask)
+ private static async Task<WebSocketFrame> ReadAsync(byte[] header, Stream stream, bool unmask)
{
/* Header */
@@ -229,7 +230,7 @@ namespace SocketHttpListener
? 2
: 8;
- var extPayloadLen = size > 0 ? stream.ReadBytes(size) : new byte[0];
+ var extPayloadLen = size > 0 ? await stream.ReadBytesAsync(size).ConfigureAwait(false) : Array.Empty<byte>();
if (size > 0 && extPayloadLen.Length != size)
throw new WebSocketException(
"The 'Extended Payload Length' of a frame cannot be read from the data source.");
@@ -239,7 +240,7 @@ namespace SocketHttpListener
/* Masking Key */
var masked = mask == Mask.Mask;
- var maskingKey = masked ? stream.ReadBytes(4) : new byte[0];
+ var maskingKey = masked ? await stream.ReadBytesAsync(4).ConfigureAwait(false) : Array.Empty<byte>();
if (masked && maskingKey.Length != 4)
throw new WebSocketException(
"The 'Masking Key' of a frame cannot be read from the data source.");
@@ -264,8 +265,8 @@ namespace SocketHttpListener
"The length of 'Payload Data' of a frame is greater than the allowable length.");
data = payloadLen > 126
- ? stream.ReadBytes((long)len, 1024)
- : stream.ReadBytes((int)len);
+ ? await stream.ReadBytesAsync((long)len, 1024).ConfigureAwait(false)
+ : await stream.ReadBytesAsync((int)len).ConfigureAwait(false);
//if (data.LongLength != (long)len)
// throw new WebSocketException(
@@ -273,7 +274,7 @@ namespace SocketHttpListener
}
else
{
- data = new byte[0];
+ data = Array.Empty<byte>();
}
var payload = new PayloadData(data, masked);
@@ -281,7 +282,7 @@ namespace SocketHttpListener
{
payload.Mask(maskingKey);
frame._mask = Mask.Unmask;
- frame._maskingKey = new byte[0];
+ frame._maskingKey = Array.Empty<byte>();
}
frame._payloadData = payload;
@@ -302,10 +303,10 @@ namespace SocketHttpListener
return new WebSocketFrame(Opcode.Close, mask, payload);
}
- internal static WebSocketFrame CreateCloseFrame(Mask mask, CloseStatusCode code, string reason)
+ internal static async Task<WebSocketFrame> CreateCloseFrameAsync(Mask mask, CloseStatusCode code, string reason)
{
return new WebSocketFrame(
- Opcode.Close, mask, new PayloadData(((ushort)code).Append(reason)));
+ Opcode.Close, mask, new PayloadData(await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)));
}
internal static WebSocketFrame CreatePingFrame(Mask mask)
@@ -329,41 +330,39 @@ namespace SocketHttpListener
return new WebSocketFrame(fin, opcode, mask, new PayloadData(data), compressed);
}
- internal static WebSocketFrame Read(Stream stream)
- {
- return Read(stream, true);
- }
+ internal static Task<WebSocketFrame> ReadAsync(Stream stream)
+ => ReadAsync(stream, true);
- internal static WebSocketFrame Read(Stream stream, bool unmask)
+ internal static async Task<WebSocketFrame> ReadAsync(Stream stream, bool unmask)
{
- var header = stream.ReadBytes(2);
+ var header = await stream.ReadBytesAsync(2).ConfigureAwait(false);
if (header.Length != 2)
+ {
throw new WebSocketException(
"The header part of a frame cannot be read from the data source.");
+ }
- return read(header, stream, unmask);
+ return await ReadAsync(header, stream, unmask).ConfigureAwait(false);
}
- internal static async void ReadAsync(
+ internal static async Task ReadAsync(
Stream stream, bool unmask, Action<WebSocketFrame> completed, Action<Exception> error)
{
try
{
var header = await stream.ReadBytesAsync(2).ConfigureAwait(false);
if (header.Length != 2)
+ {
throw new WebSocketException(
"The header part of a frame cannot be read from the data source.");
+ }
- var frame = read(header, stream, unmask);
- if (completed != null)
- completed(frame);
+ var frame = await ReadAsync(header, stream, unmask).ConfigureAwait(false);
+ completed?.Invoke(frame);
}
catch (Exception ex)
{
- if (error != null)
- {
- error(ex);
- }
+ error.Invoke(ex);
}
}
diff --git a/deployment/debian-x64/build.sh b/deployment/debian-x64/build.sh
deleted file mode 100755
index 47cfb5327..000000000
--- a/deployment/debian-x64/build.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-source ../common.build.sh
-
-VERSION=`get_version ../..`
-
-build_jellyfin ../../Jellyfin.Server Release debian-x64 `pwd`/dist/jellyfin_${VERSION}
diff --git a/deployment/osx-x64/build.sh b/deployment/macos/build.sh
index d6bfb9f5e..d6bfb9f5e 100755
--- a/deployment/osx-x64/build.sh
+++ b/deployment/macos/build.sh
diff --git a/deployment/debian-x64/clean.sh b/deployment/macos/clean.sh
index 3df2d7796..3df2d7796 100755
--- a/deployment/debian-x64/clean.sh
+++ b/deployment/macos/clean.sh
diff --git a/deployment/debian-x64/dependencies.txt b/deployment/macos/dependencies.txt
index 3d25d1bdf..3d25d1bdf 100644
--- a/deployment/debian-x64/dependencies.txt
+++ b/deployment/macos/dependencies.txt
diff --git a/deployment/debian-x64/package.sh b/deployment/macos/package.sh
index 13b943ea8..13b943ea8 100755
--- a/deployment/debian-x64/package.sh
+++ b/deployment/macos/package.sh
diff --git a/deployment/osx-x64/clean.sh b/deployment/osx-x64/clean.sh
deleted file mode 100755
index 3df2d7796..000000000
--- a/deployment/osx-x64/clean.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-source ../common.build.sh
-
-VERSION=`get_version ../..`
-
-clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION}
diff --git a/deployment/osx-x64/package.sh b/deployment/osx-x64/package.sh
deleted file mode 100755
index 13b943ea8..000000000
--- a/deployment/osx-x64/package.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-source ../common.build.sh
-
-VERSION=`get_version ../..`
-
-package_portable ../.. `pwd`/dist/jellyfin_${VERSION}
diff --git a/deployment/framework/build.sh b/deployment/portable/build.sh
index 4f2e6363e..4f2e6363e 100755
--- a/deployment/framework/build.sh
+++ b/deployment/portable/build.sh
diff --git a/deployment/framework/clean.sh b/deployment/portable/clean.sh
index 3df2d7796..3df2d7796 100755
--- a/deployment/framework/clean.sh
+++ b/deployment/portable/clean.sh
diff --git a/deployment/framework/package.sh b/deployment/portable/package.sh
index 13b943ea8..13b943ea8 100755
--- a/deployment/framework/package.sh
+++ b/deployment/portable/package.sh
diff --git a/deployment/ubuntu-x64/build.sh b/deployment/ubuntu-x64/build.sh
deleted file mode 100755
index 870bac780..000000000
--- a/deployment/ubuntu-x64/build.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-source ../common.build.sh
-
-VERSION=`get_version ../..`
-
-build_jellyfin ../../Jellyfin.Server Release ubuntu-x64 `pwd`/dist/jellyfin_${VERSION}
diff --git a/deployment/ubuntu-x64/clean.sh b/deployment/ubuntu-x64/clean.sh
deleted file mode 100755
index 3df2d7796..000000000
--- a/deployment/ubuntu-x64/clean.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-source ../common.build.sh
-
-VERSION=`get_version ../..`
-
-clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION}
diff --git a/deployment/ubuntu-x64/dependencies.txt b/deployment/ubuntu-x64/dependencies.txt
deleted file mode 100644
index 3d25d1bdf..000000000
--- a/deployment/ubuntu-x64/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-dotnet
diff --git a/deployment/ubuntu-x64/package.sh b/deployment/ubuntu-x64/package.sh
deleted file mode 100755
index 13b943ea8..000000000
--- a/deployment/ubuntu-x64/package.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-source ../common.build.sh
-
-VERSION=`get_version ../..`
-
-package_portable ../.. `pwd`/dist/jellyfin_${VERSION}
diff --git a/deployment/win-generic/dependencies.txt b/deployment/win-generic/dependencies.txt
deleted file mode 100644
index 3d25d1bdf..000000000
--- a/deployment/win-generic/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-dotnet
diff --git a/deployment/win-generic/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1
index 1121c3398..2c83f264c 100644
--- a/deployment/win-generic/build-jellyfin.ps1
+++ b/deployment/windows/build-jellyfin.ps1
@@ -102,8 +102,8 @@ if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){
Write-Verbose "Starting NSSM Install"
Install-NSSM $InstallLocation $Architecture
}
-Copy-Item .\deployment\win-generic\install-jellyfin.ps1 $InstallLocation\install-jellyfin.ps1
-Copy-Item .\deployment\win-generic\install.bat $InstallLocation\install.bat
+Copy-Item .\deployment\windows\install-jellyfin.ps1 $InstallLocation\install-jellyfin.ps1
+Copy-Item .\deployment\windows\install.bat $InstallLocation\install.bat
if($GenerateZip.IsPresent -or ($GenerateZip -eq $true)){
Compress-Archive -Path $InstallLocation -DestinationPath "$InstallLocation/jellyfin.zip" -Force
}
diff --git a/deployment/osx-x64/dependencies.txt b/deployment/windows/dependencies.txt
index 3d25d1bdf..3d25d1bdf 100644
--- a/deployment/osx-x64/dependencies.txt
+++ b/deployment/windows/dependencies.txt
diff --git a/deployment/win-generic/install-jellyfin.ps1 b/deployment/windows/install-jellyfin.ps1
index b6e00e056..b6e00e056 100644
--- a/deployment/win-generic/install-jellyfin.ps1
+++ b/deployment/windows/install-jellyfin.ps1
diff --git a/deployment/win-generic/install.bat b/deployment/windows/install.bat
index e21479a79..e21479a79 100644
--- a/deployment/win-generic/install.bat
+++ b/deployment/windows/install.bat
diff --git a/jellyfin.ruleset b/jellyfin.ruleset
index 0f8c9aa02..4381349ca 100644
--- a/jellyfin.ruleset
+++ b/jellyfin.ruleset
@@ -3,11 +3,19 @@
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<!-- disable warning SA1101: Prefix local calls with 'this.' -->
<Rule Id="SA1101" Action="None" />
+ <!-- disable warning SA1130: Use lambda syntax -->
+ <Rule Id="SA1130" Action="None" />
<!-- disable warning SA1200: 'using' directive must appear within a namespace declaration -->
<Rule Id="SA1200" Action="None" />
<!-- disable warning SA1309: Fields must not begin with an underscore -->
<Rule Id="SA1309" Action="None" />
+ <!-- disable warning SA1512: Single-line comments must not be followed by blank line -->
+ <Rule Id="SA1512" Action="None" />
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
<Rule Id="SA1633" Action="None" />
</Rules>
+ <Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
+ <!-- disable warning CA1054: Change the type of parameter url from string to System.Uri -->
+ <Rule Id="CA1054" Action="None" />
+ </Rules>
</RuleSet>