diff --git a/src/ISoulseekClient.cs b/src/ISoulseekClient.cs index 354338130..dd19815c1 100644 --- a/src/ISoulseekClient.cs +++ b/src/ISoulseekClient.cs @@ -1260,6 +1260,15 @@ public interface ISoulseekClient : IDisposable, IDiagnosticGenerator /// Thrown when an exception is encountered during the operation. Task SetStatusAsync(UserPresence status, CancellationToken? cancellationToken = null); + /// + /// Asynchronously informs the server of the user's liked and disliked interests. + /// + /// A collection of liked interests. + /// A collection of disliked interests. + /// The token to monitor for cancellation requests. + /// The representing the asynchronous operation. + Task SetInterestsAsync(IEnumerable likes, IEnumerable dislikes, CancellationToken? cancellationToken = null); + /// /// Asynchronously starts receiving public chat messages. /// diff --git a/src/Messaging/Messages/Server/HatedInterestAddCommand.cs b/src/Messaging/Messages/Server/HatedInterestAddCommand.cs new file mode 100644 index 000000000..7794825be --- /dev/null +++ b/src/Messaging/Messages/Server/HatedInterestAddCommand.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) JP Dillingham. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://www.gnu.org/licenses/. +// + +namespace Soulseek.Messaging.Messages.Server +{ + using Soulseek.Messaging; + + /// + /// Adds a disliked interest to the user's profile on the server. + /// + internal class HatedInterestAddCommand : IOutgoingMessage + { + /// + /// Initializes a new instance of the class. + /// + /// The disliked interest to add. + public HatedInterestAddCommand(string interest) + { + Interest = interest; + } + + /// + /// Gets the disliked interest to add. + /// + public string Interest { get; } + + /// + /// Constructs a array from this message. + /// + /// The constructed byte array. + public byte[] ToByteArray() + { + return new MessageBuilder() + .WriteCode((MessageCode.Server)117) + .WriteString(Interest) + .Build(); + } + } +} diff --git a/src/Messaging/Messages/Server/InterestAddCommand.cs b/src/Messaging/Messages/Server/InterestAddCommand.cs new file mode 100644 index 000000000..eace24068 --- /dev/null +++ b/src/Messaging/Messages/Server/InterestAddCommand.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) JP Dillingham. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://www.gnu.org/licenses/. +// + +namespace Soulseek.Messaging.Messages.Server +{ + using Soulseek.Messaging; + + /// + /// Adds a liked interest to the user's profile on the server. + /// + internal class InterestAddCommand : IOutgoingMessage + { + /// + /// Initializes a new instance of the class. + /// + /// The interest to add. + public InterestAddCommand(string interest) + { + Interest = interest; + } + + /// + /// Gets the interest to add. + /// + public string Interest { get; } + + /// + /// Constructs a array from this message. + /// + /// The constructed byte array. + public byte[] ToByteArray() + { + return new MessageBuilder() + .WriteCode((MessageCode.Server)51) + .WriteString(Interest) + .Build(); + } + } +} diff --git a/src/SoulseekClient.cs b/src/SoulseekClient.cs index 87c55bea0..ae14ddb43 100644 --- a/src/SoulseekClient.cs +++ b/src/SoulseekClient.cs @@ -17,6 +17,13 @@ namespace Soulseek { + using Soulseek.Diagnostics; + using Soulseek.Messaging; + using Soulseek.Messaging.Handlers; + using Soulseek.Messaging.Messages; + using Soulseek.Messaging.Messages.Server; + using Soulseek.Network; + using Soulseek.Network.Tcp; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -27,12 +34,6 @@ namespace Soulseek using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; - using Soulseek.Diagnostics; - using Soulseek.Messaging; - using Soulseek.Messaging.Handlers; - using Soulseek.Messaging.Messages; - using Soulseek.Network; - using Soulseek.Network.Tcp; /// /// A client for the Soulseek file sharing network. @@ -2479,6 +2480,58 @@ public Task SetSharedCountsAsync(int directories, int files, CancellationToken? } } + + + /// + /// Asynchronously informs the server of the user's and . + /// + /// A collection of liked interests. + /// A collection of disliked interests. + /// The token to monitor for cancellation requests. + /// The Task representing the asynchronous operation. + /// Thrown when the client is not connected or logged in. + /// Thrown when the operation has timed out. + /// Thrown when the operation has been cancelled. + /// Thrown when an exception is encountered during the operation. + public async Task SetInterestsAsync(IEnumerable likes, IEnumerable dislikes, CancellationToken? cancellationToken = null) + { + if (!State.HasFlag(SoulseekClientStates.Connected) || !State.HasFlag(SoulseekClientStates.LoggedIn)) + { + throw new InvalidOperationException($"The server connection must be connected and logged in to set interests; currently {State}."); + } + + var token = cancellationToken ?? CancellationToken.None; + + try + { + if (likes != null) + { + foreach (var like in likes) + { + if (string.IsNullOrWhiteSpace(like)) continue; + + await ServerConnection.WriteAsync(new InterestAddCommand(like), token).ConfigureAwait(false); + } + } + + if (dislikes != null) + { + foreach (var dislike in dislikes) + { + if (string.IsNullOrWhiteSpace(dislike)) continue; + + await ServerConnection.WriteAsync(new HatedInterestAddCommand(dislike), token).ConfigureAwait(false); + } + } + } + catch (Exception ex) when (!(ex is OperationCanceledException) && !(ex is TimeoutException)) + { + throw new SoulseekClientException($"Failed to set interests: {ex.Message}", ex); + } + } + + + /// /// Asynchronously informs the server of the current online of the client. ///