1
0
mirror of https://github.com/ZetaKebab/MpcNET.git synced 2025-07-01 00:37:37 +00:00
Files
MpcNET/Sources/MpcNET/MpcConnection.cs
Kim Hugener-Ohlsen 12ddc4bca4 Major refactoring
2018-05-18 15:14:20 +02:00

262 lines
9.2 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MpcConnection.cs" company="MpcNET">
// Copyright (c) MpcNET. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace MpcNET
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using MpcNET.Commands;
using MpcNET.Exceptions;
using MpcNET.Message;
using Sundew.Base.ControlFlow;
/// <summary>
/// Keeps the connection to the MPD server and handels the most basic structure of the MPD protocol.
/// class.
/// </summary>
public class MpcConnection : IMpcConnection
{
private static readonly Encoding Encoding = new UTF8Encoding();
private readonly ICommandFactory commandFactory;
private readonly IMpcConnectionReporter mpcConnectionReporter;
private readonly IPEndPoint server;
private TcpClient tcpClient;
private NetworkStream networkStream;
private string version;
/// <summary>
/// Initializes a new instance of the <see cref="MpcConnection" /> class.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="commandFactory">The command factory.</param>
/// <param name="mpcConnectionReporter">The MPC connection logger.</param>
public MpcConnection(IPEndPoint server, ICommandFactory commandFactory = null, IMpcConnectionReporter mpcConnectionReporter = null)
{
this.commandFactory = commandFactory ?? new CommandFactory();
this.mpcConnectionReporter = mpcConnectionReporter;
this.ClearConnectionFields();
this.server = server ?? throw new ArgumentNullException("Server IPEndPoint not set.", nameof(server));
}
/// <summary>
/// Gets the version.
/// </summary>
public string Version => this.version;
/// <summary>
/// Connects asynchronously.
/// </summary>
/// <returns>The connect task.</returns>
public async Task ConnectAsync()
{
if (this.tcpClient != null)
{
var pingResult = await this.PingAsync();
if (pingResult)
{
return;
}
}
await this.ReconnectAsync(false);
}
/// <summary>
/// Disconnects asynchronously.
/// </summary>
/// <returns>The disconnect task.</returns>
public Task DisconnectAsync()
{
return this.DisconnectAsync(true);
}
/// <summary>
/// Sends the command asynchronously.
/// </summary>
/// <typeparam name="TResponse">The response type.</typeparam>
/// <param name="commandSelector">The command selector.</param>
/// <returns>
/// The send task.
/// </returns>
public async Task<IMpdMessage<TResponse>> SendAsync<TResponse>(Func<ICommandFactory, IMpcCommand<TResponse>> commandSelector)
{
if (this.tcpClient == null)
{
await this.ReconnectAsync(true);
}
var command = commandSelector?.Invoke(this.commandFactory);
if (command == null)
{
throw new CommandNullException();
}
Exception lastException = null;
IReadOnlyList<string> response = new List<string>();
var sendAttempter = new Attempter(3);
var commandText = command.Serialize();
this.mpcConnectionReporter?.Sending(commandText);
while (sendAttempter.Attempt())
{
try
{
using (var writer = new StreamWriter(this.networkStream, Encoding, 512, true) { NewLine = "\n" })
{
await writer.WriteLineAsync(commandText);
await writer.FlushAsync();
}
response = await this.ReadResponseAsync();
if (response.Any())
{
lastException = null;
break;
}
throw new EmptyResponseException(commandText);
}
catch (Exception exception)
{
lastException = exception;
this.mpcConnectionReporter?.SendException(commandText, sendAttempter.CurrentAttempt, exception);
await this.ReconnectAsync(true);
this.mpcConnectionReporter?.RetrySend(commandText, sendAttempter.CurrentAttempt);
}
}
if (lastException != null)
{
try
{
await this.DisconnectAsync(false);
}
catch (Exception)
{
}
return new ErrorMpdMessage<TResponse>(command, new ErrorMpdResponse<TResponse>(lastException));
}
return new MpdMessage<TResponse>(command, true, response);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
void IDisposable.Dispose()
{
this.DisconnectAsync().Wait();
}
private async Task<bool> PingAsync()
{
try
{
using (var writer = new StreamWriter(this.networkStream, Encoding, 512, true) { NewLine = "\n" })
{
await writer.WriteLineAsync("ping");
await writer.FlushAsync();
}
using (var reader = new StreamReader(this.networkStream, Encoding, true, 512, true))
{
var responseLine = await reader.ReadLineAsync();
return responseLine == "OK";
}
}
catch (Exception)
{
return false;
}
}
private async Task ReconnectAsync(bool isReconnect)
{
var connectAttempter = new Attempter(3);
while (connectAttempter.Attempt())
{
this.mpcConnectionReporter?.Connecting(isReconnect, connectAttempter.CurrentAttempt);
await this.DisconnectAsync(false);
this.tcpClient = new TcpClient();
await this.tcpClient.ConnectAsync(this.server.Address, this.server.Port);
if (this.tcpClient.Connected)
{
this.mpcConnectionReporter?.ConnectionAccepted(isReconnect, connectAttempter.CurrentAttempt);
break;
}
}
this.networkStream = this.tcpClient.GetStream();
using (var reader = new StreamReader(this.networkStream, Encoding, true, 512, true))
{
var firstLine = await reader.ReadLineAsync();
if (!firstLine.StartsWith(Constants.FirstLinePrefix))
{
await this.DisconnectAsync(false);
throw new MpcConnectException("Response of mpd does not start with \"" + Constants.FirstLinePrefix + "\".");
}
this.version = firstLine.Substring(Constants.FirstLinePrefix.Length);
this.mpcConnectionReporter?.Connected(isReconnect, connectAttempter.CurrentAttempt, firstLine);
}
}
private async Task<IReadOnlyList<string>> ReadResponseAsync()
{
var response = new List<string>();
using (var reader = new StreamReader(this.networkStream, Encoding, true, 512, true))
{
string responseLine;
do
{
responseLine = await reader.ReadLineAsync();
this.mpcConnectionReporter.ReadResponse(responseLine);
if (responseLine == null)
{
break;
}
response.Add(responseLine);
}
while (!(responseLine.Equals(Constants.Ok) || responseLine.StartsWith(Constants.Ack)));
}
return response;
}
private Task DisconnectAsync(bool isExplicitDisconnect)
{
this.mpcConnectionReporter?.Disconnecting(isExplicitDisconnect);
if (this.tcpClient == null)
{
return Task.CompletedTask;
}
this.ClearConnectionFields();
this.mpcConnectionReporter?.Disconnected(isExplicitDisconnect);
return Task.CompletedTask;
}
private void ClearConnectionFields()
{
this.networkStream?.Dispose();
this.tcpClient?.Dispose();
this.version = string.Empty;
this.tcpClient = null;
this.networkStream = null;
}
}
}