1
0
mirror of https://github.com/ZetaKebab/MpcNET.git synced 2025-01-14 22:18:43 +00:00

ReadResponse cleaned up and async made.

This commit is contained in:
glucaci 2016-12-01 08:31:12 +01:00
parent dd941b1f83
commit c1f763a9db

View File

@ -1,234 +1,245 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LibMpc namespace LibMpc
{ {
/// <summary> /// <summary>
/// Keeps the connection to the MPD server and handels the most basic structure of the /// Keeps the connection to the MPD server and handels the most basic structure of the
/// MPD protocol. The high level commands are handeled in the <see cref="Mpc"/> /// MPD protocol. The high level commands are handeled in the <see cref="Mpc"/>
/// class. /// class.
/// </summary> /// </summary>
public class MpcConnection public class MpcConnection
{ {
private static readonly string FIRST_LINE_PREFIX = "OK MPD "; private static readonly string FIRST_LINE_PREFIX = "OK MPD ";
private static readonly string OK = "OK"; private static readonly string OK = "OK";
private static readonly string ACK = "ACK"; private static readonly string ACK = "ACK";
private static readonly Regex ACK_REGEX = new Regex("^ACK \\[(?<code>[0-9]*)@(?<nr>[0-9]*)] \\{(?<command>[a-z]*)} (?<message>.*)$");
private readonly IPEndPoint _server;
private readonly IPEndPoint _server;
private TcpClient _tcpClient;
private TcpClient _tcpClient; private NetworkStream _networkStream;
private NetworkStream _networkStream; private StreamReader _reader;
private StreamReader _reader; private StreamWriter _writer;
private StreamWriter _writer;
private string _version;
private string _version;
public MpcConnection(IPEndPoint server)
public MpcConnection(IPEndPoint server) {
{ if (IsConnected) return;
if (IsConnected) return;
ClearConnectionFields();
ClearConnectionFields(); _server = server;
_server = server; }
}
public bool IsConnected => (_tcpClient != null) && _tcpClient.Connected;
public bool IsConnected => (_tcpClient != null) && _tcpClient.Connected;
public string Version => _version; public string Version => _version;
public async Task ConnectAsync() public async Task ConnectAsync()
{ {
if (_server == null) if (_server == null)
throw new InvalidOperationException("Server IPEndPoint not set."); throw new InvalidOperationException("Server IPEndPoint not set.");
if (IsConnected) if (IsConnected)
throw new AlreadyConnectedException(); throw new AlreadyConnectedException();
_tcpClient = new TcpClient(); _tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(_server.Address, _server.Port); await _tcpClient.ConnectAsync(_server.Address, _server.Port);
_networkStream = _tcpClient.GetStream(); _networkStream = _tcpClient.GetStream();
_reader = new StreamReader(_networkStream, Encoding.UTF8); _reader = new StreamReader(_networkStream, Encoding.UTF8);
_writer = new StreamWriter(_networkStream, Encoding.UTF8) { NewLine = "\n" }; _writer = new StreamWriter(_networkStream, Encoding.UTF8) { NewLine = "\n" };
var firstLine = _reader.ReadLine(); var firstLine = _reader.ReadLine();
if (!firstLine.StartsWith(FIRST_LINE_PREFIX)) if (!firstLine.StartsWith(FIRST_LINE_PREFIX))
{ {
await Disconnect(); await Disconnect();
throw new InvalidDataException("Response of mpd does not start with \"" + FIRST_LINE_PREFIX + "\"." ); throw new InvalidDataException("Response of mpd does not start with \"" + FIRST_LINE_PREFIX + "\"." );
} }
_version = firstLine.Substring(FIRST_LINE_PREFIX.Length); _version = firstLine.Substring(FIRST_LINE_PREFIX.Length);
await _writer.WriteLineAsync(); await _writer.WriteLineAsync();
_writer.Flush(); _writer.Flush();
ReadResponse(); await ReadResponseAsync();
} }
/// <summary>
/// Disconnects from the current MPD server. private Task Disconnect()
/// </summary> {
private Task Disconnect() if (_tcpClient == null)
{ {
if (_tcpClient == null) return Task.CompletedTask;
{
return Task.CompletedTask;
}
_networkStream.Dispose();
ClearConnectionFields();
return Task.CompletedTask;
}
/// <summary>
/// Executes a simple command without arguments on the MPD server and returns the response.
/// </summary>
/// <param name="command">The command to execute.</param>
/// <returns>The MPD server response parsed into a basic object.</returns>
/// <exception cref="ArgumentException">If the command contains a space of a newline charakter.</exception>
public async Task<MpdResponse> Exec(string command)
{
if (command == null)
throw new ArgumentNullException("command");
if (command.Contains(" "))
throw new ArgumentException("command contains space");
if (command.Contains("\n"))
throw new ArgumentException("command contains newline");
// TODO: Integrate connection status in MpdResponse
var connectionResult = await CheckConnectionAsync();
try
{
_writer.WriteLine(command);
_writer.Flush();
return ReadResponse();
}
catch (Exception)
{
try { await Disconnect(); } catch (Exception) { }
return null; // TODO: Create Null Object for MpdResponse
}
}
/// <summary>
/// Executes a MPD command with arguments on the MPD server.
/// </summary>
/// <param name="command">The command to execute.</param>
/// <param name="argument">The arguments of the command.</param>
/// <returns>The MPD server response parsed into a basic object.</returns>
/// <exception cref="ArgumentException">If the command contains a space of a newline charakter.</exception>
public async Task<MpdResponse> Exec(string command, string[] argument)
{
if (command == null)
throw new ArgumentNullException("command");
if (command.Contains(" "))
throw new ArgumentException("command contains space");
if (command.Contains("\n"))
throw new ArgumentException("command contains newline");
if (argument == null)
throw new ArgumentNullException("argument");
for (int i = 0; i < argument.Length; i++)
{
if (argument[i] == null)
throw new ArgumentNullException("argument[" + i + "]");
if (argument[i].Contains("\n"))
throw new ArgumentException("argument[" + i + "] contains newline");
}
// TODO: Integrate connection status in MpdResponse
var connectionResult = await CheckConnectionAsync();
try
{
_writer.Write(command);
foreach (string arg in argument)
{
_writer.Write(' ');
WriteToken(arg);
}
_writer.WriteLine();
_writer.Flush();
return ReadResponse();
}
catch (Exception)
{
try { await Disconnect(); } catch (Exception) { }
throw;
}
}
private async Task<bool> CheckConnectionAsync()
{
if (!IsConnected)
{
await ConnectAsync();
} }
return IsConnected; _networkStream.Dispose();
} ClearConnectionFields();
private void WriteToken(string token) return Task.CompletedTask;
{ }
if (token.Contains(" ")) /// <summary>
{ /// Executes a simple command without arguments on the MPD server and returns the response.
_writer.Write("\""); /// </summary>
foreach (char chr in token) /// <param name="command">The command to execute.</param>
if (chr == '"') /// <returns>The MPD server response parsed into a basic object.</returns>
_writer.Write("\\\""); /// <exception cref="ArgumentException">If the command contains a space of a newline charakter.</exception>
else public async Task<MpdResponse> Exec(string command)
_writer.Write(chr); {
} if (command == null)
else throw new ArgumentNullException("command");
_writer.Write(token); if (command.Contains(" "))
} throw new ArgumentException("command contains space");
if (command.Contains("\n"))
private MpdResponse ReadResponse() throw new ArgumentException("command contains newline");
{
List<string> ret = new List<string>(); // TODO: Integrate connection status in MpdResponse
string line = _reader.ReadLine(); var connectionResult = await CheckConnectionAsync();
while (!(line.Equals(OK) || line.StartsWith(ACK)))
{ try
ret.Add(line); {
line = _reader.ReadLine(); _writer.WriteLine(command);
} _writer.Flush();
if (line.Equals(OK))
return new MpdResponse(new ReadOnlyCollection<string>(ret)); return await ReadResponseAsync();
else }
{ catch (Exception)
Match match = ACK_REGEX.Match(line); {
try { await Disconnect(); } catch (Exception) { }
if (match.Groups.Count != 5) return null; // TODO: Create Null Object for MpdResponse
throw new InvalidDataException( "Error response not as expected" ); }
}
return new MpdResponse( /// <summary>
int.Parse( match.Result("${code}") ), /// Executes a MPD command with arguments on the MPD server.
int.Parse( match.Result("${nr}") ), /// </summary>
match.Result("${command}"), /// <param name="command">The command to execute.</param>
match.Result("${message}"), /// <param name="argument">The arguments of the command.</param>
new ReadOnlyCollection<string>(ret) /// <returns>The MPD server response parsed into a basic object.</returns>
); /// <exception cref="ArgumentException">If the command contains a space of a newline charakter.</exception>
} public async Task<MpdResponse> Exec(string command, string[] argument)
} {
if (command == null)
private void ClearConnectionFields() throw new ArgumentNullException("command");
{ if (command.Contains(" "))
_writer?.Dispose(); throw new ArgumentException("command contains space");
_reader?.Dispose(); if (command.Contains("\n"))
_networkStream?.Dispose(); throw new ArgumentException("command contains newline");
_tcpClient?.Dispose();
_version = string.Empty; if (argument == null)
} throw new ArgumentNullException("argument");
} for (int i = 0; i < argument.Length; i++)
} {
if (argument[i] == null)
throw new ArgumentNullException("argument[" + i + "]");
if (argument[i].Contains("\n"))
throw new ArgumentException("argument[" + i + "] contains newline");
}
// TODO: Integrate connection status in MpdResponse
var connectionResult = await CheckConnectionAsync();
try
{
_writer.Write(command);
foreach (string arg in argument)
{
_writer.Write(' ');
WriteToken(arg);
}
_writer.WriteLine();
_writer.Flush();
return await ReadResponseAsync();
}
catch (Exception)
{
try { await Disconnect(); } catch (Exception) { }
throw;
}
}
private async Task<bool> CheckConnectionAsync()
{
if (!IsConnected)
{
await ConnectAsync();
}
return IsConnected;
}
private void WriteToken(string token)
{
if (token.Contains(" "))
{
_writer.Write("\"");
foreach (char chr in token)
if (chr == '"')
_writer.Write("\\\"");
else
_writer.Write(chr);
}
else
_writer.Write(token);
}
private async Task<MpdResponse> ReadResponseAsync()
{
var response = new List<string>();
var currentLine = _reader.ReadLine();
// Read response untli reach end token (OK or ACK)
while (!(currentLine.Equals(OK) || currentLine.StartsWith(ACK)))
{
response.Add(currentLine);
currentLine = await _reader.ReadLineAsync();
}
if (currentLine.Equals(OK))
{
return new MpdResponse(new ReadOnlyCollection<string>(response));
}
else
{
return AcknowledgeResponse.Parse(currentLine, response);
}
}
private void ClearConnectionFields()
{
_writer?.Dispose();
_reader?.Dispose();
_networkStream?.Dispose();
_tcpClient?.Dispose();
_version = string.Empty;
}
}
public class AcknowledgeResponse
{
private static readonly Regex AcknowledgePattern = new Regex("^ACK \\[(?<code>[0-9]*)@(?<nr>[0-9]*)] \\{(?<command>[a-z]*)} (?<message>.*)$");
public static MpdResponse Parse(string acknowledgeResponse, IList<string> payload)
{
var match = AcknowledgePattern.Match(acknowledgeResponse);
if (match.Groups.Count != 5)
throw new InvalidDataException("Error response not as expected");
return new MpdResponse(
int.Parse(match.Result("${code}")),
int.Parse(match.Result("${nr}")),
match.Result("${command}"),
match.Result("${message}"),
new ReadOnlyCollection<string>(payload));
}
}
}