diff --git a/.editorconfig b/.editorconfig index 78b36ca..c6ab75a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1 +1,8 @@ root = true + +[*.cs] +# CSharpier expects adds trailing comma, prevent editor from cleaning it up +trailing_comma_in_multiline_lists = true + +[tests/**/*.cs] +dotnet_diagnostic.CA2007.severity = none \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 2647b55..3fe4a53 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,6 +5,7 @@ + @@ -17,6 +18,7 @@ + diff --git a/src/Usenet/Exceptions/InvalidNzbDataException.cs b/src/Usenet/Exceptions/InvalidNzbDataException.cs index 453eb23..522de8e 100644 --- a/src/Usenet/Exceptions/InvalidNzbDataException.cs +++ b/src/Usenet/Exceptions/InvalidNzbDataException.cs @@ -1,7 +1,10 @@ -namespace Usenet.Exceptions; +using JetBrains.Annotations; + +namespace Usenet.Exceptions; /// /// The exception that is thrown when a data stream or text is not in a valid nzb format. +[PublicAPI] public class InvalidNzbDataException : Exception { /// diff --git a/src/Usenet/Exceptions/InvalidYencDataException.cs b/src/Usenet/Exceptions/InvalidYencDataException.cs index 01ad4ac..0bc3cc3 100644 --- a/src/Usenet/Exceptions/InvalidYencDataException.cs +++ b/src/Usenet/Exceptions/InvalidYencDataException.cs @@ -1,7 +1,10 @@ -namespace Usenet.Exceptions; +using JetBrains.Annotations; + +namespace Usenet.Exceptions; /// /// The exception that is thrown when a data stream or text is not in a valid yEnc format. +[PublicAPI] public class InvalidYencDataException : Exception { /// diff --git a/src/Usenet/Exceptions/NntpException.cs b/src/Usenet/Exceptions/NntpException.cs index ff941d4..625ed12 100644 --- a/src/Usenet/Exceptions/NntpException.cs +++ b/src/Usenet/Exceptions/NntpException.cs @@ -1,7 +1,10 @@ -namespace Usenet.Exceptions; +using JetBrains.Annotations; + +namespace Usenet.Exceptions; /// /// The exception that is thrown when communicating using the Network News Transfer Protocol. +[PublicAPI] public class NntpException : Exception { /// diff --git a/src/Usenet/Logger.cs b/src/Usenet/Logger.cs index 21980a3..4c6c0d0 100644 --- a/src/Usenet/Logger.cs +++ b/src/Usenet/Logger.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace Usenet; @@ -7,6 +8,7 @@ namespace Usenet; /// Host for a singleton /// (Source). /// +[PublicAPI] public static class Logger { /// diff --git a/src/Usenet/Nntp/Builders/NntpArticleBuilder.cs b/src/Usenet/Nntp/Builders/NntpArticleBuilder.cs index 72621b0..e6014a9 100644 --- a/src/Usenet/Nntp/Builders/NntpArticleBuilder.cs +++ b/src/Usenet/Nntp/Builders/NntpArticleBuilder.cs @@ -1,4 +1,5 @@ using System.Globalization; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Usenet.Exceptions; using Usenet.Extensions; @@ -10,6 +11,7 @@ namespace Usenet.Nntp.Builders; /// /// Represents a mutable . /// +[PublicAPI] public class NntpArticleBuilder { private readonly ILogger _log = Logger.Create(); @@ -48,7 +50,7 @@ public NntpArticleBuilder InitializeFrom(NntpArticle article) { Guard.ThrowIfNull(article); - _messageId = new(article.MessageId.Value); + _messageId = new NntpMessageId(article.MessageId.Value); _groupsBuilder = new NntpGroupsBuilder().Add(article.Groups); _headers = []; _from = string.Empty; @@ -326,13 +328,13 @@ public NntpArticle Build() _headers.Add(NntpHeaders.From, _from); _headers.Add(NntpHeaders.Subject, _subject); - if (_dateTime.HasValue) - { - var formattedDate = _dateTime - .Value.ToUniversalTime() - .ToString(DateFormat, CultureInfo.InvariantCulture); - _headers.Add(NntpHeaders.Date, $"{formattedDate} +0000"); - } + if (!_dateTime.HasValue) + return new NntpArticle(0, _messageId, groups, _headers, _body); + + var formattedDate = _dateTime + .Value.ToUniversalTime() + .ToString(DateFormat, CultureInfo.InvariantCulture); + _headers.Add(NntpHeaders.Date, $"{formattedDate} +0000"); return new NntpArticle(0, _messageId, groups, _headers, _body); } diff --git a/src/Usenet/Nntp/Builders/NntpGroupsBuilder.cs b/src/Usenet/Nntp/Builders/NntpGroupsBuilder.cs index d55437d..4c14e38 100644 --- a/src/Usenet/Nntp/Builders/NntpGroupsBuilder.cs +++ b/src/Usenet/Nntp/Builders/NntpGroupsBuilder.cs @@ -1,4 +1,5 @@ -using Usenet.Nntp.Models; +using JetBrains.Annotations; +using Usenet.Nntp.Models; using Usenet.Nntp.Parsers; using Usenet.Util; @@ -7,6 +8,7 @@ namespace Usenet.Nntp.Builders; /// /// Represents a mutable . /// +[PublicAPI] public class NntpGroupsBuilder { private readonly List _groups = []; @@ -82,11 +84,6 @@ public NntpGroupsBuilder Remove(IEnumerable values) private void AddGroups(IEnumerable values) { - if (values == null) - { - return; - } - foreach (var group in values) { if (!_groups.Contains(group)) @@ -98,11 +95,6 @@ private void AddGroups(IEnumerable values) private void RemoveGroups(IEnumerable values) { - if (values == null) - { - return; - } - foreach (var group in values) { _groups.RemoveAll(g => g == group); diff --git a/src/Usenet/Nntp/Contracts/INntpClient.cs b/src/Usenet/Nntp/Contracts/INntpClient.cs index b121e3d..8f0922f 100644 --- a/src/Usenet/Nntp/Contracts/INntpClient.cs +++ b/src/Usenet/Nntp/Contracts/INntpClient.cs @@ -1,5 +1,8 @@ +using JetBrains.Annotations; + namespace Usenet.Nntp.Contracts; +[PublicAPI] public interface INntpClient : INntpClientRfc2980, INntpClientRfc3977, diff --git a/src/Usenet/Nntp/Contracts/INntpClientCompression.cs b/src/Usenet/Nntp/Contracts/INntpClientCompression.cs index 0a3d08a..bf13eab 100644 --- a/src/Usenet/Nntp/Contracts/INntpClientCompression.cs +++ b/src/Usenet/Nntp/Contracts/INntpClientCompression.cs @@ -1,8 +1,10 @@ +using JetBrains.Annotations; using Usenet.Nntp.Models; using Usenet.Nntp.Responses; namespace Usenet.Nntp.Contracts; +[PublicAPI] public interface INntpClientCompression { /// diff --git a/src/Usenet/Nntp/Contracts/INntpClientConnection.cs b/src/Usenet/Nntp/Contracts/INntpClientConnection.cs index f6b7b91..de60cab 100644 --- a/src/Usenet/Nntp/Contracts/INntpClientConnection.cs +++ b/src/Usenet/Nntp/Contracts/INntpClientConnection.cs @@ -1,7 +1,9 @@ +using JetBrains.Annotations; using Usenet.Nntp.Responses; namespace Usenet.Nntp.Contracts; +[PublicAPI] public interface INntpClientConnection { /// diff --git a/src/Usenet/Nntp/Contracts/INntpClientPool.cs b/src/Usenet/Nntp/Contracts/INntpClientPool.cs index 350cbd3..d562c44 100644 --- a/src/Usenet/Nntp/Contracts/INntpClientPool.cs +++ b/src/Usenet/Nntp/Contracts/INntpClientPool.cs @@ -1,8 +1,11 @@ +using JetBrains.Annotations; + namespace Usenet.Nntp.Contracts; /// /// Represents a pool of authenticated NNTP clients. /// +[PublicAPI] public interface INntpClientPool : IDisposable { /// diff --git a/src/Usenet/Nntp/Contracts/INntpClientRfc2980.cs b/src/Usenet/Nntp/Contracts/INntpClientRfc2980.cs index 61d3442..e32e159 100644 --- a/src/Usenet/Nntp/Contracts/INntpClientRfc2980.cs +++ b/src/Usenet/Nntp/Contracts/INntpClientRfc2980.cs @@ -1,8 +1,10 @@ +using JetBrains.Annotations; using Usenet.Nntp.Models; using Usenet.Nntp.Responses; namespace Usenet.Nntp.Contracts; +[PublicAPI] public interface INntpClientRfc2980 { /// diff --git a/src/Usenet/Nntp/Contracts/INntpClientRfc3977.cs b/src/Usenet/Nntp/Contracts/INntpClientRfc3977.cs index a098f83..bfb4674 100644 --- a/src/Usenet/Nntp/Contracts/INntpClientRfc3977.cs +++ b/src/Usenet/Nntp/Contracts/INntpClientRfc3977.cs @@ -1,10 +1,12 @@ using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; using Usenet.Nntp.Models; using Usenet.Nntp.Responses; namespace Usenet.Nntp.Contracts; [SuppressMessage("Naming", "CA1716:Identifiers should not match keywords")] +[PublicAPI] public interface INntpClientRfc3977 { /// diff --git a/src/Usenet/Nntp/Contracts/INntpClientRfc4643.cs b/src/Usenet/Nntp/Contracts/INntpClientRfc4643.cs index 03a8e76..e2daa3f 100644 --- a/src/Usenet/Nntp/Contracts/INntpClientRfc4643.cs +++ b/src/Usenet/Nntp/Contracts/INntpClientRfc4643.cs @@ -1,5 +1,8 @@ +using JetBrains.Annotations; + namespace Usenet.Nntp.Contracts; +[PublicAPI] public interface INntpClientRfc4643 { /// diff --git a/src/Usenet/Nntp/Contracts/INntpClientRfc6048.cs b/src/Usenet/Nntp/Contracts/INntpClientRfc6048.cs index 82535dd..995ab78 100644 --- a/src/Usenet/Nntp/Contracts/INntpClientRfc6048.cs +++ b/src/Usenet/Nntp/Contracts/INntpClientRfc6048.cs @@ -1,7 +1,9 @@ +using JetBrains.Annotations; using Usenet.Nntp.Responses; namespace Usenet.Nntp.Contracts; +[PublicAPI] public interface INntpClientRfc6048 { /// diff --git a/src/Usenet/Nntp/Contracts/INntpConnection.cs b/src/Usenet/Nntp/Contracts/INntpConnection.cs index 304bf1f..91e860d 100644 --- a/src/Usenet/Nntp/Contracts/INntpConnection.cs +++ b/src/Usenet/Nntp/Contracts/INntpConnection.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using Usenet.Nntp.Parsers; using Usenet.Util; @@ -7,6 +8,7 @@ namespace Usenet.Nntp.Contracts; /// Represents an NNTP connection. /// Based on Kristian Hellang's NntpLib.Net project https://github.com/khellang/NntpLib.Net. /// +[PublicAPI] public interface INntpConnection : IDisposable { /// diff --git a/src/Usenet/Nntp/Contracts/IPooledNntpClient.cs b/src/Usenet/Nntp/Contracts/IPooledNntpClient.cs index 9e03061..591954b 100644 --- a/src/Usenet/Nntp/Contracts/IPooledNntpClient.cs +++ b/src/Usenet/Nntp/Contracts/IPooledNntpClient.cs @@ -1,8 +1,11 @@ +using JetBrains.Annotations; + namespace Usenet.Nntp.Contracts; /// /// An NNTP client for which connections and authentication are managed by a pool. /// +[PublicAPI] public interface IPooledNntpClient : INntpClientRfc2980, INntpClientRfc3977, diff --git a/src/Usenet/Nntp/Contracts/IPooledNntpClientLease.cs b/src/Usenet/Nntp/Contracts/IPooledNntpClientLease.cs index d20901d..58b5354 100644 --- a/src/Usenet/Nntp/Contracts/IPooledNntpClientLease.cs +++ b/src/Usenet/Nntp/Contracts/IPooledNntpClientLease.cs @@ -1,5 +1,8 @@ +using JetBrains.Annotations; + namespace Usenet.Nntp.Contracts; +[PublicAPI] public interface IPooledNntpClientLease : IDisposable { public IPooledNntpClient Client { get; } diff --git a/src/Usenet/Nntp/Models/NntpArticle.cs b/src/Usenet/Nntp/Models/NntpArticle.cs index 14980e5..75da9a5 100644 --- a/src/Usenet/Nntp/Models/NntpArticle.cs +++ b/src/Usenet/Nntp/Models/NntpArticle.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using JetBrains.Annotations; using Usenet.Util; using HashCode = Usenet.Util.HashCode; @@ -7,6 +8,7 @@ namespace Usenet.Nntp.Models; /// /// Represents an NNTP article. /// +[PublicAPI] public class NntpArticle : IEquatable { /// @@ -42,25 +44,23 @@ public class NntpArticle : IEquatable /// The NNTP newsgroups this is posted in. /// The headers of the . /// The body of the . - public NntpArticle( + internal NntpArticle( long number, - NntpMessageId? messageId, - NntpGroups? groups, - IDictionary>? headers, - IList? body + NntpMessageId messageId, + NntpGroups groups, + IDictionary> headers, + IList body ) { Number = number; - MessageId = messageId ?? NntpMessageId.Empty; - Groups = groups ?? NntpGroups.Empty; - Headers = ( - headers ?? MultiValueDictionary.EmptyIgnoreCase - ).ToImmutableDictionary( + MessageId = messageId; + Groups = groups; + Headers = headers.ToImmutableDictionary( x => x.Key, x => x.Value.ToImmutableList(), keyComparer: StringComparer.OrdinalIgnoreCase ); - Body = (body ?? []).ToImmutableList(); + Body = body.ToImmutableList(); } /// diff --git a/src/Usenet/Nntp/Models/NntpArticleRange.cs b/src/Usenet/Nntp/Models/NntpArticleRange.cs index 95b7230..d78534a 100644 --- a/src/Usenet/Nntp/Models/NntpArticleRange.cs +++ b/src/Usenet/Nntp/Models/NntpArticleRange.cs @@ -1,4 +1,5 @@ using System.Globalization; +using JetBrains.Annotations; using Usenet.Util; using HashCode = Usenet.Util.HashCode; @@ -12,6 +13,7 @@ namespace Usenet.Nntp.Models; /// XPAT and /// XROVER commands. /// +[PublicAPI] public class NntpArticleRange : IEquatable { /// @@ -29,7 +31,7 @@ public class NntpArticleRange : IEquatable /// /// The article number to start the range from. /// The optional article number to end the range with. - public NntpArticleRange(long from, long? to) + private NntpArticleRange(long from, long? to) { From = from; To = to; @@ -47,7 +49,7 @@ public NntpArticleRange(long from, long? to) /// /// The article number to start the range from. /// A new range containing the given article and all following. - public static NntpArticleRange AllFollowing(long from) => new(@from, null); + public static NntpArticleRange AllFollowing(long from) => new(from, null); /// /// Creates a range containing all articles between and including and . @@ -55,20 +57,16 @@ public NntpArticleRange(long from, long? to) /// The article number to start the range from. /// The article number to end the range with. /// A new range containg all articles between and including and . - public static NntpArticleRange Range(long from, long to) => new(@from, to); + public static NntpArticleRange Range(long from, long to) => new(from, to); /// /// Returns the text representation of the value formatted according to the NNTP specifications. /// /// The text representation of the value formatted according to the NNTP specifications public override string ToString() => - To == null - ? $"{From}-" - : ( - To.Value == From - ? From.ToString(CultureInfo.InvariantCulture) - : $"{From}-{To.Value}" - ); + To == null ? $"{From}-" + : To.Value == From ? From.ToString(CultureInfo.InvariantCulture) + : $"{From}-{To.Value}"; /// /// Returns the hash code for this instance. diff --git a/src/Usenet/Nntp/Models/NntpDateTime.cs b/src/Usenet/Nntp/Models/NntpDateTime.cs index da646e1..d05a4b1 100644 --- a/src/Usenet/Nntp/Models/NntpDateTime.cs +++ b/src/Usenet/Nntp/Models/NntpDateTime.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using Usenet.Util; namespace Usenet.Nntp.Models; @@ -5,6 +6,7 @@ namespace Usenet.Nntp.Models; /// /// Represents an NNTP datetime object. /// +[PublicAPI] public class NntpDateTime : IEquatable { /// diff --git a/src/Usenet/Nntp/Models/NntpGroup.cs b/src/Usenet/Nntp/Models/NntpGroup.cs index 45a72db..31685d8 100644 --- a/src/Usenet/Nntp/Models/NntpGroup.cs +++ b/src/Usenet/Nntp/Models/NntpGroup.cs @@ -1,4 +1,6 @@ using System.Collections.Immutable; +using System.Text.Json.Serialization; +using JetBrains.Annotations; using Usenet.Util; using Usenet.Util.Compatibility; using HashCode = Usenet.Util.HashCode; @@ -8,6 +10,7 @@ namespace Usenet.Nntp.Models; /// /// Represents an NNTP newsgroup. /// +[PublicAPI] public class NntpGroup : IEquatable { /// @@ -57,7 +60,8 @@ public class NntpGroup : IEquatable /// The name of the other under which the articles are filed when the /// is . /// A list of numbers in the . - public NntpGroup( + [JsonConstructor] + internal NntpGroup( string name, long articleCount, long lowWaterMark, @@ -67,15 +71,15 @@ public NntpGroup( IImmutableList articleNumbers ) { - Name = name ?? string.Empty; + Name = name; ArticleCount = articleCount; LowWaterMark = lowWaterMark; HighWaterMark = highWaterMark; PostingStatus = EnumShim.IsDefined(postingStatus) ? postingStatus : NntpPostingStatus.Unknown; - OtherGroup = otherGroup ?? string.Empty; - ArticleNumbers = (articleNumbers ?? []).OrderBy(n => n).ToImmutableList(); + OtherGroup = otherGroup; + ArticleNumbers = articleNumbers.OrderBy(n => n).ToImmutableList(); } /// diff --git a/src/Usenet/Nntp/Models/NntpGroupOrigin.cs b/src/Usenet/Nntp/Models/NntpGroupOrigin.cs index 000d20a..d601cd7 100644 --- a/src/Usenet/Nntp/Models/NntpGroupOrigin.cs +++ b/src/Usenet/Nntp/Models/NntpGroupOrigin.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using Usenet.Util; using HashCode = Usenet.Util.HashCode; @@ -9,6 +10,7 @@ namespace Usenet.Nntp.Models; /// LIST ACTIVE.TIMES command. /// (Some older information). /// +[PublicAPI] public class NntpGroupOrigin : IEquatable { /// @@ -34,11 +36,11 @@ public class NntpGroupOrigin : IEquatable /// The date and time the was created. /// A description of the entity that created the ; /// it is often a mailbox as described in RFC 2822. - public NntpGroupOrigin(string name, DateTimeOffset createdAt, string createdBy) + internal NntpGroupOrigin(string name, DateTimeOffset createdAt, string createdBy) { - Name = name ?? string.Empty; + Name = name; CreatedAt = createdAt; - CreatedBy = createdBy ?? string.Empty; + CreatedBy = createdBy; } /// diff --git a/src/Usenet/Nntp/Models/NntpGroups.cs b/src/Usenet/Nntp/Models/NntpGroups.cs index f10b741..01a2e98 100644 --- a/src/Usenet/Nntp/Models/NntpGroups.cs +++ b/src/Usenet/Nntp/Models/NntpGroups.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Immutable; +using JetBrains.Annotations; using Usenet.Nntp.Builders; using Usenet.Nntp.Parsers; using Usenet.Util; @@ -10,6 +11,7 @@ namespace Usenet.Nntp.Models; /// /// Represent a list of NNTP newsgroups. /// +[PublicAPI] public class NntpGroups : IEnumerable, IEquatable { /// @@ -22,7 +24,7 @@ public class NntpGroups : IEnumerable, IEquatable /// /// Creates a new object. /// - public NntpGroups(string groups) + internal NntpGroups(string groups) { Guard.ThrowIfNull(groups); _groups = GroupsParser.Parse(groups).OrderBy(g => g).ToImmutableList(); @@ -31,7 +33,7 @@ public NntpGroups(string groups) /// /// Creates a new object. /// - public NntpGroups(IEnumerable groups) + internal NntpGroups(IEnumerable groups) { Guard.ThrowIfNull(groups); _groups = GroupsParser.Parse(groups).OrderBy(g => g).ToImmutableList(); diff --git a/src/Usenet/Nntp/Models/NntpMessageId.cs b/src/Usenet/Nntp/Models/NntpMessageId.cs index e69f2ea..5705ad4 100644 --- a/src/Usenet/Nntp/Models/NntpMessageId.cs +++ b/src/Usenet/Nntp/Models/NntpMessageId.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using Usenet.Extensions; using Usenet.Util; @@ -7,6 +8,7 @@ namespace Usenet.Nntp.Models; /// Represents an NNTP Message-ID. /// (More information). /// +[PublicAPI] public class NntpMessageId : IEquatable { /// diff --git a/src/Usenet/Nntp/Models/NntpPostingStatus.cs b/src/Usenet/Nntp/Models/NntpPostingStatus.cs index 845ae9f..2fac9ec 100644 --- a/src/Usenet/Nntp/Models/NntpPostingStatus.cs +++ b/src/Usenet/Nntp/Models/NntpPostingStatus.cs @@ -1,10 +1,13 @@ -namespace Usenet.Nntp.Models; +using JetBrains.Annotations; + +namespace Usenet.Nntp.Models; /// /// Represents the status a can have on the server. Can be retrieved with /// the LIST ACTIVE command. /// (More information). /// +[PublicAPI] public enum NntpPostingStatus { /// diff --git a/src/Usenet/Nntp/NntpClient.cs b/src/Usenet/Nntp/NntpClient.cs index 55c3739..35eadbf 100644 --- a/src/Usenet/Nntp/NntpClient.cs +++ b/src/Usenet/Nntp/NntpClient.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using Usenet.Extensions; using Usenet.Nntp.Contracts; using Usenet.Nntp.Models; @@ -16,10 +17,10 @@ namespace Usenet.Nntp; /// RFC 6048. /// Based on Kristian Hellang's NntpLib.Net project https://github.com/khellang/NntpLib.Net. /// +[PublicAPI] public partial class NntpClient : INntpClient { - private readonly INntpConnection _connection; - protected INntpConnection Connection => _connection; + protected INntpConnection Connection { get; } /// /// Creates a new instance of the class. @@ -28,25 +29,25 @@ public partial class NntpClient : INntpClient public NntpClient(INntpConnection connection) { Guard.ThrowIfNull(connection); - _connection = connection; + Connection = connection; } /// /// The number of bytes read. /// - public long BytesRead => _connection.Stream?.BytesRead ?? 0; + public long BytesRead => Connection.Stream?.BytesRead ?? 0; /// /// The number of bytes written. /// - public long BytesWritten => _connection.Stream?.BytesWritten ?? 0; + public long BytesWritten => Connection.Stream?.BytesWritten ?? 0; /// /// Resets the counters. /// public void ResetCounters() { - _connection.Stream?.ResetCounters(); + Connection.Stream?.ResetCounters(); } /// @@ -58,7 +59,7 @@ CancellationToken cancellationToken ) { Guard.ThrowIfNullOrWhiteSpace(hostname, nameof(hostname)); - var response = await _connection + var response = await Connection .ConnectAsync(hostname, port, useSsl, new ResponseParser(200, 201), cancellationToken) .ConfigureAwait(false); return response.Success; @@ -72,7 +73,7 @@ CancellationToken cancellationToken ) { Guard.ThrowIfNullOrWhiteSpace(username, nameof(username)); - var userResponse = await _connection + var userResponse = await Connection .CommandAsync($"AUTHINFO USER {username}", new ResponseParser(281), cancellationToken) .ConfigureAwait(false); if (userResponse.Success) @@ -85,7 +86,7 @@ CancellationToken cancellationToken return false; } - var passResponse = await _connection + var passResponse = await Connection .CommandAsync($"AUTHINFO PASS {password}", new ResponseParser(281), cancellationToken) .ConfigureAwait(false); return passResponse.Success; @@ -93,7 +94,7 @@ CancellationToken cancellationToken /// public Task CapabilitiesAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "CAPABILITIES", new MultiLineResponseParser(101), cancellationToken @@ -104,7 +105,7 @@ public Task CapabilitiesAsync( string keyword, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"CAPABILITIES {keyword.ThrowIfNullOrWhiteSpace(nameof(keyword))}", new MultiLineResponseParser(101), cancellationToken @@ -112,15 +113,15 @@ CancellationToken cancellationToken /// public Task ModeReaderAsync(CancellationToken cancellationToken) => - _connection.CommandAsync("MODE READER", new ModeReaderResponseParser(), cancellationToken); + Connection.CommandAsync("MODE READER", new ModeReaderResponseParser(), cancellationToken); /// public Task QuitAsync(CancellationToken cancellationToken) => - _connection.CommandAsync("QUIT", new ResponseParser(205), cancellationToken); + Connection.CommandAsync("QUIT", new ResponseParser(205), cancellationToken); /// public Task GroupAsync(string group, CancellationToken cancellationToken) => - _connection.CommandAsync( + Connection.CommandAsync( $"GROUP {group.ThrowIfNullOrWhiteSpace(nameof(group))}", new GroupResponseParser(), cancellationToken @@ -132,7 +133,7 @@ public Task ListGroupAsync( NntpArticleRange range, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"LISTGROUP {group.ThrowIfNullOrWhiteSpace(nameof(group))} {range}", new ListGroupResponseParser(), cancellationToken @@ -143,7 +144,7 @@ public Task ListGroupAsync( string group, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"LISTGROUP {group.ThrowIfNullOrWhiteSpace(nameof(group))}", new ListGroupResponseParser(), cancellationToken @@ -151,7 +152,7 @@ CancellationToken cancellationToken /// public Task ListGroupAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LISTGROUP", new ListGroupResponseParser(), cancellationToken @@ -159,18 +160,18 @@ public Task ListGroupAsync(CancellationToken cancellationToke /// public Task LastAsync(CancellationToken cancellationToken) => - _connection.CommandAsync("LAST", new LastResponseParser(), cancellationToken); + Connection.CommandAsync("LAST", new LastResponseParser(), cancellationToken); /// public Task NextAsync(CancellationToken cancellationToken) => - _connection.CommandAsync("NEXT", new NextResponseParser(), cancellationToken); + Connection.CommandAsync("NEXT", new NextResponseParser(), cancellationToken); /// public Task ArticleAsync( NntpMessageId messageId, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"ARTICLE {messageId.ThrowIfNullOrWhiteSpace(nameof(messageId))}", new ArticleResponseParser(ArticleRequestType.Article), cancellationToken @@ -181,7 +182,7 @@ public Task ArticleAsync( long number, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"ARTICLE {number}", new ArticleResponseParser(ArticleRequestType.Article), cancellationToken @@ -189,7 +190,7 @@ CancellationToken cancellationToken /// public Task ArticleAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "ARTICLE", new ArticleResponseParser(ArticleRequestType.Article), cancellationToken @@ -200,7 +201,7 @@ public Task HeadAsync( NntpMessageId messageId, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"HEAD {messageId.ThrowIfNullOrWhiteSpace(nameof(messageId))}", new ArticleResponseParser(ArticleRequestType.Head), cancellationToken @@ -208,7 +209,7 @@ CancellationToken cancellationToken /// public Task HeadAsync(long number, CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"HEAD {number}", new ArticleResponseParser(ArticleRequestType.Head), cancellationToken @@ -216,7 +217,7 @@ public Task HeadAsync(long number, CancellationToken cancel /// public Task HeadAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "HEAD", new ArticleResponseParser(ArticleRequestType.Head), cancellationToken @@ -227,7 +228,7 @@ public Task BodyAsync( NntpMessageId messageId, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"BODY {messageId.ThrowIfNullOrWhiteSpace(nameof(messageId))}", new ArticleResponseParser(ArticleRequestType.Body), cancellationToken @@ -235,7 +236,7 @@ CancellationToken cancellationToken /// public Task BodyAsync(long number, CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"BODY {number}", new ArticleResponseParser(ArticleRequestType.Body), cancellationToken @@ -243,7 +244,7 @@ public Task BodyAsync(long number, CancellationToken cancel /// public Task BodyAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "BODY", new ArticleResponseParser(ArticleRequestType.Body), cancellationToken @@ -254,7 +255,7 @@ public Task StatAsync( NntpMessageId messageId, CancellationToken cancellationToken ) => - _connection.CommandAsync( + Connection.CommandAsync( $"STAT {messageId.ThrowIfNullOrWhiteSpace(nameof(messageId))}", new StatResponseParser(), cancellationToken @@ -262,16 +263,16 @@ CancellationToken cancellationToken /// public Task StatAsync(long number, CancellationToken cancellationToken) => - _connection.CommandAsync($"STAT {number}", new StatResponseParser(), cancellationToken); + Connection.CommandAsync($"STAT {number}", new StatResponseParser(), cancellationToken); /// public Task StatAsync(CancellationToken cancellationToken) => - _connection.CommandAsync("STAT", new StatResponseParser(), cancellationToken); + Connection.CommandAsync("STAT", new StatResponseParser(), cancellationToken); /// public async Task PostAsync(NntpArticle article, CancellationToken cancellationToken) { - var initialResponse = await _connection + var initialResponse = await Connection .CommandAsync("POST", new ResponseParser(340), cancellationToken) .ConfigureAwait(false); if (!initialResponse.Success) @@ -280,9 +281,9 @@ public async Task PostAsync(NntpArticle article, CancellationToken cancell } await ArticleWriter - .WriteAsync(_connection, article, cancellationToken) + .WriteAsync(Connection, article, cancellationToken) .ConfigureAwait(false); - var subsequentResponse = await _connection + var subsequentResponse = await Connection .GetResponseAsync(new ResponseParser(240), cancellationToken) .ConfigureAwait(false); return subsequentResponse.Success; @@ -291,7 +292,7 @@ await ArticleWriter /// public async Task IhaveAsync(NntpArticle article, CancellationToken cancellationToken) { - var initialResponse = await _connection + var initialResponse = await Connection .CommandAsync("IHAVE", new ResponseParser(335), cancellationToken) .ConfigureAwait(false); if (!initialResponse.Success) @@ -300,9 +301,9 @@ public async Task IhaveAsync(NntpArticle article, CancellationToken cancel } await ArticleWriter - .WriteAsync(_connection, article, cancellationToken) + .WriteAsync(Connection, article, cancellationToken) .ConfigureAwait(false); - var subsequentResponse = await _connection + var subsequentResponse = await Connection .GetResponseAsync(new ResponseParser(235), cancellationToken) .ConfigureAwait(false); return subsequentResponse.Success; @@ -310,11 +311,11 @@ await ArticleWriter /// public Task DateAsync(CancellationToken cancellationToken) => - _connection.CommandAsync("DATE", new DateResponseParser(), cancellationToken); + Connection.CommandAsync("DATE", new DateResponseParser(), cancellationToken); /// public Task HelpAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "HELP", new MultiLineResponseParser(100), cancellationToken @@ -325,7 +326,7 @@ public Task NewGroupsAsync( NntpDateTime sinceDateTime, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"NEWGROUPS {sinceDateTime}", new GroupsResponseParser(231, GroupStatusRequestType.Basic), cancellationToken @@ -337,7 +338,7 @@ public Task NewNewsAsync( NntpDateTime sinceDateTime, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"NEWNEWS {wildmat} {sinceDateTime}", new MultiLineResponseParser(230), cancellationToken @@ -347,7 +348,7 @@ CancellationToken cancellationToken public Task ListActiveTimesAsync( CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LIST ACTIVE.TIMES", new GroupOriginsResponseParser(), cancellationToken @@ -358,7 +359,7 @@ public Task ListActiveTimesAsync( string wildmat, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"LIST ACTIVE.TIMES {wildmat}", new GroupOriginsResponseParser(), cancellationToken @@ -366,7 +367,7 @@ CancellationToken cancellationToken /// public Task ListDistribPatsAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LIST DISTRIB.PATS", new MultiLineResponseParser(215), cancellationToken @@ -374,7 +375,7 @@ public Task ListDistribPatsAsync(CancellationToken cancel /// public Task ListNewsgroupsAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LIST NEWSGROUPS", new MultiLineResponseParser(215), cancellationToken @@ -385,7 +386,7 @@ public Task ListNewsgroupsAsync( string wildmat, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"LIST NEWSGROUPS {wildmat}", new MultiLineResponseParser(215), cancellationToken @@ -396,7 +397,7 @@ public Task OverAsync( NntpMessageId messageId, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"OVER {messageId}", new MultiLineResponseParser(224), cancellationToken @@ -407,7 +408,7 @@ public Task OverAsync( NntpArticleRange range, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"OVER {range}", new MultiLineResponseParser(224), cancellationToken @@ -415,7 +416,7 @@ CancellationToken cancellationToken /// public Task OverAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "OVER", new MultiLineResponseParser(224), cancellationToken @@ -425,7 +426,7 @@ public Task OverAsync(CancellationToken cancellationToken public Task ListOverviewFormatAsync( CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LIST OVERVIEW.FMT", new MultiLineResponseParser(215), cancellationToken @@ -437,7 +438,7 @@ public Task HdrAsync( NntpMessageId messageId, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"HDR {field} {messageId}", new MultiLineResponseParser(225), cancellationToken @@ -449,7 +450,7 @@ public Task HdrAsync( NntpArticleRange range, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"HDR {field} {range}", new MultiLineResponseParser(225), cancellationToken @@ -460,7 +461,7 @@ public Task HdrAsync( string field, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"HDR {field}", new MultiLineResponseParser(225), cancellationToken @@ -471,7 +472,7 @@ public Task ListHeadersAsync( NntpMessageId messageId, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"LIST HEADERS {messageId}", new MultiLineResponseParser(215), cancellationToken @@ -482,7 +483,7 @@ public Task ListHeadersAsync( NntpArticleRange range, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"LIST HEADERS {range}", new MultiLineResponseParser(215), cancellationToken @@ -490,7 +491,7 @@ CancellationToken cancellationToken /// public Task ListHeadersAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LIST HEADERS", new MultiLineResponseParser(215), cancellationToken @@ -502,7 +503,7 @@ public Task XhdrAsync( NntpMessageId messageId, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"XHDR {field} {messageId}", new MultiLineResponseParser(221), cancellationToken @@ -514,7 +515,7 @@ public Task XhdrAsync( NntpArticleRange range, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"XHDR {field} {range}", new MultiLineResponseParser(221), cancellationToken @@ -525,7 +526,7 @@ public Task XhdrAsync( string field, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"XHDR {field}", new MultiLineResponseParser(221), cancellationToken @@ -536,7 +537,7 @@ public Task XoverAsync( NntpArticleRange range, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"XOVER {range}", new MultiLineResponseParser(224), cancellationToken @@ -544,7 +545,7 @@ CancellationToken cancellationToken /// public Task XoverAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "XOVER", new MultiLineResponseParser(224), cancellationToken @@ -552,7 +553,7 @@ public Task XoverAsync(CancellationToken cancellationToke /// public Task ListCountsAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LIST COUNTS", new GroupsResponseParser(215, GroupStatusRequestType.Extended), cancellationToken @@ -563,7 +564,7 @@ public Task ListCountsAsync( string wildmat, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"LIST COUNTS {wildmat}", new GroupsResponseParser(215, GroupStatusRequestType.Extended), cancellationToken @@ -573,7 +574,7 @@ CancellationToken cancellationToken public Task ListDistributionsAsync( CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LIST DISTRIBUTIONS", new MultiLineResponseParser(215), cancellationToken @@ -581,7 +582,7 @@ CancellationToken cancellationToken /// public Task ListModeratorsAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LIST MODERATORS", new MultiLineResponseParser(215), cancellationToken @@ -589,7 +590,7 @@ public Task ListModeratorsAsync(CancellationToken cancell /// public Task ListMotdAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LIST MOTD", new MultiLineResponseParser(215), cancellationToken @@ -599,7 +600,7 @@ public Task ListMotdAsync(CancellationToken cancellationT public Task ListSubscriptionsAsync( CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LIST SUBSCRIPTIONS", new MultiLineResponseParser(215), cancellationToken @@ -607,7 +608,7 @@ CancellationToken cancellationToken /// public Task ListActiveAsync(CancellationToken cancellationToken) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( "LIST ACTIVE", new GroupsResponseParser(215, GroupStatusRequestType.Basic), cancellationToken @@ -618,7 +619,7 @@ public Task ListActiveAsync( string wildmat, CancellationToken cancellationToken ) => - _connection.MultiLineCommandAsync( + Connection.MultiLineCommandAsync( $"LIST ACTIVE {wildmat}", new GroupsResponseParser(215, GroupStatusRequestType.Basic), cancellationToken diff --git a/src/Usenet/Nntp/NntpClientPool.cs b/src/Usenet/Nntp/NntpClientPool.cs index 23cb466..a641c06 100644 --- a/src/Usenet/Nntp/NntpClientPool.cs +++ b/src/Usenet/Nntp/NntpClientPool.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Usenet.Extensions; using Usenet.Nntp.Contracts; @@ -7,6 +8,7 @@ namespace Usenet.Nntp; /// +[PublicAPI] public sealed class NntpClientPool : INntpClientPool { #if NET9_0_OR_GREATER @@ -83,7 +85,7 @@ private async Task BorrowClient(CancellationToken can var client = BorrowClientInternal(); - if (client.Connected && client.Authenticated) + if (client is { Connected: true, Authenticated: true }) return client; if (!client.Connected) diff --git a/src/Usenet/Nntp/NntpConnection.cs b/src/Usenet/Nntp/NntpConnection.cs index 697e68f..3cc7401 100644 --- a/src/Usenet/Nntp/NntpConnection.cs +++ b/src/Usenet/Nntp/NntpConnection.cs @@ -1,6 +1,7 @@ using System.Net.Security; using System.Net.Sockets; using System.Runtime.CompilerServices; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Usenet.Exceptions; using Usenet.Extensions; @@ -17,6 +18,7 @@ namespace Usenet.Nntp; /// /// This implementation of the interface does support SSL encryption but /// does not support compressed multi-line results. +[PublicAPI] public sealed partial class NntpConnection : INntpConnection { private readonly ILogger _log = Logger.Create(); @@ -157,7 +159,7 @@ [EnumeratorCancellation] CancellationToken cancellationToken /// public void Dispose() { - _client?.Dispose(); + _client.Dispose(); _writer?.Dispose(); _reader?.Dispose(); } diff --git a/src/Usenet/Nntp/NntpHeaders.cs b/src/Usenet/Nntp/NntpHeaders.cs index a75875c..2078451 100644 --- a/src/Usenet/Nntp/NntpHeaders.cs +++ b/src/Usenet/Nntp/NntpHeaders.cs @@ -1,8 +1,11 @@ -namespace Usenet.Nntp; +using JetBrains.Annotations; + +namespace Usenet.Nntp; /// /// Defines NNTP news header fields. /// +[PublicAPI] public static class NntpHeaders { /// diff --git a/src/Usenet/Nntp/Parsers/ArticleResponseParser.cs b/src/Usenet/Nntp/Parsers/ArticleResponseParser.cs index 067836f..a3764f3 100644 --- a/src/Usenet/Nntp/Parsers/ArticleResponseParser.cs +++ b/src/Usenet/Nntp/Parsers/ArticleResponseParser.cs @@ -49,18 +49,7 @@ public NntpArticleResponse Parse(int code, string message, IEnumerable d } _ = long.TryParse(responseSplit.Length > 0 ? responseSplit[0] : null, out var number); - var messageId = responseSplit.Length > 1 ? responseSplit[1] : string.Empty; - - if (dataBlock == null) - { - // no headers and no body - return new NntpArticleResponse( - code, - message, - true, - new NntpArticle(number, messageId, null, null, null) - ); - } + NntpMessageId messageId = responseSplit.Length > 1 ? responseSplit[1] : NntpMessageId.Empty; using var enumerator = dataBlock.GetEnumerator(); @@ -73,7 +62,7 @@ public NntpArticleResponse Parse(int code, string message, IEnumerable d // get groups var groups = headers.TryGetValue(NntpHeaders.Newsgroups, out var values) ? new NntpGroupsBuilder().Add(values).Build() - : null; + : NntpGroups.Empty; // get body if requested var bodyLines = @@ -133,7 +122,10 @@ private MultiValueDictionary GetHeaders(IEnumerator enum private static IEnumerable GetBody(IEnumerator enumerator) { while (enumerator.MoveNext()) - yield return enumerator.Current; + { + if (enumerator.Current is not null) + yield return enumerator.Current; + } } private class Header diff --git a/src/Usenet/Nntp/Parsers/GroupOriginsResponseParser.cs b/src/Usenet/Nntp/Parsers/GroupOriginsResponseParser.cs index 6678a76..fc8bbfb 100644 --- a/src/Usenet/Nntp/Parsers/GroupOriginsResponseParser.cs +++ b/src/Usenet/Nntp/Parsers/GroupOriginsResponseParser.cs @@ -14,7 +14,7 @@ internal class GroupOriginsResponseParser : IMultiLineResponseParser dataBlock) { - if (!IsSuccessResponse(code) || dataBlock == null) + if (!IsSuccessResponse(code)) { return new NntpGroupOriginsResponse(code, message, false, []); } @@ -26,11 +26,6 @@ public NntpGroupOriginsResponse Parse(int code, string message, IEnumerable EnumerateGroupOrigins(IEnumerable dataBlock) { - if (dataBlock == null) - { - yield break; - } - foreach (var line in dataBlock) { var lineSplit = line.Split(' '); diff --git a/src/Usenet/Nntp/Parsers/GroupsParser.cs b/src/Usenet/Nntp/Parsers/GroupsParser.cs index 28f981d..05175e0 100644 --- a/src/Usenet/Nntp/Parsers/GroupsParser.cs +++ b/src/Usenet/Nntp/Parsers/GroupsParser.cs @@ -1,6 +1,8 @@ using Usenet.Extensions; using Usenet.Nntp.Models; +#if NETSTANDARD2_0 using Usenet.Util.Compatibility; +#endif namespace Usenet.Nntp.Parsers; diff --git a/src/Usenet/Nntp/Parsers/GroupsResponseParser.cs b/src/Usenet/Nntp/Parsers/GroupsResponseParser.cs index e5b8209..6ccc956 100644 --- a/src/Usenet/Nntp/Parsers/GroupsResponseParser.cs +++ b/src/Usenet/Nntp/Parsers/GroupsResponseParser.cs @@ -27,7 +27,7 @@ public GroupsResponseParser(int successCode, GroupStatusRequestType requestType) public NntpGroupsResponse Parse(int code, string message, IEnumerable dataBlock) { - if (!IsSuccessResponse(code) || dataBlock == null) + if (!IsSuccessResponse(code)) { return new NntpGroupsResponse(code, message, false, []); } @@ -39,11 +39,6 @@ public NntpGroupsResponse Parse(int code, string message, IEnumerable da private IEnumerable EnumerateGroups(IEnumerable dataBlock) { - if (dataBlock == null) - { - yield break; - } - var checkParameterCount = _requestType == GroupStatusRequestType.Basic ? 4 : 5; foreach (var line in dataBlock) diff --git a/src/Usenet/Nntp/Parsers/HeaderDateParser.cs b/src/Usenet/Nntp/Parsers/HeaderDateParser.cs index 3ae0e78..1b00900 100644 --- a/src/Usenet/Nntp/Parsers/HeaderDateParser.cs +++ b/src/Usenet/Nntp/Parsers/HeaderDateParser.cs @@ -1,8 +1,10 @@ using System.Globalization; using System.Text.RegularExpressions; +using JetBrains.Annotations; namespace Usenet.Nntp.Parsers; +[PublicAPI] public static class HeaderDateParser { private const string DateTimeRegexString = diff --git a/src/Usenet/Nntp/Parsers/IMultiLineResponseParser.cs b/src/Usenet/Nntp/Parsers/IMultiLineResponseParser.cs index c3f479b..e778469 100644 --- a/src/Usenet/Nntp/Parsers/IMultiLineResponseParser.cs +++ b/src/Usenet/Nntp/Parsers/IMultiLineResponseParser.cs @@ -1,9 +1,12 @@ -namespace Usenet.Nntp.Parsers; +using JetBrains.Annotations; + +namespace Usenet.Nntp.Parsers; /// /// Represents a multi-line response parser. /// /// +[PublicAPI] public interface IMultiLineResponseParser { /// diff --git a/src/Usenet/Nntp/Parsers/IResponseParser.cs b/src/Usenet/Nntp/Parsers/IResponseParser.cs index 2288d1e..32d45eb 100644 --- a/src/Usenet/Nntp/Parsers/IResponseParser.cs +++ b/src/Usenet/Nntp/Parsers/IResponseParser.cs @@ -1,9 +1,12 @@ -namespace Usenet.Nntp.Parsers; +using JetBrains.Annotations; + +namespace Usenet.Nntp.Parsers; /// /// Represents a single-line response parser. /// /// +[PublicAPI] public interface IResponseParser { /// diff --git a/src/Usenet/Nntp/Parsers/ListGroupResponseParser.cs b/src/Usenet/Nntp/Parsers/ListGroupResponseParser.cs index a2bfee8..6b20d80 100644 --- a/src/Usenet/Nntp/Parsers/ListGroupResponseParser.cs +++ b/src/Usenet/Nntp/Parsers/ListGroupResponseParser.cs @@ -58,11 +58,6 @@ out var highWaterMark private static IEnumerable EnumerateArticleNumbers(IEnumerable dataBlock) { - if (dataBlock == null) - { - yield break; - } - foreach (var line in dataBlock) { if (!long.TryParse(line, out var number)) diff --git a/src/Usenet/Nntp/Parsers/MultiLineResponseParser.cs b/src/Usenet/Nntp/Parsers/MultiLineResponseParser.cs index 676006f..0762c04 100644 --- a/src/Usenet/Nntp/Parsers/MultiLineResponseParser.cs +++ b/src/Usenet/Nntp/Parsers/MultiLineResponseParser.cs @@ -6,10 +6,7 @@ internal class MultiLineResponseParser : IMultiLineResponseParser _successCodes = successCodes; public bool IsSuccessResponse(int code) => _successCodes.Contains(code); diff --git a/src/Usenet/Nntp/Parsers/ResponseParser.cs b/src/Usenet/Nntp/Parsers/ResponseParser.cs index 1b1a12f..0f8f365 100644 --- a/src/Usenet/Nntp/Parsers/ResponseParser.cs +++ b/src/Usenet/Nntp/Parsers/ResponseParser.cs @@ -6,10 +6,7 @@ internal class ResponseParser : IResponseParser { private readonly int[] _successCodes; - public ResponseParser(params int[] successCodes) - { - _successCodes = successCodes ?? []; - } + public ResponseParser(params int[] successCodes) => _successCodes = successCodes; public bool IsSuccessResponse(int code) => _successCodes.Contains(code); diff --git a/src/Usenet/Nntp/Responses/NntpArticleResponse.cs b/src/Usenet/Nntp/Responses/NntpArticleResponse.cs index e1539ab..7b7e62b 100644 --- a/src/Usenet/Nntp/Responses/NntpArticleResponse.cs +++ b/src/Usenet/Nntp/Responses/NntpArticleResponse.cs @@ -1,4 +1,5 @@ -using Usenet.Nntp.Models; +using JetBrains.Annotations; +using Usenet.Nntp.Models; namespace Usenet.Nntp.Responses; @@ -8,6 +9,7 @@ namespace Usenet.Nntp.Responses; /// HEAD and /// BODY commands. /// +[PublicAPI] public class NntpArticleResponse : NntpResponse { /// @@ -22,7 +24,7 @@ public class NntpArticleResponse : NntpResponse /// The response message received from the server. /// A value indicating whether the command succeeded or failed. /// The received from the server. - public NntpArticleResponse(int code, string message, bool success, NntpArticle? article) + internal NntpArticleResponse(int code, string message, bool success, NntpArticle? article) : base(code, message, success) { Article = article; diff --git a/src/Usenet/Nntp/Responses/NntpDateResponse.cs b/src/Usenet/Nntp/Responses/NntpDateResponse.cs index 8382e2c..f557f84 100644 --- a/src/Usenet/Nntp/Responses/NntpDateResponse.cs +++ b/src/Usenet/Nntp/Responses/NntpDateResponse.cs @@ -1,9 +1,12 @@ -namespace Usenet.Nntp.Responses; +using JetBrains.Annotations; + +namespace Usenet.Nntp.Responses; /// /// Represents a response to the /// DATE command. /// +[PublicAPI] public class NntpDateResponse : NntpResponse { /// @@ -18,7 +21,7 @@ public class NntpDateResponse : NntpResponse /// The response message received from the server. /// A value indicating whether the command succeeded or failed. /// The date and time received from the server. - public NntpDateResponse(int code, string message, bool success, DateTimeOffset dateTime) + internal NntpDateResponse(int code, string message, bool success, DateTimeOffset dateTime) : base(code, message, success) { DateTime = dateTime; diff --git a/src/Usenet/Nntp/Responses/NntpGroupOriginsResponse.cs b/src/Usenet/Nntp/Responses/NntpGroupOriginsResponse.cs index 5ea377a..975a7d9 100644 --- a/src/Usenet/Nntp/Responses/NntpGroupOriginsResponse.cs +++ b/src/Usenet/Nntp/Responses/NntpGroupOriginsResponse.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using JetBrains.Annotations; using Usenet.Nntp.Models; namespace Usenet.Nntp.Responses; @@ -8,6 +9,7 @@ namespace Usenet.Nntp.Responses; /// LIST ACTIVE.TIMES /// (ad 1) command. /// +[PublicAPI] public class NntpGroupOriginsResponse : NntpResponse { /// @@ -22,7 +24,7 @@ public class NntpGroupOriginsResponse : NntpResponse /// The response message received from the server. /// A value indicating whether the command succeeded or failed. /// The list of objects received from the server. - public NntpGroupOriginsResponse( + internal NntpGroupOriginsResponse( int code, string message, bool success, @@ -30,6 +32,6 @@ IList groupOrigins ) : base(code, message, success) { - GroupOrigins = (groupOrigins ?? []).ToImmutableList(); + GroupOrigins = groupOrigins.ToImmutableList(); } } diff --git a/src/Usenet/Nntp/Responses/NntpGroupResponse.cs b/src/Usenet/Nntp/Responses/NntpGroupResponse.cs index f972ea5..37290f7 100644 --- a/src/Usenet/Nntp/Responses/NntpGroupResponse.cs +++ b/src/Usenet/Nntp/Responses/NntpGroupResponse.cs @@ -1,4 +1,5 @@ -using Usenet.Nntp.Models; +using JetBrains.Annotations; +using Usenet.Nntp.Models; namespace Usenet.Nntp.Responses; @@ -7,6 +8,7 @@ namespace Usenet.Nntp.Responses; /// GROUP and /// LISTGROUP commands. /// +[PublicAPI] public class NntpGroupResponse : NntpResponse { /// @@ -21,7 +23,7 @@ public class NntpGroupResponse : NntpResponse /// The response message received from the server. /// A value indicating whether the command succeeded or failed. /// The received from the server. - public NntpGroupResponse(int code, string message, bool success, NntpGroup group) + internal NntpGroupResponse(int code, string message, bool success, NntpGroup group) : base(code, message, success) { Group = group; diff --git a/src/Usenet/Nntp/Responses/NntpGroupsResponse.cs b/src/Usenet/Nntp/Responses/NntpGroupsResponse.cs index 649be5d..978daab 100644 --- a/src/Usenet/Nntp/Responses/NntpGroupsResponse.cs +++ b/src/Usenet/Nntp/Responses/NntpGroupsResponse.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using JetBrains.Annotations; using Usenet.Nntp.Models; namespace Usenet.Nntp.Responses; @@ -11,6 +12,7 @@ namespace Usenet.Nntp.Responses; /// ad 2) and /// NEWGROUPS commands. /// +[PublicAPI] public class NntpGroupsResponse : NntpResponse { /// @@ -25,9 +27,9 @@ public class NntpGroupsResponse : NntpResponse /// The response message received from the server. /// A value indicating whether the command succeeded or failed. /// The list of objects received from the server. - public NntpGroupsResponse(int code, string message, bool success, IList groups) + internal NntpGroupsResponse(int code, string message, bool success, IList groups) : base(code, message, success) { - Groups = (groups ?? []).ToImmutableList(); + Groups = groups.ToImmutableList(); } } diff --git a/src/Usenet/Nntp/Responses/NntpLastResponse.cs b/src/Usenet/Nntp/Responses/NntpLastResponse.cs index e82b158..f8d3d80 100644 --- a/src/Usenet/Nntp/Responses/NntpLastResponse.cs +++ b/src/Usenet/Nntp/Responses/NntpLastResponse.cs @@ -1,4 +1,5 @@ -using Usenet.Nntp.Models; +using JetBrains.Annotations; +using Usenet.Nntp.Models; namespace Usenet.Nntp.Responses; @@ -6,6 +7,7 @@ namespace Usenet.Nntp.Responses; /// Represents a response to the /// LAST command. /// +[PublicAPI] public class NntpLastResponse : NntpResponse { /// @@ -34,7 +36,7 @@ public class NntpLastResponse : NntpResponse /// The type of the response received from the server. /// The number received from the server. /// The received from the server. - public NntpLastResponse( + internal NntpLastResponse( int code, string message, bool success, @@ -46,6 +48,6 @@ NntpMessageId messageId { ResponseType = responseType; Number = number; - MessageId = messageId ?? NntpMessageId.Empty; + MessageId = messageId; } } diff --git a/src/Usenet/Nntp/Responses/NntpLastResponseType.cs b/src/Usenet/Nntp/Responses/NntpLastResponseType.cs index b2dca53..7e80d45 100644 --- a/src/Usenet/Nntp/Responses/NntpLastResponseType.cs +++ b/src/Usenet/Nntp/Responses/NntpLastResponseType.cs @@ -1,9 +1,12 @@ -namespace Usenet.Nntp.Responses; +using JetBrains.Annotations; + +namespace Usenet.Nntp.Responses; /// /// Represents all possible response codes for the /// LAST command. /// +[PublicAPI] public enum NntpLastResponseType { /// diff --git a/src/Usenet/Nntp/Responses/NntpModeReaderResponse.cs b/src/Usenet/Nntp/Responses/NntpModeReaderResponse.cs index a5d325d..3af303e 100644 --- a/src/Usenet/Nntp/Responses/NntpModeReaderResponse.cs +++ b/src/Usenet/Nntp/Responses/NntpModeReaderResponse.cs @@ -1,10 +1,13 @@ -namespace Usenet.Nntp.Responses; +using JetBrains.Annotations; + +namespace Usenet.Nntp.Responses; /// /// Represents the response to the /// MODE READER /// (ad 1) command. /// +[PublicAPI] public class NntpModeReaderResponse : NntpResponse { /// @@ -19,7 +22,7 @@ public class NntpModeReaderResponse : NntpResponse /// The response message received from the server. /// A value indicating whether the command succeeded or failed. /// The type of the response received from the server. - public NntpModeReaderResponse( + internal NntpModeReaderResponse( int code, string message, bool success, diff --git a/src/Usenet/Nntp/Responses/NntpModeReaderResponseType.cs b/src/Usenet/Nntp/Responses/NntpModeReaderResponseType.cs index 27bd037..01c6738 100644 --- a/src/Usenet/Nntp/Responses/NntpModeReaderResponseType.cs +++ b/src/Usenet/Nntp/Responses/NntpModeReaderResponseType.cs @@ -1,10 +1,13 @@ -namespace Usenet.Nntp.Responses; +using JetBrains.Annotations; + +namespace Usenet.Nntp.Responses; /// /// Represents all possible response codes for the /// MODE READER /// (ad 1) command. /// +[PublicAPI] public enum NntpModeReaderResponseType { /// diff --git a/src/Usenet/Nntp/Responses/NntpMultiLineResponse.cs b/src/Usenet/Nntp/Responses/NntpMultiLineResponse.cs index 3f940bb..72a799e 100644 --- a/src/Usenet/Nntp/Responses/NntpMultiLineResponse.cs +++ b/src/Usenet/Nntp/Responses/NntpMultiLineResponse.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using JetBrains.Annotations; namespace Usenet.Nntp.Responses; @@ -6,6 +7,7 @@ namespace Usenet.Nntp.Responses; /// Represents a generic multi-line response. /// Based on Kristian Hellang's NntpLib.Net project https://github.com/khellang/NntpLib.Net. /// +[PublicAPI] public class NntpMultiLineResponse : NntpResponse { /// @@ -20,9 +22,14 @@ public class NntpMultiLineResponse : NntpResponse /// The response message received from the server. /// A value indicating whether the command succeeded or failed. /// The lines received from the server. - public NntpMultiLineResponse(int code, string message, bool success, IEnumerable lines) + internal NntpMultiLineResponse( + int code, + string message, + bool success, + IEnumerable lines + ) : base(code, message, success) { - Lines = (lines ?? []).ToImmutableList(); + Lines = lines.ToImmutableList(); } } diff --git a/src/Usenet/Nntp/Responses/NntpNextResponse.cs b/src/Usenet/Nntp/Responses/NntpNextResponse.cs index cf1ab00..72fbb12 100644 --- a/src/Usenet/Nntp/Responses/NntpNextResponse.cs +++ b/src/Usenet/Nntp/Responses/NntpNextResponse.cs @@ -1,4 +1,5 @@ -using Usenet.Nntp.Models; +using JetBrains.Annotations; +using Usenet.Nntp.Models; namespace Usenet.Nntp.Responses; @@ -6,6 +7,7 @@ namespace Usenet.Nntp.Responses; /// Represents a response to the /// NEXT command. /// +[PublicAPI] public class NntpNextResponse : NntpResponse { /// @@ -34,7 +36,7 @@ public class NntpNextResponse : NntpResponse /// The type of the response received from the server. /// The number received from the server. /// The received from the server. - public NntpNextResponse( + internal NntpNextResponse( int code, string message, bool success, @@ -46,6 +48,6 @@ NntpMessageId messageId { ResponseType = responseType; Number = number; - MessageId = messageId ?? NntpMessageId.Empty; + MessageId = messageId; } } diff --git a/src/Usenet/Nntp/Responses/NntpNextResponseType.cs b/src/Usenet/Nntp/Responses/NntpNextResponseType.cs index 68c9337..fe38b46 100644 --- a/src/Usenet/Nntp/Responses/NntpNextResponseType.cs +++ b/src/Usenet/Nntp/Responses/NntpNextResponseType.cs @@ -1,9 +1,12 @@ -namespace Usenet.Nntp.Responses; +using JetBrains.Annotations; + +namespace Usenet.Nntp.Responses; /// /// Represents all possible response codes for the /// NEXT command. /// +[PublicAPI] public enum NntpNextResponseType { /// diff --git a/src/Usenet/Nntp/Responses/NntpResponse.cs b/src/Usenet/Nntp/Responses/NntpResponse.cs index 1118250..8b002ea 100644 --- a/src/Usenet/Nntp/Responses/NntpResponse.cs +++ b/src/Usenet/Nntp/Responses/NntpResponse.cs @@ -1,9 +1,12 @@ -namespace Usenet.Nntp.Responses; +using JetBrains.Annotations; + +namespace Usenet.Nntp.Responses; /// /// Represents a single-line response. /// Based on Kristian Hellang's NntpLib.Net project https://github.com/khellang/NntpLib.Net. /// +[PublicAPI] public class NntpResponse { /// @@ -27,7 +30,7 @@ public class NntpResponse /// The response code received from the server. /// The response message received from the server. /// A value indicating whether the command succeeded or failed. - public NntpResponse(int code, string message, bool success) + internal NntpResponse(int code, string message, bool success) { Code = code; Message = message; diff --git a/src/Usenet/Nntp/Responses/NntpStatResponse.cs b/src/Usenet/Nntp/Responses/NntpStatResponse.cs index c705b11..e1fa4ec 100644 --- a/src/Usenet/Nntp/Responses/NntpStatResponse.cs +++ b/src/Usenet/Nntp/Responses/NntpStatResponse.cs @@ -1,4 +1,5 @@ -using Usenet.Nntp.Models; +using JetBrains.Annotations; +using Usenet.Nntp.Models; namespace Usenet.Nntp.Responses; @@ -6,6 +7,7 @@ namespace Usenet.Nntp.Responses; /// Represents a response to the /// STAT command. /// +[PublicAPI] public class NntpStatResponse : NntpResponse { /// @@ -32,7 +34,7 @@ public class NntpStatResponse : NntpResponse /// The type of the response received from the server. /// The number received from the server. /// The received from the server. - public NntpStatResponse( + internal NntpStatResponse( int code, string message, bool success, @@ -44,6 +46,6 @@ NntpMessageId messageId { ResponseType = responseType; Number = number; - MessageId = messageId ?? NntpMessageId.Empty; + MessageId = messageId; } } diff --git a/src/Usenet/Nntp/Responses/NntpStatResponseType.cs b/src/Usenet/Nntp/Responses/NntpStatResponseType.cs index 0f32550..c4b7249 100644 --- a/src/Usenet/Nntp/Responses/NntpStatResponseType.cs +++ b/src/Usenet/Nntp/Responses/NntpStatResponseType.cs @@ -1,9 +1,12 @@ -namespace Usenet.Nntp.Responses; +using JetBrains.Annotations; + +namespace Usenet.Nntp.Responses; /// /// Represents all possible response codes for the /// STAT command. /// +[PublicAPI] public enum NntpStatResponseType { /// diff --git a/src/Usenet/Nzb/NzbBuilder.cs b/src/Usenet/Nzb/NzbBuilder.cs index e8991b8..f2cbdb5 100644 --- a/src/Usenet/Nzb/NzbBuilder.cs +++ b/src/Usenet/Nzb/NzbBuilder.cs @@ -1,4 +1,5 @@ using System.Globalization; +using JetBrains.Annotations; using Microsoft.Extensions.FileProviders; using Usenet.Nntp.Builders; using Usenet.Nntp.Models; @@ -9,6 +10,7 @@ namespace Usenet.Nzb; /// /// Represents a mutable . /// +[PublicAPI] public class NzbBuilder { private readonly List _files = []; diff --git a/src/Usenet/Nzb/NzbDocument.cs b/src/Usenet/Nzb/NzbDocument.cs index e16bf58..523ad27 100644 --- a/src/Usenet/Nzb/NzbDocument.cs +++ b/src/Usenet/Nzb/NzbDocument.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Text; +using JetBrains.Annotations; using Usenet.Extensions; using Usenet.Util; using HashCode = Usenet.Util.HashCode; @@ -10,6 +11,7 @@ namespace Usenet.Nzb; /// Represents a NZB document. /// Based on Kristian Hellang's Nzb project https://github.com/khellang/Nzb. /// +[PublicAPI] public class NzbDocument : IEquatable { /// @@ -32,15 +34,13 @@ public class NzbDocument : IEquatable /// /// A collection of metadata elements found in the NZB file. /// A collection of files found in the NZB file. - public NzbDocument( - IDictionary>? metaData, - IEnumerable? files + internal NzbDocument( + IDictionary> metaData, + IEnumerable files ) { - MetaData = ( - metaData ?? MultiValueDictionary.Empty - ).ToImmutableDictionaryWithHashSets(); - Files = (files ?? []).OrderBy(f => f.FileName).ToImmutableList(); + MetaData = metaData.ToImmutableDictionaryWithHashSets(); + Files = files.OrderBy(f => f.FileName).ToImmutableList(); Size = Files.Sum(f => f.Size); } diff --git a/src/Usenet/Nzb/NzbFile.cs b/src/Usenet/Nzb/NzbFile.cs index c3a9431..4977b74 100644 --- a/src/Usenet/Nzb/NzbFile.cs +++ b/src/Usenet/Nzb/NzbFile.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using JetBrains.Annotations; using Usenet.Nntp.Models; using Usenet.Util; using HashCode = Usenet.Util.HashCode; @@ -9,6 +10,7 @@ namespace Usenet.Nzb; /// Represents a file in a NZB document. /// Based on Kristian Hellang's Nzb project https://github.com/khellang/Nzb. /// +[PublicAPI] public class NzbFile : IEquatable { /// @@ -59,7 +61,7 @@ public class NzbFile : IEquatable /// The date the server saw this article. /// The list of groups that reference this file. /// The list of segments that make up this file. - public NzbFile( + internal NzbFile( string poster, string subject, string fileName, @@ -72,8 +74,8 @@ IEnumerable segments Subject = subject; FileName = fileName; Date = date; - Groups = groups ?? NntpGroups.Empty; - Segments = (segments ?? new List(0)).OrderBy(s => s.Number).ToImmutableList(); + Groups = groups; + Segments = segments.OrderBy(s => s.Number).ToImmutableList(); Size = Segments.Sum(s => s.Size); } diff --git a/src/Usenet/Nzb/NzbKeywords.cs b/src/Usenet/Nzb/NzbKeywords.cs index 86b7a6b..2a40e99 100644 --- a/src/Usenet/Nzb/NzbKeywords.cs +++ b/src/Usenet/Nzb/NzbKeywords.cs @@ -4,7 +4,7 @@ /// Defines NZB xml elements and attributes. /// Based on Kristian Hellang's Nzb project https://github.com/khellang/Nzb. /// -internal class NzbKeywords +internal static class NzbKeywords { /// /// NZB document public identifier. diff --git a/src/Usenet/Nzb/NzbParser.cs b/src/Usenet/Nzb/NzbParser.cs index 8d63a3a..8fd2f60 100644 --- a/src/Usenet/Nzb/NzbParser.cs +++ b/src/Usenet/Nzb/NzbParser.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using System.Xml.Linq; +using JetBrains.Annotations; using Usenet.Exceptions; using Usenet.Extensions; using Usenet.Nntp.Models; @@ -13,6 +14,7 @@ namespace Usenet.Nzb; /// It takes an xml string as input and parses it into an instance of the class. /// Based on Kristian Hellang's Nzb project https://github.com/khellang/Nzb. /// +[PublicAPI] public static class NzbParser { private static readonly Regex FileNameRegex = new("\"([^\"]*)\"", RegexOptions.Compiled); @@ -131,7 +133,7 @@ private static NzbDocument ParseDocument(XDocument doc) return new NzbDocument(metaData, files); } - private static MultiValueDictionary? GetMetaData( + private static MultiValueDictionary GetMetaData( NzbParserContext context, XContainer nzbElement ) @@ -139,7 +141,7 @@ XContainer nzbElement var headElement = nzbElement.Element(context.Namespace + NzbKeywords.Head); if (headElement == null) { - return null; + return []; } var headers = @@ -236,15 +238,10 @@ XContainer segmentsElement private static NzbSegment GetSegment(XElement element, long offset) { if (!int.TryParse((string?)element.Attribute(NzbKeywords.Number), out var number)) - { throw new InvalidNzbDataException(Resources.Nzb.InvalidOrMissingNumberAttribute); - } - - if (!long.TryParse((string?)element.Attribute(NzbKeywords.Bytes), out var size)) - { - throw new InvalidNzbDataException(Resources.Nzb.InvalidOrMissingBytesAttribute); - } - return new NzbSegment(number, offset, size, element.Value); + return !long.TryParse((string?)element.Attribute(NzbKeywords.Bytes), out var size) + ? throw new InvalidNzbDataException(Resources.Nzb.InvalidOrMissingBytesAttribute) + : new NzbSegment(number, offset, size, element.Value); } } diff --git a/src/Usenet/Nzb/NzbSegment.cs b/src/Usenet/Nzb/NzbSegment.cs index ec9f67e..a4dc6e2 100644 --- a/src/Usenet/Nzb/NzbSegment.cs +++ b/src/Usenet/Nzb/NzbSegment.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using Usenet.Nntp.Models; using Usenet.Util; using HashCode = Usenet.Util.HashCode; @@ -8,6 +9,7 @@ namespace Usenet.Nzb; /// Represents a segment of a file in a NZB document. /// Based on Kristian Hellang's Nzb project https://github.com/khellang/Nzb. /// +[PublicAPI] public class NzbSegment : IEquatable { /// @@ -38,12 +40,12 @@ public class NzbSegment : IEquatable /// Offset of the segment in the file. /// Size of the article, in bytes, as a number, with no comma separation. /// The Message-ID of this article. - public NzbSegment(int number, long offset, long size, NntpMessageId messageId) + internal NzbSegment(int number, long offset, long size, NntpMessageId messageId) { Number = number; Offset = offset; Size = size; - MessageId = messageId ?? NntpMessageId.Empty; + MessageId = messageId; } /// diff --git a/src/Usenet/Nzb/NzbWriter.cs b/src/Usenet/Nzb/NzbWriter.cs index 43678a7..11d645d 100644 --- a/src/Usenet/Nzb/NzbWriter.cs +++ b/src/Usenet/Nzb/NzbWriter.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Xml; +using JetBrains.Annotations; using Usenet.Util; namespace Usenet.Nzb; @@ -7,6 +8,7 @@ namespace Usenet.Nzb; /// /// Represents an NZB document writer. /// +[PublicAPI] public class NzbWriter { private readonly TextWriter _textWriter; diff --git a/src/Usenet/Nzb/TextWriterExtensions.cs b/src/Usenet/Nzb/TextWriterExtensions.cs index fe51c85..cd4c44c 100644 --- a/src/Usenet/Nzb/TextWriterExtensions.cs +++ b/src/Usenet/Nzb/TextWriterExtensions.cs @@ -1,8 +1,11 @@ +using JetBrains.Annotations; + namespace Usenet.Nzb; /// /// TextWriter extension methods. /// +[PublicAPI] public static class TextWriterExtensions { /// @@ -12,7 +15,7 @@ public static class TextWriterExtensions /// The to write. /// A that can be awaited. public static Task WriteNzbDocumentAsync(this TextWriter textWriter, NzbDocument nzbDocument) => - WriteNzbDocumentAsync(textWriter, nzbDocument, CancellationToken.None); + textWriter.WriteNzbDocumentAsync(nzbDocument, CancellationToken.None); /// /// Writes the specified asynchronously to the stream. diff --git a/src/Usenet/PublicAPI.Shipped.txt b/src/Usenet/PublicAPI.Shipped.txt index a2c0805..a4c3fee 100644 --- a/src/Usenet/PublicAPI.Shipped.txt +++ b/src/Usenet/PublicAPI.Shipped.txt @@ -329,12 +329,10 @@ Usenet.Nntp.Models.NntpArticle.Equals(Usenet.Nntp.Models.NntpArticle? other) -> Usenet.Nntp.Models.NntpArticle.Groups.get -> Usenet.Nntp.Models.NntpGroups! Usenet.Nntp.Models.NntpArticle.Headers.get -> System.Collections.Immutable.ImmutableDictionary!>! Usenet.Nntp.Models.NntpArticle.MessageId.get -> Usenet.Nntp.Models.NntpMessageId! -Usenet.Nntp.Models.NntpArticle.NntpArticle(long number, Usenet.Nntp.Models.NntpMessageId? messageId, Usenet.Nntp.Models.NntpGroups? groups, System.Collections.Generic.IDictionary!>? headers, System.Collections.Generic.IList? body) -> void Usenet.Nntp.Models.NntpArticle.Number.get -> long Usenet.Nntp.Models.NntpArticleRange Usenet.Nntp.Models.NntpArticleRange.Equals(Usenet.Nntp.Models.NntpArticleRange? other) -> bool Usenet.Nntp.Models.NntpArticleRange.From.get -> long -Usenet.Nntp.Models.NntpArticleRange.NntpArticleRange(long from, long? to) -> void Usenet.Nntp.Models.NntpArticleRange.To.get -> long? Usenet.Nntp.Models.NntpDateTime Usenet.Nntp.Models.NntpDateTime.Equals(Usenet.Nntp.Models.NntpDateTime? other) -> bool @@ -348,7 +346,6 @@ Usenet.Nntp.Models.NntpGroup.Equals(Usenet.Nntp.Models.NntpGroup? other) -> bool Usenet.Nntp.Models.NntpGroup.HighWaterMark.get -> long Usenet.Nntp.Models.NntpGroup.LowWaterMark.get -> long Usenet.Nntp.Models.NntpGroup.Name.get -> string! -Usenet.Nntp.Models.NntpGroup.NntpGroup(string! name, long articleCount, long lowWaterMark, long highWaterMark, Usenet.Nntp.Models.NntpPostingStatus postingStatus, string! otherGroup, System.Collections.Immutable.IImmutableList! articleNumbers) -> void Usenet.Nntp.Models.NntpGroup.OtherGroup.get -> string! Usenet.Nntp.Models.NntpGroup.PostingStatus.get -> Usenet.Nntp.Models.NntpPostingStatus Usenet.Nntp.Models.NntpGroupOrigin @@ -356,13 +353,10 @@ Usenet.Nntp.Models.NntpGroupOrigin.CreatedAt.get -> System.DateTimeOffset Usenet.Nntp.Models.NntpGroupOrigin.CreatedBy.get -> string! Usenet.Nntp.Models.NntpGroupOrigin.Equals(Usenet.Nntp.Models.NntpGroupOrigin? other) -> bool Usenet.Nntp.Models.NntpGroupOrigin.Name.get -> string! -Usenet.Nntp.Models.NntpGroupOrigin.NntpGroupOrigin(string! name, System.DateTimeOffset createdAt, string! createdBy) -> void Usenet.Nntp.Models.NntpGroups Usenet.Nntp.Models.NntpGroups.Equals(Usenet.Nntp.Models.NntpGroups? other) -> bool Usenet.Nntp.Models.NntpGroups.GetEnumerator() -> System.Collections.Generic.IEnumerator! Usenet.Nntp.Models.NntpGroups.IsEmpty.get -> bool -Usenet.Nntp.Models.NntpGroups.NntpGroups(string! groups) -> void -Usenet.Nntp.Models.NntpGroups.NntpGroups(System.Collections.Generic.IEnumerable! groups) -> void Usenet.Nntp.Models.NntpMessageId Usenet.Nntp.Models.NntpMessageId.Equals(Usenet.Nntp.Models.NntpMessageId? other) -> bool Usenet.Nntp.Models.NntpMessageId.HasValue.get -> bool @@ -545,22 +539,16 @@ Usenet.Nntp.Parsers.IResponseParser.IsSuccessResponse(int code) -> bo Usenet.Nntp.Parsers.IResponseParser.Parse(int code, string! message) -> TResponse Usenet.Nntp.Responses.NntpArticleResponse Usenet.Nntp.Responses.NntpArticleResponse.Article.get -> Usenet.Nntp.Models.NntpArticle? -Usenet.Nntp.Responses.NntpArticleResponse.NntpArticleResponse(int code, string! message, bool success, Usenet.Nntp.Models.NntpArticle? article) -> void Usenet.Nntp.Responses.NntpDateResponse Usenet.Nntp.Responses.NntpDateResponse.DateTime.get -> System.DateTimeOffset -Usenet.Nntp.Responses.NntpDateResponse.NntpDateResponse(int code, string! message, bool success, System.DateTimeOffset dateTime) -> void Usenet.Nntp.Responses.NntpGroupOriginsResponse Usenet.Nntp.Responses.NntpGroupOriginsResponse.GroupOrigins.get -> System.Collections.Immutable.IImmutableList! -Usenet.Nntp.Responses.NntpGroupOriginsResponse.NntpGroupOriginsResponse(int code, string! message, bool success, System.Collections.Generic.IList! groupOrigins) -> void Usenet.Nntp.Responses.NntpGroupResponse Usenet.Nntp.Responses.NntpGroupResponse.Group.get -> Usenet.Nntp.Models.NntpGroup! -Usenet.Nntp.Responses.NntpGroupResponse.NntpGroupResponse(int code, string! message, bool success, Usenet.Nntp.Models.NntpGroup! group) -> void Usenet.Nntp.Responses.NntpGroupsResponse Usenet.Nntp.Responses.NntpGroupsResponse.Groups.get -> System.Collections.Immutable.IImmutableList! -Usenet.Nntp.Responses.NntpGroupsResponse.NntpGroupsResponse(int code, string! message, bool success, System.Collections.Generic.IList! groups) -> void Usenet.Nntp.Responses.NntpLastResponse Usenet.Nntp.Responses.NntpLastResponse.MessageId.get -> Usenet.Nntp.Models.NntpMessageId! -Usenet.Nntp.Responses.NntpLastResponse.NntpLastResponse(int code, string! message, bool success, Usenet.Nntp.Responses.NntpLastResponseType responseType, long number, Usenet.Nntp.Models.NntpMessageId! messageId) -> void Usenet.Nntp.Responses.NntpLastResponse.Number.get -> long Usenet.Nntp.Responses.NntpLastResponse.ResponseType.get -> Usenet.Nntp.Responses.NntpLastResponseType Usenet.Nntp.Responses.NntpLastResponseType @@ -570,7 +558,6 @@ Usenet.Nntp.Responses.NntpLastResponseType.NoGroupSelected = 412 -> Usenet.Nntp. Usenet.Nntp.Responses.NntpLastResponseType.NoPreviousArticleInGroup = 422 -> Usenet.Nntp.Responses.NntpLastResponseType Usenet.Nntp.Responses.NntpLastResponseType.Unknown = 0 -> Usenet.Nntp.Responses.NntpLastResponseType Usenet.Nntp.Responses.NntpModeReaderResponse -Usenet.Nntp.Responses.NntpModeReaderResponse.NntpModeReaderResponse(int code, string! message, bool success, Usenet.Nntp.Responses.NntpModeReaderResponseType responseType) -> void Usenet.Nntp.Responses.NntpModeReaderResponse.ResponseType.get -> Usenet.Nntp.Responses.NntpModeReaderResponseType Usenet.Nntp.Responses.NntpModeReaderResponseType Usenet.Nntp.Responses.NntpModeReaderResponseType.PostingAllowed = 200 -> Usenet.Nntp.Responses.NntpModeReaderResponseType @@ -579,10 +566,8 @@ Usenet.Nntp.Responses.NntpModeReaderResponseType.ReadingServiceUnavailable = 502 Usenet.Nntp.Responses.NntpModeReaderResponseType.Unknown = 0 -> Usenet.Nntp.Responses.NntpModeReaderResponseType Usenet.Nntp.Responses.NntpMultiLineResponse Usenet.Nntp.Responses.NntpMultiLineResponse.Lines.get -> System.Collections.Immutable.IImmutableList! -Usenet.Nntp.Responses.NntpMultiLineResponse.NntpMultiLineResponse(int code, string! message, bool success, System.Collections.Generic.IEnumerable! lines) -> void Usenet.Nntp.Responses.NntpNextResponse Usenet.Nntp.Responses.NntpNextResponse.MessageId.get -> Usenet.Nntp.Models.NntpMessageId! -Usenet.Nntp.Responses.NntpNextResponse.NntpNextResponse(int code, string! message, bool success, Usenet.Nntp.Responses.NntpNextResponseType responseType, long number, Usenet.Nntp.Models.NntpMessageId! messageId) -> void Usenet.Nntp.Responses.NntpNextResponse.Number.get -> long Usenet.Nntp.Responses.NntpNextResponse.ResponseType.get -> Usenet.Nntp.Responses.NntpNextResponseType Usenet.Nntp.Responses.NntpNextResponseType @@ -594,11 +579,9 @@ Usenet.Nntp.Responses.NntpNextResponseType.Unknown = 0 -> Usenet.Nntp.Responses. Usenet.Nntp.Responses.NntpResponse Usenet.Nntp.Responses.NntpResponse.Code.get -> int Usenet.Nntp.Responses.NntpResponse.Message.get -> string! -Usenet.Nntp.Responses.NntpResponse.NntpResponse(int code, string! message, bool success) -> void Usenet.Nntp.Responses.NntpResponse.Success.get -> bool Usenet.Nntp.Responses.NntpStatResponse Usenet.Nntp.Responses.NntpStatResponse.MessageId.get -> Usenet.Nntp.Models.NntpMessageId! -Usenet.Nntp.Responses.NntpStatResponse.NntpStatResponse(int code, string! message, bool success, Usenet.Nntp.Responses.NntpStatResponseType responseType, long number, Usenet.Nntp.Models.NntpMessageId! messageId) -> void Usenet.Nntp.Responses.NntpStatResponse.Number.get -> long Usenet.Nntp.Responses.NntpStatResponse.ResponseType.get -> Usenet.Nntp.Responses.NntpStatResponseType Usenet.Nntp.Responses.NntpStatResponseType @@ -621,14 +604,12 @@ Usenet.Nzb.NzbDocument Usenet.Nzb.NzbDocument.Equals(Usenet.Nzb.NzbDocument? other) -> bool Usenet.Nzb.NzbDocument.Files.get -> System.Collections.Immutable.ImmutableList! Usenet.Nzb.NzbDocument.MetaData.get -> System.Collections.Immutable.ImmutableDictionary!>! -Usenet.Nzb.NzbDocument.NzbDocument(System.Collections.Generic.IDictionary!>? metaData, System.Collections.Generic.IEnumerable? files) -> void Usenet.Nzb.NzbDocument.Size.get -> long Usenet.Nzb.NzbFile Usenet.Nzb.NzbFile.Date.get -> System.DateTimeOffset Usenet.Nzb.NzbFile.Equals(Usenet.Nzb.NzbFile? other) -> bool Usenet.Nzb.NzbFile.FileName.get -> string! Usenet.Nzb.NzbFile.Groups.get -> Usenet.Nntp.Models.NntpGroups! -Usenet.Nzb.NzbFile.NzbFile(string! poster, string! subject, string! fileName, System.DateTimeOffset date, Usenet.Nntp.Models.NntpGroups! groups, System.Collections.Generic.IEnumerable! segments) -> void Usenet.Nzb.NzbFile.Poster.get -> string! Usenet.Nzb.NzbFile.Segments.get -> System.Collections.Immutable.ImmutableList! Usenet.Nzb.NzbFile.Size.get -> long @@ -638,7 +619,6 @@ Usenet.Nzb.NzbSegment Usenet.Nzb.NzbSegment.Equals(Usenet.Nzb.NzbSegment? other) -> bool Usenet.Nzb.NzbSegment.MessageId.get -> Usenet.Nntp.Models.NntpMessageId! Usenet.Nzb.NzbSegment.Number.get -> int -Usenet.Nzb.NzbSegment.NzbSegment(int number, long offset, long size, Usenet.Nntp.Models.NntpMessageId! messageId) -> void Usenet.Nzb.NzbSegment.Offset.get -> long Usenet.Nzb.NzbSegment.Size.get -> long Usenet.Nzb.NzbWriter @@ -660,11 +640,9 @@ Usenet.Util.ValidationFailure Usenet.Util.ValidationFailure.Code.get -> string! Usenet.Util.ValidationFailure.Data.get -> object? Usenet.Util.ValidationFailure.Message.get -> string! -Usenet.Util.ValidationFailure.ValidationFailure(string! code, string! message, object? data = null) -> void Usenet.Util.ValidationResult -Usenet.Util.ValidationResult.Failures.get -> System.Collections.Generic.IList! +Usenet.Util.ValidationResult.Failures.get -> System.Collections.Generic.IReadOnlyList! Usenet.Util.ValidationResult.IsValid.get -> bool -Usenet.Util.ValidationResult.ValidationResult(System.Collections.Generic.IList! failures) -> void Usenet.Yenc.YencArticle Usenet.Yenc.YencArticle.Data.get -> System.Collections.Generic.IReadOnlyList! Usenet.Yenc.YencArticle.Footer.get -> Usenet.Yenc.YencFooter? diff --git a/src/Usenet/Usenet.csproj b/src/Usenet/Usenet.csproj index 2ccf518..2a9cdcc 100644 --- a/src/Usenet/Usenet.csproj +++ b/src/Usenet/Usenet.csproj @@ -25,6 +25,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -42,6 +43,7 @@ Include="System.Linq.Async" Condition="'$(TargetFramework)' == 'netstandard2.0' Or '$(TargetFramework)' == 'netstandard2.1'" /> + diff --git a/src/Usenet/Util/AbstractBaseStream.cs b/src/Usenet/Util/AbstractBaseStream.cs index 70110df..0fabce8 100644 --- a/src/Usenet/Util/AbstractBaseStream.cs +++ b/src/Usenet/Util/AbstractBaseStream.cs @@ -1,9 +1,12 @@ -namespace Usenet.Util; +using JetBrains.Annotations; + +namespace Usenet.Util; /// /// Represents an abstract base stream that overrides all abstract methods of the class. /// Derived classes only need to override the methods they need. /// +[PublicAPI] public abstract class AbstractBaseStream : Stream { /// diff --git a/src/Usenet/Util/Compatibility/ObjectDisposedExceptionShims.cs b/src/Usenet/Util/Compatibility/ObjectDisposedExceptionShims.cs index 26eaa78..159b07b 100644 --- a/src/Usenet/Util/Compatibility/ObjectDisposedExceptionShims.cs +++ b/src/Usenet/Util/Compatibility/ObjectDisposedExceptionShims.cs @@ -8,7 +8,7 @@ public static void ThrowIf(bool condition, object instance) ObjectDisposedException.ThrowIf(condition, instance); #else if (condition) - throw new ObjectDisposedException(instance?.GetType().FullName); + throw new ObjectDisposedException(instance.GetType().FullName); #endif } } diff --git a/src/Usenet/Util/CountingStream.cs b/src/Usenet/Util/CountingStream.cs index ff53e58..a3ffeb9 100644 --- a/src/Usenet/Util/CountingStream.cs +++ b/src/Usenet/Util/CountingStream.cs @@ -1,8 +1,11 @@ -namespace Usenet.Util; +using JetBrains.Annotations; + +namespace Usenet.Util; /// /// Represents a counting stream. It can be used to count the number of bytes read and written. /// +[PublicAPI] public class CountingStream : AbstractBaseStream { private readonly Stream _innerStream; diff --git a/src/Usenet/Util/Crc32.cs b/src/Usenet/Util/Crc32.cs index adff791..5a1c50d 100644 --- a/src/Usenet/Util/Crc32.cs +++ b/src/Usenet/Util/Crc32.cs @@ -14,11 +14,10 @@ public static uint CalculateChecksum(IEnumerable buffer) { Guard.ThrowIfNull(buffer); - var value = Seed; - foreach (var b in buffer) - { - value = (value >> 8) ^ LookupTable[(value & 0xFF) ^ b]; - } + var value = buffer.Aggregate( + Seed, + (current, b) => (current >> 8) ^ LookupTable[(current & 0xFF) ^ b] + ); return value ^ Seed; } diff --git a/src/Usenet/Util/EnumerableStream.cs b/src/Usenet/Util/EnumerableStream.cs index 4aa12dc..31076ed 100644 --- a/src/Usenet/Util/EnumerableStream.cs +++ b/src/Usenet/Util/EnumerableStream.cs @@ -1,10 +1,13 @@ -namespace Usenet.Util; +using JetBrains.Annotations; + +namespace Usenet.Util; /// /// Represents an enumerable stream. Can be used to stream an enumerable collection of /// byte buffers. /// -public class EnumerableStream : AbstractBaseStream +[PublicAPI] +public abstract class EnumerableStream : AbstractBaseStream { private readonly IEnumerator _enumerator; private byte[]? _currentChunk; @@ -14,7 +17,7 @@ public class EnumerableStream : AbstractBaseStream /// Creates a new instance of the class. /// /// An enumerable collection of byte buffers. - public EnumerableStream(IEnumerable input) + protected EnumerableStream(IEnumerable input) { Guard.ThrowIfNull(input); _enumerator = input.GetEnumerator(); @@ -26,9 +29,7 @@ protected override void Dispose(bool disposing) try { if (disposing) - { - _enumerator?.Dispose(); - } + _enumerator.Dispose(); } finally { diff --git a/src/Usenet/Util/Guard.cs b/src/Usenet/Util/Guard.cs index a6386c9..0097193 100644 --- a/src/Usenet/Util/Guard.cs +++ b/src/Usenet/Util/Guard.cs @@ -36,7 +36,7 @@ public static void ThrowIfNull( /// The name of the string /// ArgumentNullException /// ArgumentException - public static void ThrowIfNullOrEmpty([NotNull] string str, string name) + public static void ThrowIfNullOrEmpty(string str, string name) { #if NET8_0_OR_GREATER ArgumentException.ThrowIfNullOrEmpty(str, name); @@ -55,7 +55,7 @@ public static void ThrowIfNullOrEmpty([NotNull] string str, string name) /// The name of the string /// ArgumentNullException /// ArgumentException - public static void ThrowIfNullOrWhiteSpace([NotNull] string str, string name) + public static void ThrowIfNullOrWhiteSpace(string str, string name) { #if NET8_0_OR_GREATER ArgumentException.ThrowIfNullOrWhiteSpace(str, name); diff --git a/src/Usenet/Util/HashCode.cs b/src/Usenet/Util/HashCode.cs index 9f3a9fc..39cee8a 100644 --- a/src/Usenet/Util/HashCode.cs +++ b/src/Usenet/Util/HashCode.cs @@ -30,7 +30,7 @@ public static int Hash(this int hash, T? obj) => /// The collection. /// A 32-bit signed integer hash code. public static int Hash(this int hash, IEnumerable collection) => - collection?.Aggregate(hash, Hash) ?? hash; + collection.Aggregate(hash, Hash); /// /// Combine the current hash code with the combined hash codes of all the keys and values in the @@ -43,6 +43,5 @@ public static int Hash(this int hash, IEnumerable collection) => /// The dictionary. /// public static int Hash(this int hash, IDictionary dictionary) => - dictionary?.Aggregate(hash, (current, pair) => current.Hash(pair.Key).Hash(pair.Value)) - ?? hash; + dictionary.Aggregate(hash, (current, pair) => current.Hash(pair.Key).Hash(pair.Value)); } diff --git a/src/Usenet/Util/MultiSetComparer.cs b/src/Usenet/Util/MultiSetComparer.cs index 445e410..2121b49 100644 --- a/src/Usenet/Util/MultiSetComparer.cs +++ b/src/Usenet/Util/MultiSetComparer.cs @@ -16,16 +16,13 @@ internal class MultiSetComparer : IEqualityComparer> /// using the specified . /// /// The equality comparer to use. - public MultiSetComparer(IEqualityComparer comparer) - { - _comparer = comparer ?? EqualityComparer.Default; - } + private MultiSetComparer(IEqualityComparer comparer) => _comparer = comparer; /// /// Creates a new instance of the class /// using the default for the type specified by the generic argument. /// - public MultiSetComparer() + private MultiSetComparer() : this(EqualityComparer.Default) { } /// diff --git a/src/Usenet/Util/MultiValueDictionary.cs b/src/Usenet/Util/MultiValueDictionary.cs index 4db3a9e..752da52 100644 --- a/src/Usenet/Util/MultiValueDictionary.cs +++ b/src/Usenet/Util/MultiValueDictionary.cs @@ -60,7 +60,7 @@ IEqualityComparer keyComparer /// The value of the element to add. public virtual void Add(TKey key, TValue value) { - if (!TryGetValue(key, out var values) || values == null) + if (!TryGetValue(key, out var values)) { values = _collectionFactory(); Add(key, values); @@ -77,7 +77,7 @@ public virtual void Add(TKey key, TValue value) /// true if the element is successfully found and removed; otherwise, false. public virtual bool Remove(TKey key, TValue value) { - if (!TryGetValue(key, out var values) || values == null) + if (!TryGetValue(key, out var values)) return false; if (!values.Remove(value)) return false; @@ -90,10 +90,7 @@ public virtual bool Remove(TKey key, TValue value) /// Gets the number of elements contained in the . /// The number of elements contained in the . - public new int Count => - Values - .Where(valueCollection => valueCollection != null) - .Sum(valueCollection => valueCollection.Count); + public new int Count => Values.Sum(valueCollection => valueCollection.Count); /// /// Represents an empty . diff --git a/src/Usenet/Util/UsenetEncoding.cs b/src/Usenet/Util/UsenetEncoding.cs index 984dfc7..4c5acfc 100644 --- a/src/Usenet/Util/UsenetEncoding.cs +++ b/src/Usenet/Util/UsenetEncoding.cs @@ -1,10 +1,12 @@ using System.Text; +using JetBrains.Annotations; namespace Usenet.Util; /// /// This class defines the default usenet character encoding. /// +[PublicAPI] public static class UsenetEncoding { /// diff --git a/src/Usenet/Util/ValidationFailure.cs b/src/Usenet/Util/ValidationFailure.cs index 25ecc86..9a94db4 100644 --- a/src/Usenet/Util/ValidationFailure.cs +++ b/src/Usenet/Util/ValidationFailure.cs @@ -1,10 +1,12 @@ -using Usenet.Extensions; +using JetBrains.Annotations; +using Usenet.Extensions; namespace Usenet.Util; /// /// Represents a validation failure. /// +[PublicAPI] public class ValidationFailure { /// @@ -28,7 +30,7 @@ public class ValidationFailure /// A code associated with the validation failure. /// A message describing the validation failure. /// A data object containing information about the validation failure. - public ValidationFailure(string code, string message, object? data = null) + internal ValidationFailure(string code, string message, object? data = null) { Code = code.ThrowIfNullOrWhiteSpace(nameof(code)); Message = message; diff --git a/src/Usenet/Util/ValidationResult.cs b/src/Usenet/Util/ValidationResult.cs index 26ee81e..59906ab 100644 --- a/src/Usenet/Util/ValidationResult.cs +++ b/src/Usenet/Util/ValidationResult.cs @@ -1,23 +1,23 @@ -namespace Usenet.Util; +using JetBrains.Annotations; + +namespace Usenet.Util; /// /// Represents a validation result. /// +[PublicAPI] public class ValidationResult { /// /// A collection of objects. /// - public IList Failures { get; } + public IReadOnlyList Failures { get; } /// /// Creates a new instance of the class. /// /// A collection of objects. - public ValidationResult(IList failures) - { - Failures = failures ?? new List(0); - } + internal ValidationResult(IReadOnlyList failures) => Failures = failures; /// /// A property indicating whether the validation result is valid or not. diff --git a/src/Usenet/Yenc/YencArticle.cs b/src/Usenet/Yenc/YencArticle.cs index 75ec43f..29decb1 100644 --- a/src/Usenet/Yenc/YencArticle.cs +++ b/src/Usenet/Yenc/YencArticle.cs @@ -1,4 +1,4 @@ -using Usenet.Extensions; +using JetBrains.Annotations; using Usenet.Util; namespace Usenet.Yenc; @@ -6,6 +6,7 @@ namespace Usenet.Yenc; /// /// Represents a decoded yEnc-encoded article. /// +[PublicAPI] public class YencArticle { /// diff --git a/src/Usenet/Yenc/YencArticleDecoder.cs b/src/Usenet/Yenc/YencArticleDecoder.cs index 6920d5c..fcc4202 100644 --- a/src/Usenet/Yenc/YencArticleDecoder.cs +++ b/src/Usenet/Yenc/YencArticleDecoder.cs @@ -1,4 +1,5 @@ using System.Text; +using JetBrains.Annotations; using Usenet.Extensions; using Usenet.Util; @@ -9,6 +10,7 @@ namespace Usenet.Yenc; /// The article is completely decoded in memory. /// Based on Kristian Hellang's yEnc project https://github.com/khellang/yEnc. /// +[PublicAPI] public static class YencArticleDecoder { private const string YEnd = YencKeywords.YEnd + " "; diff --git a/src/Usenet/Yenc/YencEncoder.cs b/src/Usenet/Yenc/YencEncoder.cs index 5282942..319e1f2 100644 --- a/src/Usenet/Yenc/YencEncoder.cs +++ b/src/Usenet/Yenc/YencEncoder.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Text; +using JetBrains.Annotations; using Usenet.Extensions; using Usenet.Util; @@ -8,6 +9,7 @@ namespace Usenet.Yenc; /// /// Represents an yEnc encoder. /// +[PublicAPI] public static class YencEncoder { /// @@ -68,9 +70,8 @@ CancellationToken cancellationToken Guard.ThrowIfNull(stream); Guard.ThrowIfNull(encoding); - var lines = new List(); + List lines = [GetHeaderLine(header)]; - lines.Add(GetHeaderLine(header)); if (header.IsFilePart) { lines.Add(GetPartHeaderLine(header)); diff --git a/src/Usenet/Yenc/YencFooter.cs b/src/Usenet/Yenc/YencFooter.cs index 88b4576..a245732 100644 --- a/src/Usenet/Yenc/YencFooter.cs +++ b/src/Usenet/Yenc/YencFooter.cs @@ -1,9 +1,12 @@ -namespace Usenet.Yenc; +using JetBrains.Annotations; + +namespace Usenet.Yenc; /// /// Represents the yEnc footer (=yend) line. /// Based on Kristian Hellang's yEnc project https://github.com/khellang/yEnc. /// +[PublicAPI] public class YencFooter { /// diff --git a/src/Usenet/Yenc/YencHeader.cs b/src/Usenet/Yenc/YencHeader.cs index 126b4fc..fdf0ad7 100644 --- a/src/Usenet/Yenc/YencHeader.cs +++ b/src/Usenet/Yenc/YencHeader.cs @@ -1,10 +1,13 @@ -namespace Usenet.Yenc; +using JetBrains.Annotations; + +namespace Usenet.Yenc; /// /// Represents the combined information of the yEnc header (=ybegin) line /// and the yEnc part header (=ypart) line if present. /// Based on Kristian Hellang's yEnc project https://github.com/khellang/yEnc. /// +[PublicAPI] public class YencHeader { /// diff --git a/src/Usenet/Yenc/YencKeyWords.cs b/src/Usenet/Yenc/YencKeyWords.cs index 6515f85..b8a0300 100644 --- a/src/Usenet/Yenc/YencKeyWords.cs +++ b/src/Usenet/Yenc/YencKeyWords.cs @@ -4,7 +4,7 @@ /// yEnc keywords /// Based on Kristian Hellang's yEnc project https://github.com/khellang/yEnc. /// -internal class YencKeywords +internal static class YencKeywords { public const string YBegin = "=ybegin"; public const string YPart = "=ypart"; diff --git a/src/Usenet/Yenc/YencLineDecoder.cs b/src/Usenet/Yenc/YencLineDecoder.cs index 5f55cb0..0a72c6c 100644 --- a/src/Usenet/Yenc/YencLineDecoder.cs +++ b/src/Usenet/Yenc/YencLineDecoder.cs @@ -4,12 +4,12 @@ /// yEnc line decoder. /// Based on Kristian Hellang's yEnc project https://github.com/khellang/yEnc. /// -internal class YencLineDecoder +internal static class YencLineDecoder { public static int Decode(byte[] encodedBytes, byte[] decodedBytes, int decodedOffset) => Decode(encodedBytes, 0, encodedBytes.Length, decodedBytes, decodedOffset); - public static int Decode( + private static int Decode( byte[] encodedBytes, int encodedOffset, int encodedCount, diff --git a/src/Usenet/Yenc/YencMeta.cs b/src/Usenet/Yenc/YencMeta.cs index a5861e5..cd96a83 100644 --- a/src/Usenet/Yenc/YencMeta.cs +++ b/src/Usenet/Yenc/YencMeta.cs @@ -1,6 +1,8 @@ using Usenet.Exceptions; using Usenet.Extensions; +#if NETSTANDARD2_0 using Usenet.Util.Compatibility; +#endif namespace Usenet.Yenc; @@ -8,7 +10,7 @@ namespace Usenet.Yenc; /// Utiltiy class to retrieve yEnc metadata. /// Based on Kristian Hellang's yEnc project https://github.com/khellang/yEnc. /// -internal class YencMeta +internal static class YencMeta { private const string YBegin = $"{YencKeywords.YBegin} "; private const string YPart = $"{YencKeywords.YPart} "; @@ -46,7 +48,9 @@ public static IDictionary GetPartHeaders(IEnumerator enu if ( enumerator.MoveNext() +#if NETSTANDARD2_0 || NETSTANDARD2_1 && enumerator.Current != null +#endif && enumerator.Current.StartsWith(YPart, StringComparison.Ordinal) ) { @@ -95,11 +99,6 @@ public static YencFooter ParseFooter(IDictionary footer) public static Dictionary ParseLine(string line) { - if (line == null) - { - return new Dictionary(0); - } - // name is always last item on the header line var nameSplit = line.Split(NameSeparator, StringSplitOptions.RemoveEmptyEntries); if (nameSplit.Length == 0) diff --git a/src/Usenet/Yenc/YencStream.cs b/src/Usenet/Yenc/YencStream.cs index 7044f74..efcb229 100644 --- a/src/Usenet/Yenc/YencStream.cs +++ b/src/Usenet/Yenc/YencStream.cs @@ -1,4 +1,5 @@ -using Usenet.Util; +using JetBrains.Annotations; +using Usenet.Util; namespace Usenet.Yenc; @@ -6,6 +7,7 @@ namespace Usenet.Yenc; /// Represents a decoded yEnc-encoded article as a stream. /// Based on Kristian Hellang's yEnc project https://github.com/khellang/yEnc. /// +[PublicAPI] public class YencStream : EnumerableStream { /// diff --git a/src/Usenet/Yenc/YencStreamDecoder.cs b/src/Usenet/Yenc/YencStreamDecoder.cs index f65bb1d..3bd3143 100644 --- a/src/Usenet/Yenc/YencStreamDecoder.cs +++ b/src/Usenet/Yenc/YencStreamDecoder.cs @@ -1,4 +1,5 @@ using System.Text; +using JetBrains.Annotations; using Usenet.Extensions; using Usenet.Util; @@ -9,6 +10,7 @@ namespace Usenet.Yenc; /// The article is decoded streaming. /// Based on Kristian Hellang's yEnc project https://github.com/khellang/yEnc. /// +[PublicAPI] public static class YencStreamDecoder { private const string YEnd = YencKeywords.YEnd + " "; diff --git a/src/Usenet/Yenc/YencValidationErrorCodes.cs b/src/Usenet/Yenc/YencValidationErrorCodes.cs index fdaf6b0..941ac8a 100644 --- a/src/Usenet/Yenc/YencValidationErrorCodes.cs +++ b/src/Usenet/Yenc/YencValidationErrorCodes.cs @@ -4,7 +4,7 @@ /// yEnc validation error codes. /// Based on Kristian Hellang's yEnc project https://github.com/khellang/yEnc. /// -internal class YencValidationErrorCodes +internal static class YencValidationErrorCodes { public const string MissingChecksum = "MissingChecksum"; public const string ChecksumMismatch = "ChecksumMismatch"; diff --git a/src/Usenet/Yenc/YencValidator.cs b/src/Usenet/Yenc/YencValidator.cs index 0a1fdb9..8ca4550 100644 --- a/src/Usenet/Yenc/YencValidator.cs +++ b/src/Usenet/Yenc/YencValidator.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using Usenet.Util; namespace Usenet.Yenc; @@ -6,6 +7,7 @@ namespace Usenet.Yenc; /// Represents a yEnc-encoded article validator. /// Based on Kristian Hellang's yEnc project https://github.com/khellang/yEnc. /// +[PublicAPI] public static class YencValidator { /// diff --git a/tests/Usenet.Tests/Nntp/Builders/NntpArticleBuilderTests.cs b/tests/Usenet.Tests/Nntp/Builders/NntpArticleBuilderTests.cs index 0c1b2d5..4843e80 100644 --- a/tests/Usenet.Tests/Nntp/Builders/NntpArticleBuilderTests.cs +++ b/tests/Usenet.Tests/Nntp/Builders/NntpArticleBuilderTests.cs @@ -1,11 +1,10 @@ -using Usenet.Exceptions; +using System.Diagnostics.CodeAnalysis; +using Usenet.Exceptions; using Usenet.Nntp; using Usenet.Nntp.Builders; using Usenet.Nntp.Models; using Usenet.Util; -// ReSharper disable DuplicateKeyCollectionInitialization - namespace Usenet.Tests.Nntp.Builders; internal sealed class NntpArticleBuilderTests @@ -142,6 +141,7 @@ string expectedParamName } [Test] + [SuppressMessage("ReSharper", "DuplicateKeyCollectionInitialization")] public async Task BuildShouldBuildArticle() { var expected = new NntpArticle( @@ -155,7 +155,7 @@ public async Task BuildShouldBuildArticle() { "Header1", "Value1" }, { "Header1", "Value2" }, }, - null + [] ); var actual = new NntpArticleBuilder() @@ -174,6 +174,7 @@ public async Task BuildShouldBuildArticle() } [Test] + [SuppressMessage("ReSharper", "DuplicateKeyCollectionInitialization")] public async Task BuildInitializedFromExistingArticleShouldBuildSameArticle() { var expected = new NntpArticle( @@ -187,7 +188,7 @@ public async Task BuildInitializedFromExistingArticleShouldBuildSameArticle() { "Header1", "Value1" }, { "Header1", "Value2" }, }, - null + [] ); var actual = new NntpArticleBuilder().InitializeFrom(expected).Build(); diff --git a/tests/Usenet.Tests/Nntp/Models/NntpArticleTests.cs b/tests/Usenet.Tests/Nntp/Models/NntpArticleTests.cs index 0f79951..5b64ac5 100644 --- a/tests/Usenet.Tests/Nntp/Models/NntpArticleTests.cs +++ b/tests/Usenet.Tests/Nntp/Models/NntpArticleTests.cs @@ -1,4 +1,5 @@ -using Usenet.Nntp.Models; +using System.Diagnostics.CodeAnalysis; +using Usenet.Nntp.Models; using Usenet.Util; namespace Usenet.Tests.Nntp.Models; @@ -6,12 +7,13 @@ namespace Usenet.Tests.Nntp.Models; internal sealed class NntpArticleTests { [Test] + [SuppressMessage("ReSharper", "DuplicateKeyCollectionInitialization")] internal async Task EqualsWithSameValuesShouldReturnTrue() { var article1 = new NntpArticle( 0, "123@bla.nl", - null, + NntpGroups.Empty, new MultiValueDictionary { { "h1", "val1" }, @@ -25,7 +27,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue() var article2 = new NntpArticle( 0, "123@bla.nl", - null, + NntpGroups.Empty, new MultiValueDictionary { { "h3", "val4" }, diff --git a/tests/Usenet.Tests/Nntp/Models/NntpGroupTests.cs b/tests/Usenet.Tests/Nntp/Models/NntpGroupTests.cs index 25cc73e..a7741d8 100644 --- a/tests/Usenet.Tests/Nntp/Models/NntpGroupTests.cs +++ b/tests/Usenet.Tests/Nntp/Models/NntpGroupTests.cs @@ -7,10 +7,10 @@ namespace Usenet.Tests.Nntp.Models; internal sealed class NntpGroupTests { public static IEnumerable< - Func<(string, long, long, long, NntpPostingStatus, string?, long[]?)> + Func<(string, long, long, long, NntpPostingStatus, string, long[])> > SerializationData() { - yield return () => ("test", 10, 2, 11, NntpPostingStatus.PostingPermitted, null, null); + yield return () => ("test", 10, 2, 11, NntpPostingStatus.PostingPermitted, "", []); yield return () => ("alt.rfc-writers.recovery", 0, 1, 4, NntpPostingStatus.PostingPermitted, "", []); yield return () => @@ -29,8 +29,8 @@ public async Task SerializedInstanceShouldBeDeserializedCorrectly( long lowWaterMark, long highWaterMark, NntpPostingStatus postingStatus, - string? otherGroup, - long[]? articleNumbers + string otherGroup, + long[] articleNumbers ) { var expected = new NntpGroup( @@ -39,8 +39,8 @@ public async Task SerializedInstanceShouldBeDeserializedCorrectly( lowWaterMark, highWaterMark, postingStatus, - otherGroup!, - articleNumbers?.ToImmutableList()! + otherGroup, + articleNumbers.ToImmutableList() ); var json = JsonSerializer.Serialize(expected); @@ -57,14 +57,22 @@ public async Task SerializedInstanceShouldBeDeserializedCorrectly( 1, 10, NntpPostingStatus.PostingPermitted, - null!, + string.Empty, [1, 2, 3] ), - new NntpGroup("group1", 10, 1, 10, NntpPostingStatus.PostingPermitted, null!, [1, 2, 3]) + new NntpGroup( + "group1", + 10, + 1, + 10, + NntpPostingStatus.PostingPermitted, + string.Empty, + [1, 2, 3] + ) ); yield return ( - new NntpGroup("group1", 10, 1, 10, NntpPostingStatus.PostingPermitted, "other", null!), - new NntpGroup("group1", 10, 1, 10, NntpPostingStatus.PostingPermitted, "other", null!) + new NntpGroup("group1", 10, 1, 10, NntpPostingStatus.PostingPermitted, "other", []), + new NntpGroup("group1", 10, 1, 10, NntpPostingStatus.PostingPermitted, "other", []) ); } @@ -207,7 +215,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue(NntpGroup group1, NntpG "other", [1, 2, 3] ), - new NntpGroup("group1", 10, 1, 10, NntpPostingStatus.PostingPermitted, "other", null!) + new NntpGroup("group1", 10, 1, 10, NntpPostingStatus.PostingPermitted, "other", []) ); } diff --git a/tests/Usenet.Tests/Nntp/Parsers/ArticleResponseParserTests.cs b/tests/Usenet.Tests/Nntp/Parsers/ArticleResponseParserTests.cs index 2a08af7..e4afe28 100644 --- a/tests/Usenet.Tests/Nntp/Parsers/ArticleResponseParserTests.cs +++ b/tests/Usenet.Tests/Nntp/Parsers/ArticleResponseParserTests.cs @@ -15,7 +15,13 @@ internal sealed class ArticleResponseParserTests "123 <123@poster.com>", (int)ArticleRequestType.Article, [], - new NntpArticle(123, "<123@poster.com>", null, null, new List(0)) + new NntpArticle( + 123, + "<123@poster.com>", + NntpGroups.Empty, + MultiValueDictionary.EmptyIgnoreCase, + new List(0) + ) ); yield return () => @@ -33,7 +39,7 @@ internal sealed class ArticleResponseParserTests new NntpArticle( 123, "<123@poster.com>", - null, + NntpGroups.Empty, new MultiValueDictionary { { "Path", "pathost!demo!whitehouse!not-for-mail" }, @@ -52,8 +58,8 @@ internal sealed class ArticleResponseParserTests new NntpArticle( 123, "<123@poster.com>", - null, - null, + NntpGroups.Empty, + MultiValueDictionary.EmptyIgnoreCase, new List { "This is just a test article (2).", "With two lines." } ) ); @@ -67,7 +73,7 @@ internal sealed class ArticleResponseParserTests new NntpArticle( 123, "<123@poster.com>", - null, + NntpGroups.Empty, new MultiValueDictionary { { "Multi", "line1 line2 line3" }, @@ -86,7 +92,7 @@ internal sealed class ArticleResponseParserTests new NntpArticle( 123, "<123@poster.com>", - null, + NntpGroups.Empty, new MultiValueDictionary { { "Path", "pathost!demo!whitehouse!not-for-mail" }, diff --git a/tests/Usenet.Tests/Nntp/Parsers/GroupsResponseParserTests.cs b/tests/Usenet.Tests/Nntp/Parsers/GroupsResponseParserTests.cs index 27cad01..18b93e0 100644 --- a/tests/Usenet.Tests/Nntp/Parsers/GroupsResponseParserTests.cs +++ b/tests/Usenet.Tests/Nntp/Parsers/GroupsResponseParserTests.cs @@ -17,7 +17,7 @@ public static IEnumerable< (int)GroupStatusRequestType.Basic, ["alt.rfc-writers.recovery 4 1 y", "tx.natives.recovery 89 56 y"], [ - new( + new NntpGroup( "alt.rfc-writers.recovery", 0, 1, @@ -26,7 +26,7 @@ public static IEnumerable< string.Empty, [] ), - new( + new NntpGroup( "tx.natives.recovery", 0, 56, @@ -49,7 +49,7 @@ public static IEnumerable< (int)GroupStatusRequestType.Extended, ["misc.test 3002322 3000234 1234 y", "rec.food.drink.tea 100 51 3 y"], [ - new( + new NntpGroup( "misc.test", 1234, 3000234, @@ -58,7 +58,7 @@ public static IEnumerable< string.Empty, [] ), - new( + new NntpGroup( "rec.food.drink.tea", 3, 51, diff --git a/tests/Usenet.Tests/Nntp/Pooling/NntpClientPoolTests.cs b/tests/Usenet.Tests/Nntp/Pooling/NntpClientPoolTests.cs index e9dbab1..34d4485 100644 --- a/tests/Usenet.Tests/Nntp/Pooling/NntpClientPoolTests.cs +++ b/tests/Usenet.Tests/Nntp/Pooling/NntpClientPoolTests.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using NSubstitute; using Usenet.Nntp; using Usenet.Nntp.Contracts; @@ -20,6 +21,7 @@ await Assert } [Test] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] public async Task AllClientsBorrowed(CancellationToken cancellationToken) { using var pool = new NntpClientPool( @@ -36,11 +38,11 @@ public async Task AllClientsBorrowed(CancellationToken cancellationToken) }; // Get the first lease, this should succeed - await pool.GetLease(cancellationToken).ConfigureAwait(true); + await pool.GetLease(cancellationToken); // Get the second lease, should throw because the client does not become available again in time await Assert - .That(async () => await pool.GetLease(cancellationToken).ConfigureAwait(true)) + .That(async () => await pool.GetLease(cancellationToken)) .ThrowsExactly(); } @@ -61,15 +63,16 @@ public async Task ClientAvailable(CancellationToken cancellationToken) }; // Get the first lease, this should succeed - var lease1 = await pool.GetLease(cancellationToken).ConfigureAwait(true); + var lease1 = await pool.GetLease(cancellationToken); lease1.Dispose(); // Get the second lease, this should succeed because the first client was returned to the pool - var lease2 = await pool.GetLease(cancellationToken).ConfigureAwait(true); + var lease2 = await pool.GetLease(cancellationToken); lease2.Dispose(); } [Test] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] public async Task DisposeClientAfterError(CancellationToken cancellationToken) { using var server = new TestNntpServer(); @@ -86,18 +89,14 @@ public async Task DisposeClientAfterError(CancellationToken cancellationToken) }; // Get the first lease - var lease1 = await pool.GetLease(cancellationToken).ConfigureAwait(true); + var lease1 = await pool.GetLease(cancellationToken); var client1 = lease1.Client; try { // Group command triggers disconnect on server side - throws IOException when connection is reset // or NntpException when no response is received await Assert - .That(async () => - await lease1 - .Client.GroupAsync("some.group", cancellationToken) - .ConfigureAwait(true) - ) + .That(async () => await lease1.Client.GroupAsync("some.group", cancellationToken)) .ThrowsException(); } finally @@ -107,16 +106,14 @@ await lease1 // The client should be disposed after the disconnect await Assert - .That(async () => - await client1.ArticleAsync("123", cancellationToken).ConfigureAwait(true) - ) + .That(async () => await client1.ArticleAsync("123", cancellationToken)) .ThrowsExactly(); // Get the second lease // This should return a new client - var lease2 = await pool.GetLease(cancellationToken).ConfigureAwait(true); + var lease2 = await pool.GetLease(cancellationToken); var client2 = lease2.Client; - await lease2.Client.ArticleAsync("123", cancellationToken).ConfigureAwait(true); + await lease2.Client.ArticleAsync("123", cancellationToken); lease2.Dispose(); await Assert.That(client2).IsNotEqualTo(client1); diff --git a/tests/Usenet.Tests/Nntp/Writers/ArticleWriterTests.cs b/tests/Usenet.Tests/Nntp/Writers/ArticleWriterTests.cs index a9e7fad..72e5869 100644 --- a/tests/Usenet.Tests/Nntp/Writers/ArticleWriterTests.cs +++ b/tests/Usenet.Tests/Nntp/Writers/ArticleWriterTests.cs @@ -12,13 +12,25 @@ internal sealed class ArticleWriterTests { yield return () => ( - new NntpArticle(0, "1@example.com", "group", null, new List(0)), + new NntpArticle( + 0, + "1@example.com", + "group", + MultiValueDictionary.EmptyIgnoreCase, + new List(0) + ), ["Message-ID: <1@example.com>", "Newsgroups: group", "", "."] ); yield return () => ( - new NntpArticle(0, "<2@example.com>", "group", null, new List(0)), + new NntpArticle( + 0, + "<2@example.com>", + "group", + MultiValueDictionary.EmptyIgnoreCase, + new List(0) + ), ["Message-ID: <2@example.com>", "Newsgroups: group", "", "."] ); @@ -96,7 +108,7 @@ CancellationToken cancellationToken ) { using var connection = new MockConnection(); - await ArticleWriter.WriteAsync(connection, article, cancellationToken).ConfigureAwait(true); + await ArticleWriter.WriteAsync(connection, article, cancellationToken); await Assert.That(connection.GetLines()).IsEquivalentTo(expectedLines); } } diff --git a/tests/Usenet.Tests/Nzb/NzbDocumentTests.cs b/tests/Usenet.Tests/Nzb/NzbDocumentTests.cs index 2981f4d..9c55e3d 100644 --- a/tests/Usenet.Tests/Nzb/NzbDocumentTests.cs +++ b/tests/Usenet.Tests/Nzb/NzbDocumentTests.cs @@ -1,4 +1,5 @@ using Usenet.Nzb; +using Usenet.Util; namespace Usenet.Tests.Nzb; @@ -6,11 +7,14 @@ internal sealed class NzbDocumentTests { public static IEnumerable<(NzbDocument, NzbDocument)> EqualsWithSameValues() { - yield return (new NzbDocument(null, null), new NzbDocument(null, null)); + yield return ( + new NzbDocument(MultiValueDictionary.Empty, []), + new NzbDocument(MultiValueDictionary.Empty, []) + ); yield return ( new NzbDocument( - null, + MultiValueDictionary.Empty, [ new NzbFile( "poster", @@ -18,7 +22,7 @@ internal sealed class NzbDocumentTests "fileName1", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), new NzbFile( "poster", @@ -26,12 +30,12 @@ internal sealed class NzbDocumentTests "fileName2", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), ] ), new NzbDocument( - null, + MultiValueDictionary.Empty, [ new NzbFile( "poster", @@ -39,7 +43,7 @@ internal sealed class NzbDocumentTests "fileName1", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), new NzbFile( "poster", @@ -47,7 +51,7 @@ internal sealed class NzbDocumentTests "fileName2", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), ] ) @@ -55,7 +59,7 @@ internal sealed class NzbDocumentTests yield return ( new NzbDocument( - null, + MultiValueDictionary.Empty, [ new NzbFile( "poster", @@ -63,7 +67,7 @@ internal sealed class NzbDocumentTests "fileName3", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), new NzbFile( "poster", @@ -71,12 +75,12 @@ internal sealed class NzbDocumentTests "fileName4", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), ] ), new NzbDocument( - null, + MultiValueDictionary.Empty, [ new NzbFile( "poster", @@ -84,7 +88,7 @@ internal sealed class NzbDocumentTests "fileName4", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), new NzbFile( "poster", @@ -92,7 +96,7 @@ internal sealed class NzbDocumentTests "fileName3", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), ] ) diff --git a/tests/Usenet.Tests/Nzb/NzbFileTests.cs b/tests/Usenet.Tests/Nzb/NzbFileTests.cs index 09c329a..5f2c9c3 100644 --- a/tests/Usenet.Tests/Nzb/NzbFileTests.cs +++ b/tests/Usenet.Tests/Nzb/NzbFileTests.cs @@ -13,7 +13,7 @@ internal sealed class NzbFileTests "fileName1", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), new NzbFile( "poster", @@ -21,7 +21,7 @@ internal sealed class NzbFileTests "fileName1", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ) ); @@ -92,7 +92,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue(NzbFile expected, NzbFi "fileName", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), new NzbFile( "blabla", @@ -100,7 +100,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue(NzbFile expected, NzbFi "fileName", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ) ); @@ -111,7 +111,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue(NzbFile expected, NzbFi "fileName", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), new NzbFile( "poster", @@ -119,7 +119,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue(NzbFile expected, NzbFi "fileName", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ) ); @@ -130,7 +130,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue(NzbFile expected, NzbFi "fileName", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), new NzbFile( "poster", @@ -138,7 +138,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue(NzbFile expected, NzbFi "blabla", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ) ); @@ -149,7 +149,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue(NzbFile expected, NzbFi "fileName", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), new NzbFile( "poster", @@ -157,7 +157,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue(NzbFile expected, NzbFi "fileName", DateTimeOffset.MinValue, "group1;group2", - null! + [] ) ); @@ -168,7 +168,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue(NzbFile expected, NzbFi "fileName", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "group1;group2", - null! + [] ), new NzbFile( "poster", @@ -176,7 +176,7 @@ internal async Task EqualsWithSameValuesShouldReturnTrue(NzbFile expected, NzbFi "fileName", new DateTimeOffset(2017, 12, 8, 22, 44, 0, TimeSpan.Zero), "blabla", - null! + [] ) ); diff --git a/tests/Usenet.Tests/Nzb/NzbParserTests.cs b/tests/Usenet.Tests/Nzb/NzbParserTests.cs index 03b3cf0..f037092 100644 --- a/tests/Usenet.Tests/Nzb/NzbParserTests.cs +++ b/tests/Usenet.Tests/Nzb/NzbParserTests.cs @@ -19,9 +19,7 @@ CancellationToken cancellationToken ) { var nzbData = file.ReadAllText(UsenetEncoding.Default); - var actualDocument = await NzbParser - .ParseAsync(nzbData, cancellationToken) - .ConfigureAwait(true); + var actualDocument = await NzbParser.ParseAsync(nzbData, cancellationToken); await Assert.That(actualDocument.MetaData["title"].Single()).IsEqualTo("Your File!"); await Assert.That(actualDocument.MetaData["password"].Single()).IsEqualTo("secret"); @@ -40,9 +38,7 @@ public static IEnumerable> GetValidNzbFiles() internal async Task MinimalNzbDataShouldBeParsed(CancellationToken cancellationToken) { const string nzbText = """"""; - var actualDocument = await NzbParser - .ParseAsync(nzbText, cancellationToken) - .ConfigureAwait(true); + var actualDocument = await NzbParser.ParseAsync(nzbText, cancellationToken); await Assert.That(actualDocument.MetaData).IsEmpty(); await Assert.That(actualDocument.Files).IsEmpty(); @@ -59,9 +55,7 @@ internal async Task MultipleMetaDataKeysShouldBeParsed(CancellationToken cancell """; - var actualDocument = await NzbParser - .ParseAsync(nzbText, cancellationToken) - .ConfigureAwait(true); + var actualDocument = await NzbParser.ParseAsync(nzbText, cancellationToken); await Assert.That(actualDocument.MetaData).HasSingleItem(); await Assert.That(actualDocument.MetaData["tag"].Count).IsEqualTo(2); @@ -82,9 +76,7 @@ internal async Task MinimalFileShouldBeParsed(CancellationToken cancellationToke """; - var actualDocument = await NzbParser - .ParseAsync(nzbText, cancellationToken) - .ConfigureAwait(true); + var actualDocument = await NzbParser.ParseAsync(nzbText, cancellationToken); await Assert.That(actualDocument.MetaData).IsEmpty(); await Assert.That(actualDocument.Files).HasSingleItem(); @@ -103,9 +95,7 @@ internal async Task FileDateShouldBeParsed(CancellationToken cancellationToken) """; - var actualDocument = await NzbParser - .ParseAsync(nzbText, cancellationToken) - .ConfigureAwait(true); + var actualDocument = await NzbParser.ParseAsync(nzbText, cancellationToken); await Assert.That(actualDocument.Files[0].Date).IsEqualTo(expected); } @@ -119,9 +109,7 @@ internal async Task InvalidFileDateShouldThrow(CancellationToken cancellationTok """; await Assert - .That(async () => - await NzbParser.ParseAsync(nzbText, cancellationToken).ConfigureAwait(true) - ) + .That(async () => await NzbParser.ParseAsync(nzbText, cancellationToken)) .ThrowsExactly(); } @@ -139,9 +127,7 @@ internal async Task InvalidSegmentNumberShouldThrow(CancellationToken cancellati """; await Assert - .That(async () => - await NzbParser.ParseAsync(nzbText, cancellationToken).ConfigureAwait(true) - ) + .That(async () => await NzbParser.ParseAsync(nzbText, cancellationToken)) .ThrowsExactly(); } @@ -159,9 +145,7 @@ internal async Task MissingSegmentNumberShouldThrow(CancellationToken cancellati """; await Assert - .That(async () => - await NzbParser.ParseAsync(nzbText, cancellationToken).ConfigureAwait(true) - ) + .That(async () => await NzbParser.ParseAsync(nzbText, cancellationToken)) .ThrowsExactly(); } @@ -179,9 +163,7 @@ internal async Task InvalidSegmentSizeShouldThrow(CancellationToken cancellation """; await Assert - .That(async () => - await NzbParser.ParseAsync(nzbText, cancellationToken).ConfigureAwait(true) - ) + .That(async () => await NzbParser.ParseAsync(nzbText, cancellationToken)) .ThrowsExactly(); } @@ -199,9 +181,7 @@ internal async Task MissingSegmentSizeShouldThrow(CancellationToken cancellation """; await Assert - .That(async () => - await NzbParser.ParseAsync(nzbText, cancellationToken).ConfigureAwait(true) - ) + .That(async () => await NzbParser.ParseAsync(nzbText, cancellationToken)) .ThrowsExactly(); } @@ -210,9 +190,7 @@ internal async Task InvalidXmlShouldThrow(CancellationToken cancellationToken) { const string nzbText = "sdfsfasfasdfasdf"; await Assert - .That(async () => - await NzbParser.ParseAsync(nzbText, cancellationToken).ConfigureAwait(true) - ) + .That(async () => await NzbParser.ParseAsync(nzbText, cancellationToken)) .ThrowsExactly(); } @@ -221,9 +199,7 @@ internal async Task InvalidNzbShouldThrow(CancellationToken cancellationToken) { const string nzbText = ""; await Assert - .That(async () => - await NzbParser.ParseAsync(nzbText, cancellationToken).ConfigureAwait(true) - ) + .That(async () => await NzbParser.ParseAsync(nzbText, cancellationToken)) .ThrowsExactly(); } @@ -238,9 +214,7 @@ CancellationToken cancellationToken """; - var actualDocument = await NzbParser - .ParseAsync(nzbText, cancellationToken) - .ConfigureAwait(true); + var actualDocument = await NzbParser.ParseAsync(nzbText, cancellationToken); await Assert.That(actualDocument.Files.Single().FileName).IsEqualTo("TWD151 - 153.rar"); } @@ -255,9 +229,7 @@ CancellationToken cancellationToken """; - var actualDocument = await NzbParser - .ParseAsync(nzbText, cancellationToken) - .ConfigureAwait(true); + var actualDocument = await NzbParser.ParseAsync(nzbText, cancellationToken); await Assert .That(actualDocument.Files.Single().FileName) .IsEqualTo("(TWD151 - 153)[2 / 9] - TWD151 - 153.rar"); @@ -274,9 +246,7 @@ CancellationToken cancellationToken """; - var actualDocument = await NzbParser - .ParseAsync(nzbText, cancellationToken) - .ConfigureAwait(true); + var actualDocument = await NzbParser.ParseAsync(nzbText, cancellationToken); await Assert .That(actualDocument.Files.Single().FileName) .IsEqualTo("[2 / 9] - TWD151 - 153.rar"); diff --git a/tests/Usenet.Tests/Nzb/NzbWriterTests.cs b/tests/Usenet.Tests/Nzb/NzbWriterTests.cs index 997462e..d817590 100644 --- a/tests/Usenet.Tests/Nzb/NzbWriterTests.cs +++ b/tests/Usenet.Tests/Nzb/NzbWriterTests.cs @@ -15,18 +15,19 @@ internal async Task ShouldWriteDocumentToFile( CancellationToken cancellationToken ) { - var expected = await NzbParser - .ParseAsync(file.ReadAllText(UsenetEncoding.Default), cancellationToken) - .ConfigureAwait(true); + var expected = await NzbParser.ParseAsync( + file.ReadAllText(UsenetEncoding.Default), + cancellationToken + ); using var stream = new MemoryStream(); - using var writer = new StreamWriter(stream, UsenetEncoding.Default); + await using var writer = new StreamWriter(stream, UsenetEncoding.Default); using var reader = new StreamReader(stream, UsenetEncoding.Default); // write to file and read back for comparison - await writer.WriteNzbDocumentAsync(expected, cancellationToken).ConfigureAwait(true); + await writer.WriteNzbDocumentAsync(expected, cancellationToken); stream.Position = 0; - var actual = await NzbParser.ParseAsync(reader, cancellationToken).ConfigureAwait(true); + var actual = await NzbParser.ParseAsync(reader, cancellationToken); // compare await Assert.That(actual).IsEqualTo(expected); diff --git a/tests/Usenet.Tests/TestHelpers/TestNntpServer.cs b/tests/Usenet.Tests/TestHelpers/TestNntpServer.cs index 7339ead..c44ad40 100644 --- a/tests/Usenet.Tests/TestHelpers/TestNntpServer.cs +++ b/tests/Usenet.Tests/TestHelpers/TestNntpServer.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Sockets; using System.Text; @@ -26,9 +27,7 @@ private async Task AcceptLoop() TcpClient client; try { - client = await _listener - .AcceptTcpClientAsync(cancellationToken) - .ConfigureAwait(false); + client = await _listener.AcceptTcpClientAsync(cancellationToken); } catch (OperationCanceledException) { @@ -45,7 +44,6 @@ CancellationToken cancellationToken { try { -#pragma warning disable CA2007 var stream = client.GetStream(); await using var writer = new StreamWriter(stream, Encoding.ASCII); using var reader = new StreamReader( @@ -55,15 +53,15 @@ CancellationToken cancellationToken 1024, leaveOpen: true ); -#pragma warning restore CA2007 + writer.NewLine = "\r\n"; writer.AutoFlush = true; // Send NNTP greeting - await writer.WriteLineAsync("200 Dummy server ready").ConfigureAwait(false); + await writer.WriteLineAsync("200 Dummy server ready"); while (!cancellationToken.IsCancellationRequested) { - var line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false); + var line = await reader.ReadLineAsync(cancellationToken); if (line == null) break; @@ -74,7 +72,7 @@ CancellationToken cancellationToken switch (command) { case "AUTHINFO": - await writer.WriteLineAsync("281 Success").ConfigureAwait(false); + await writer.WriteLineAsync("281 Success"); break; case "GROUP": // Group command is used to force immediate connection reset @@ -82,12 +80,10 @@ CancellationToken cancellationToken client.Client.LingerState = new LingerOption(true, 0); return; case "QUIT": - await writer.WriteLineAsync("205 Goodbye").ConfigureAwait(false); + await writer.WriteLineAsync("205 Goodbye"); return; default: - await writer - .WriteLineAsync("500 Command not recognized") - .ConfigureAwait(false); + await writer.WriteLineAsync("500 Command not recognized"); break; } } @@ -99,6 +95,7 @@ await writer } } + [SuppressMessage("ReSharper", "UnusedTupleComponentInReturnValue")] private static (string? Command, string? SubCommand, string? Arguments) ParseCommand( string line ) diff --git a/tests/Usenet.Tests/Util/CountingStreamTests.cs b/tests/Usenet.Tests/Util/CountingStreamTests.cs index c9e94e7..c89f9c0 100644 --- a/tests/Usenet.Tests/Util/CountingStreamTests.cs +++ b/tests/Usenet.Tests/Util/CountingStreamTests.cs @@ -8,7 +8,7 @@ internal sealed class CountingStreamTests public async Task CountingStreamShouldCountBytesRead() { using var memStream = new MemoryStream(new byte[10]); - using var stream = new CountingStream(memStream); + await using var stream = new CountingStream(memStream); stream.ReadByte(); stream.ReadByte(); stream.ReadByte(); @@ -22,7 +22,7 @@ public async Task CountingStreamShouldCountBytesRead() public async Task CountingStreamShouldCountBytesWritten() { using var memStream = new MemoryStream(new byte[10]); - using var stream = new CountingStream(memStream); + await using var stream = new CountingStream(memStream); stream.WriteByte(1); stream.WriteByte(2); stream.WriteByte(3); @@ -36,7 +36,7 @@ public async Task CountingStreamShouldCountBytesWritten() public async Task ResetCountersShouldResetBytesReadAndBytesWritten() { using var memStream = new MemoryStream(new byte[10]); - using var stream = new CountingStream(memStream); + await using var stream = new CountingStream(memStream); stream.ReadByte(); stream.ReadByte(); stream.WriteByte(1); diff --git a/tests/Usenet.Tests/Util/MultiValueDictionaryTests.cs b/tests/Usenet.Tests/Util/MultiValueDictionaryTests.cs index 3e36988..8eba9f4 100644 --- a/tests/Usenet.Tests/Util/MultiValueDictionaryTests.cs +++ b/tests/Usenet.Tests/Util/MultiValueDictionaryTests.cs @@ -1,10 +1,10 @@ -using System.Text.Json; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using Usenet.Util; -// ReSharper disable DuplicateKeyCollectionInitialization - namespace Usenet.Tests.Util; +[SuppressMessage("ReSharper", "DuplicateKeyCollectionInitialization")] internal sealed class MultiValueDictionaryTests { [Test] diff --git a/tests/Usenet.Tests/Yenc/YencArticleDecoderTests.cs b/tests/Usenet.Tests/Yenc/YencArticleDecoderTests.cs index 811abbb..a36830c 100644 --- a/tests/Usenet.Tests/Yenc/YencArticleDecoderTests.cs +++ b/tests/Usenet.Tests/Yenc/YencArticleDecoderTests.cs @@ -77,14 +77,10 @@ IFileInfo part2File using var actual = new MemoryStream(); actual.Seek(part1.Header.PartOffset, SeekOrigin.Begin); - await actual - .WriteAsync(part1.Data.ToArray().AsMemory(0, (int)part1.Header.PartSize)) - .ConfigureAwait(true); + await actual.WriteAsync(part1.Data.ToArray().AsMemory(0, (int)part1.Header.PartSize)); actual.Seek(part2.Header.PartOffset, SeekOrigin.Begin); - await actual - .WriteAsync(part2.Data.ToArray().AsMemory(0, (int)part2.Header.PartSize)) - .ConfigureAwait(true); + await actual.WriteAsync(part2.Data.ToArray().AsMemory(0, (int)part2.Header.PartSize)); var actualFileName = part1.Header.FileName; diff --git a/tests/Usenet.Tests/Yenc/YencEncoderTests.cs b/tests/Usenet.Tests/Yenc/YencEncoderTests.cs index 6dfdf97..bf19504 100644 --- a/tests/Usenet.Tests/Yenc/YencEncoderTests.cs +++ b/tests/Usenet.Tests/Yenc/YencEncoderTests.cs @@ -23,9 +23,7 @@ CancellationToken cancellationToken using var stream = new MemoryStream(data); var header = new YencHeader("test (1.2).txt", data.Length, 10, 0, 1, data.Length, 0); - var actualText = await YencEncoder - .EncodeAsync(header, stream, cancellationToken) - .ConfigureAwait(true); + var actualText = await YencEncoder.EncodeAsync(header, stream, cancellationToken); await Assert.That(actualText).IsEquivalentTo(expectedText); } @@ -54,9 +52,7 @@ CancellationToken cancellationToken using var stream = new MemoryStream(data); var header = new YencHeader("test (1.2).txt", 120, 10, 1, 2, data.Length, 0); - var actualText = await YencEncoder - .EncodeAsync(header, stream, cancellationToken) - .ConfigureAwait(true); + var actualText = await YencEncoder.EncodeAsync(header, stream, cancellationToken); await Assert.That(actualText).IsEquivalentTo(expectedText); } diff --git a/tests/Usenet.Tests/Yenc/YencStreamDecoderTests.cs b/tests/Usenet.Tests/Yenc/YencStreamDecoderTests.cs index f98dc51..22af4db 100644 --- a/tests/Usenet.Tests/Yenc/YencStreamDecoderTests.cs +++ b/tests/Usenet.Tests/Yenc/YencStreamDecoderTests.cs @@ -90,10 +90,10 @@ CancellationToken cancellationToken using var actual = new MemoryStream(); actual.Seek(part1.Header.PartOffset, SeekOrigin.Begin); - await part1.CopyToAsync(actual, cancellationToken).ConfigureAwait(true); + await part1.CopyToAsync(actual, cancellationToken); actual.Seek(part2.Header.PartOffset, SeekOrigin.Begin); - await part2.CopyToAsync(actual, cancellationToken).ConfigureAwait(true); + await part2.CopyToAsync(actual, cancellationToken); var actualFileName = part1.Header.FileName;