diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3b2db81 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,200 @@ +# With more recent updates Visual Studio 2017 supports EditorConfig files out of the box +# Visual Studio Code needs an extension: https://github.com/editorconfig/editorconfig-vscode +# For emacs, vim, np++ and other editors, see here: https://github.com/editorconfig +############################### +# Core EditorConfig Options # +############################### +root = true +# All files +[*] +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +end_of_line = lf +max_line_length = off + +# YAML indentation +[*.{yml,yaml}] +indent_size = 2 + +# XML indentation +[*.{csproj,xml}] +indent_size = 2 + +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +# this. preferences +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:suggestion +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent + +############################### +# Naming Conventions # +############################### +# 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 + +############################### +# Code Quality # +############################### +[*.cs] +dotnet_code_quality.CA1826.exclude_ordefault_methods = true + +############################### +# C# Coding Conventions # +############################### +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +############################### +# C# Formatting Rules # +############################### +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true diff --git a/jellyfin.ruleset b/jellyfin.ruleset new file mode 100644 index 0000000..8af791c --- /dev/null +++ b/jellyfin.ruleset @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Jellyfin.Plugin.Dlna.Model/ContentFeatureBuilder.cs b/src/Jellyfin.Plugin.Dlna.Model/ContentFeatureBuilder.cs index d503219..b863011 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/ContentFeatureBuilder.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/ContentFeatureBuilder.cs @@ -1,6 +1,3 @@ -#nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -10,15 +7,27 @@ namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public static class ContentFeatureBuilder { + /// + /// Gets the image header. + /// + /// The . + /// The container. + /// The width. + /// The height. + /// Value indicating wether the stream is direct. + /// The orgPn. public static string BuildImageHeader( DlnaDeviceProfile profile, string container, int? width, int? height, bool isDirectStream, - string orgPn = null) + string? orgPn = null) { string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetImageOrgOpValue(); @@ -36,7 +45,7 @@ public static string BuildImageHeader( if (string.IsNullOrEmpty(orgPn)) { - ResponseProfile mediaProfile = profile.GetImageMediaProfile( + ResponseProfile? mediaProfile = profile.GetImageMediaProfile( container, width, height); @@ -57,10 +66,23 @@ public static string BuildImageHeader( return "DLNA.ORG_PN=" + orgPn + orgOp + orgCi + dlnaflags; } + /// + /// Gets the audio header. + /// + /// The . + /// The container. + /// The codec. + /// The bitrate. + /// The sample rate. + /// The channel count. + /// The bit depth. + /// Value indicating wether the stream is direct. + /// The runtime ticks. + /// The . public static string BuildAudioHeader( DlnaDeviceProfile profile, - string container, - string audioCodec, + string? container, + string? audioCodec, int? audioBitrate, int? audioSampleRate, int? audioChannels, @@ -94,7 +116,7 @@ public static string BuildAudioHeader( ";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue)); - ResponseProfile mediaProfile = profile.GetAudioMediaProfile( + ResponseProfile? mediaProfile = profile.GetAudioMediaProfile( container, audioCodec, audioChannels, @@ -102,7 +124,7 @@ public static string BuildAudioHeader( audioSampleRate, audioBitDepth); - string orgPn = mediaProfile?.OrgPn; + string? orgPn = mediaProfile?.OrgPn; if (string.IsNullOrEmpty(orgPn)) { @@ -117,11 +139,38 @@ public static string BuildAudioHeader( return "DLNA.ORG_PN=" + orgPn + orgOp + orgCi + dlnaflags; } + /// + /// Gets the auvideodio header. + /// + /// The . + /// The container. + /// The video codec. + /// The audio codec. + /// The width. + /// The height. + /// The bit depth. + /// The video bitrate. + /// The . + /// Value indicating wether the stream is direct. + /// The runtime ticks. + /// The video profile. + /// The . + /// The video level. + /// The video framerate. + /// The packet length. + /// The . + /// Value indicating wether the stream is anamorphic. + /// Value indicating wether the stream is interlaced. + /// The reference frames. + /// The number of video streams. + /// The number of audio streams. + /// The video codec tag. + /// Value indicating wether the stream is AVC. public static IEnumerable BuildVideoHeader( DlnaDeviceProfile profile, - string container, - string videoCodec, - string audioCodec, + string? container, + string? videoCodec, + string? audioCodec, int? width, int? height, int? bitDepth, @@ -129,7 +178,7 @@ public static IEnumerable BuildVideoHeader( TransportStreamTimestamp timestamp, bool isDirectStream, long? runtimeTicks, - string videoProfile, + string? videoProfile, VideoRangeType videoRangeType, double? videoLevel, float? videoFramerate, @@ -140,7 +189,7 @@ public static IEnumerable BuildVideoHeader( int? refFrames, int? numVideoStreams, int? numAudioStreams, - string videoCodecTag, + string? videoCodecTag, bool? isAvc) { // first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none @@ -170,7 +219,7 @@ public static IEnumerable BuildVideoHeader( ";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue)); - ResponseProfile mediaProfile = profile.GetVideoMediaProfile( + ResponseProfile? mediaProfile = profile.GetVideoMediaProfile( container, audioCodec, videoCodec, @@ -234,14 +283,14 @@ public static IEnumerable BuildVideoHeader( return contentFeatureList; } - private static string GetImageOrgPnValue(string container, int? width, int? height) + private static string? GetImageOrgPnValue(string container, int? width, int? height) { MediaFormatProfile? format = MediaFormatProfileResolver.ResolveImageFormat(container, width, height); return format.HasValue ? format.Value.ToString() : null; } - private static string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels) + private static string? GetAudioOrgPnValue(string? container, int? audioBitrate, int? audioSampleRate, int? audioChannels) { MediaFormatProfile? format = MediaFormatProfileResolver.ResolveAudioFormat( container, @@ -252,8 +301,8 @@ private static string GetAudioOrgPnValue(string container, int? audioBitrate, in return format.HasValue ? format.Value.ToString() : null; } - private static MediaFormatProfile[] GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp) + private static MediaFormatProfile[] GetVideoOrgPnValue(string? container, string? videoCodec, string? audioCodec, int? width, int? height, TransportStreamTimestamp timestamp) { return MediaFormatProfileResolver.ResolveVideoFormat(container, videoCodec, audioCodec, width, height, timestamp); } -} \ No newline at end of file +} diff --git a/src/Jellyfin.Plugin.Dlna.Model/DeviceIdentification.cs b/src/Jellyfin.Plugin.Dlna.Model/DeviceIdentification.cs index 2e193d5..8773109 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/DeviceIdentification.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/DeviceIdentification.cs @@ -1,7 +1,10 @@ -using System; +#pragma warning disable CA1819 // Properties should not return arrays namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public class DeviceIdentification { /// @@ -56,5 +59,5 @@ public class DeviceIdentification /// Gets or sets the headers. /// /// The headers. - public HttpHeaderInfo[] Headers { get; set; } = Array.Empty(); + public HttpHeaderInfo[] Headers { get; set; } = []; } diff --git a/src/Jellyfin.Plugin.Dlna.Model/DeviceProfileInfo.cs b/src/Jellyfin.Plugin.Dlna.Model/DeviceProfileInfo.cs index 9fe13e1..de07df4 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/DeviceProfileInfo.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/DeviceProfileInfo.cs @@ -2,6 +2,9 @@ namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public class DeviceProfileInfo { /// diff --git a/src/Jellyfin.Plugin.Dlna.Model/DeviceProfileType.cs b/src/Jellyfin.Plugin.Dlna.Model/DeviceProfileType.cs index e5f495d..b754467 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/DeviceProfileType.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/DeviceProfileType.cs @@ -1,7 +1,17 @@ namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public enum DeviceProfileType { + /// + /// System profile. + /// System = 0, + + /// + /// User profile. + /// User = 1 } diff --git a/src/Jellyfin.Plugin.Dlna.Model/DlnaDeviceProfile.cs b/src/Jellyfin.Plugin.Dlna.Model/DlnaDeviceProfile.cs index f096de1..6e9af05 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/DlnaDeviceProfile.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/DlnaDeviceProfile.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1819 // Properties should not return arrays + using System; using System.ComponentModel; using System.Linq; @@ -10,6 +12,9 @@ namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// [XmlRoot("Profile")] public class DlnaDeviceProfile : DeviceProfile { @@ -154,18 +159,18 @@ public class DlnaDeviceProfile : DeviceProfile /// /// Gets or sets the XmlRootAttributes. /// - public XmlAttribute[] XmlRootAttributes { get; set; } = Array.Empty(); + public XmlAttribute[] XmlRootAttributes { get; set; } = []; /// /// Gets or sets the ResponseProfiles. /// - public ResponseProfile[] ResponseProfiles { get; set; } = Array.Empty(); + public ResponseProfile[] ResponseProfiles { get; set; } = []; /// - /// The GetSupportedMediaTypes. + /// The supported media types. /// /// The . - public MediaType[] GetSupportedMediaTypes() + public MediaType[] FetchSupportedMediaTypes() { return ContainerHelper.Split(SupportedMediaTypes) .Select(m => Enum.TryParse(m, out var parsed) ? parsed : MediaType.Unknown) @@ -265,8 +270,13 @@ public MediaType[] GetSupportedMediaTypes() /// The width. /// The height. /// The . - public ResponseProfile? GetImageMediaProfile(string container, int? width, int? height) + public ResponseProfile? GetImageMediaProfile(string? container, int? width, int? height) { + if (container is null) + { + return null; + } + foreach (var i in ResponseProfiles) { if (i.Type != DlnaProfileType.Photo) diff --git a/src/Jellyfin.Plugin.Dlna.Model/DlnaFlags.cs b/src/Jellyfin.Plugin.Dlna.Model/DlnaFlags.cs index 71c360f..68e97f8 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/DlnaFlags.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/DlnaFlags.cs @@ -1,49 +1,90 @@ -#pragma warning disable CS1591 +#pragma warning disable CA1711, CA1028 using System; namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// [Flags] public enum DlnaFlags : ulong { - /*! Background transfer mode. - For use with upload and download transfers to and from the server. - The primary difference between \ref DH_TransferMode_Interactive and - \ref DH_TransferMode_Bulk is that the latter assumes that the user - is not relying on the transfer for immediately rendering the content - and there are no issues with causing a buffer overflow if the - receiver uses TCP flow control to reduce total throughput. - */ + /// + /// Defines the . + /// + /// + /// Background transfer mode. + /// For use with upload and download transfers to and from the server. + /// The primary difference between DH_TransferMode_Interactive and DH_TransferMode_Bulk is that the latter assumes that the user is not relying on the transfer + /// for immediately rendering the content and there are no issues with causing a buffer overflow if the receiver uses TCP flow control to reduce total throughput. + /// BackgroundTransferMode = 1 << 22, + /// + /// Byte based seek. + /// ByteBasedSeek = 1 << 29, + + /// + /// Connection stall. + /// ConnectionStall = 1 << 21, + /// + /// DLNA v1.5. + /// DlnaV15 = 1 << 20, - /*! Interactive transfer mode. - For best effort transfer of images and non-real-time transfers. - URIs with image content usually support \ref DH_TransferMode_Bulk too. - The primary difference between \ref DH_TransferMode_Interactive and - \ref DH_TransferMode_Bulk is that the former assumes that the - transfer is intended for immediate rendering. - */ + /// + /// Interactive transfer mode. + /// + /// + /// Interactive transfer mode. + /// For best effort transfer of images and non-real-time transfers. + /// URIs with image content usually support \ref DH_TransferMode_Bulk too. + /// The primary difference between DH_TransferMode_Interactive and DH_TransferMode_Bulk is that the former assumes that the transfer is intended for immediate rendering. + /// InteractiveTransferMode = 1 << 23, + /// + /// Play container. + /// PlayContainer = 1 << 28, + + /// + /// RTSP pause. + /// RtspPause = 1 << 25, + + /// + /// S0 increase. + /// S0Increase = 1 << 27, + + /// + /// Sender paced. + /// SenderPaced = 1L << 31, + + /// + /// Sn increase. + /// SnIncrease = 1 << 26, - /*! Streaming transfer mode. - The server transmits at a throughput sufficient for real-time playback of - audio or video. URIs with audio or video often support the - \ref DH_TransferMode_Interactive and \ref DH_TransferMode_Bulk transfer modes. - The most well-known exception to this general claim is for live streams. - */ + /// + /// Byte based seek. + /// + /// + /// Streaming transfer mode. + /// The server transmits at a throughput sufficient for real-time playback of audio or video. + /// URIs with audio or video often support the DH_TransferMode_Interactive and DH_TransferMode_Bulk transfer modes. + /// The most well-known exception to this general claim is for live streams. + /// StreamingTransferMode = 1 << 24, + /// + /// Time based seek. + /// TimeBasedSeek = 1 << 30 } diff --git a/src/Jellyfin.Plugin.Dlna.Model/DlnaMaps.cs b/src/Jellyfin.Plugin.Dlna.Model/DlnaMaps.cs index c907d4f..483af19 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/DlnaMaps.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/DlnaMaps.cs @@ -1,17 +1,30 @@ -#pragma warning disable CS1591 - using System.Globalization; using MediaBrowser.Model.Dlna; namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public static class DlnaMaps { + /// + /// Takes DLNA flags and stringifies them. + /// + /// The . + /// The flags string. public static string FlagsToString(DlnaFlags flags) { return string.Format(CultureInfo.InvariantCulture, "{0:X8}{1:D24}", (ulong)flags, 0); } + /// + /// Gets the org operation value. + /// + /// Value indicating whether the stream has a known runtime. + /// Value indicating whether the stream is a direct stream. + /// The . + /// System.String. public static string GetOrgOpValue(bool hasKnownRuntime, bool isDirectStream, TranscodeSeekInfo profileTranscodeSeekInfo) { if (hasKnownRuntime) @@ -31,6 +44,9 @@ public static string GetOrgOpValue(bool hasKnownRuntime, bool isDirectStream, Tr return "00"; } + /// + /// Gets the image org operation value. + /// public static string GetImageOrgOpValue() { string orgOp = string.Empty; diff --git a/src/Jellyfin.Plugin.Dlna.Model/HeaderMatchType.cs b/src/Jellyfin.Plugin.Dlna.Model/HeaderMatchType.cs index 158f3d5..335a9a3 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/HeaderMatchType.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/HeaderMatchType.cs @@ -1,8 +1,22 @@ namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public enum HeaderMatchType { + /// + /// Equals. + /// Equals = 0, + + /// + /// Regex. + /// Regex = 1, + + /// + /// Substring. + /// Substring = 2 } diff --git a/src/Jellyfin.Plugin.Dlna.Model/HttpHeaderInfo.cs b/src/Jellyfin.Plugin.Dlna.Model/HttpHeaderInfo.cs index 910ca03..46ecce1 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/HttpHeaderInfo.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/HttpHeaderInfo.cs @@ -4,14 +4,26 @@ namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public class HttpHeaderInfo { + /// + /// The name. + /// [XmlAttribute("name")] public string Name { get; set; } + /// + /// The value. + /// [XmlAttribute("value")] public string Value { get; set; } + /// + /// The . + /// [XmlAttribute("match")] public HeaderMatchType Match { get; set; } } diff --git a/src/Jellyfin.Plugin.Dlna.Model/IDeviceDiscovery.cs b/src/Jellyfin.Plugin.Dlna.Model/IDeviceDiscovery.cs index 37bb4ad..cbc59f4 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/IDeviceDiscovery.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/IDeviceDiscovery.cs @@ -1,13 +1,21 @@ -#pragma warning disable CS1591 - using System; using Jellyfin.Data.Events; namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the interface. +/// + public interface IDeviceDiscovery { + /// + /// Occurs when [device discovered]. + /// event EventHandler> DeviceDiscovered; + /// + /// Occurs when [device left]. + /// event EventHandler> DeviceLeft; } diff --git a/src/Jellyfin.Plugin.Dlna.Model/IDlnaManager.cs b/src/Jellyfin.Plugin.Dlna.Model/IDlnaManager.cs index ff62401..8159f0e 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/IDlnaManager.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/IDlnaManager.cs @@ -1,13 +1,12 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using System.IO; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Model.Dlna; using Microsoft.AspNetCore.Http; namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the interface. +/// public interface IDlnaManager { /// diff --git a/src/Jellyfin.Plugin.Dlna.Model/Jellyfin.Plugin.Dlna.Model.csproj b/src/Jellyfin.Plugin.Dlna.Model/Jellyfin.Plugin.Dlna.Model.csproj index 7e2be85..f9f4ce9 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/Jellyfin.Plugin.Dlna.Model.csproj +++ b/src/Jellyfin.Plugin.Dlna.Model/Jellyfin.Plugin.Dlna.Model.csproj @@ -1,7 +1,11 @@ net8.0 + true + true enable + AllEnabledByDefault + ../../jellyfin.ruleset diff --git a/src/Jellyfin.Plugin.Dlna.Model/MediaFormatProfile.cs b/src/Jellyfin.Plugin.Dlna.Model/MediaFormatProfile.cs index 15a12f7..7ab5a42 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/MediaFormatProfile.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/MediaFormatProfile.cs @@ -1,113 +1,529 @@ -#pragma warning disable CS1591, CA1707 +#pragma warning disable CA1707 namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public enum MediaFormatProfile { + /// + /// MP3. + /// MP3, + + /// + /// WMA_BASE. + /// WMA_BASE, + + /// + /// WMA_FULL. + /// WMA_FULL, + + /// + /// LPCM16_44_MONO. + /// LPCM16_44_MONO, + + /// + /// LPCM16_44_STEREO. + /// LPCM16_44_STEREO, + + /// + /// LPCM16_48_MONO. + /// LPCM16_48_MONO, + + /// + /// LPCM16_48_STEREO. + /// LPCM16_48_STEREO, + + /// + /// AAC_ISO. + /// AAC_ISO, + + /// + /// AAC_ISO_320. + /// AAC_ISO_320, + + /// + /// AAC_ADTS. + /// AAC_ADTS, + + /// + /// AAC_ADTS_320. + /// AAC_ADTS_320, + + /// + /// FLAC. + /// FLAC, + + /// + /// OGG. + /// OGG, + /// + /// JPEG_SM. + /// JPEG_SM, + + /// + /// JPEG_MED. + /// JPEG_MED, + + /// + /// JPEG_LRG. + /// JPEG_LRG, + + /// + /// JPEG_TN. + /// JPEG_TN, + + /// + /// PNG_LRG. + /// PNG_LRG, + + /// + /// PNG_TN. + /// PNG_TN, + + /// + /// GIF_LRG. + /// GIF_LRG, + + /// + /// RAW. + /// RAW, + /// + /// MPEG1. + /// MPEG1, + + /// + /// MPEG_PS_PAL. + /// MPEG_PS_PAL, + + /// + /// MPEG_PS_NTSC. + /// MPEG_PS_NTSC, + + /// + /// MPEG_TS_SD_EU. + /// MPEG_TS_SD_EU, + + /// + /// MPEG_TS_SD_EU_ISO. + /// MPEG_TS_SD_EU_ISO, + + /// + /// MPEG_TS_SD_EU_T. + /// MPEG_TS_SD_EU_T, + + /// + /// MPEG_TS_SD_NA. + /// MPEG_TS_SD_NA, + + /// + /// MPEG_TS_SD_NA_ISO. + /// MPEG_TS_SD_NA_ISO, + + /// + /// MPEG_TS_SD_NA_T. + /// MPEG_TS_SD_NA_T, + + /// + /// MPEG_TS_SD_KO. + /// MPEG_TS_SD_KO, + + /// + /// MPEG_TS_SD_KO_ISO. + /// MPEG_TS_SD_KO_ISO, + + /// + /// MPEG_TS_SD_KO_T. + /// MPEG_TS_SD_KO_T, + + /// + /// MPEG_TS_JP_T. + /// MPEG_TS_JP_T, + + /// + /// AVI. + /// AVI, + + /// + /// MATROSKA. + /// MATROSKA, + + /// + /// FLV. + /// FLV, + + /// + /// DVR_MS. + /// DVR_MS, + + /// + /// WTV. + /// WTV, + + /// + /// OGV. + /// OGV, + + /// + /// AVC_MP4_MP_SD_AAC_MULT5. + /// AVC_MP4_MP_SD_AAC_MULT5, + + /// + /// AVC_MP4_MP_SD_MPEG1_L3. + /// AVC_MP4_MP_SD_MPEG1_L3, + + /// + /// AVC_MP4_MP_SD_AC3. + /// AVC_MP4_MP_SD_AC3, + + /// + /// AVC_MP4_MP_HD_720p_AAC. + /// AVC_MP4_MP_HD_720p_AAC, + + /// + /// AVC_MP4_MP_HD_1080i_AAC. + /// AVC_MP4_MP_HD_1080i_AAC, + + /// + /// AVC_MP4_HP_HD_AAC. + /// AVC_MP4_HP_HD_AAC, + + /// + /// AVC_TS_MP_HD_AAC_MULT5. + /// AVC_TS_MP_HD_AAC_MULT5, + + /// + /// AVC_TS_MP_HD_AAC_MULT5_T. + /// AVC_TS_MP_HD_AAC_MULT5_T, + + /// + /// AVC_TS_MP_HD_AAC_MULT5_ISO. + /// AVC_TS_MP_HD_AAC_MULT5_ISO, + + /// + /// AVC_TS_MP_HD_MPEG1_L3. + /// AVC_TS_MP_HD_MPEG1_L3, + + /// + /// AVC_TS_MP_HD_MPEG1_L3_T. + /// AVC_TS_MP_HD_MPEG1_L3_T, + + /// + /// AVC_TS_MP_HD_MPEG1_L3_ISO. + /// AVC_TS_MP_HD_MPEG1_L3_ISO, + + /// + /// AVC_TS_MP_HD_AC3. + /// AVC_TS_MP_HD_AC3, + + /// + /// AVC_TS_MP_HD_AC3_T. + /// AVC_TS_MP_HD_AC3_T, + + /// + /// AVC_TS_MP_HD_AC3_ISO. + /// AVC_TS_MP_HD_AC3_ISO, + + /// + /// AVC_TS_HP_HD_MPEG1_L2_T. + /// AVC_TS_HP_HD_MPEG1_L2_T, + + /// + /// AVC_TS_HP_HD_MPEG1_L2_ISO. + /// AVC_TS_HP_HD_MPEG1_L2_ISO, + + /// + /// AVC_TS_MP_SD_AAC_MULT5. + /// AVC_TS_MP_SD_AAC_MULT5, + + /// + /// AVC_TS_MP_SD_AAC_MULT5_T. + /// AVC_TS_MP_SD_AAC_MULT5_T, + + /// + /// AVC_TS_MP_SD_AAC_MULT5_ISO. + /// AVC_TS_MP_SD_AAC_MULT5_ISO, + + /// + /// AVC_TS_MP_SD_MPEG1_L3. + /// AVC_TS_MP_SD_MPEG1_L3, + + /// + /// AVC_TS_MP_SD_MPEG1_L3_T. + /// AVC_TS_MP_SD_MPEG1_L3_T, + + /// + /// AVC_TS_MP_SD_MPEG1_L3_ISO. + /// AVC_TS_MP_SD_MPEG1_L3_ISO, + + /// + /// AVC_TS_HP_SD_MPEG1_L2_T. + /// AVC_TS_HP_SD_MPEG1_L2_T, + + /// + /// AVC_TS_HP_SD_MPEG1_L2_ISO. + /// AVC_TS_HP_SD_MPEG1_L2_ISO, + + /// + /// AVC_TS_MP_SD_AC3. + /// AVC_TS_MP_SD_AC3, + + /// + /// AVC_TS_MP_SD_AC3_T. + /// AVC_TS_MP_SD_AC3_T, + + /// + /// AVC_TS_MP_SD_AC3_ISO. + /// AVC_TS_MP_SD_AC3_ISO, + + /// + /// AVC_TS_HD_DTS_T. + /// AVC_TS_HD_DTS_T, + + /// + /// AVC_TS_HD_DTS_ISO. + /// AVC_TS_HD_DTS_ISO, + + /// + /// WMVMED_BASE. + /// WMVMED_BASE, + + /// + /// WMVMED_FULL. + /// WMVMED_FULL, + + /// + /// WMVMED_PRO. + /// WMVMED_PRO, + + /// + /// WMVHIGH_FULL. + /// WMVHIGH_FULL, + + /// + /// WMVHIGH_PRO. + /// WMVHIGH_PRO, + + /// + /// VC1_ASF_AP_L1_WMA. + /// VC1_ASF_AP_L1_WMA, + + /// + /// VC1_ASF_AP_L2_WMA. + /// VC1_ASF_AP_L2_WMA, + + /// + /// VC1_ASF_AP_L3_WMA. + /// VC1_ASF_AP_L3_WMA, + + /// + /// VC1_TS_AP_L1_AC3_ISO. + /// VC1_TS_AP_L1_AC3_ISO, + + /// + /// VC1_TS_AP_L2_AC3_ISO. + /// VC1_TS_AP_L2_AC3_ISO, + + /// + /// VC1_TS_HD_DTS_ISO. + /// VC1_TS_HD_DTS_ISO, + + /// + /// VC1_TS_HD_DTS_T. + /// VC1_TS_HD_DTS_T, + + /// + /// MPEG4_P2_MP4_ASP_AAC. + /// MPEG4_P2_MP4_ASP_AAC, + + /// + /// MPEG4_P2_MP4_SP_L6_AAC. + /// MPEG4_P2_MP4_SP_L6_AAC, + + /// + /// MPEG4_P2_MP4_NDSD. + /// MPEG4_P2_MP4_NDSD, + + /// + /// MPEG4_P2_TS_ASP_AAC. + /// MPEG4_P2_TS_ASP_AAC, + + /// + /// MPEG4_P2_TS_ASP_AAC_T. + /// MPEG4_P2_TS_ASP_AAC_T, + + /// + /// MPEG4_P2_TS_ASP_AAC_ISO. + /// MPEG4_P2_TS_ASP_AAC_ISO, + + /// + /// MPEG4_P2_TS_ASP_MPEG1_L3. + /// MPEG4_P2_TS_ASP_MPEG1_L3, + + /// + /// MPEG4_P2_TS_ASP_MPEG1_L3_T. + /// MPEG4_P2_TS_ASP_MPEG1_L3_T, + + /// + /// MPEG4_P2_TS_ASP_MPEG1_L3_ISO. + /// MPEG4_P2_TS_ASP_MPEG1_L3_ISO, + + /// + /// MPEG4_P2_TS_ASP_MPEG2_L2. + /// MPEG4_P2_TS_ASP_MPEG2_L2, + + /// + /// MPEG4_P2_TS_ASP_MPEG2_L2_T. + /// MPEG4_P2_TS_ASP_MPEG2_L2_T, + + /// + /// MPEG4_P2_TS_ASP_MPEG2_L2_ISO. + /// MPEG4_P2_TS_ASP_MPEG2_L2_ISO, + + /// + /// MPEG4_P2_TS_ASP_AC3. + /// MPEG4_P2_TS_ASP_AC3, + + /// + /// MPEG4_P2_TS_ASP_AC3_T. + /// MPEG4_P2_TS_ASP_AC3_T, + + /// + /// MPEG4_P2_TS_ASP_AC3_ISO. + /// MPEG4_P2_TS_ASP_AC3_ISO, + + /// + /// AVC_TS_HD_50_LPCM_T. + /// AVC_TS_HD_50_LPCM_T, + + /// + /// AVC_MP4_LPCM. + /// AVC_MP4_LPCM, + + /// + /// MPEG4_P2_3GPP_SP_L0B_AAC. + /// MPEG4_P2_3GPP_SP_L0B_AAC, + + /// + /// MPEG4_P2_3GPP_SP_L0B_AMR. + /// MPEG4_P2_3GPP_SP_L0B_AMR, + + /// + /// AVC_3GPP_BL_QCIF15_AAC. + /// AVC_3GPP_BL_QCIF15_AAC, + + /// + /// MPEG4_H263_3GPP_P0_L10_AMR. + /// MPEG4_H263_3GPP_P0_L10_AMR, + + /// + /// MPEG4_H263_MP4_P0_L10_AAC. + /// MPEG4_H263_MP4_P0_L10_AAC } diff --git a/src/Jellyfin.Plugin.Dlna.Model/MediaFormatProfileResolver.cs b/src/Jellyfin.Plugin.Dlna.Model/MediaFormatProfileResolver.cs index 5bad5f2..8f7b3ba 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/MediaFormatProfileResolver.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/MediaFormatProfileResolver.cs @@ -1,6 +1,3 @@ -#nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -8,41 +5,54 @@ namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public static class MediaFormatProfileResolver { - public static MediaFormatProfile[] ResolveVideoFormat(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) + /// + /// Gets the video format profiles. + /// + /// The container. + /// The video codec. + /// The audio codec. + /// The width. + /// The height. + /// The . + /// DeviceProfile[]. + public static MediaFormatProfile[] ResolveVideoFormat(string? container, string? videoCodec, string? audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) { if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase)) { MediaFormatProfile? val = ResolveVideoASFFormat(videoCodec, audioCodec, width, height); - return val.HasValue ? new MediaFormatProfile[] { val.Value } : Array.Empty(); + return val.HasValue ? [val.Value] : []; } if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase)) { MediaFormatProfile? val = ResolveVideoMP4Format(videoCodec, audioCodec, width, height); - return val.HasValue ? new MediaFormatProfile[] { val.Value } : Array.Empty(); + return val.HasValue ? [val.Value] : []; } if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { MediaFormatProfile.AVI }; + return [MediaFormatProfile.AVI]; } if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { MediaFormatProfile.MATROSKA }; + return [MediaFormatProfile.MATROSKA]; } if (string.Equals(container, "mpeg2ps", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { MediaFormatProfile.MPEG_PS_NTSC, MediaFormatProfile.MPEG_PS_PAL }; + return [MediaFormatProfile.MPEG_PS_NTSC, MediaFormatProfile.MPEG_PS_PAL]; } if (string.Equals(container, "mpeg1video", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { MediaFormatProfile.MPEG1 }; + return [MediaFormatProfile.MPEG1]; } if (string.Equals(container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) || @@ -54,29 +64,29 @@ public static MediaFormatProfile[] ResolveVideoFormat(string container, string v if (string.Equals(container, "flv", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { MediaFormatProfile.FLV }; + return [MediaFormatProfile.FLV]; } if (string.Equals(container, "wtv", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { MediaFormatProfile.WTV }; + return [MediaFormatProfile.WTV]; } if (string.Equals(container, "3gp", StringComparison.OrdinalIgnoreCase)) { MediaFormatProfile? val = ResolveVideo3GPFormat(videoCodec, audioCodec); - return val.HasValue ? new MediaFormatProfile[] { val.Value } : Array.Empty(); + return val.HasValue ? [val.Value] : []; } if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { MediaFormatProfile.OGV }; + return [MediaFormatProfile.OGV]; } - return Array.Empty(); + return []; } - private static MediaFormatProfile[] ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) + private static MediaFormatProfile[] ResolveVideoMPEG2TSFormat(string? videoCodec, string? audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) { string suffix = string.Empty; @@ -117,43 +127,43 @@ private static MediaFormatProfile[] ResolveVideoMPEG2TSFormat(string videoCodec, { if (string.Equals(audioCodec, "lpcm", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { MediaFormatProfile.AVC_TS_HD_50_LPCM_T }; + return [MediaFormatProfile.AVC_TS_HD_50_LPCM_T]; } if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)) { if (timestampType == TransportStreamTimestamp.None) { - return new MediaFormatProfile[] { MediaFormatProfile.AVC_TS_HD_DTS_ISO }; + return [MediaFormatProfile.AVC_TS_HD_DTS_ISO]; } - return new MediaFormatProfile[] { MediaFormatProfile.AVC_TS_HD_DTS_T }; + return [MediaFormatProfile.AVC_TS_HD_DTS_T]; } if (string.Equals(audioCodec, "mp2", StringComparison.OrdinalIgnoreCase)) { if (timestampType == TransportStreamTimestamp.None) { - return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_HP_{0}D_MPEG1_L2_ISO", resolution)) }; + return [ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_HP_{0}D_MPEG1_L2_ISO", resolution))]; } - return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_HP_{0}D_MPEG1_L2_T", resolution)) }; + return [ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_HP_{0}D_MPEG1_L2_T", resolution))]; } if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_MP_{0}D_AAC_MULT5{1}", resolution, suffix)) }; + return [ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_MP_{0}D_AAC_MULT5{1}", resolution, suffix))]; } if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_MP_{0}D_MPEG1_L3{1}", resolution, suffix)) }; + return [ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_MP_{0}D_MPEG1_L3{1}", resolution, suffix))]; } if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_MP_{0}D_AC3{1}", resolution, suffix)) }; + return [ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_MP_{0}D_AC3{1}", resolution, suffix))]; } } else if (string.Equals(videoCodec, "vc1", StringComparison.OrdinalIgnoreCase)) @@ -162,43 +172,43 @@ private static MediaFormatProfile[] ResolveVideoMPEG2TSFormat(string videoCodec, { if ((width.HasValue && width.Value > 720) || (height.HasValue && height.Value > 576)) { - return new MediaFormatProfile[] { MediaFormatProfile.VC1_TS_AP_L2_AC3_ISO }; + return [MediaFormatProfile.VC1_TS_AP_L2_AC3_ISO]; } - return new MediaFormatProfile[] { MediaFormatProfile.VC1_TS_AP_L1_AC3_ISO }; + return [MediaFormatProfile.VC1_TS_AP_L1_AC3_ISO]; } if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)) { suffix = string.Equals(suffix, "_ISO", StringComparison.OrdinalIgnoreCase) ? suffix : "_T"; - return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "VC1_TS_HD_DTS{0}", suffix)) }; + return [ValueOf(string.Format(CultureInfo.InvariantCulture, "VC1_TS_HD_DTS{0}", suffix))]; } } else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase)) { if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_AAC{0}", suffix)) }; + return [ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_AAC{0}", suffix))]; } if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_MPEG1_L3{0}", suffix)) }; + return [ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_MPEG1_L3{0}", suffix))]; } if (string.Equals(audioCodec, "mp2", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_MPEG2_L2{0}", suffix)) }; + return [ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_MPEG2_L2{0}", suffix))]; } if (string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)) { - return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_AC3{0}", suffix)) }; + return [ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_AC3{0}", suffix))]; } } - return Array.Empty(); + return []; } private static MediaFormatProfile ValueOf(string value) @@ -206,7 +216,7 @@ private static MediaFormatProfile ValueOf(string value) return (MediaFormatProfile)Enum.Parse(typeof(MediaFormatProfile), value, true); } - private static MediaFormatProfile? ResolveVideoMP4Format(string videoCodec, string audioCodec, int? width, int? height) + private static MediaFormatProfile? ResolveVideoMP4Format(string? videoCodec, string? audioCodec, int? width, int? height) { if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) { @@ -279,7 +289,7 @@ private static MediaFormatProfile ValueOf(string value) return null; } - private static MediaFormatProfile? ResolveVideo3GPFormat(string videoCodec, string audioCodec) + private static MediaFormatProfile? ResolveVideo3GPFormat(string? videoCodec, string? audioCodec) { if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) { @@ -309,7 +319,7 @@ private static MediaFormatProfile ValueOf(string value) return null; } - private static MediaFormatProfile? ResolveVideoASFFormat(string videoCodec, string audioCodec, int? width, int? height) + private static MediaFormatProfile? ResolveVideoASFFormat(string? videoCodec, string? audioCodec, int? width, int? height) { if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase) && (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "wmapro", StringComparison.OrdinalIgnoreCase))) @@ -363,7 +373,15 @@ private static MediaFormatProfile ValueOf(string value) return null; } - public static MediaFormatProfile? ResolveAudioFormat(string container, int? bitrate, int? frequency, int? channels) + /// + /// Gets the audio format profile. + /// + /// The container. + /// The bitrate. + /// The frequency. + /// The channel count. + /// MediaFormatProfile. + public static MediaFormatProfile? ResolveAudioFormat(string? container, int? bitrate, int? frequency, int? channels) { if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase)) { @@ -465,6 +483,13 @@ private static MediaFormatProfile ResolveAudioADTSFormat(int? bitrate) return MediaFormatProfile.AAC_ADTS; } + /// + /// Gets the image format profile. + /// + /// The container. + /// The width. + /// The height. + /// MediaFormatProfile. public static MediaFormatProfile? ResolveImageFormat(string container, int? width, int? height) { if (string.Equals(container, "jpeg", StringComparison.OrdinalIgnoreCase) || diff --git a/src/Jellyfin.Plugin.Dlna.Model/ResponseProfile.cs b/src/Jellyfin.Plugin.Dlna.Model/ResponseProfile.cs index cbd7b6b..42f58d5 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/ResponseProfile.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/ResponseProfile.cs @@ -1,45 +1,81 @@ +#pragma warning disable CA1819 // Properties should not return arrays #nullable disable -using System; using System.Xml.Serialization; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Extensions; namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public class ResponseProfile { + /// + /// Initializes a new instance of the class. + /// public ResponseProfile() { - Conditions = Array.Empty(); + Conditions = []; } + /// + /// Gets or sets the container. + /// [XmlAttribute("container")] public string Container { get; set; } + /// + /// Gets or sets the audio codec. + /// [XmlAttribute("audioCodec")] public string AudioCodec { get; set; } + /// + /// Gets or sets the video codec. + /// [XmlAttribute("videoCodec")] public string VideoCodec { get; set; } + /// + /// Gets or sets the type. + /// [XmlAttribute("type")] public DlnaProfileType Type { get; set; } + /// + /// Gets or sets the orgPn. + /// [XmlAttribute("orgPn")] public string OrgPn { get; set; } + /// + /// Gets or sets the MIME type. + /// [XmlAttribute("mimeType")] public string MimeType { get; set; } + /// + /// Gets or sets the conditions. + /// public ProfileCondition[] Conditions { get; set; } + /// + /// Gets the containers. + /// public string[] GetContainers() => ContainerHelper.Split(Container); + /// + /// Gets the audio codecs. + /// public string[] GetAudioCodecs() => ContainerHelper.Split(AudioCodec); + /// + /// Gets the video codecs. + /// public string[] GetVideoCodecs() => ContainerHelper.Split(VideoCodec); } diff --git a/src/Jellyfin.Plugin.Dlna.Model/SearchCriteria.cs b/src/Jellyfin.Plugin.Dlna.Model/SearchCriteria.cs index 12da5b4..9d9344b 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/SearchCriteria.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/SearchCriteria.cs @@ -1,12 +1,17 @@ -#pragma warning disable CS1591 - using System; using System.Text.RegularExpressions; namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public partial class SearchCriteria { + /// + /// Initializes a new instance of the class. + /// + /// The search string. public SearchCriteria(string search) { ArgumentException.ThrowIfNullOrEmpty(search); @@ -44,6 +49,9 @@ public SearchCriteria(string search) } } + /// + /// Gets or sets the search type. + /// public SearchType SearchType { get; set; } [GeneratedRegex("\\s")] diff --git a/src/Jellyfin.Plugin.Dlna.Model/SearchType.cs b/src/Jellyfin.Plugin.Dlna.Model/SearchType.cs index 31f8d19..dc76ce4 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/SearchType.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/SearchType.cs @@ -1,13 +1,37 @@ -#pragma warning disable CS1591 - namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public enum SearchType { + /// + /// Unknown. + /// Unknown = 0, + + /// + /// Audio. + /// Audio = 1, + + /// + /// Image. + /// Image = 2, + + /// + /// Video. + /// Video = 3, + + /// + /// Playlist. + /// Playlist = 4, + + /// + /// Music Album. + /// MusicAlbum = 5 } diff --git a/src/Jellyfin.Plugin.Dlna.Model/SortCriteria.cs b/src/Jellyfin.Plugin.Dlna.Model/SortCriteria.cs index 4a8a351..a4efec5 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/SortCriteria.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/SortCriteria.cs @@ -1,12 +1,17 @@ -#pragma warning disable CS1591 - using System; using Jellyfin.Data.Enums; namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public class SortCriteria { + /// + /// Initializes a new instance of the class. + /// + /// The sort order. public SortCriteria(string sortOrder) { if (Enum.TryParse(sortOrder, true, out var sortOrderValue)) @@ -19,5 +24,8 @@ public SortCriteria(string sortOrder) } } + /// + /// Gets the sort order. + /// public SortOrder SortOrder { get; } } diff --git a/src/Jellyfin.Plugin.Dlna.Model/UpnpDeviceInfo.cs b/src/Jellyfin.Plugin.Dlna.Model/UpnpDeviceInfo.cs index 6a18e9e..fca0caf 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/UpnpDeviceInfo.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/UpnpDeviceInfo.cs @@ -1,5 +1,6 @@ #nullable disable -#pragma warning disable CS1591 + +#pragma warning disable CA2227 using System; using System.Collections.Generic; @@ -7,15 +8,33 @@ namespace Jellyfin.Plugin.Dlna.Model; +/// +/// Defines the . +/// public class UpnpDeviceInfo { + /// + /// Gets or sets the location. + /// public Uri Location { get; set; } + /// + /// Gets the headers. + /// public Dictionary Headers { get; set; } + /// + /// Gets or sets local IP address. + /// public IPAddress LocalIPAddress { get; set; } + /// + /// Gets or sets the local port. + /// public int LocalPort { get; set; } + /// + /// Gets or sets the remote IP address. + /// public IPAddress RemoteIPAddress { get; set; } } diff --git a/src/Jellyfin.Plugin.Dlna.Model/XmlAttribute.cs b/src/Jellyfin.Plugin.Dlna.Model/XmlAttribute.cs index c480fad..9c533d3 100644 --- a/src/Jellyfin.Plugin.Dlna.Model/XmlAttribute.cs +++ b/src/Jellyfin.Plugin.Dlna.Model/XmlAttribute.cs @@ -1,5 +1,7 @@ #nullable disable +#pragma warning disable CA1711 + using System.Xml.Serialization; namespace Jellyfin.Plugin.Dlna.Model; diff --git a/src/Jellyfin.Plugin.Dlna.Playback/DynamicHlsHelper.cs b/src/Jellyfin.Plugin.Dlna.Playback/DynamicHlsHelper.cs index 2cbf6d8..537cfb5 100644 --- a/src/Jellyfin.Plugin.Dlna.Playback/DynamicHlsHelper.cs +++ b/src/Jellyfin.Plugin.Dlna.Playback/DynamicHlsHelper.cs @@ -12,7 +12,6 @@ using Jellyfin.Extensions; using Jellyfin.Plugin.Dlna.Model; using Jellyfin.Plugin.Dlna.Playback.Extensions; -using Jellyfin.Plugin.Dlna.Playback.Model; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -151,7 +150,7 @@ private async Task GetMasterPlaylistInternal( _httpContextAccessor.HttpContext.Response.Headers.Append(HeaderNames.Expires, "0"); if (isHeadRequest) { - return new FileContentResult(Array.Empty(), MimeTypes.GetMimeType("playlist.m3u8")); + return new FileContentResult([], MimeTypes.GetMimeType("playlist.m3u8")); } var totalBitrate = (state.OutputAudioBitrate ?? 0) + (state.OutputVideoBitrate ?? 0); @@ -569,7 +568,7 @@ private void AddTrickplay(StreamState state, Dictionary tric && state.VideoStream is not null && state.VideoStream.Level.HasValue) { - levelString = state.VideoStream.Level.ToString() ?? string.Empty; + levelString = state.VideoStream.Level?.ToString(CultureInfo.InvariantCulture) ?? string.Empty; } else { diff --git a/src/Jellyfin.Plugin.Dlna.Playback/FileStreamResponseHelpers.cs b/src/Jellyfin.Plugin.Dlna.Playback/FileStreamResponseHelpers.cs index e533006..e05eb0c 100644 --- a/src/Jellyfin.Plugin.Dlna.Playback/FileStreamResponseHelpers.cs +++ b/src/Jellyfin.Plugin.Dlna.Playback/FileStreamResponseHelpers.cs @@ -93,7 +93,7 @@ public static async Task GetTranscodedFile( return new OkResult(); } - using var transcodingLock = await transcodeManager.LockAsync(outputPath, cancellationTokenSource.Token); + using var transcodingLock = await transcodeManager.LockAsync(outputPath, cancellationTokenSource.Token).ConfigureAwait(false); TranscodingJob? job; if (!File.Exists(outputPath)) diff --git a/src/Jellyfin.Plugin.Dlna.Playback/Jellyfin.Plugin.Dlna.Playback.csproj b/src/Jellyfin.Plugin.Dlna.Playback/Jellyfin.Plugin.Dlna.Playback.csproj index c70babd..0621bc1 100644 --- a/src/Jellyfin.Plugin.Dlna.Playback/Jellyfin.Plugin.Dlna.Playback.csproj +++ b/src/Jellyfin.Plugin.Dlna.Playback/Jellyfin.Plugin.Dlna.Playback.csproj @@ -1,7 +1,11 @@ net8.0 + true + true enable + AllEnabledByDefault + ../../jellyfin.ruleset diff --git a/src/Jellyfin.Plugin.Dlna.Playback/Model/DlnaStreamState.cs b/src/Jellyfin.Plugin.Dlna.Playback/Model/DlnaStreamState.cs index 56cd183..959fb49 100644 --- a/src/Jellyfin.Plugin.Dlna.Playback/Model/DlnaStreamState.cs +++ b/src/Jellyfin.Plugin.Dlna.Playback/Model/DlnaStreamState.cs @@ -5,8 +5,17 @@ namespace Jellyfin.Plugin.Dlna.Playback.Model; +/// +/// Defines the . +/// public class DlnaStreamState : StreamState { + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// The . + /// Instance of the interface. public DlnaStreamState( IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, diff --git a/src/Jellyfin.Plugin.Dlna.Playback/StreamingHelpers.cs b/src/Jellyfin.Plugin.Dlna.Playback/StreamingHelpers.cs index d00ccd2..78b54e6 100644 --- a/src/Jellyfin.Plugin.Dlna.Playback/StreamingHelpers.cs +++ b/src/Jellyfin.Plugin.Dlna.Playback/StreamingHelpers.cs @@ -130,7 +130,7 @@ public static async Task GetStreamingState( var item = libraryManager.GetItemById(streamingRequest.Id); - state.IsInputVideo = item.MediaType == MediaType.Video; + state.IsInputVideo = item?.MediaType == MediaType.Video; MediaSourceInfo? mediaSource = null; if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)) @@ -251,8 +251,8 @@ public static async Task GetStreamingState( } var deviceProfileId = state.IsVideoRequest - ? (streamingRequest as DlnaVideoRequestDto).DeviceProfileId - : (streamingRequest as DlnaStreamingRequestDto).DeviceProfileId; + ? (streamingRequest as DlnaVideoRequestDto)?.DeviceProfileId + : (streamingRequest as DlnaStreamingRequestDto)?.DeviceProfileId; ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, httpRequest, deviceProfileId, streamingRequest.Static); var ext = string.IsNullOrWhiteSpace(state.OutputContainer) diff --git a/src/Jellyfin.Plugin.Dlna/Api/DlnaServerController.cs b/src/Jellyfin.Plugin.Dlna/Api/DlnaServerController.cs index 99630c3..ea09310 100644 --- a/src/Jellyfin.Plugin.Dlna/Api/DlnaServerController.cs +++ b/src/Jellyfin.Plugin.Dlna/Api/DlnaServerController.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net.Mime; using System.Threading.Tasks; @@ -22,7 +21,7 @@ namespace Jellyfin.Plugin.Dlna.Api; [Authorize(Policy = Policies.AnonymousLanAccessPolicy)] public class DlnaServerController : ControllerBase { - private static readonly string[] _relativePathUserAgents = { "Bigscreen" }; + private static readonly string[] _relativePathUserAgents = ["Bigscreen"]; private readonly IDlnaManager _dlnaManager; private readonly IContentDirectory _contentDirectory; @@ -66,7 +65,7 @@ public ActionResult GetDescriptionXml([FromRoute, Required] string serve string? userAgent = Request.Headers.UserAgent; if (userAgent is not null) { - var firstIndexOfSlash = userAgent.IndexOf('/'); + var firstIndexOfSlash = userAgent.IndexOf('/', StringComparison.OrdinalIgnoreCase); if (firstIndexOfSlash > 0) { userAgent = userAgent.Substring(0, firstIndexOfSlash); @@ -94,7 +93,6 @@ public ActionResult GetDescriptionXml([FromRoute, Required] string serve [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] public ActionResult GetContentDirectory([FromRoute, Required] string serverId) { return Ok(_contentDirectory.GetServiceXml()); @@ -113,7 +111,6 @@ public ActionResult GetContentDirectory([FromRoute, Required] string ser [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] string serverId) { return Ok(_mediaReceiverRegistrar.GetServiceXml()); @@ -132,7 +129,6 @@ public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] stri [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] public ActionResult GetConnectionManager([FromRoute, Required] string serverId) { return Ok(_connectionManager.GetServiceXml()); @@ -195,8 +191,7 @@ public async Task> ProcessMediaReceiverRegistrarCo /// Event subscription response. [HttpSubscribe("{serverId}/MediaReceiverRegistrar/Events")] [HttpUnsubscribe("{serverId}/MediaReceiverRegistrar/Events")] - [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] + [ApiExplorerSettings(IgnoreApi = true)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] @@ -214,8 +209,7 @@ public ActionResult ProcessMediaReceiverRegistrarEven /// Event subscription response. [HttpSubscribe("{serverId}/ContentDirectory/Events")] [HttpUnsubscribe("{serverId}/ContentDirectory/Events")] - [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] + [ApiExplorerSettings(IgnoreApi = true)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] @@ -233,8 +227,7 @@ public ActionResult ProcessContentDirectoryEventReque /// Event subscription response. [HttpSubscribe("{serverId}/ConnectionManager/Events")] [HttpUnsubscribe("{serverId}/ConnectionManager/Events")] - [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] + [ApiExplorerSettings(IgnoreApi = true)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] @@ -253,7 +246,6 @@ public ActionResult ProcessConnectionManagerEventRequ /// DLNA is disabled. /// Icon stream. [HttpGet("{serverId}/icons/{fileName}")] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] diff --git a/src/Jellyfin.Plugin.Dlna/Api/HttpSubscribeAttribute.cs b/src/Jellyfin.Plugin.Dlna/Api/HttpSubscribeAttribute.cs index c95b3c4..0bdf189 100644 --- a/src/Jellyfin.Plugin.Dlna/Api/HttpSubscribeAttribute.cs +++ b/src/Jellyfin.Plugin.Dlna/Api/HttpSubscribeAttribute.cs @@ -9,7 +9,7 @@ namespace Jellyfin.Plugin.Dlna.Api; /// public sealed class HttpSubscribeAttribute : HttpMethodAttribute { - private static readonly IEnumerable _supportedMethods = new[] { "SUBSCRIBE" }; + private static readonly IEnumerable _supportedMethods = ["SUBSCRIBE"]; /// /// Initializes a new instance of the class. diff --git a/src/Jellyfin.Plugin.Dlna/Api/HttpUnsubscribeAttribute.cs b/src/Jellyfin.Plugin.Dlna/Api/HttpUnsubscribeAttribute.cs index 0505ab1..0f31943 100644 --- a/src/Jellyfin.Plugin.Dlna/Api/HttpUnsubscribeAttribute.cs +++ b/src/Jellyfin.Plugin.Dlna/Api/HttpUnsubscribeAttribute.cs @@ -9,7 +9,7 @@ namespace Jellyfin.Plugin.Dlna.Api; /// public sealed class HttpUnsubscribeAttribute : HttpMethodAttribute { - private static readonly IEnumerable _supportedMethods = new[] { "UNSUBSCRIBE" }; + private static readonly IEnumerable _supportedMethods = ["UNSUBSCRIBE"]; /// /// Initializes a new instance of the class. diff --git a/src/Jellyfin.Plugin.Dlna/Common/ServiceAction.cs b/src/Jellyfin.Plugin.Dlna/Common/ServiceAction.cs index a103b87..83968ae 100644 --- a/src/Jellyfin.Plugin.Dlna/Common/ServiceAction.cs +++ b/src/Jellyfin.Plugin.Dlna/Common/ServiceAction.cs @@ -12,7 +12,7 @@ public class ServiceAction /// public ServiceAction() { - ArgumentList = new List(); + ArgumentList = []; } /// @@ -23,7 +23,7 @@ public ServiceAction() /// /// Gets the ArgumentList. /// - public List ArgumentList { get; } + public IReadOnlyList ArgumentList { get; set;} /// public override string ToString() => Name; diff --git a/src/Jellyfin.Plugin.Dlna/Configuration/DlnaPluginConfiguration.cs b/src/Jellyfin.Plugin.Dlna/Configuration/DlnaPluginConfiguration.cs index 1a04a15..e007340 100644 --- a/src/Jellyfin.Plugin.Dlna/Configuration/DlnaPluginConfiguration.cs +++ b/src/Jellyfin.Plugin.Dlna/Configuration/DlnaPluginConfiguration.cs @@ -3,6 +3,9 @@ namespace Jellyfin.Plugin.Dlna.Configuration; +/// +/// Defines the . +/// public class DlnaPluginConfiguration : BasePluginConfiguration { /// diff --git a/src/Jellyfin.Plugin.Dlna/ConnectionManager/ConnectionManagerService.cs b/src/Jellyfin.Plugin.Dlna/ConnectionManager/ConnectionManagerService.cs index 15999c6..89dcc66 100644 --- a/src/Jellyfin.Plugin.Dlna/ConnectionManager/ConnectionManagerService.cs +++ b/src/Jellyfin.Plugin.Dlna/ConnectionManager/ConnectionManagerService.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Net.Http; using System.Threading.Tasks; using Jellyfin.Plugin.Dlna.Service; diff --git a/src/Jellyfin.Plugin.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs b/src/Jellyfin.Plugin.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs index d63179c..1502e0c 100644 --- a/src/Jellyfin.Plugin.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs +++ b/src/Jellyfin.Plugin.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using Jellyfin.Plugin.Dlna.Common; using Jellyfin.Plugin.Dlna.Service; @@ -18,7 +16,7 @@ public static class ConnectionManagerXmlBuilder /// An XML description of this service. public static string GetXml() { - return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); + return ServiceXmlBuilder.GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); } /// @@ -27,8 +25,8 @@ public static string GetXml() /// The . private static IEnumerable GetStateVariables() { - return new StateVariable[] - { + return + [ new StateVariable { Name = "SourceProtocolInfo", @@ -56,14 +54,14 @@ private static IEnumerable GetStateVariables() DataType = "string", SendsEvents = false, - AllowedValues = new[] - { + AllowedValues = + [ "OK", "ContentFormatMismatch", "InsufficientBandwidth", "UnreliableChannel", "Unknown" - } + ] }, new StateVariable @@ -79,11 +77,11 @@ private static IEnumerable GetStateVariables() DataType = "string", SendsEvents = false, - AllowedValues = new[] - { + AllowedValues = + [ "Output", "Input" - } + ] }, new StateVariable @@ -113,6 +111,6 @@ private static IEnumerable GetStateVariables() DataType = "ui4", SendsEvents = false } - }; + ]; } } diff --git a/src/Jellyfin.Plugin.Dlna/ConnectionManager/ControlHandler.cs b/src/Jellyfin.Plugin.Dlna/ConnectionManager/ControlHandler.cs index 3a3a101..60a7f2d 100644 --- a/src/Jellyfin.Plugin.Dlna/ConnectionManager/ControlHandler.cs +++ b/src/Jellyfin.Plugin.Dlna/ConnectionManager/ControlHandler.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Xml; @@ -20,8 +18,8 @@ public class ControlHandler : BaseControlHandler /// /// Initializes a new instance of the class. /// - /// The for use with the instance. - /// The for use with the instance. + /// The . + /// The . public ControlHandler(ILogger logger, DlnaDeviceProfile profile) : base(logger) { diff --git a/src/Jellyfin.Plugin.Dlna/ConnectionManager/ServiceActionListBuilder.cs b/src/Jellyfin.Plugin.Dlna/ConnectionManager/ServiceActionListBuilder.cs index 8dc2e15..5954407 100644 --- a/src/Jellyfin.Plugin.Dlna/ConnectionManager/ServiceActionListBuilder.cs +++ b/src/Jellyfin.Plugin.Dlna/ConnectionManager/ServiceActionListBuilder.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using Jellyfin.Plugin.Dlna.Common; @@ -11,7 +9,7 @@ namespace Jellyfin.Plugin.Dlna.ConnectionManager; public static class ServiceActionListBuilder { /// - /// Returns an enumerable of the ConnectionManagar:1 DLNA actions. + /// Returns an enumerable of the ConnectionManager:1 DLNA actions. /// /// An . public static IEnumerable GetActions() @@ -36,58 +34,53 @@ private static ServiceAction PrepareForConnection() { var action = new ServiceAction { - Name = "PrepareForConnection" + Name = "PrepareForConnection", + ArgumentList = [ + new Argument + { + Name = "RemoteProtocolInfo", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo" + }, + new Argument + { + Name = "PeerConnectionManager", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_ConnectionManager" + }, + new Argument + { + Name = "PeerConnectionID", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_ConnectionID" + }, + new Argument + { + Name = "Direction", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_Direction" + }, + new Argument + { + Name = "ConnectionID", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_ConnectionID" + }, + new Argument + { + Name = "AVTransportID", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_AVTransportID" + }, + new Argument + { + Name = "RcsID", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_RcsID" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "RemoteProtocolInfo", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo" - }); - - action.ArgumentList.Add(new Argument - { - Name = "PeerConnectionManager", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ConnectionManager" - }); - - action.ArgumentList.Add(new Argument - { - Name = "PeerConnectionID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ConnectionID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Direction", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Direction" - }); - - action.ArgumentList.Add(new Argument - { - Name = "ConnectionID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_ConnectionID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "AVTransportID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_AVTransportID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RcsID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_RcsID" - }); - return action; } @@ -99,65 +92,59 @@ private static ServiceAction GetCurrentConnectionInfo() { var action = new ServiceAction { - Name = "GetCurrentConnectionInfo" + Name = "GetCurrentConnectionInfo", + ArgumentList = [ + new Argument + { + Name = "ConnectionID", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_ConnectionID" + }, + new Argument + { + Name = "RcsID", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_RcsID" + }, + new Argument + { + Name = "AVTransportID", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_AVTransportID" + }, + new Argument + { + Name = "ProtocolInfo", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo" + }, + new Argument + { + Name = "PeerConnectionManager", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_ConnectionManager" + }, + new Argument + { + Name = "PeerConnectionID", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_ConnectionID" + }, + new Argument + { + Name = "Direction", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Direction" + }, + new Argument + { + Name = "Status", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_ConnectionStatus" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "ConnectionID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ConnectionID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RcsID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_RcsID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "AVTransportID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_AVTransportID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "ProtocolInfo", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo" - }); - - action.ArgumentList.Add(new Argument - { - Name = "PeerConnectionManager", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_ConnectionManager" - }); - - action.ArgumentList.Add(new Argument - { - Name = "PeerConnectionID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_ConnectionID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Direction", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Direction" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Status", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_ConnectionStatus" - }); - return action; } @@ -169,23 +156,23 @@ private static ServiceAction GetProtocolInfo() { var action = new ServiceAction { - Name = "GetProtocolInfo" + Name = "GetProtocolInfo", + ArgumentList = [ + new Argument + { + Name = "Source", + Direction = "out", + RelatedStateVariable = "SourceProtocolInfo" + }, + new Argument + { + Name = "Sink", + Direction = "out", + RelatedStateVariable = "SinkProtocolInfo" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "Source", - Direction = "out", - RelatedStateVariable = "SourceProtocolInfo" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Sink", - Direction = "out", - RelatedStateVariable = "SinkProtocolInfo" - }); - return action; } @@ -197,16 +184,17 @@ private static ServiceAction GetCurrentConnectionIDs() { var action = new ServiceAction { - Name = "GetCurrentConnectionIDs" + Name = "GetCurrentConnectionIDs", + ArgumentList = [ + new Argument + { + Name = "ConnectionIDs", + Direction = "out", + RelatedStateVariable = "CurrentConnectionIDs" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "ConnectionIDs", - Direction = "out", - RelatedStateVariable = "CurrentConnectionIDs" - }); - return action; } @@ -218,16 +206,17 @@ private static ServiceAction ConnectionComplete() { var action = new ServiceAction { - Name = "ConnectionComplete" + Name = "ConnectionComplete", + ArgumentList = [ + new Argument + { + Name = "ConnectionID", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_ConnectionID" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "ConnectionID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ConnectionID" - }); - return action; } } diff --git a/src/Jellyfin.Plugin.Dlna/ContentDirectory/ContentDirectoryService.cs b/src/Jellyfin.Plugin.Dlna/ContentDirectory/ContentDirectoryService.cs index 0ee0b95..ef7d7bb 100644 --- a/src/Jellyfin.Plugin.Dlna/ContentDirectory/ContentDirectoryService.cs +++ b/src/Jellyfin.Plugin.Dlna/ContentDirectory/ContentDirectoryService.cs @@ -1,11 +1,10 @@ -#pragma warning disable CS1591 - using System; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using Jellyfin.Plugin.Dlna.Model; using Jellyfin.Plugin.Dlna.Service; using MediaBrowser.Controller.Drawing; diff --git a/src/Jellyfin.Plugin.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs b/src/Jellyfin.Plugin.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs index e82711d..17e1c23 100644 --- a/src/Jellyfin.Plugin.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs +++ b/src/Jellyfin.Plugin.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using Jellyfin.Plugin.Dlna.Common; using Jellyfin.Plugin.Dlna.Service; @@ -18,7 +16,7 @@ public static class ContentDirectoryXmlBuilder /// An XML description of this service. public static string GetXml() { - return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); + return ServiceXmlBuilder.GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); } /// @@ -27,8 +25,8 @@ public static string GetXml() /// The . private static IEnumerable GetStateVariables() { - return new StateVariable[] - { + return + [ new StateVariable { Name = "A_ARG_TYPE_Filter", @@ -112,11 +110,11 @@ private static IEnumerable GetStateVariables() DataType = "string", SendsEvents = false, - AllowedValues = new[] - { + AllowedValues = + [ "BrowseMetadata", "BrowseDirectChildren" - } + ] }, new StateVariable @@ -153,6 +151,6 @@ private static IEnumerable GetStateVariables() DataType = "string", SendsEvents = false } - }; + ]; } } diff --git a/src/Jellyfin.Plugin.Dlna/ContentDirectory/ControlHandler.cs b/src/Jellyfin.Plugin.Dlna/ContentDirectory/ControlHandler.cs index 53eddfa..5b2cb1e 100644 --- a/src/Jellyfin.Plugin.Dlna/ContentDirectory/ControlHandler.cs +++ b/src/Jellyfin.Plugin.Dlna/ContentDirectory/ControlHandler.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Globalization; @@ -43,7 +41,7 @@ public class ControlHandler : BaseControlHandler private readonly ILibraryManager _libraryManager; private readonly IUserDataManager _userDataManager; - private readonly User _user; + private readonly User? _user; private readonly IUserViewManager _userViewManager; private readonly ITVSeriesManager _tvSeriesManager; @@ -56,29 +54,29 @@ public class ControlHandler : BaseControlHandler /// /// Initializes a new instance of the class. /// - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The server address to use in this instance> for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The system id for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. + /// The . + /// Instance of the interface. + /// The . + /// The server address. + /// The access token. + /// Instance of the interface. + /// Instance of the interface. + /// The . + /// The system id. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. public ControlHandler( ILogger logger, ILibraryManager libraryManager, DlnaDeviceProfile profile, string serverAddress, - string accessToken, + string? accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, - User user, + User? user, int systemUpdateId, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, @@ -186,6 +184,11 @@ protected override void WriteResult(string methodName, IReadOnlyDictionaryThe method parameters. private void HandleXSetBookmark(IReadOnlyDictionary sparams) { + if (_user is null) + { + return; + } + var id = sparams["ObjectID"]; var serverItem = GetItemFromObjectId(id); @@ -487,25 +490,25 @@ private void HandleSearch(XmlWriter xmlWriter, IReadOnlyDictionaryThe start index. /// The maximum number to return. /// The . - private static QueryResult GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) + private static QueryResult GetChildrenSorted(BaseItem item, User? user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) { var folder = (Folder)item; - MediaType[] mediaTypes = Array.Empty(); + MediaType[] mediaTypes = []; bool? isFolder = null; switch (search.SearchType) { case SearchType.Audio: - mediaTypes = new[] { MediaType.Audio }; + mediaTypes = [MediaType.Audio]; isFolder = false; break; case SearchType.Video: - mediaTypes = new[] { MediaType.Video }; + mediaTypes = [MediaType.Video]; isFolder = false; break; case SearchType.Image: - mediaTypes = new[] { MediaType.Photo }; + mediaTypes = [MediaType.Photo]; isFolder = false; break; case SearchType.Playlist: @@ -522,7 +525,7 @@ private static QueryResult GetChildrenSorted(BaseItem item, User user, User = user, Recursive = true, IsMissing = false, - ExcludeItemTypes = new[] { BaseItemKind.Book }, + ExcludeItemTypes = [BaseItemKind.Book], IsFolder = isFolder, MediaTypes = mediaTypes, DtoOptions = GetDtoOptions() @@ -548,32 +551,35 @@ private static DtoOptions GetDtoOptions() /// The start index. /// The maximum number to return. /// The . - private QueryResult GetUserItems(BaseItem item, StubType? stubType, User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetUserItems(BaseItem item, StubType? stubType, User? user, SortCriteria sort, int? startIndex, int? limit) { - switch (item) + if (user is not null) { - case MusicGenre: - return GetMusicGenreItems(item, user, sort, startIndex, limit); - case MusicArtist: - return GetMusicArtistItems(item, user, sort, startIndex, limit); - case Genre: - return GetGenreItems(item, user, sort, startIndex, limit); - } + switch (item) + { + case MusicGenre: + return GetMusicGenreItems(item, user, sort, startIndex, limit); + case MusicArtist: + return GetMusicArtistItems(item, user, sort, startIndex, limit); + case Genre: + return GetGenreItems(item, user, sort, startIndex, limit); + } - if (stubType != StubType.Folder && item is IHasCollectionType collectionFolder) - { - switch (collectionFolder.CollectionType) + if (stubType != StubType.Folder && item is IHasCollectionType collectionFolder) { - case CollectionType.music: - return GetMusicFolders(item, user, stubType, sort, startIndex, limit); - case CollectionType.movies: - return GetMovieFolders(item, user, stubType, sort, startIndex, limit); - case CollectionType.tvshows: - return GetTvFolders(item, user, stubType, sort, startIndex, limit); - case CollectionType.folders: - return GetFolders(user, startIndex, limit); - case CollectionType.livetv: - return GetLiveTvChannels(user, sort, startIndex, limit); + switch (collectionFolder.CollectionType) + { + case CollectionType.music: + return GetMusicFolders(item, user, stubType, sort, startIndex, limit); + case CollectionType.movies: + return GetMovieFolders(item, user, stubType, sort, startIndex, limit); + case CollectionType.tvshows: + return GetTvFolders(item, user, stubType, sort, startIndex, limit); + case CollectionType.folders: + return GetFolders(user, startIndex, limit); + case CollectionType.livetv: + return GetLiveTvChannels(user, sort, startIndex, limit); + } } } @@ -590,7 +596,7 @@ private QueryResult GetUserItems(BaseItem item, StubType? stubType, Limit = limit, StartIndex = startIndex, IsVirtualItem = false, - ExcludeItemTypes = new[] { BaseItemKind.Book }, + ExcludeItemTypes = [BaseItemKind.Book], IsPlaceHolder = false, DtoOptions = GetDtoOptions(), OrderBy = GetOrderBy(sort, folder.IsPreSorted) @@ -615,7 +621,7 @@ private QueryResult GetLiveTvChannels(User user, SortCriteria sort, { StartIndex = startIndex, Limit = limit, - IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel }, + IncludeItemTypes = [BaseItemKind.LiveTvChannel], OrderBy = GetOrderBy(sort, false) }; @@ -831,11 +837,11 @@ private QueryResult GetMovieContinueWatching(BaseItem parent, Intern query.Recursive = true; query.Parent = parent; - query.OrderBy = new[] - { + query.OrderBy = + [ (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Ascending) - }; + ]; query.IsResumable = true; query.Limit ??= 10; @@ -853,7 +859,7 @@ private QueryResult GetMovieContinueWatching(BaseItem parent, Intern private QueryResult GetMovieCollections(InternalItemsQuery query) { query.Recursive = true; - query.IncludeItemTypes = new[] { BaseItemKind.BoxSet }; + query.IncludeItemTypes = [BaseItemKind.BoxSet]; var result = _libraryManager.GetItemsResult(query); @@ -873,7 +879,7 @@ private QueryResult GetChildrenOfItem(BaseItem parent, InternalItems query.Recursive = true; query.Parent = parent; query.IsFavorite = isFavorite; - query.IncludeItemTypes = new[] { itemType }; + query.IncludeItemTypes = [itemType]; var result = _libraryManager.GetItemsResult(query); @@ -891,7 +897,7 @@ private QueryResult GetGenres(BaseItem parent, InternalItemsQuery qu { // Don't sort query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - query.AncestorIds = new[] { parent.Id }; + query.AncestorIds = [parent.Id]; var genresResult = _libraryManager.GetGenres(query); return ToResult(query.StartIndex, genresResult); @@ -907,7 +913,7 @@ private QueryResult GetMusicGenres(BaseItem parent, InternalItemsQue { // Don't sort query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - query.AncestorIds = new[] { parent.Id }; + query.AncestorIds = [parent.Id]; var genresResult = _libraryManager.GetMusicGenres(query); return ToResult(query.StartIndex, genresResult); @@ -923,7 +929,7 @@ private QueryResult GetMusicAlbumArtists(BaseItem parent, InternalIt { // Don't sort query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - query.AncestorIds = new[] { parent.Id }; + query.AncestorIds = [parent.Id]; var artists = _libraryManager.GetAlbumArtists(query); return ToResult(query.StartIndex, artists); @@ -939,7 +945,7 @@ private QueryResult GetMusicArtists(BaseItem parent, InternalItemsQu { // Don't sort query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - query.AncestorIds = new[] { parent.Id }; + query.AncestorIds = [parent.Id]; var artists = _libraryManager.GetArtists(query); return ToResult(query.StartIndex, artists); } @@ -954,7 +960,7 @@ private QueryResult GetFavoriteArtists(BaseItem parent, InternalItem { // Don't sort query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - query.AncestorIds = new[] { parent.Id }; + query.AncestorIds = [parent.Id]; query.IsFavorite = true; var artists = _libraryManager.GetArtists(query); return ToResult(query.StartIndex, artists); @@ -968,7 +974,7 @@ private QueryResult GetFavoriteArtists(BaseItem parent, InternalItem private QueryResult GetMusicPlaylists(InternalItemsQuery query) { query.Parent = null; - query.IncludeItemTypes = new[] { BaseItemKind.Playlist }; + query.IncludeItemTypes = [BaseItemKind.Playlist]; query.Recursive = true; var result = _libraryManager.GetItemsResult(query); @@ -994,7 +1000,7 @@ private QueryResult GetNextUp(BaseItem parent, InternalItemsQuery qu // User cannot be null here as the caller has set it User = query.User! }, - new[] { parent }, + [parent], query.DtoOptions); return ToResult(query.StartIndex, result); @@ -1028,15 +1034,15 @@ private QueryResult GetLatest(BaseItem parent, InternalItemsQuery qu // User cannot be null here as the caller has set it User = query.User!, Limit = limit, - IncludeItemTypes = new[] { itemType }, + IncludeItemTypes = [itemType], ParentId = parent?.Id ?? Guid.Empty, GroupItems = true }, - query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i is not null).ToArray(); + query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).OfType().ToArray(); if (query.StartIndex > 0) { - items = (items.Length <= query.StartIndex) ? Array.Empty() : items[query.StartIndex.Value..]; + items = (items.Length <= query.StartIndex) ? [] : items[query.StartIndex.Value..]; } return ToResult(query.StartIndex, items); @@ -1056,8 +1062,8 @@ private QueryResult GetMusicArtistItems(BaseItem item, User user, So var query = new InternalItemsQuery(user) { Recursive = true, - ArtistIds = new[] { item.Id }, - IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, + ArtistIds = [item.Id], + IncludeItemTypes = [BaseItemKind.MusicAlbum], Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions(), @@ -1083,12 +1089,12 @@ private QueryResult GetGenreItems(BaseItem item, User user, SortCrit var query = new InternalItemsQuery(user) { Recursive = true, - GenreIds = new[] { item.Id }, - IncludeItemTypes = new[] - { + GenreIds = [item.Id], + IncludeItemTypes = + [ BaseItemKind.Movie, BaseItemKind.Series - }, + ], Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions(), @@ -1114,8 +1120,8 @@ private QueryResult GetMusicGenreItems(BaseItem item, User user, Sor var query = new InternalItemsQuery(user) { Recursive = true, - GenreIds = new[] { item.Id }, - IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, + GenreIds = [item.Id], + IncludeItemTypes = [BaseItemKind.MusicAlbum], Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions(), @@ -1133,16 +1139,16 @@ private QueryResult GetMusicGenreItems(BaseItem item, User user, Sor /// The start index. /// An array of . /// A . - private static QueryResult ToResult(int? startIndex, IReadOnlyCollection result) + private static QueryResult ToResult(int? startIndex, IReadOnlyCollection? result) { - var serverItems = result + var serverItems = result? .Select(i => new ServerItem(i, null)) .ToArray(); return new QueryResult( startIndex, - result.Count, - serverItems); + result?.Count ?? 0, + serverItems ?? []); } /// @@ -1194,7 +1200,7 @@ private static QueryResult ToResult(int? startIndex, QueryResult<(Ba /// True if pre-sorted. private static (ItemSortBy SortName, SortOrder SortOrder)[] GetOrderBy(SortCriteria sort, bool isPreSorted) { - return isPreSorted ? Array.Empty<(ItemSortBy, SortOrder)>() : new[] { (ItemSortBy.SortName, sort.SortOrder) }; + return isPreSorted ? Array.Empty<(ItemSortBy, SortOrder)>() : [(ItemSortBy.SortName, sort.SortOrder)]; } /// @@ -1239,8 +1245,10 @@ private ServerItem ParseItemId(string id) if (Guid.TryParse(id, out var itemId)) { var item = _libraryManager.GetItemById(itemId); - - return new ServerItem(item, stubType); + if (item is not null) + { + return new ServerItem(item, stubType); + } } Logger.LogError("Error parsing item Id: {Id}. Returning user root folder.", id); @@ -1259,7 +1267,7 @@ private static ServerItem[] GetTrimmedServerItemsArray(ServerItem[] serverItems, { if (startIndex >= serverItems.Length) { - return Array.Empty(); + return []; } if (startIndex > 0) diff --git a/src/Jellyfin.Plugin.Dlna/ContentDirectory/ServerItem.cs b/src/Jellyfin.Plugin.Dlna/ContentDirectory/ServerItem.cs index 10253d6..5eefe57 100644 --- a/src/Jellyfin.Plugin.Dlna/ContentDirectory/ServerItem.cs +++ b/src/Jellyfin.Plugin.Dlna/ContentDirectory/ServerItem.cs @@ -5,7 +5,7 @@ namespace Jellyfin.Plugin.Dlna.ContentDirectory; /// /// Defines the . /// -internal class ServerItem +internal sealed class ServerItem { /// /// Initializes a new instance of the class. diff --git a/src/Jellyfin.Plugin.Dlna/ContentDirectory/ServiceActionListBuilder.cs b/src/Jellyfin.Plugin.Dlna/ContentDirectory/ServiceActionListBuilder.cs index f5fe977..9551454 100644 --- a/src/Jellyfin.Plugin.Dlna/ContentDirectory/ServiceActionListBuilder.cs +++ b/src/Jellyfin.Plugin.Dlna/ContentDirectory/ServiceActionListBuilder.cs @@ -14,8 +14,8 @@ public static class ServiceActionListBuilder /// An . public static IEnumerable GetActions() { - return new[] - { + return + [ GetSearchCapabilitiesAction(), GetSortCapabilitiesAction(), GetGetSystemUpdateIDAction(), @@ -24,7 +24,7 @@ public static IEnumerable GetActions() GetX_GetFeatureListAction(), GetXSetBookmarkAction(), GetBrowseByLetterAction() - }; + ]; } /// @@ -35,16 +35,17 @@ private static ServiceAction GetGetSystemUpdateIDAction() { var action = new ServiceAction { - Name = "GetSystemUpdateID" + Name = "GetSystemUpdateID", + ArgumentList = [ + new Argument + { + Name = "Id", + Direction = "out", + RelatedStateVariable = "SystemUpdateID" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "Id", - Direction = "out", - RelatedStateVariable = "SystemUpdateID" - }); - return action; } @@ -56,16 +57,17 @@ private static ServiceAction GetSearchCapabilitiesAction() { var action = new ServiceAction { - Name = "GetSearchCapabilities" + Name = "GetSearchCapabilities", + ArgumentList = [ + new Argument + { + Name = "SearchCaps", + Direction = "out", + RelatedStateVariable = "SearchCapabilities" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "SearchCaps", - Direction = "out", - RelatedStateVariable = "SearchCapabilities" - }); - return action; } @@ -77,16 +79,17 @@ private static ServiceAction GetSortCapabilitiesAction() { var action = new ServiceAction { - Name = "GetSortCapabilities" + Name = "GetSortCapabilities", + ArgumentList = [ + new Argument + { + Name = "SortCaps", + Direction = "out", + RelatedStateVariable = "SortCapabilities" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "SortCaps", - Direction = "out", - RelatedStateVariable = "SortCapabilities" - }); - return action; } @@ -98,16 +101,17 @@ private static ServiceAction GetX_GetFeatureListAction() { var action = new ServiceAction { - Name = "X_GetFeatureList" + Name = "X_GetFeatureList", + ArgumentList = [ + new Argument + { + Name = "FeatureList", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Featurelist" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "FeatureList", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Featurelist" - }); - return action; } @@ -119,79 +123,71 @@ private static ServiceAction GetSearchAction() { var action = new ServiceAction { - Name = "Search" + Name = "Search", + ArgumentList = [ + new Argument + { + Name = "ContainerID", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_ObjectID" + }, + new Argument + { + Name = "SearchCriteria", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_SearchCriteria" + }, + new Argument + { + Name = "Filter", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_Filter" + }, + new Argument + { + Name = "StartingIndex", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_Index" + }, + new Argument + { + Name = "RequestedCount", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_Count" + }, + new Argument + { + Name = "SortCriteria", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_SortCriteria" + }, + new Argument + { + Name = "Result", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Result" + }, + new Argument + { + Name = "NumberReturned", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Count" + }, + new Argument + { + Name = "TotalMatches", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Count" + }, + new Argument + { + Name = "UpdateID", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_UpdateID" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "ContainerID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ObjectID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "SearchCriteria", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_SearchCriteria" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Filter", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Filter" - }); - - action.ArgumentList.Add(new Argument - { - Name = "StartingIndex", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Index" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RequestedCount", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "SortCriteria", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_SortCriteria" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Result", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Result" - }); - - action.ArgumentList.Add(new Argument - { - Name = "NumberReturned", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "TotalMatches", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "UpdateID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_UpdateID" - }); - return action; } @@ -203,79 +199,71 @@ private static ServiceAction GetBrowseAction() { var action = new ServiceAction { - Name = "Browse" + Name = "Browse", + ArgumentList = [ + new Argument + { + Name = "ObjectID", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_ObjectID" + }, + new Argument + { + Name = "BrowseFlag", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_BrowseFlag" + }, + new Argument + { + Name = "Filter", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_Filter" + }, + new Argument + { + Name = "StartingIndex", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_Index" + }, + new Argument + { + Name = "RequestedCount", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_Count" + }, + new Argument + { + Name = "SortCriteria", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_SortCriteria" + }, + new Argument + { + Name = "Result", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Result" + }, + new Argument + { + Name = "NumberReturned", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Count" + }, + new Argument + { + Name = "TotalMatches", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Count" + }, + new Argument + { + Name = "UpdateID", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_UpdateID" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "ObjectID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ObjectID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "BrowseFlag", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_BrowseFlag" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Filter", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Filter" - }); - - action.ArgumentList.Add(new Argument - { - Name = "StartingIndex", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Index" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RequestedCount", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "SortCriteria", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_SortCriteria" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Result", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Result" - }); - - action.ArgumentList.Add(new Argument - { - Name = "NumberReturned", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "TotalMatches", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "UpdateID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_UpdateID" - }); - return action; } @@ -287,86 +275,77 @@ private static ServiceAction GetBrowseByLetterAction() { var action = new ServiceAction { - Name = "X_BrowseByLetter" + Name = "X_BrowseByLetter", + ArgumentList = [ + new Argument + { + Name = "ObjectID", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_ObjectID" + }, + new Argument + { + Name = "BrowseFlag", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_BrowseFlag" + }, + new Argument + { + Name = "Filter", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_Filter" + }, + new Argument + { + Name = "StartingLetter", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_BrowseLetter" + }, + new Argument + { + Name = "RequestedCount", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_Count" + }, + new Argument + { + Name = "SortCriteria", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_SortCriteria" + }, + new Argument + { + Name = "Result", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Result" + }, + new Argument + { + Name = "NumberReturned", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Count" + }, + new Argument + { + Name = "TotalMatches", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Count" + }, + new Argument + { + Name = "UpdateID", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_UpdateID" + }, + new Argument + { + Name = "StartingIndex", + Direction = "out", + RelatedStateVariable = "A_ARG_TYPE_Index" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "ObjectID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ObjectID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "BrowseFlag", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_BrowseFlag" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Filter", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Filter" - }); - - action.ArgumentList.Add(new Argument - { - Name = "StartingLetter", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_BrowseLetter" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RequestedCount", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "SortCriteria", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_SortCriteria" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Result", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Result" - }); - - action.ArgumentList.Add(new Argument - { - Name = "NumberReturned", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "TotalMatches", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "UpdateID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_UpdateID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "StartingIndex", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Index" - }); - return action; } @@ -378,37 +357,35 @@ private static ServiceAction GetXSetBookmarkAction() { var action = new ServiceAction { - Name = "X_SetBookmark" + Name = "X_SetBookmark", + ArgumentList = [ + new Argument + { + Name = "CategoryType", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_CategoryType" + }, + new Argument + { + Name = "RID", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_RID" + }, + new Argument + { + Name = "ObjectID", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_ObjectID" + }, + new Argument + { + Name = "PosSecond", + Direction = "in", + RelatedStateVariable = "A_ARG_TYPE_PosSec" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "CategoryType", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_CategoryType" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_RID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "ObjectID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ObjectID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "PosSecond", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_PosSec" - }); - return action; } } diff --git a/src/Jellyfin.Plugin.Dlna/ContentDirectory/StubType.cs b/src/Jellyfin.Plugin.Dlna/ContentDirectory/StubType.cs index b1964d8..edc20d6 100644 --- a/src/Jellyfin.Plugin.Dlna/ContentDirectory/StubType.cs +++ b/src/Jellyfin.Plugin.Dlna/ContentDirectory/StubType.cs @@ -1,29 +1,102 @@ -#pragma warning disable CS1591 - namespace Jellyfin.Plugin.Dlna.ContentDirectory; /// -/// Defines the DLNA item types. +/// DLNA item types. /// public enum StubType { + /// + /// Folder stub. + /// Folder = 0, + + /// + /// Latest stub. + /// Latest = 2, + + /// + /// Playlists stub. + /// Playlists = 3, + + /// + /// Albums stub. + /// Albums = 4, + + /// + /// AlbumArtists stub. + /// AlbumArtists = 5, + + /// + /// Artists stub. + /// Artists = 6, + + /// + /// Songs stub. + /// Songs = 7, + + /// + /// Genres stub. + /// Genres = 8, + + /// + /// FavoriteSongs stub. + /// FavoriteSongs = 9, + + /// + /// FavoriteArtists stub. + /// FavoriteArtists = 10, + + /// + /// FavoriteAlbums stub. + /// FavoriteAlbums = 11, + + /// + /// ContinueWatching stub. + /// ContinueWatching = 12, + + /// + /// Movies stub. + /// Movies = 13, + + /// + /// Collections stub. + /// Collections = 14, + + /// + /// Favorites stub. + /// Favorites = 15, + + /// + /// NextUp stub. + /// NextUp = 16, + + /// + /// Series stub. + /// Series = 17, + + /// + /// FavoriteSeries stub. + /// FavoriteSeries = 18, + + /// + /// FavoriteEpisodes stub. + /// FavoriteEpisodes = 19 } diff --git a/src/Jellyfin.Plugin.Dlna/ControlRequest.cs b/src/Jellyfin.Plugin.Dlna/ControlRequest.cs index 5739c26..847a247 100644 --- a/src/Jellyfin.Plugin.Dlna/ControlRequest.cs +++ b/src/Jellyfin.Plugin.Dlna/ControlRequest.cs @@ -1,24 +1,41 @@ #nullable disable -#pragma warning disable CS1591 - using System.IO; using Microsoft.AspNetCore.Http; namespace Jellyfin.Plugin.Dlna; +/// +/// Defines the . +/// public class ControlRequest { + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. public ControlRequest(IHeaderDictionary headers) { Headers = headers; } + /// + /// Gets the instance. + /// public IHeaderDictionary Headers { get; } + /// + /// Gets or sets the . + /// public Stream InputXml { get; set; } + /// + /// Gets or sets the target server UUID. + /// public string TargetServerUuId { get; set; } + /// + /// Gets or sets the request URL. + /// public string RequestedUrl { get; set; } } diff --git a/src/Jellyfin.Plugin.Dlna/ControlResponse.cs b/src/Jellyfin.Plugin.Dlna/ControlResponse.cs index 41d8d14..4650425 100644 --- a/src/Jellyfin.Plugin.Dlna/ControlResponse.cs +++ b/src/Jellyfin.Plugin.Dlna/ControlResponse.cs @@ -1,11 +1,17 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; namespace Jellyfin.Plugin.Dlna; +/// +/// Defines the . +/// public class ControlResponse { + /// + /// Initializes a new instance of the class. + /// + /// The XML. + /// A value indicating wether the triggering action is successful or not. public ControlResponse(string xml, bool isSuccessful) { Headers = new Dictionary(); @@ -13,10 +19,19 @@ public ControlResponse(string xml, bool isSuccessful) IsSuccessful = isSuccessful; } + /// + /// Gets the headers dictionary. + /// public IDictionary Headers { get; } + /// + /// Gets or sets the XML. + /// public string Xml { get; set; } + /// + /// Gets or sets a value indicating whether the triggering action is successful. + /// public bool IsSuccessful { get; set; } /// diff --git a/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs b/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs index 0d33267..1ef685d 100644 --- a/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs +++ b/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Globalization; using System.IO; @@ -24,7 +22,6 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; -using DeviceProfile = MediaBrowser.Model.Dlna.DeviceProfile; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Genre = MediaBrowser.Controller.Entities.Genre; using MediaOptions = MediaBrowser.Model.Dlna.MediaOptions; @@ -40,6 +37,9 @@ namespace Jellyfin.Plugin.Dlna.Didl; +/// +/// Defines the . +/// public class DidlBuilder { private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; @@ -59,6 +59,20 @@ public class DidlBuilder private readonly IMediaEncoder _mediaEncoder; private readonly ILibraryManager _libraryManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// Instance of the interface. + /// The server address. + /// The access token. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. public DidlBuilder( DlnaDeviceProfile profile, User? user, @@ -85,11 +99,24 @@ public DidlBuilder( _libraryManager = libraryManager; } + /// + /// Gets the normalized DLNA media URL. + /// The URL to normalize. + /// public static string NormalizeDlnaMediaUrl(string url) { return url + "&dlnaheaders=true"; } + /// + /// Gets the item DIDL. + /// The . + /// The . + /// The context. + /// The device id. + /// The . + /// The . + /// public string GetItemDidl(BaseItem item, User? user, BaseItem? context, string deviceId, Filter filter, StreamInfo streamInfo) { var settings = new XmlWriterSettings @@ -126,6 +153,11 @@ public string GetItemDidl(BaseItem item, User? user, BaseItem? context, string d } } + /// + /// Writes XML attributes of a profile the item DIDL. + /// The . + /// The . + /// public static void WriteXmlRootAttributes(DlnaDeviceProfile profile, XmlWriter writer) { foreach (var att in profile.XmlRootAttributes) @@ -142,6 +174,17 @@ public static void WriteXmlRootAttributes(DlnaDeviceProfile profile, XmlWriter w } } + /// + /// Writes an XML item element. + /// The . + /// The . + /// The . + /// The context. + /// The of the context. + /// The device id. + /// The . + /// The . + /// public void WriteItemElement( XmlWriter writer, BaseItem item, @@ -218,8 +261,8 @@ private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, var contentFeatureList = ContentFeatureBuilder.BuildVideoHeader( _profile, streamInfo.Container, - streamInfo.TargetVideoCodec.FirstOrDefault(), - streamInfo.TargetAudioCodec.FirstOrDefault(), + streamInfo.TargetVideoCodec[0], + streamInfo.TargetAudioCodec[0], targetWidth, targetHeight, streamInfo.TargetVideoBitDepth, @@ -476,13 +519,13 @@ private string GetEpisodeDisplayName(Episode episode, BaseItem? context) // inside a season use simple format (ex. '12 - Episode Name') var epNumberName = GetEpisodeIndexFullName(episode); - components = new[] { epNumberName, episode.Name }; + components = [epNumberName, episode.Name]; } else { // outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name') var epNumberName = GetEpisodeNumberDisplayName(episode); - components = new[] { episode.SeriesName, epNumberName, episode.Name }; + components = [episode.SeriesName, epNumberName, episode.Name]; } return string.Join(" - ", components.Where(NotNullOrWhiteSpace)); @@ -493,7 +536,7 @@ private string GetEpisodeDisplayName(Episode episode, BaseItem? context) /// /// The episode. /// For single episodes returns just the number. For double episodes - current and ending numbers. - private string GetEpisodeIndexFullName(Episode episode) + private static string GetEpisodeIndexFullName(Episode episode) { var name = string.Empty; if (episode.IndexNumber.HasValue) @@ -509,11 +552,6 @@ private string GetEpisodeIndexFullName(Episode episode) return name; } - /// - /// Gets episode number formatted as 'S##E##'. - /// - /// The episode. - /// Formatted episode number. private string GetEpisodeNumberDisplayName(Episode episode) { var name = string.Empty; @@ -611,7 +649,7 @@ private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, var contentFeatures = ContentFeatureBuilder.BuildAudioHeader( _profile, - streamInfo.Container, + streamInfo.Container?.FirstOrDefault().ToString(), streamInfo.TargetAudioCodec.FirstOrDefault(), targetAudioBitrate, targetSampleRate, @@ -634,13 +672,28 @@ private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, writer.WriteFullEndElement(); } + /// + /// Gets a value indicating whether the id is a root id. + /// + /// The id. + /// true if the id is a root id; otherwise, false. public static bool IsIdRoot(string id) => string.IsNullOrWhiteSpace(id) || string.Equals(id, "0", StringComparison.OrdinalIgnoreCase) // Samsung sometimes uses 1 as root || string.Equals(id, "1", StringComparison.OrdinalIgnoreCase); - public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string? requestedId = null) + /// + /// Writes an XML folder element. + /// The . + /// The . + /// The . + /// The context. + /// The child count. + /// The . + /// The request id. + /// + public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem? context, int childCount, Filter filter, string? requestedId = null) { writer.WriteStartElement(string.Empty, "container", NsDidl); @@ -720,9 +773,6 @@ private void AddSamsungBookmarkInfo(BaseItem item, User? user, XmlWriter writer, } } - /// - /// Adds fields used by both items and folders. - /// private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem? context, XmlWriter writer, Filter filter) { // Don't filter on dc:title because not all devices will include it in the filter @@ -1123,7 +1173,7 @@ private void AddImageResElement( return null; } - private BaseItem? GetFirstParentWithImageBelowUserRoot(BaseItem item) + private static BaseItem? GetFirstParentWithImageBelowUserRoot(BaseItem item) { if (item is null) { @@ -1194,11 +1244,23 @@ private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type) }; } + /// + /// Gets the client id of an based on the . + /// + /// The . + /// Current . + /// The client id public static string GetClientId(BaseItem item, StubType? stubType) { return GetClientId(item.Id, stubType); } + /// + /// Gets the client id of an based on the . + /// + /// The . + /// Current . + /// The client id public static string GetClientId(Guid idValue, StubType? stubType) { var id = idValue.ToString("N", CultureInfo.InvariantCulture); @@ -1245,13 +1307,13 @@ public static string GetClientId(Guid idValue, StubType? stubType) } } - // just lie + // Just lie info.IsDirectStream = true; return (url, width, height); } - private class ImageDownloadInfo + private sealed class ImageDownloadInfo { internal Guid ItemId { get; set; } diff --git a/src/Jellyfin.Plugin.Dlna/Didl/Filter.cs b/src/Jellyfin.Plugin.Dlna/Didl/Filter.cs index 4f20451..9173dbd 100644 --- a/src/Jellyfin.Plugin.Dlna/Didl/Filter.cs +++ b/src/Jellyfin.Plugin.Dlna/Didl/Filter.cs @@ -1,25 +1,37 @@ -#pragma warning disable CS1591 - using System; namespace Jellyfin.Plugin.Dlna.Didl; +/// +/// Defines the . +/// public class Filter { private readonly string[] _fields; private readonly bool _all; + /// + /// Initializes a new instance of the class. + /// public Filter() : this("*") { } - + /// + /// Initializes a new instance of the class. + /// + /// The Filter. public Filter(string filter) { _all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase); _fields = filter.Split(',', StringSplitOptions.RemoveEmptyEntries); } + /// + /// Gets a value indicating whether the filter contains a field. + /// + /// The field to check. + /// true if this filter contains the field; otherwise, false. public bool Contains(string field) { return _all || Array.Exists(_fields, x => x.Equals(field, StringComparison.OrdinalIgnoreCase)); diff --git a/src/Jellyfin.Plugin.Dlna/Didl/StringWriterWithEncoding.cs b/src/Jellyfin.Plugin.Dlna/Didl/StringWriterWithEncoding.cs index b63b590..abeca40 100644 --- a/src/Jellyfin.Plugin.Dlna/Didl/StringWriterWithEncoding.cs +++ b/src/Jellyfin.Plugin.Dlna/Didl/StringWriterWithEncoding.cs @@ -1,57 +1,24 @@ -#pragma warning disable CS1591 -#pragma warning disable CA1305 - -using System; using System.IO; using System.Text; namespace Jellyfin.Plugin.Dlna.Didl; +/// +/// Defines the . +/// public class StringWriterWithEncoding : StringWriter { private readonly Encoding? _encoding; - public StringWriterWithEncoding() - { - } - - public StringWriterWithEncoding(IFormatProvider formatProvider) - : base(formatProvider) - { - } - - public StringWriterWithEncoding(StringBuilder sb) - : base(sb) - { - } - - public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider) - : base(sb, formatProvider) - { - } - + /// + /// Initializes a new instance of the class. + /// + /// The . public StringWriterWithEncoding(Encoding encoding) { _encoding = encoding; } - public StringWriterWithEncoding(IFormatProvider formatProvider, Encoding encoding) - : base(formatProvider) - { - _encoding = encoding; - } - - public StringWriterWithEncoding(StringBuilder sb, Encoding encoding) - : base(sb) - { - _encoding = encoding; - } - - public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider, Encoding encoding) - : base(sb, formatProvider) - { - _encoding = encoding; - } - + /// public override Encoding Encoding => _encoding ?? base.Encoding; } diff --git a/src/Jellyfin.Plugin.Dlna/DlnaManager.cs b/src/Jellyfin.Plugin.Dlna/DlnaManager.cs index 82ba152..47d2115 100644 --- a/src/Jellyfin.Plugin.Dlna/DlnaManager.cs +++ b/src/Jellyfin.Plugin.Dlna/DlnaManager.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -17,9 +15,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Drawing; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using Microsoft.AspNetCore.Http; @@ -29,6 +25,9 @@ namespace Jellyfin.Plugin.Dlna; +/// +/// Defines the . +/// public class DlnaManager : IDlnaManager { private readonly IApplicationPaths _appPaths; @@ -42,6 +41,14 @@ public class DlnaManager : IDlnaManager private readonly Dictionary> _profiles = new(StringComparer.Ordinal); + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. public DlnaManager( IXmlSerializer xmlSerializer, IFileSystem fileSystem, @@ -58,8 +65,11 @@ public DlnaManager( private string UserProfilesPath => Path.Combine(_appPaths.PluginConfigurationsPath, "dlna", "user"); - private string SystemProfilesPath => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "profiles"); + private static string SystemProfilesPath => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "profiles"); + /// + /// Initializes the profiles asynchronously. + /// public async Task InitProfilesAsync() { try @@ -87,6 +97,9 @@ private void LoadProfiles() .OrderBy(i => i.Name)); } + /// + /// Gets the profiles. + /// public IEnumerable GetProfiles() { lock (_profiles) @@ -196,7 +209,7 @@ private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo return profileInfo.Headers.Any(i => IsMatch(headers, i)); } - private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header) + private static bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header) { // Handle invalid user setup if (string.IsNullOrEmpty(header.Name)) @@ -216,7 +229,7 @@ private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header) case HeaderMatchType.Equals: return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase); case HeaderMatchType.Substring: - var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; + var isMatch = value.ToString().Contains(header.Value, StringComparison.OrdinalIgnoreCase); // _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch); return isMatch; case HeaderMatchType.Regex: @@ -396,7 +409,7 @@ public void UpdateProfile(string profileId, DlnaDeviceProfile profile) if (profile.Id.IsNullOrEmpty()) { - throw new ArgumentException("Profile id cannot be empty.", nameof(profile.Id)); + throw new ArgumentException("Profile id cannot be empty. ProfileId: {Id}", nameof(profile)); } ArgumentException.ThrowIfNullOrEmpty(profile.Name); @@ -472,7 +485,7 @@ public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUu return _assembly.GetManifestResourceStream(resource); } - private class InternalProfileInfo + private sealed class InternalProfileInfo { internal InternalProfileInfo(DeviceProfileInfo info, string path) { diff --git a/src/Jellyfin.Plugin.Dlna/DlnaPlugin.cs b/src/Jellyfin.Plugin.Dlna/DlnaPlugin.cs index 697debd..25622d2 100644 --- a/src/Jellyfin.Plugin.Dlna/DlnaPlugin.cs +++ b/src/Jellyfin.Plugin.Dlna/DlnaPlugin.cs @@ -13,8 +13,16 @@ namespace Jellyfin.Plugin.Dlna; /// public class DlnaPlugin : BasePlugin, IHasWebPages { + /// + /// The instance. + /// public static DlnaPlugin Instance { get; private set; } = null!; + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. public DlnaPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { @@ -29,12 +37,12 @@ public DlnaPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializ /// public override string Description => "Use Jellyfin as a DLNA server."; - + /// public IEnumerable GetPages() { - return new[] - { + return + [ new PluginPageInfo { Name = "dlna", @@ -46,6 +54,6 @@ public IEnumerable GetPages() Name = "dlnajs", EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.js" }, - }; + ]; } -} \ No newline at end of file +} diff --git a/src/Jellyfin.Plugin.Dlna/DlnaServiceRegistrator.cs b/src/Jellyfin.Plugin.Dlna/DlnaServiceRegistrator.cs index e4fa2d8..05a8cb8 100644 --- a/src/Jellyfin.Plugin.Dlna/DlnaServiceRegistrator.cs +++ b/src/Jellyfin.Plugin.Dlna/DlnaServiceRegistrator.cs @@ -19,11 +19,15 @@ namespace Jellyfin.Plugin.Dlna; +/// +/// Defines the . +/// public class DlnaServiceRegistrator : IPluginServiceRegistrator { - public void RegisterServices(IServiceCollection services, IServerApplicationHost applicationHost) + /// + public void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost) { - services.AddHttpClient(NamedClient.Dlna, c => + serviceCollection.AddHttpClient(NamedClient.Dlna, c => { c.DefaultRequestHeaders.UserAgent.ParseAdd( string.Format( @@ -43,22 +47,22 @@ public void RegisterServices(IServiceCollection services, IServerApplicationHost RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8 }); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - services.AddScoped(); - services.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); - services.AddSingleton(provider => new SsdpCommunicationsServer( + serviceCollection.AddSingleton(provider => new SsdpCommunicationsServer( provider.GetRequiredService(), provider.GetRequiredService>()) { IsShared = true }); - services.AddHostedService(); + serviceCollection.AddHostedService(); } -} \ No newline at end of file +} diff --git a/src/Jellyfin.Plugin.Dlna/EventSubscriptionResponse.cs b/src/Jellyfin.Plugin.Dlna/EventSubscriptionResponse.cs index 601efef..440e67c 100644 --- a/src/Jellyfin.Plugin.Dlna/EventSubscriptionResponse.cs +++ b/src/Jellyfin.Plugin.Dlna/EventSubscriptionResponse.cs @@ -1,21 +1,36 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; namespace Jellyfin.Plugin.Dlna; +/// +/// Defines the . +/// public class EventSubscriptionResponse { + /// + /// Initializes a new instance of the class. + /// + /// The content. + /// The content type. public EventSubscriptionResponse(string content, string contentType) { Content = content; ContentType = contentType; - Headers = new Dictionary(); + Headers = []; } + /// + /// Gets or sets the content. + /// public string Content { get; set; } + /// + /// Gets or sets the content type. + /// public string ContentType { get; set; } + /// + /// Gets the headers dictionary. + /// public Dictionary Headers { get; } } diff --git a/src/Jellyfin.Plugin.Dlna/Eventing/DlnaEventManager.cs b/src/Jellyfin.Plugin.Dlna/Eventing/DlnaEventManager.cs index f69249f..346ccc0 100644 --- a/src/Jellyfin.Plugin.Dlna/Eventing/DlnaEventManager.cs +++ b/src/Jellyfin.Plugin.Dlna/Eventing/DlnaEventManager.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -18,24 +14,40 @@ namespace Jellyfin.Plugin.Dlna.Eventing; +/// +/// Defines the . +/// public class DlnaEventManager : IDlnaEventManager { private readonly ConcurrentDictionary _subscriptions = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + new(StringComparer.OrdinalIgnoreCase); private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; _logger = logger; } - public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl) + /// + /// Renews an event subscription. + /// + /// The subscription id. + /// The notification type. + /// The requested timeout string. + /// The callback URL. + /// EventSubscriptionResponse. + public EventSubscriptionResponse RenewEventSubscription(string? subscriptionId, string? notificationType, string? requestedTimeoutString, string? callbackUrl) { var subscription = GetSubscription(subscriptionId, false); - if (subscription is not null) + if (subscription is not null && subscriptionId is not null) { subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300; int timeoutSeconds = subscription.TimeoutSeconds; @@ -53,7 +65,14 @@ public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, s return new EventSubscriptionResponse(string.Empty, "text/plain"); } - public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl) + /// + /// Creates an event subscription. + /// + /// The notification type. + /// The requested timeout string. + /// The callback URL. + /// EventSubscriptionResponse. + public EventSubscriptionResponse CreateEventSubscription(string? notificationType, string? requestedTimeoutString, string? callbackUrl) { var timeout = ParseTimeout(requestedTimeoutString) ?? 300; var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); @@ -76,7 +95,7 @@ public EventSubscriptionResponse CreateEventSubscription(string notificationType return GetEventSubscriptionResponse(id, requestedTimeoutString, timeout); } - private int? ParseTimeout(string header) + private static int? ParseTimeout(string? header) { if (!string.IsNullOrEmpty(header)) { @@ -90,16 +109,24 @@ public EventSubscriptionResponse CreateEventSubscription(string notificationType return null; } - public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) + /// + /// Cancels the event subscription of an subscriptionId. + /// + /// The subscription id. + /// EventSubscriptionResponse. + public EventSubscriptionResponse CancelEventSubscription(string? subscriptionId) { _logger.LogDebug("Cancelling event subscription {0}", subscriptionId); - _subscriptions.TryRemove(subscriptionId, out _); + if (subscriptionId is not null) + { + _subscriptions.TryRemove(subscriptionId, out _); + } return new EventSubscriptionResponse(string.Empty, "text/plain"); } - private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds) + private static EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string? requestedTimeoutString, int timeoutSeconds) { var response = new EventSubscriptionResponse(string.Empty, "text/plain"); @@ -109,14 +136,19 @@ private EventSubscriptionResponse GetEventSubscriptionResponse(string subscripti return response; } - public EventSubscription GetSubscription(string id) + /// + /// Gets the subscription of an id. + /// + /// The id. + /// EventSubscription. + public EventSubscription? GetSubscription(string id) { return GetSubscription(id, false); } - private EventSubscription GetSubscription(string id, bool throwOnMissing) + private EventSubscription? GetSubscription(string? id, bool throwOnMissing) { - if (!_subscriptions.TryGetValue(id, out EventSubscription e) && throwOnMissing) + if (id is null || !_subscriptions.TryGetValue(id, out var e) && throwOnMissing) { throw new ResourceNotFoundException("Event with Id " + id + " not found."); } @@ -124,6 +156,12 @@ private EventSubscription GetSubscription(string id, bool throwOnMissing) return e; } + /// + /// Triggers an event. + /// + /// The notification type. + /// The state variables. + /// Task. public Task TriggerEvent(string notificationType, IDictionary stateVariables) { var subs = _subscriptions.Values diff --git a/src/Jellyfin.Plugin.Dlna/Eventing/EventSubscription.cs b/src/Jellyfin.Plugin.Dlna/Eventing/EventSubscription.cs index 3c080d1..a71d9f1 100644 --- a/src/Jellyfin.Plugin.Dlna/Eventing/EventSubscription.cs +++ b/src/Jellyfin.Plugin.Dlna/Eventing/EventSubscription.cs @@ -1,27 +1,51 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; namespace Jellyfin.Plugin.Dlna.Eventing; +/// +/// Defines the . +/// public class EventSubscription { - public string Id { get; set; } - - public string CallbackUrl { get; set; } - - public string NotificationType { get; set; } - + /// + /// Gets the id. + /// + public string? Id { get; set; } + + /// + /// Gets the callback URL. + /// + public string? CallbackUrl { get; set; } + + /// + /// Gets the notification type. + /// + public string? NotificationType { get; set; } + + /// + /// Gets the subscription time. + /// public DateTime SubscriptionTime { get; set; } + /// + /// Gets the timeout seconds. + /// public int TimeoutSeconds { get; set; } + /// + /// Gets the trigger count. + /// public long TriggerCount { get; set; } + /// + /// Gets or sets a value indicating whether this instance is expired. + /// + /// true if this instance is expired; otherwise, false. public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow; + /// + /// Increments the trigger count. + /// public void IncrementTriggerCount() { if (TriggerCount == long.MaxValue) diff --git a/src/Jellyfin.Plugin.Dlna/Extensions/StreamInfoExtensions.cs b/src/Jellyfin.Plugin.Dlna/Extensions/StreamInfoExtensions.cs index 101998d..0ab20d3 100644 --- a/src/Jellyfin.Plugin.Dlna/Extensions/StreamInfoExtensions.cs +++ b/src/Jellyfin.Plugin.Dlna/Extensions/StreamInfoExtensions.cs @@ -7,8 +7,18 @@ namespace Jellyfin.Plugin.Dlna.Extensions; +/// +/// Extensions for . +/// public static class StreamInfoExtensions { + /// + /// Get the DLNA URL. + /// + /// The . + /// The base URL. + /// The access token. + /// User id. public static string ToDlnaUrl(this StreamInfo streamInfo, string baseUrl, string? accessToken) { ArgumentException.ThrowIfNullOrEmpty(baseUrl); @@ -77,7 +87,7 @@ private static string GetUrl(StreamInfo streamInfo, string baseUrl, string query return string.Format(CultureInfo.InvariantCulture, "{0}/dlna/videos/{1}/stream{2}?{3}", baseUrl, itemId, extension, queryString); } - private static IEnumerable BuildParams(StreamInfo item, string? accessToken) + private static List BuildParams(StreamInfo item, string? accessToken) { var list = new List(); diff --git a/src/Jellyfin.Plugin.Dlna/IConnectionManager.cs b/src/Jellyfin.Plugin.Dlna/IConnectionManager.cs index 8618c2c..042d2bc 100644 --- a/src/Jellyfin.Plugin.Dlna/IConnectionManager.cs +++ b/src/Jellyfin.Plugin.Dlna/IConnectionManager.cs @@ -1,7 +1,8 @@ -#pragma warning disable CS1591 - namespace Jellyfin.Plugin.Dlna; +/// +/// Defines the interface. +/// public interface IConnectionManager : IDlnaEventManager, IUpnpService { } diff --git a/src/Jellyfin.Plugin.Dlna/IContentDirectory.cs b/src/Jellyfin.Plugin.Dlna/IContentDirectory.cs index 9f0e478..370195c 100644 --- a/src/Jellyfin.Plugin.Dlna/IContentDirectory.cs +++ b/src/Jellyfin.Plugin.Dlna/IContentDirectory.cs @@ -1,7 +1,8 @@ -#pragma warning disable CS1591 - namespace Jellyfin.Plugin.Dlna; +/// +/// Defines the interface. +/// public interface IContentDirectory : IDlnaEventManager, IUpnpService { } diff --git a/src/Jellyfin.Plugin.Dlna/IDlnaEventManager.cs b/src/Jellyfin.Plugin.Dlna/IDlnaEventManager.cs index a4d4017..128f88d 100644 --- a/src/Jellyfin.Plugin.Dlna/IDlnaEventManager.cs +++ b/src/Jellyfin.Plugin.Dlna/IDlnaEventManager.cs @@ -1,8 +1,8 @@ -#nullable disable -#pragma warning disable CS1591 - namespace Jellyfin.Plugin.Dlna; +/// +/// Defines the interface. +/// public interface IDlnaEventManager { /// @@ -10,7 +10,7 @@ public interface IDlnaEventManager /// /// The subscription identifier. /// The response. - EventSubscriptionResponse CancelEventSubscription(string subscriptionId); + EventSubscriptionResponse CancelEventSubscription(string? subscriptionId); /// /// Renews the event subscription. @@ -20,7 +20,7 @@ public interface IDlnaEventManager /// The requested timeout as a string. /// The callback url. /// The response. - EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl); + EventSubscriptionResponse RenewEventSubscription(string? subscriptionId, string? notificationType, string? requestedTimeoutString, string? callbackUrl); /// /// Creates the event subscription. @@ -29,5 +29,5 @@ public interface IDlnaEventManager /// The requested timeout as a string. /// The callback url. /// The response. - EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl); + EventSubscriptionResponse CreateEventSubscription(string? notificationType, string? requestedTimeoutString, string? callbackUrl); } diff --git a/src/Jellyfin.Plugin.Dlna/IMediaReceiverRegistrar.cs b/src/Jellyfin.Plugin.Dlna/IMediaReceiverRegistrar.cs index 86f002d..aeb646c 100644 --- a/src/Jellyfin.Plugin.Dlna/IMediaReceiverRegistrar.cs +++ b/src/Jellyfin.Plugin.Dlna/IMediaReceiverRegistrar.cs @@ -1,7 +1,8 @@ -#pragma warning disable CS1591 - namespace Jellyfin.Plugin.Dlna; +/// +/// Defines the interface. +/// public interface IMediaReceiverRegistrar : IDlnaEventManager, IUpnpService { } diff --git a/src/Jellyfin.Plugin.Dlna/IUpnpService.cs b/src/Jellyfin.Plugin.Dlna/IUpnpService.cs index da8589e..25f7da2 100644 --- a/src/Jellyfin.Plugin.Dlna/IUpnpService.cs +++ b/src/Jellyfin.Plugin.Dlna/IUpnpService.cs @@ -1,9 +1,10 @@ -#pragma warning disable CS1591 - using System.Threading.Tasks; namespace Jellyfin.Plugin.Dlna; +/// +/// Defines the interface. +/// public interface IUpnpService { /// diff --git a/src/Jellyfin.Plugin.Dlna/Jellyfin.Plugin.Dlna.csproj b/src/Jellyfin.Plugin.Dlna/Jellyfin.Plugin.Dlna.csproj index 6f6ca2d..dc00765 100644 --- a/src/Jellyfin.Plugin.Dlna/Jellyfin.Plugin.Dlna.csproj +++ b/src/Jellyfin.Plugin.Dlna/Jellyfin.Plugin.Dlna.csproj @@ -1,7 +1,11 @@ net8.0 + true + true enable + AllEnabledByDefault + ../../jellyfin.ruleset diff --git a/src/Jellyfin.Plugin.Dlna/Main/DlnaHost.cs b/src/Jellyfin.Plugin.Dlna/Main/DlnaHost.cs index 0374bb9..9f63e29 100644 --- a/src/Jellyfin.Plugin.Dlna/Main/DlnaHost.cs +++ b/src/Jellyfin.Plugin.Dlna/Main/DlnaHost.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA1031 // Do not catch general exception types. - using System; using System.Globalization; using System.Linq; @@ -270,7 +268,7 @@ private void RegisterServerEndpoints() uri.Scheme = "http://"; uri.Port = httpBindPort; - _logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress} with uri {fulluri}", fullService, intf.Address, uri); + _logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress} with uri {FullUri}", fullService, intf.Address, uri); var device = new SsdpRootDevice { diff --git a/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/ControlHandler.cs index d14ca9c..63736b9 100644 --- a/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -15,7 +15,7 @@ public class ControlHandler : BaseControlHandler /// /// Initializes a new instance of the class. /// - /// The for use with the instance. + /// The . public ControlHandler(ILogger logger) : base(logger) { diff --git a/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs b/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs index 9422c54..aaaba64 100644 --- a/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs +++ b/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs @@ -16,74 +16,64 @@ public static class MediaReceiverRegistrarXmlBuilder /// An XML representation of this service. public static string GetXml() { - return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); + return ServiceXmlBuilder.GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); } /// /// The a list of all the state variables for this invocation. /// /// The . - private static IEnumerable GetStateVariables() + private static IReadOnlyList GetStateVariables() { - var list = new List - { - new StateVariable - { + return + [ + new() { Name = "AuthorizationGrantedUpdateID", DataType = "ui4", SendsEvents = true }, - new StateVariable - { + new() { Name = "A_ARG_TYPE_DeviceID", DataType = "string", SendsEvents = false }, - new StateVariable - { + new() { Name = "AuthorizationDeniedUpdateID", DataType = "ui4", SendsEvents = true }, - new StateVariable - { + new() { Name = "ValidationSucceededUpdateID", DataType = "ui4", SendsEvents = true }, - new StateVariable - { + new() { Name = "A_ARG_TYPE_RegistrationRespMsg", DataType = "bin.base64", SendsEvents = false }, - new StateVariable - { + new() { Name = "A_ARG_TYPE_RegistrationReqMsg", DataType = "bin.base64", SendsEvents = false }, - new StateVariable - { + new() { Name = "ValidationRevokedUpdateID", DataType = "ui4", SendsEvents = true }, - new StateVariable - { + new() { Name = "A_ARG_TYPE_Result", DataType = "int", SendsEvents = false } - }; - - return list; + ]; } } diff --git a/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs b/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs index bd050c8..73f3985 100644 --- a/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs +++ b/src/Jellyfin.Plugin.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs @@ -14,8 +14,8 @@ public static class ServiceActionListBuilder /// An . public static IEnumerable GetActions() { - return new[] - { + return + [ GetIsValidated(), GetIsAuthorized(), GetRegisterDevice(), @@ -23,7 +23,7 @@ public static IEnumerable GetActions() GetGetAuthorizationGrantedUpdateID(), GetGetValidationRevokedUpdateID(), GetGetValidationSucceededUpdateID() - }; + ]; } /// @@ -34,21 +34,21 @@ private static ServiceAction GetIsValidated() { var action = new ServiceAction { - Name = "IsValidated" + Name = "IsValidated", + ArgumentList = [ + new Argument + { + Name = "DeviceID", + Direction = "in" + }, + new Argument + { + Name = "Result", + Direction = "out" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "DeviceID", - Direction = "in" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Result", - Direction = "out" - }); - return action; } @@ -60,21 +60,21 @@ private static ServiceAction GetIsAuthorized() { var action = new ServiceAction { - Name = "IsAuthorized" + Name = "IsAuthorized", + ArgumentList = [ + new Argument + { + Name = "DeviceID", + Direction = "in" + }, + new Argument + { + Name = "Result", + Direction = "out" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "DeviceID", - Direction = "in" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Result", - Direction = "out" - }); - return action; } @@ -86,21 +86,21 @@ private static ServiceAction GetRegisterDevice() { var action = new ServiceAction { - Name = "RegisterDevice" + Name = "RegisterDevice", + ArgumentList = [ + new Argument + { + Name = "RegistrationReqMsg", + Direction = "in" + }, + new Argument + { + Name = "RegistrationRespMsg", + Direction = "out" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "RegistrationReqMsg", - Direction = "in" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RegistrationRespMsg", - Direction = "out" - }); - return action; } @@ -112,15 +112,16 @@ private static ServiceAction GetGetValidationSucceededUpdateID() { var action = new ServiceAction { - Name = "GetValidationSucceededUpdateID" + Name = "GetValidationSucceededUpdateID", + ArgumentList = [ + new Argument + { + Name = "ValidationSucceededUpdateID", + Direction = "out" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "ValidationSucceededUpdateID", - Direction = "out" - }); - return action; } @@ -132,15 +133,16 @@ private static ServiceAction GetGetAuthorizationDeniedUpdateID() { var action = new ServiceAction { - Name = "GetAuthorizationDeniedUpdateID" + Name = "GetAuthorizationDeniedUpdateID", + ArgumentList = [ + new Argument + { + Name = "AuthorizationDeniedUpdateID", + Direction = "out" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "AuthorizationDeniedUpdateID", - Direction = "out" - }); - return action; } @@ -152,15 +154,16 @@ private static ServiceAction GetGetValidationRevokedUpdateID() { var action = new ServiceAction { - Name = "GetValidationRevokedUpdateID" + Name = "GetValidationRevokedUpdateID", + ArgumentList = [ + new Argument + { + Name = "ValidationRevokedUpdateID", + Direction = "out" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "ValidationRevokedUpdateID", - Direction = "out" - }); - return action; } @@ -172,15 +175,16 @@ private static ServiceAction GetGetAuthorizationGrantedUpdateID() { var action = new ServiceAction { - Name = "GetAuthorizationGrantedUpdateID" + Name = "GetAuthorizationGrantedUpdateID", + ArgumentList = [ + new Argument + { + Name = "AuthorizationGrantedUpdateID", + Direction = "out" + } + ] }; - action.ArgumentList.Add(new Argument - { - Name = "AuthorizationGrantedUpdateID", - Direction = "out" - }); - return action; } } diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/Device.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/Device.cs index da744c6..0def51f 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/Device.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/Device.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -16,13 +14,14 @@ namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public class Device : IDisposable { private readonly IHttpClientFactory _httpClientFactory; - private readonly ILogger _logger; - - private readonly object _timerLock = new object(); + private readonly object _timerLock = new(); private Timer? _timer; private int _muteVol; private int _volume; @@ -31,6 +30,12 @@ public class Device : IDisposable private int _connectFailureCount; private bool _disposed; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// Instance of the interface. + /// Instance of the interface. public Device(DeviceInfo deviceProperties, IHttpClientFactory httpClientFactory, ILogger logger) { Properties = deviceProperties; @@ -38,18 +43,39 @@ public Device(DeviceInfo deviceProperties, IHttpClientFactory httpClientFactory, _logger = logger; } + /// + /// Raised when playback starts. + /// public event EventHandler? PlaybackStart; + /// + /// Raised when playback progresses. + /// public event EventHandler? PlaybackProgress; + /// + /// Raised when playback stopped. + /// public event EventHandler? PlaybackStopped; + /// + /// Raised when media changed. + /// public event EventHandler? MediaChanged; + /// + /// Gets or sets the properties. + /// public DeviceInfo Properties { get; set; } + /// + /// Gets or sets a value indicating whether the device is muted. + /// public bool IsMuted { get; set; } + /// + /// Gets or sets the volume. + /// public int Volume { get @@ -61,26 +87,59 @@ public int Volume set => _volume = value; } + /// + /// Gets or sets the playback duration. + /// public TimeSpan? Duration { get; set; } + /// + /// Gets or sets the playback position. + /// public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0); + /// + /// Gets or sets the transport state. + /// public TransportState TransportState { get; private set; } + /// + /// Gets or sets a value indicating whether the device is playing. + /// public bool IsPlaying => TransportState == TransportState.PLAYING; + /// + /// Gets or sets a value indicating whether the device is paused. + /// public bool IsPaused => TransportState == TransportState.PAUSED_PLAYBACK; + /// + /// Gets or sets a value indicating whether the device is stopped. + /// public bool IsStopped => TransportState == TransportState.STOPPED; + /// + /// Gets or sets the action to be executed when the device becomes unavailable. + /// public Action? OnDeviceUnavailable { get; set; } + /// + /// Gets or sets the AV commands. + /// private TransportCommands? AvCommands { get; set; } + /// + /// Gets or sets the render commands. + /// private TransportCommands? RendererCommands { get; set; } + /// + /// Gets or sets the current media info. + /// public UBaseObject? CurrentMediaInfo { get; private set; } + /// + /// Starts the device. + /// public void Start() { _logger.LogDebug("Dlna Device.Start"); @@ -151,6 +210,9 @@ private void RestartTimerInactive() } } + /// + /// Lowers the volume. + /// public Task VolumeDown(CancellationToken cancellationToken) { var sendVolume = Math.Max(Volume - 5, 0); @@ -158,6 +220,9 @@ public Task VolumeDown(CancellationToken cancellationToken) return SetVolume(sendVolume, cancellationToken); } + /// + /// Rises the volume. + /// public Task VolumeUp(CancellationToken cancellationToken) { var sendVolume = Math.Min(Volume + 5, 100); @@ -165,6 +230,9 @@ public Task VolumeUp(CancellationToken cancellationToken) return SetVolume(sendVolume, cancellationToken); } + /// + /// Toggles mute. + /// public Task ToggleMute(CancellationToken cancellationToken) { if (IsMuted) @@ -175,6 +243,9 @@ public Task ToggleMute(CancellationToken cancellationToken) return Mute(cancellationToken); } + /// + /// Mutes the device. + /// public async Task Mute(CancellationToken cancellationToken) { var success = await SetMute(true, cancellationToken).ConfigureAwait(true); @@ -185,6 +256,9 @@ public async Task Mute(CancellationToken cancellationToken) } } + /// + /// Un-mutes the device. + /// public async Task Unmute(CancellationToken cancellationToken) { var success = await SetMute(false, cancellationToken).ConfigureAwait(true); @@ -279,6 +353,11 @@ public async Task SetVolume(int value, CancellationToken cancellationToken) .ConfigureAwait(false); } + /// + /// Seeks playback. + /// + /// The value to seek to. + /// The . public async Task Seek(TimeSpan value, CancellationToken cancellationToken) { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); @@ -302,6 +381,13 @@ public async Task Seek(TimeSpan value, CancellationToken cancellationToken) RestartTimer(true); } + /// + /// Sets AV transport. + /// + /// The URL. + /// The header. + /// The meta data. + /// The . public async Task SetAvTransport(string url, string? header, string metaData, CancellationToken cancellationToken) { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); @@ -349,10 +435,17 @@ public async Task SetAvTransport(string url, string? header, string metaData, Ca RestartTimer(true); } - /* - * SetNextAvTransport is used to specify to the DLNA device what is the next track to play. - * Without that information, the next track command on the device does not work. - */ + /// + /// Sets next AV transport. + /// + /// The URL. + /// The header. + /// The meta data. + /// The . + /// + /// SetNextAvTransport is used to specify to the DLNA device what is the next track to play. + /// Without that information, the next track command on the device does not work. + /// public async Task SetNextAvTransport(string url, string? header, string metaData, CancellationToken cancellationToken = default) { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); @@ -407,6 +500,10 @@ private Task SetPlay(TransportCommands avCommands, CancellationToken cancellatio cancellationToken: cancellationToken); } + /// + /// Sends play command. + /// + /// The . public async Task SetPlay(CancellationToken cancellationToken) { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); @@ -420,6 +517,10 @@ public async Task SetPlay(CancellationToken cancellationToken) RestartTimer(true); } + /// + /// Sends stop command. + /// + /// The . public async Task SetStop(CancellationToken cancellationToken) { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); @@ -443,6 +544,10 @@ public async Task SetStop(CancellationToken cancellationToken) RestartTimer(true); } + /// + /// Sends pause command. + /// + /// The . public async Task SetPause(CancellationToken cancellationToken) { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); @@ -863,7 +968,7 @@ private async Task GetMute(CancellationToken cancellationToken) return (true, uTrack); } - private XElement? ParseResponse(string xml) + private static XElement? ParseResponse(string xml) { // Handle different variations sent back by devices. try @@ -944,10 +1049,7 @@ private static string[] GetProtocolInfo(XElement container) return AvCommands; } - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } + ObjectDisposedException.ThrowIf(_disposed, GetType().Name); var avService = GetAvTransportService(); if (avService is null) @@ -976,10 +1078,7 @@ private static string[] GetProtocolInfo(XElement container) return RendererCommands; } - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } + ObjectDisposedException.ThrowIf(_disposed, GetType().Name); var avService = GetServiceRenderingControl(); ArgumentNullException.ThrowIfNull(avService); @@ -998,7 +1097,7 @@ private static string[] GetProtocolInfo(XElement container) return RendererCommands; } - private string NormalizeUrl(string baseUrl, string url) + private static string NormalizeUrl(string baseUrl, string url) { // If it's already a complete url, don't stick anything onto the front of it if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) @@ -1019,6 +1118,13 @@ private string NormalizeUrl(string baseUrl, string url) return baseUrl + url; } + /// + /// Creates uPNP device. + /// + /// The . + /// Instance of the interface. + /// Instance of the interface. + /// The . public static async Task CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken) { var ssdpHttpClient = new DlnaHttpClient(logger, httpClientFactory); @@ -1046,7 +1152,8 @@ private string NormalizeUrl(string baseUrl, string url) var deviceProperties = new DeviceInfo() { Name = string.Join(' ', friendlyNames), - BaseUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", url.Host, url.Port) + BaseUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", url.Host, url.Port), + Services = GetServices(document) }; var model = document.Descendants(UPnpNamespaces.Ud.GetName("modelName")).FirstOrDefault(); @@ -1109,30 +1216,6 @@ private string NormalizeUrl(string baseUrl, string url) deviceProperties.Icon = CreateIcon(icon); } - foreach (var services in document.Descendants(UPnpNamespaces.Ud.GetName("serviceList"))) - { - if (services is null) - { - continue; - } - - var servicesList = services.Descendants(UPnpNamespaces.Ud.GetName("service")); - if (servicesList is null) - { - continue; - } - - foreach (var element in servicesList) - { - var service = Create(element); - - if (service is not null) - { - deviceProperties.Services.Add(service); - } - } - } - return new Device(deviceProperties, httpClientFactory, logger); } @@ -1157,7 +1240,7 @@ private static DeviceIcon CreateIcon(XElement element) } private static DeviceService Create(XElement element) - => new DeviceService() + => new() { ControlUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL")) ?? string.Empty, EventSubUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL")) ?? string.Empty, @@ -1166,6 +1249,36 @@ private static DeviceService Create(XElement element) ServiceType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType")) ?? string.Empty }; + private static List GetServices(XDocument document) + { + List deviceServices = []; + foreach (var services in document.Descendants(UPnpNamespaces.Ud.GetName("serviceList"))) + { + if (services is null) + { + continue; + } + + var servicesList = services.Descendants(UPnpNamespaces.Ud.GetName("service")); + if (servicesList is null) + { + continue; + } + + foreach (var element in servicesList) + { + var service = Create(element); + + if (service is not null) + { + deviceServices.Add(service); + } + } + } + + return deviceServices; + } + private void UpdateMediaInfo(UBaseObject? mediaInfo, TransportState state) { TransportState = state; diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/DeviceInfo.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/DeviceInfo.cs index ec6ec51..93cf59f 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/DeviceInfo.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/DeviceInfo.cs @@ -1,53 +1,106 @@ -#nullable disable - -#pragma warning disable CS1591 - using System.Collections.Generic; using Jellyfin.Plugin.Dlna.Common; using Jellyfin.Plugin.Dlna.Model; namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public class DeviceInfo { - private readonly List _services = new List(); private string _baseUrl = string.Empty; + /// + /// Initializes a new instance of the class. + /// public DeviceInfo() { + UUID = string.Empty; Name = "Generic Device"; + ModelName = string.Empty; + ModelNumber = string.Empty; + ModelDescription = string.Empty; + ModelUrl = string.Empty; + Manufacturer = string.Empty; + ManufacturerUrl = string.Empty; + SerialNumber = string.Empty; + PresentationUrl = string.Empty; + Services = []; } + /// + /// Gets or sets the UUID. + /// public string UUID { get; set; } + /// + /// Gets or sets the name. + /// public string Name { get; set; } + /// + /// Gets or sets the model name. + /// public string ModelName { get; set; } + /// + /// Gets or sets the model number. + /// public string ModelNumber { get; set; } + /// + /// Gets or sets the model description. + /// public string ModelDescription { get; set; } + /// + /// Gets or sets the model URL. + /// public string ModelUrl { get; set; } + /// + /// Gets or sets the manufacturer. + /// public string Manufacturer { get; set; } - public string SerialNumber { get; set; } - + /// + /// Gets or sets the manufacturer URL. + /// public string ManufacturerUrl { get; set; } + /// + /// Gets or sets the serial number. + /// + public string SerialNumber { get; set; } + + /// + /// Gets or sets the presentation URL. + /// public string PresentationUrl { get; set; } + /// + /// Gets or sets the base URL. + /// public string BaseUrl { get => _baseUrl; set => _baseUrl = value; } - public DeviceIcon Icon { get; set; } + /// + /// Gets or sets the icon. + /// + public DeviceIcon? Icon { get; set; } - public List Services => _services; + /// + /// Gets or sets the device services. + /// + public IReadOnlyList Services { get; set; } + /// + /// Gets the . + /// public DeviceIdentification ToDeviceIdentification() { return new DeviceIdentification diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/DlnaHttpClient.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/DlnaHttpClient.cs index 0ee426a..f2b3762 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/DlnaHttpClient.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/DlnaHttpClient.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Globalization; using System.IO; @@ -25,6 +23,11 @@ public partial class DlnaHttpClient private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. public DlnaHttpClient(ILogger logger, IHttpClientFactory httpClientFactory) { _logger = logger; @@ -69,7 +72,7 @@ private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) { // try correcting the Xml response with common errors stream.Position = 0; - using StreamReader sr = new StreamReader(stream); + using StreamReader sr = new(stream); var xmlString = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false); // find and replace unescaped ampersands (&) @@ -95,6 +98,12 @@ private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) } } + /// + /// Gets data of a URL. + /// + /// The URL. + /// The . + /// Task. public async Task GetDataAsync(string url, CancellationToken cancellationToken) { using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -103,6 +112,16 @@ private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false); } + /// + /// Sends command async. + /// + /// The base URL. + /// The . + /// The command. + /// The POST data. + /// The header. + /// The . + /// Task. public async Task SendCommandAsync( string baseUrl, DeviceService service, diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/MediaChangedEventArgs.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/MediaChangedEventArgs.cs index 2861f59..542132e 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/MediaChangedEventArgs.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/MediaChangedEventArgs.cs @@ -1,18 +1,30 @@ -#pragma warning disable CS1591 - using System; namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public class MediaChangedEventArgs : EventArgs { + /// + /// Initializes a new instance of the class. + /// + /// The old media info . + /// The new media info . public MediaChangedEventArgs(UBaseObject oldMediaInfo, UBaseObject newMediaInfo) { OldMediaInfo = oldMediaInfo; NewMediaInfo = newMediaInfo; } + /// + /// Gets or sets the old media info. + /// public UBaseObject OldMediaInfo { get; set; } + /// + /// Gets or sets the new media info. + /// public UBaseObject NewMediaInfo { get; set; } } diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs index 93b53fe..6d923ad 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -31,7 +29,10 @@ namespace Jellyfin.Plugin.Dlna.PlayTo; -public class PlayToController2 : ISessionController, IDisposable +/// +/// Defines the . +/// +public class PlayToController : ISessionController, IDisposable { private readonly SessionInfo _session; private readonly ISessionManager _sessionManager; @@ -44,18 +45,33 @@ public class PlayToController2 : ISessionController, IDisposable private readonly ILocalizationManager _localization; private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaEncoder _mediaEncoder; - private readonly IDeviceDiscovery _deviceDiscovery; private readonly string _serverAddress; private readonly string? _accessToken; - - private readonly List _playlist = new List(); + private readonly List _playlist = []; private Device _device; private int _currentPlaylistIndex; - private bool _disposed; - public PlayToController2( + /// + /// Initializes a new instance of the class. + /// + /// The . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// The server address. + /// The access token. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// The . + public PlayToController( SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, @@ -99,8 +115,14 @@ public PlayToController2( _deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft; } + /// + /// Gets or sets a value indicating the session is active. + /// public bool IsSessionActive => !_disposed; + /// + /// Gets or sets a value indicating whether media control is supported. + /// public bool SupportsMediaControl => IsSessionActive; /* @@ -114,16 +136,21 @@ private async Task SendNextTrackMessage(int currentPlayListItemIndex, Cancellati var nextItemIndex = currentPlayListItemIndex + 1; var nextItem = _playlist[nextItemIndex]; + if (nextItem is null) + { + return; + } + // Send the SetNextAvTransport message. await _device.SetNextAvTransport(nextItem.StreamUrl, GetDlnaHeaders(nextItem), nextItem.Didl, cancellationToken).ConfigureAwait(false); } } - private void OnDeviceUnavailable() + private async void OnDeviceUnavailable() { try { - _sessionManager.ReportSessionEnded(_session.Id); + await _sessionManager.ReportSessionEnded(_session.Id).ConfigureAwait(false); } catch (Exception ex) { @@ -138,10 +165,10 @@ private void OnDeviceDiscoveryDeviceLeft(object? sender, GenericEventArgs + /// Sends a play command. + /// + /// The . + /// The . + /// Task. public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) { _logger.LogDebug("{0} - Received PlayRequest: {1}", _session.DeviceName, command.PlayCommand); @@ -466,12 +499,12 @@ private async Task Seek(long newPosition) } } - private bool EnableClientSideSeek(StreamParams info) + private static bool EnableClientSideSeek(StreamParams info) { return info.IsDirectStream; } - private bool EnableClientSideSeek(StreamInfo info) + private static bool EnableClientSideSeek(StreamInfo info) { return info.IsDirectStream; } @@ -479,7 +512,7 @@ private bool EnableClientSideSeek(StreamInfo info) private void AddItemFromId(Guid id, List list) { var item = _libraryManager.GetItemById(id); - if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video) + if (item?.MediaType == MediaType.Audio || item?.MediaType == MediaType.Video) { list.Add(item); } @@ -500,7 +533,7 @@ private PlaylistItem CreatePlaylistItem( var mediaSources = item is IHasMediaSources ? _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray() - : Array.Empty(); + : []; var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); playlistItem.StreamInfo.StartPositionTicks = startPostionTicks; @@ -526,7 +559,7 @@ private PlaylistItem CreatePlaylistItem( return playlistItem; } - private string? GetDlnaHeaders(PlaylistItem item) + private static string? GetDlnaHeaders(PlaylistItem item) { var profile = item.Profile; var streamInfo = item.StreamInfo; @@ -846,12 +879,9 @@ private static long GetLongValue(IReadOnlyDictionary values, str /// public Task SendMessage(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken) { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - - return name switch + return _disposed + ? throw new ObjectDisposedException(GetType().Name) + : name switch { SessionMessageType.Play => SendPlayCommand((data as PlayRequest)!, cancellationToken), SessionMessageType.Playstate => SendPlaystateCommand((data as PlaystateRequest)!, cancellationToken), @@ -860,7 +890,7 @@ public Task SendMessage(SessionMessageType name, Guid messageId, T data, Canc }; } - private class StreamParams + private sealed class StreamParams { private MediaSourceInfo? _mediaSource; private IMediaSourceManager? _mediaSourceManager; diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToManager.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToManager.cs index 5c61e6d..24f7246 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToManager.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToManager.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Globalization; using System.Linq; @@ -21,11 +19,13 @@ namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public sealed class PlayToManager : IDisposable { private readonly ILogger _logger; private readonly ISessionManager _sessionManager; - private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IDlnaManager _dlnaManager; @@ -34,16 +34,43 @@ public sealed class PlayToManager : IDisposable private readonly IHttpClientFactory _httpClientFactory; private readonly IUserDataManager _userDataManager; private readonly ILocalizationManager _localization; - private readonly IDeviceDiscovery _deviceDiscovery; private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaEncoder _mediaEncoder; - - private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); - private readonly CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); + private readonly SemaphoreSlim _sessionLock = new(1, 1); + private readonly CancellationTokenSource _disposeCancellationTokenSource = new(); private bool _disposed; - public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) + /// + /// Initializes a new instance of the class. + /// + /// The . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public PlayToManager( + ILogger logger, + ISessionManager sessionManager, + ILibraryManager libraryManager, + IUserManager userManager, + IDlnaManager dlnaManager, + IServerApplicationHost appHost, + IImageProcessor imageProcessor, + IDeviceDiscovery deviceDiscovery, + IHttpClientFactory httpClientFactory, + IUserDataManager userDataManager, + ILocalizationManager localization, + IMediaSourceManager mediaSourceManager, + IMediaEncoder mediaEncoder) { _logger = logger; _sessionManager = sessionManager; @@ -60,6 +87,9 @@ public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryMan _mediaEncoder = mediaEncoder; } + /// + /// Starts device discovery. + /// public void Start() { _deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered; @@ -102,7 +132,7 @@ private async void OnDeviceDiscoveryDeviceDiscovered(object? sender, GenericEven return; } - if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1)) + if (_sessionManager.Sessions.Any(i => usn.Contains(i.DeviceId, StringComparison.OrdinalIgnoreCase))) { return; } @@ -172,7 +202,7 @@ private async Task AddDevice(UpnpDeviceInfo info, CancellationToken cancellation .LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null) .ConfigureAwait(false); - var controller = sessionInfo.SessionControllers.OfType().FirstOrDefault(); + var controller = sessionInfo.SessionControllers.OfType().FirstOrDefault(); if (controller is null) { @@ -189,7 +219,7 @@ private async Task AddDevice(UpnpDeviceInfo info, CancellationToken cancellation string serverAddress = _appHost.GetSmartApiUrl(info.RemoteIPAddress); - controller = new PlayToController2( + controller = new PlayToController( sessionInfo, _sessionManager, _libraryManager, @@ -213,10 +243,10 @@ private async Task AddDevice(UpnpDeviceInfo info, CancellationToken cancellation _sessionManager.ReportCapabilities(sessionInfo.Id, new ClientCapabilities { - PlayableMediaTypes = profile.GetSupportedMediaTypes(), + PlayableMediaTypes = profile.FetchSupportedMediaTypes(), - SupportedCommands = new[] - { + SupportedCommands = + [ GeneralCommandType.VolumeDown, GeneralCommandType.VolumeUp, GeneralCommandType.Mute, @@ -226,7 +256,7 @@ private async Task AddDevice(UpnpDeviceInfo info, CancellationToken cancellation GeneralCommandType.SetAudioStreamIndex, GeneralCommandType.SetSubtitleStreamIndex, GeneralCommandType.PlayMediaSource - }, + ], SupportsMediaControl = true }); diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackProgressEventArgs.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackProgressEventArgs.cs index 6bea3b5..193cd96 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackProgressEventArgs.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackProgressEventArgs.cs @@ -1,15 +1,23 @@ -#pragma warning disable CS1591 - using System; namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public class PlaybackProgressEventArgs : EventArgs { + /// + /// Initializes a new instance of the class. + /// + /// The media info . public PlaybackProgressEventArgs(UBaseObject mediaInfo) { MediaInfo = mediaInfo; } + /// + /// Gets or sets the media info. + /// public UBaseObject MediaInfo { get; set; } } diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackStartEventArgs.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackStartEventArgs.cs index c8580d3..930a3fb 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackStartEventArgs.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackStartEventArgs.cs @@ -1,15 +1,23 @@ -#pragma warning disable CS1591 - using System; namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public class PlaybackStartEventArgs : EventArgs { + /// + /// Initializes a new instance of the class. + /// + /// The media info . public PlaybackStartEventArgs(UBaseObject mediaInfo) { MediaInfo = mediaInfo; } + /// + /// Gets or sets the media info. + /// public UBaseObject MediaInfo { get; set; } } diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackStoppedEventArgs.cs index b4aef04..fde3183 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackStoppedEventArgs.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/PlaybackStoppedEventArgs.cs @@ -1,15 +1,23 @@ -#pragma warning disable CS1591 - using System; namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public class PlaybackStoppedEventArgs : EventArgs { + /// + /// Initializes a new instance of the class. + /// + /// The media info . public PlaybackStoppedEventArgs(UBaseObject mediaInfo) { MediaInfo = mediaInfo; } + /// + /// Gets or sets the media info. + /// public UBaseObject MediaInfo { get; set; } } diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/PlaylistItem.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/PlaylistItem.cs index 3baa014..4ddc2e4 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/PlaylistItem.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/PlaylistItem.cs @@ -1,19 +1,32 @@ -#nullable disable - -#pragma warning disable CS1591 +# nullable disable using Jellyfin.Plugin.Dlna.Model; using MediaBrowser.Model.Dlna; namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public class PlaylistItem { + /// + /// Gets or sets the stream URL. + /// public string StreamUrl { get; set; } + /// + /// Gets or sets the DIDL. + /// public string Didl { get; set; } + /// + /// Gets or sets the stream info. + /// public StreamInfo StreamInfo { get; set; } + /// + /// Gets or sets the profile. + /// public DlnaDeviceProfile Profile { get; set; } } diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/PlaylistItemFactory.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/PlaylistItemFactory.cs index 436ef34..3570766 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/PlaylistItemFactory.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/PlaylistItemFactory.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CS1591 - using System.IO; using System.Linq; using Jellyfin.Plugin.Dlna.Model; @@ -11,8 +7,16 @@ namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public static class PlaylistItemFactory { + /// + /// Creates a new playlist item. + /// + /// The . + /// The . public static PlaylistItem Create(Photo item, DlnaDeviceProfile profile) { var playlistItem = new PlaylistItem diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/TransportCommands.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/TransportCommands.cs index bed5732..9ff7d02 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/TransportCommands.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/TransportCommands.cs @@ -1,62 +1,69 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text; using System.Xml.Linq; using Jellyfin.Plugin.Dlna.Common; using Jellyfin.Plugin.Dlna.Ssdp; namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public class TransportCommands { - private const string CommandBase = "\r\n" + "" + "" + "" + "{2}" + "" + ""; - - public List StateVariables { get; } = new List(); - - public List ServiceActions { get; } = new List(); - + private static readonly CompositeFormat _commandBase = CompositeFormat.Parse("\r\n" + "" + "" + "" + "{2}" + "" + ""); + + /// + /// Gets or sets the state variables. + /// + public IReadOnlyList StateVariables { get; set; } = []; + + /// + /// Gets or sets the service actions. + /// + public IReadOnlyList ServiceActions { get; set; } = []; + + /// + /// Creates based on the input . + /// + /// The . public static TransportCommands Create(XDocument document) { - var command = new TransportCommands(); - var actionList = document.Descendants(UPnpNamespaces.Svc + "actionList"); - - foreach (var container in actionList.Descendants(UPnpNamespaces.Svc + "action")) - { - command.ServiceActions.Add(ServiceActionFromXml(container)); - } - var stateValues = document.Descendants(UPnpNamespaces.ServiceStateTable).FirstOrDefault(); - if (stateValues is not null) + return new() { - foreach (var container in stateValues.Elements(UPnpNamespaces.Svc + "stateVariable")) - { - command.StateVariables.Add(FromXml(container)); - } - } + ServiceActions = GetServiceActions(actionList), + StateVariables = GetStateVariables(stateValues) + }; + } + + private static List GetStateVariables(XElement? stateValues) + { + return stateValues?.Descendants(UPnpNamespaces.Svc + "stateVariable").Select(FromXml).ToList() ?? []; + } - return command; + private static List GetServiceActions(IEnumerable actionList) + { + return actionList.Descendants(UPnpNamespaces.Svc + "action").Select(ServiceActionFromXml).ToList(); } private static ServiceAction ServiceActionFromXml(XElement container) { - var serviceAction = new ServiceAction + return new() { Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty, + ArgumentList = GetArguments(container) }; + } - var argumentList = serviceAction.ArgumentList; - - foreach (var arg in container.Descendants(UPnpNamespaces.Svc + "argument")) - { - argumentList.Add(ArgumentFromXml(arg)); - } - - return serviceAction; + private static List GetArguments(XElement container) + { + return container.Descendants(UPnpNamespaces.Svc + "argument").Select(ArgumentFromXml).ToList(); } private static Argument ArgumentFromXml(XElement container) @@ -92,6 +99,11 @@ private static StateVariable FromXml(XElement container) }; } + /// + /// Builds the POST payload for a . + /// + /// The . + /// The XML namespace. public string BuildPost(ServiceAction action, string xmlNamespace) { var stateString = string.Empty; @@ -113,9 +125,16 @@ public string BuildPost(ServiceAction action, string xmlNamespace) } } - return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString); + return string.Format(CultureInfo.InvariantCulture, _commandBase, action.Name, xmlNamespace, stateString); } + /// + /// Builds the POST payload for a . + /// + /// The . + /// The XML namespace. + /// The value. + /// The command parameter. public string BuildPost(ServiceAction action, string xmlNamespace, object value, string commandParameter = "") { var stateString = string.Empty; @@ -137,10 +156,17 @@ public string BuildPost(ServiceAction action, string xmlNamespace, object value, } } - return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString); + return string.Format(CultureInfo.InvariantCulture, _commandBase, action.Name, xmlNamespace, stateString); } - public string BuildPost(ServiceAction action, string xmlNamespace, object value, Dictionary dictionary) + /// + /// Builds the POST payload for a . + /// + /// The . + /// The XML namespace. + /// The value. + /// The argument values. + public string BuildPost(ServiceAction action, string xmlNamespace, object value, Dictionary argumentValueDictionary) { var stateString = string.Empty; @@ -150,7 +176,7 @@ public string BuildPost(ServiceAction action, string xmlNamespace, object value, { stateString += BuildArgumentXml(arg, "0"); } - else if (dictionary.TryGetValue(arg.Name, out var argValue)) + else if (argumentValueDictionary.TryGetValue(arg.Name, out var argValue)) { stateString += BuildArgumentXml(arg, argValue); } @@ -160,7 +186,7 @@ public string BuildPost(ServiceAction action, string xmlNamespace, object value, } } - return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString); + return string.Format(CultureInfo.InvariantCulture, _commandBase, action.Name, xmlNamespace, stateString); } private string BuildArgumentXml(Argument argument, string? value, string commandParameter = "") diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/TransportState.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/TransportState.cs index daf3e23..5e3f3a6 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/TransportState.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/TransportState.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CA1707 namespace Jellyfin.Plugin.Dlna.PlayTo; @@ -8,8 +8,23 @@ namespace Jellyfin.Plugin.Dlna.PlayTo; /// public enum TransportState { + /// + /// Stopped state. + /// STOPPED, + + /// + /// Playing state. + /// PLAYING, + + /// + /// Transitioning state. + /// TRANSITIONING, + + /// + /// Paused state. + /// PAUSED_PLAYBACK } diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/UpnpContainer.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/UpnpContainer.cs index cdb2cf1..17ac5ee 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/UpnpContainer.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/UpnpContainer.cs @@ -1,13 +1,18 @@ -#pragma warning disable CS1591 - using System; using System.Xml.Linq; using Jellyfin.Plugin.Dlna.Ssdp; namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public class UpnpContainer : UBaseObject { + /// + /// Create a . + /// + /// The . public static UBaseObject Create(XElement container) { ArgumentNullException.ThrowIfNull(container); diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/uBaseObject.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/uBaseObject.cs index edf7167..326412d 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/uBaseObject.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/uBaseObject.cs @@ -1,32 +1,63 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public class UBaseObject { + /// + /// Gets or sets the id. + /// public string Id { get; set; } + /// + /// Gets or sets the parent id. + /// public string ParentId { get; set; } + /// + /// Gets or sets the title. + /// public string Title { get; set; } + /// + /// Gets or sets the second text. + /// public string SecondText { get; set; } + /// + /// Gets or sets the icon URL. + /// public string IconUrl { get; set; } + /// + /// Gets or sets the meta data. + /// public string MetaData { get; set; } + /// + /// Gets or sets the URL. + /// public string Url { get; set; } + /// + /// Gets or sets the protocol info. + /// public IReadOnlyList ProtocolInfo { get; set; } + /// + /// Gets or sets the UPnP class. + /// public string UpnpClass { get; set; } + /// + /// Gets or sets the media type. + /// public string MediaType { get @@ -52,6 +83,7 @@ public string MediaType } } + /// public bool Equals(UBaseObject obj) { ArgumentNullException.ThrowIfNull(obj); diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/uPnpNamespaces.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/uPnpNamespaces.cs index 0d2ca8f..0c2cf47 100644 --- a/src/Jellyfin.Plugin.Dlna/PlayTo/uPnpNamespaces.cs +++ b/src/Jellyfin.Plugin.Dlna/PlayTo/uPnpNamespaces.cs @@ -1,66 +1,154 @@ -#pragma warning disable CS1591 - using System.Xml.Linq; namespace Jellyfin.Plugin.Dlna.PlayTo; +/// +/// Defines the . +/// public static class UPnpNamespaces { + /// + /// Gets the Dc namespace. + /// public static XNamespace Dc { get; } = "http://purl.org/dc/elements/1.1/"; + /// + /// Gets the Ns namespace. + /// public static XNamespace Ns { get; } = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; + /// + /// Gets the service namespace. + /// public static XNamespace Svc { get; } = "urn:schemas-upnp-org:service-1-0"; + /// + /// Gets the device namespace. + /// public static XNamespace Ud { get; } = "urn:schemas-upnp-org:device-1-0"; + /// + /// Gets the Upnp namespace. + /// public static XNamespace UPnp { get; } = "urn:schemas-upnp-org:metadata-1-0/upnp/"; + /// + /// Gets the RenderingControl namespace. + /// public static XNamespace RenderingControl { get; } = "urn:schemas-upnp-org:service:RenderingControl:1"; + /// + /// Gets the AvTransport namespace. + /// public static XNamespace AvTransport { get; } = "urn:schemas-upnp-org:service:AVTransport:1"; + /// + /// Gets the ContentDirectory namespace. + /// public static XNamespace ContentDirectory { get; } = "urn:schemas-upnp-org:service:ContentDirectory:1"; + /// + /// Gets the container element name. + /// public static XName Containers { get; } = Ns + "container"; + /// + /// Gets the item element name. + /// public static XName Items { get; } = Ns + "item"; + /// + /// Gets the title element name. + /// public static XName Title { get; } = Dc + "title"; + /// + /// Gets the creator element name. + /// public static XName Creator { get; } = Dc + "creator"; + /// + /// Gets the artist element name. + /// public static XName Artist { get; } = UPnp + "artist"; + /// + /// Gets the id element name. + /// public static XName Id { get; } = "id"; + /// + /// Gets the parent id element name. + /// public static XName ParentId { get; } = "parentID"; + /// + /// Gets the class element name. + /// public static XName Class { get; } = UPnp + "class"; + /// + /// Gets the artwork element name. + /// public static XName Artwork { get; } = UPnp + "albumArtURI"; + /// + /// Gets the description element name. + /// public static XName Description { get; } = Dc + "description"; + /// + /// Gets the long description element name. + /// public static XName LongDescription { get; } = UPnp + "longDescription"; + /// + /// Gets the album element name. + /// public static XName Album { get; } = UPnp + "album"; + /// + /// Gets the author element name. + /// public static XName Author { get; } = UPnp + "author"; + /// + /// Gets the director element name. + /// public static XName Director { get; } = UPnp + "director"; + /// + /// Gets the playback count element name. + /// public static XName PlayCount { get; } = UPnp + "playbackCount"; + /// + /// Gets the track number element name. + /// public static XName Tracknumber { get; } = UPnp + "originalTrackNumber"; + /// + /// Gets the resolution element name. + /// public static XName Res { get; } = Ns + "res"; + /// + /// Gets the duration element name. + /// public static XName Duration { get; } = "duration"; + /// + /// Gets the protocol info element name. + /// public static XName ProtocolInfo { get; } = "protocolInfo"; + /// + /// Gets the service state table element name. + /// public static XName ServiceStateTable { get; } = Svc + "serviceStateTable"; + /// + /// Gets the state variable element name. + /// public static XName StateVariable { get; } = Svc + "stateVariable"; } diff --git a/src/Jellyfin.Plugin.Dlna/Profiles/DefaultProfile.cs b/src/Jellyfin.Plugin.Dlna/Profiles/DefaultProfile.cs index 71c809f..60f05bb 100644 --- a/src/Jellyfin.Plugin.Dlna/Profiles/DefaultProfile.cs +++ b/src/Jellyfin.Plugin.Dlna/Profiles/DefaultProfile.cs @@ -1,15 +1,18 @@ -#pragma warning disable CS1591 - using System; -using System.Globalization; using Jellyfin.Plugin.Dlna.Model; using MediaBrowser.Model.Dlna; namespace Jellyfin.Plugin.Dlna.Profiles; +/// +/// Defines the . +/// [System.Xml.Serialization.XmlRoot("Profile")] public class DefaultProfile : DlnaDeviceProfile { + /// + /// Initializes a new instance of the class. + /// public DefaultProfile() { Id = Guid.NewGuid(); @@ -38,8 +41,8 @@ public DefaultProfile() EnableAlbumArtInDidl = false; - TranscodingProfiles = new[] - { + TranscodingProfiles = + [ new TranscodingProfile { Container = "mp3", @@ -60,10 +63,10 @@ public DefaultProfile() Container = "jpeg", Type = DlnaProfileType.Photo } - }; + ]; - DirectPlayProfiles = new[] - { + DirectPlayProfiles = + [ new DirectPlayProfile { // play all @@ -77,10 +80,10 @@ public DefaultProfile() Container = string.Empty, Type = DlnaProfileType.Audio } - }; + ]; - SubtitleProfiles = new[] - { + SubtitleProfiles = + [ new SubtitleProfile { Format = "srt", @@ -164,16 +167,16 @@ public DefaultProfile() Format = "vtt", Method = SubtitleDeliveryMethod.Embed } - }; + ]; - ResponseProfiles = new[] - { + ResponseProfiles = + [ new ResponseProfile { Container = "m4v", Type = DlnaProfileType.Video, MimeType = "video/mp4" } - }; + ]; } } diff --git a/src/Jellyfin.Plugin.Dlna/Server/DescriptionXmlBuilder.cs b/src/Jellyfin.Plugin.Dlna/Server/DescriptionXmlBuilder.cs index 24e2ff8..f95389a 100644 --- a/src/Jellyfin.Plugin.Dlna/Server/DescriptionXmlBuilder.cs +++ b/src/Jellyfin.Plugin.Dlna/Server/DescriptionXmlBuilder.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -11,6 +9,9 @@ namespace Jellyfin.Plugin.Dlna.Server; +/// +/// Defines the . +/// public class DescriptionXmlBuilder { private readonly DlnaDeviceProfile _profile; @@ -20,6 +21,14 @@ public class DescriptionXmlBuilder private readonly string _serverName; private readonly string _serverId; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The server UDN. + /// The address. + /// The name. + /// The id. public DescriptionXmlBuilder(DlnaDeviceProfile profile, string serverUdn, string serverAddress, string serverName, string serverId) { ArgumentException.ThrowIfNullOrEmpty(serverUdn); @@ -31,6 +40,9 @@ public DescriptionXmlBuilder(DlnaDeviceProfile profile, string serverUdn, string _serverId = serverId; } + /// + /// Gets the description XML. + /// public string GetXml() { var builder = new StringBuilder(); @@ -254,9 +266,9 @@ private string BuildUrl(string url) return SecurityElement.Escape(url); } - private IEnumerable GetIcons() - => new[] - { + private static IEnumerable GetIcons() + => + [ new DeviceIcon { MimeType = "image/png", @@ -310,29 +322,27 @@ private IEnumerable GetIcons() Height = 48, Url = "icons/logo48.jpg" } - }; + ]; - private IEnumerable GetServices() + private List GetServices() { - var list = new List(); - - list.Add(new DeviceService + var list = new List { - ServiceType = "urn:schemas-upnp-org:service:ContentDirectory:1", - ServiceId = "urn:upnp-org:serviceId:ContentDirectory", - ScpdUrl = "contentdirectory/contentdirectory.xml", - ControlUrl = "contentdirectory/control", - EventSubUrl = "contentdirectory/events" - }); - - list.Add(new DeviceService - { - ServiceType = "urn:schemas-upnp-org:service:ConnectionManager:1", - ServiceId = "urn:upnp-org:serviceId:ConnectionManager", - ScpdUrl = "connectionmanager/connectionmanager.xml", - ControlUrl = "connectionmanager/control", - EventSubUrl = "connectionmanager/events" - }); + new() { + ServiceType = "urn:schemas-upnp-org:service:ContentDirectory:1", + ServiceId = "urn:upnp-org:serviceId:ContentDirectory", + ScpdUrl = "contentdirectory/contentdirectory.xml", + ControlUrl = "contentdirectory/control", + EventSubUrl = "contentdirectory/events" + }, + new() { + ServiceType = "urn:schemas-upnp-org:service:ConnectionManager:1", + ServiceId = "urn:upnp-org:serviceId:ConnectionManager", + ScpdUrl = "connectionmanager/connectionmanager.xml", + ControlUrl = "connectionmanager/control", + EventSubUrl = "connectionmanager/events" + } + }; if (_profile.EnableMSMediaReceiverRegistrar) { @@ -349,6 +359,7 @@ private IEnumerable GetServices() return list; } + /// public override string ToString() { return GetXml(); diff --git a/src/Jellyfin.Plugin.Dlna/Service/BaseControlHandler.cs b/src/Jellyfin.Plugin.Dlna/Service/BaseControlHandler.cs index 330c3de..3b1ed3a 100644 --- a/src/Jellyfin.Plugin.Dlna/Service/BaseControlHandler.cs +++ b/src/Jellyfin.Plugin.Dlna/Service/BaseControlHandler.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.IO; @@ -12,17 +10,32 @@ namespace Jellyfin.Plugin.Dlna.Service; +/// +/// Defines the . +/// public abstract class BaseControlHandler { private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/"; + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. protected BaseControlHandler(ILogger logger) { Logger = logger; } + /// + /// Gets the instance. + /// protected ILogger Logger { get; } + /// + /// Processes a control request asynchronously. + /// + /// The . + /// Task{ControlResponse}. public async Task ProcessControlRequestAsync(ControlRequest request) { try @@ -178,7 +191,7 @@ private async Task ParseBodyTagAsync(XmlReader reader) throw new EndOfStreamException("Stream ended but no control found."); } - private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary headers) + private static async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary headers) { await reader.MoveToContentAsync().ConfigureAwait(false); await reader.ReadAsync().ConfigureAwait(false); @@ -198,9 +211,15 @@ private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary + /// Writes the result. + /// + /// The method name. + /// The method parameters. + /// The . protected abstract void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter); - private class ControlRequestInfo + private sealed class ControlRequestInfo { public ControlRequestInfo(string localName, string namespaceUri) { diff --git a/src/Jellyfin.Plugin.Dlna/Service/BaseService.cs b/src/Jellyfin.Plugin.Dlna/Service/BaseService.cs index 4d700cf..24235b4 100644 --- a/src/Jellyfin.Plugin.Dlna/Service/BaseService.cs +++ b/src/Jellyfin.Plugin.Dlna/Service/BaseService.cs @@ -1,35 +1,66 @@ -#nullable disable -#pragma warning disable CS1591 - using System.Net.Http; using Jellyfin.Plugin.Dlna.Eventing; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.Dlna.Service; +/// +/// Defines the . +/// public class BaseService : IDlnaEventManager { + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. protected BaseService(ILogger logger, IHttpClientFactory httpClientFactory) { Logger = logger; EventManager = new DlnaEventManager(logger, httpClientFactory); } + /// + /// Gets the instance. + /// protected IDlnaEventManager EventManager { get; } + /// + /// Gets the instance. + /// protected ILogger Logger { get; } - public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) + /// + /// Cancels an event subscription. + /// + /// The subscription id. + /// EventSubscriptionResponse. + public EventSubscriptionResponse CancelEventSubscription(string? subscriptionId) { return EventManager.CancelEventSubscription(subscriptionId); } - public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl) + /// + /// Renews an event subscription. + /// + /// The subscription id. + /// The notification type. + /// The requested timeout string. + /// The callback URL. + /// EventSubscriptionResponse. + public EventSubscriptionResponse RenewEventSubscription(string? subscriptionId, string? notificationType, string? requestedTimeoutString, string? callbackUrl) { return EventManager.RenewEventSubscription(subscriptionId, notificationType, requestedTimeoutString, callbackUrl); } - public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl) + /// + /// Creates an event subscription. + /// + /// The notification type. + /// The requested timeout string. + /// The callback URL. + /// EventSubscriptionResponse. + public EventSubscriptionResponse CreateEventSubscription(string? notificationType, string? requestedTimeoutString, string? callbackUrl) { return EventManager.CreateEventSubscription(notificationType, requestedTimeoutString, callbackUrl); } diff --git a/src/Jellyfin.Plugin.Dlna/Service/ControlErrorHandler.cs b/src/Jellyfin.Plugin.Dlna/Service/ControlErrorHandler.cs index 442be4e..9b2bbae 100644 --- a/src/Jellyfin.Plugin.Dlna/Service/ControlErrorHandler.cs +++ b/src/Jellyfin.Plugin.Dlna/Service/ControlErrorHandler.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.IO; using System.Text; @@ -8,10 +6,17 @@ namespace Jellyfin.Plugin.Dlna.Service; +/// +/// Defines the . +/// public static class ControlErrorHandler { private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/"; + /// + /// Gets the response for an . + /// + /// The . public static ControlResponse GetResponse(Exception ex) { var settings = new XmlWriterSettings diff --git a/src/Jellyfin.Plugin.Dlna/Service/ServiceXmlBuilder.cs b/src/Jellyfin.Plugin.Dlna/Service/ServiceXmlBuilder.cs index be599f2..a5b9aec 100644 --- a/src/Jellyfin.Plugin.Dlna/Service/ServiceXmlBuilder.cs +++ b/src/Jellyfin.Plugin.Dlna/Service/ServiceXmlBuilder.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using System.Security; using System.Text; @@ -7,9 +5,17 @@ namespace Jellyfin.Plugin.Dlna.Service; -public class ServiceXmlBuilder +/// +/// Defines the . +/// +public static class ServiceXmlBuilder { - public string GetXml(IEnumerable actions, IEnumerable stateVariables) + /// + /// Gets the XML equivalent of the action and state variable inputs. + /// + /// The actions. + /// The state variables. + public static string GetXml(IEnumerable actions, IEnumerable stateVariables) { var builder = new StringBuilder(); diff --git a/src/Jellyfin.Plugin.Dlna/Ssdp/DeviceDiscovery.cs b/src/Jellyfin.Plugin.Dlna/Ssdp/DeviceDiscovery.cs index 9f2a01d..cf5ef01 100644 --- a/src/Jellyfin.Plugin.Dlna/Ssdp/DeviceDiscovery.cs +++ b/src/Jellyfin.Plugin.Dlna/Ssdp/DeviceDiscovery.cs @@ -1,9 +1,6 @@ #nullable disable -#pragma warning disable CS1591 - using System; -using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Events; using Jellyfin.Plugin.Dlna.Model; @@ -12,9 +9,12 @@ namespace Jellyfin.Plugin.Dlna.Ssdp; +/// +/// Defines the . +/// public sealed class DeviceDiscovery : IDeviceDiscovery, IDisposable { - private readonly object _syncLock = new object(); + private readonly object _syncLock = new(); private SsdpDeviceLocator _deviceLocator; private ISsdpCommunicationsServer _commsServer; @@ -51,7 +51,9 @@ public event EventHandler> DeviceDiscovered /// public event EventHandler> DeviceLeft; - // Call this method from somewhere in your code to start the search. + /// + /// Starts device discovery. + /// public void Start(ISsdpCommunicationsServer communicationsServer) { _commsServer = communicationsServer; @@ -94,7 +96,7 @@ private void OnDeviceLocatorDeviceAvailable(object sender, DeviceAvailableEventA { var originalHeaders = e.DiscoveredDevice.ResponseHeaders; - var headerDict = originalHeaders is null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); + var headerDict = originalHeaders is null ? [] : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); @@ -113,7 +115,7 @@ private void OnDeviceLocatorDeviceUnavailable(object sender, DeviceUnavailableEv { var originalHeaders = e.DiscoveredDevice.ResponseHeaders; - var headerDict = originalHeaders is null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); + var headerDict = originalHeaders is null ? [] : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); diff --git a/src/Jellyfin.Plugin.Dlna/Ssdp/SsdpExtensions.cs b/src/Jellyfin.Plugin.Dlna/Ssdp/SsdpExtensions.cs index 21ecb22..a954797 100644 --- a/src/Jellyfin.Plugin.Dlna/Ssdp/SsdpExtensions.cs +++ b/src/Jellyfin.Plugin.Dlna/Ssdp/SsdpExtensions.cs @@ -1,12 +1,18 @@ -#pragma warning disable CS1591 - using System.Linq; using System.Xml.Linq; namespace Jellyfin.Plugin.Dlna.Ssdp; +/// +/// Defines the . +/// public static class SsdpExtensions { + /// + /// Gets the value. + /// + /// The . + /// The . public static string? GetValue(this XElement container, XName name) { var node = container.Element(name); @@ -14,6 +20,11 @@ public static class SsdpExtensions return node?.Value; } + /// + /// Gets the attribute value. + /// + /// The . + /// The . public static string? GetAttributeValue(this XElement container, XName name) { var node = container.Attribute(name); @@ -21,6 +32,11 @@ public static class SsdpExtensions return node?.Value; } + /// + /// Gets the descendant value. + /// + /// The . + /// The . public static string? GetDescendantValue(this XElement container, XName name) => container.Descendants(name).FirstOrDefault()?.Value; } diff --git a/src/Rssdp/HttpParserBase.cs b/src/Rssdp/HttpParserBase.cs index 1949a9d..9770ce3 100644 --- a/src/Rssdp/HttpParserBase.cs +++ b/src/Rssdp/HttpParserBase.cs @@ -46,7 +46,7 @@ protected virtual void Parse(T message, System.Net.Http.Headers.HttpHeaders head throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", nameof(data)); } - using (var retVal = new ByteArrayContent(Array.Empty())) + using (var retVal = new ByteArrayContent([])) { var lines = data.Split(LineTerminators, StringSplitOptions.None); diff --git a/src/Rssdp/IEnumerableExtensions.cs b/src/Rssdp/IEnumerableExtensions.cs index 1f0daad..3a0f03c 100644 --- a/src/Rssdp/IEnumerableExtensions.cs +++ b/src/Rssdp/IEnumerableExtensions.cs @@ -28,7 +28,7 @@ public static IEnumerable SelectManyRecursive(this IEnumerable source, public static IEnumerable EmptyIfNull(this IEnumerable source) { - return source ?? Enumerable.Empty(); + return source ?? []; } } } diff --git a/src/Rssdp/SsdpCommunicationsServer.cs b/src/Rssdp/SsdpCommunicationsServer.cs index c567002..9aa0203 100644 --- a/src/Rssdp/SsdpCommunicationsServer.cs +++ b/src/Rssdp/SsdpCommunicationsServer.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text; using System.Threading; @@ -441,13 +440,13 @@ private void EnsureSendSocketCreated() } } - private void ProcessMessage(string data, IPEndPoint endPoint, IPAddress receivedOnlocalIPAddress) + private void ProcessMessage(string data, IPEndPoint endPoint, IPAddress receivedOnLocalIPAddress) { // Responses start with the HTTP version, prefixed with HTTP/ while // requests start with a method which can vary and might be one we haven't // seen/don't know. We'll check if this message is a request or a response // by checking for the HTTP/ prefix on the start of the message. - _logger.LogDebug("Received data from {From} on {Port} at {Address}:\n{Data}", endPoint.Address, endPoint.Port, receivedOnlocalIPAddress, data); + _logger.LogDebug("Received data from {From} on {Port} at {Address}:\n{Data}", endPoint.Address, endPoint.Port, receivedOnLocalIPAddress, data); if (data.StartsWith("HTTP/", StringComparison.OrdinalIgnoreCase)) { HttpResponseMessage responseMessage = null; @@ -462,7 +461,7 @@ private void ProcessMessage(string data, IPEndPoint endPoint, IPAddress received if (responseMessage is not null) { - OnResponseReceived(responseMessage, endPoint, receivedOnlocalIPAddress); + OnResponseReceived(responseMessage, endPoint, receivedOnLocalIPAddress); } } else @@ -479,12 +478,12 @@ private void ProcessMessage(string data, IPEndPoint endPoint, IPAddress received if (requestMessage is not null) { - OnRequestReceived(requestMessage, endPoint, receivedOnlocalIPAddress); + OnRequestReceived(requestMessage, endPoint, receivedOnLocalIPAddress); } } } - private void OnRequestReceived(HttpRequestMessage data, IPEndPoint remoteEndPoint, IPAddress receivedOnlocalIPAddress) + private void OnRequestReceived(HttpRequestMessage data, IPEndPoint remoteEndPoint, IPAddress receivedOnLocalIPAddress) { // SSDP specification says only * is currently used but other uri's might // be implemented in the future and should be ignored unless understood. @@ -495,7 +494,7 @@ private void OnRequestReceived(HttpRequestMessage data, IPEndPoint remoteEndPoin } var handlers = RequestReceived; - handlers?.Invoke(this, new RequestReceivedEventArgs(data, remoteEndPoint, receivedOnlocalIPAddress)); + handlers?.Invoke(this, new RequestReceivedEventArgs(data, remoteEndPoint, receivedOnLocalIPAddress)); } private void OnResponseReceived(HttpResponseMessage data, IPEndPoint endPoint, IPAddress localIPAddress)