From c1f763a9dbcf45b22d59d96c4cdf4d9e18cb37bd Mon Sep 17 00:00:00 2001 From: glucaci Date: Thu, 1 Dec 2016 08:31:12 +0100 Subject: [PATCH] ReadResponse cleaned up and async made. --- LibMpc/MpcConnection.cs | 471 ++++++++++++++++++++-------------------- 1 file changed, 241 insertions(+), 230 deletions(-) diff --git a/LibMpc/MpcConnection.cs b/LibMpc/MpcConnection.cs index 9fb1f7a..804aa0d 100644 --- a/LibMpc/MpcConnection.cs +++ b/LibMpc/MpcConnection.cs @@ -1,234 +1,245 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace LibMpc -{ - /// - /// 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 - /// class. - /// - public class MpcConnection - { - private static readonly string FIRST_LINE_PREFIX = "OK MPD "; - - private static readonly string OK = "OK"; - private static readonly string ACK = "ACK"; - - private static readonly Regex ACK_REGEX = new Regex("^ACK \\[(?[0-9]*)@(?[0-9]*)] \\{(?[a-z]*)} (?.*)$"); - - private readonly IPEndPoint _server; - - private TcpClient _tcpClient; - private NetworkStream _networkStream; - private StreamReader _reader; - private StreamWriter _writer; - - private string _version; - - public MpcConnection(IPEndPoint server) - { - if (IsConnected) return; - - ClearConnectionFields(); - _server = server; - } - - public bool IsConnected => (_tcpClient != null) && _tcpClient.Connected; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace LibMpc +{ + /// + /// 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 + /// class. + /// + public class MpcConnection + { + private static readonly string FIRST_LINE_PREFIX = "OK MPD "; + + private static readonly string OK = "OK"; + private static readonly string ACK = "ACK"; + + + private readonly IPEndPoint _server; + + private TcpClient _tcpClient; + private NetworkStream _networkStream; + private StreamReader _reader; + private StreamWriter _writer; + + private string _version; + + public MpcConnection(IPEndPoint server) + { + if (IsConnected) return; + + ClearConnectionFields(); + _server = server; + } + + public bool IsConnected => (_tcpClient != null) && _tcpClient.Connected; public string Version => _version; - public async Task ConnectAsync() - { - if (_server == null) - throw new InvalidOperationException("Server IPEndPoint not set."); - - if (IsConnected) - throw new AlreadyConnectedException(); - - - _tcpClient = new TcpClient(); - await _tcpClient.ConnectAsync(_server.Address, _server.Port); - - _networkStream = _tcpClient.GetStream(); - - _reader = new StreamReader(_networkStream, Encoding.UTF8); - _writer = new StreamWriter(_networkStream, Encoding.UTF8) { NewLine = "\n" }; - - var firstLine = _reader.ReadLine(); - if (!firstLine.StartsWith(FIRST_LINE_PREFIX)) - { - await Disconnect(); - throw new InvalidDataException("Response of mpd does not start with \"" + FIRST_LINE_PREFIX + "\"." ); - } - _version = firstLine.Substring(FIRST_LINE_PREFIX.Length); - - await _writer.WriteLineAsync(); - _writer.Flush(); - - ReadResponse(); - } - /// - /// Disconnects from the current MPD server. - /// - private Task Disconnect() - { - if (_tcpClient == null) - { - return Task.CompletedTask; - } - - _networkStream.Dispose(); - ClearConnectionFields(); - - return Task.CompletedTask; - } - /// - /// Executes a simple command without arguments on the MPD server and returns the response. - /// - /// The command to execute. - /// The MPD server response parsed into a basic object. - /// If the command contains a space of a newline charakter. - public async Task 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 - } - } - /// - /// Executes a MPD command with arguments on the MPD server. - /// - /// The command to execute. - /// The arguments of the command. - /// The MPD server response parsed into a basic object. - /// If the command contains a space of a newline charakter. - public async Task 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 CheckConnectionAsync() - { - if (!IsConnected) - { - await ConnectAsync(); + public async Task ConnectAsync() + { + if (_server == null) + throw new InvalidOperationException("Server IPEndPoint not set."); + + if (IsConnected) + throw new AlreadyConnectedException(); + + + _tcpClient = new TcpClient(); + await _tcpClient.ConnectAsync(_server.Address, _server.Port); + + _networkStream = _tcpClient.GetStream(); + + _reader = new StreamReader(_networkStream, Encoding.UTF8); + _writer = new StreamWriter(_networkStream, Encoding.UTF8) { NewLine = "\n" }; + + var firstLine = _reader.ReadLine(); + if (!firstLine.StartsWith(FIRST_LINE_PREFIX)) + { + await Disconnect(); + throw new InvalidDataException("Response of mpd does not start with \"" + FIRST_LINE_PREFIX + "\"." ); + } + _version = firstLine.Substring(FIRST_LINE_PREFIX.Length); + + await _writer.WriteLineAsync(); + _writer.Flush(); + + await ReadResponseAsync(); + } + + private Task Disconnect() + { + if (_tcpClient == null) + { + return Task.CompletedTask; } - 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 MpdResponse ReadResponse() - { - List ret = new List(); - string line = _reader.ReadLine(); - while (!(line.Equals(OK) || line.StartsWith(ACK))) - { - ret.Add(line); - line = _reader.ReadLine(); - } - if (line.Equals(OK)) - return new MpdResponse(new ReadOnlyCollection(ret)); - else - { - Match match = ACK_REGEX.Match(line); - - 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(ret) - ); - } - } - - private void ClearConnectionFields() - { - _writer?.Dispose(); - _reader?.Dispose(); - _networkStream?.Dispose(); - _tcpClient?.Dispose(); - _version = string.Empty; - } - } -} + _networkStream.Dispose(); + ClearConnectionFields(); + + return Task.CompletedTask; + } + /// + /// Executes a simple command without arguments on the MPD server and returns the response. + /// + /// The command to execute. + /// The MPD server response parsed into a basic object. + /// If the command contains a space of a newline charakter. + public async Task 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 await ReadResponseAsync(); + } + catch (Exception) + { + try { await Disconnect(); } catch (Exception) { } + return null; // TODO: Create Null Object for MpdResponse + } + } + /// + /// Executes a MPD command with arguments on the MPD server. + /// + /// The command to execute. + /// The arguments of the command. + /// The MPD server response parsed into a basic object. + /// If the command contains a space of a newline charakter. + public async Task 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 await ReadResponseAsync(); + } + catch (Exception) + { + try { await Disconnect(); } catch (Exception) { } + throw; + } + } + + private async Task 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 ReadResponseAsync() + { + var response = new List(); + 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(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 \\[(?[0-9]*)@(?[0-9]*)] \\{(?[a-z]*)} (?.*)$"); + + public static MpdResponse Parse(string acknowledgeResponse, IList 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(payload)); + } + } +}