diff --git a/LibMpc/Constants.cs b/LibMpc/Constants.cs new file mode 100644 index 0000000..d375ee2 --- /dev/null +++ b/LibMpc/Constants.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LibMpc +{ + public class Constants + { + public static readonly string Ok = "OK"; + public static readonly string Ack = "ACK"; + + public static readonly string FirstLinePrefix = "OK MPD "; + } +} diff --git a/LibMpc/Message/MpdMessage.cs b/LibMpc/Message/MpdMessage.cs new file mode 100644 index 0000000..5d3da1b --- /dev/null +++ b/LibMpc/Message/MpdMessage.cs @@ -0,0 +1,24 @@ +namespace LibMpc +{ + public interface IMpdMessage + { + IMpdRequest Request { get; } + IMpdResponse Response { get; } + } + + public class MpdMessage : IMpdMessage + { + public MpdMessage(IMpcCommand command) + { + Request = new MpdRequest(command); + } + + public void AddResponse(IMpdResponse response) + { + Response = response; + } + + public IMpdRequest Request { get; } + public IMpdResponse Response { get; private set; } + } +} \ No newline at end of file diff --git a/LibMpc/Message/MpdRequest.cs b/LibMpc/Message/MpdRequest.cs new file mode 100644 index 0000000..c078f38 --- /dev/null +++ b/LibMpc/Message/MpdRequest.cs @@ -0,0 +1,17 @@ +namespace LibMpc +{ + public interface IMpdRequest + { + IMpcCommand Command { get; } + } + + public class MpdRequest : IMpdRequest + { + public MpdRequest(IMpcCommand command) + { + Command = command; + } + + public IMpcCommand Command { get; } + } +} \ No newline at end of file diff --git a/LibMpc/Message/MpdResponse.cs b/LibMpc/Message/MpdResponse.cs new file mode 100644 index 0000000..dacf15b --- /dev/null +++ b/LibMpc/Message/MpdResponse.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace LibMpc +{ + public interface IMpdResponse + { + IEnumerable Response { get; } + IReadOnlyDictionary Values { get; } + IMpdResponseState State { get; } + } + + public class MpdResponse : IMpdResponse + { + private static readonly Regex LinePattern = new Regex("^(?[A-Za-z]*):[ ]{0,1}(?.*)$"); + + public MpdResponse(ICollection response) + { + response.CheckNotNull(); + + var endLine = response.Take(response.Count - 2).Single(); + + Response = response.Take(response.Count - 2).ToList(); + State = new MpdResponseState(endLine); + Values = GetValuesFromResponse(); + } + + public IMpdResponseState State { get; } + + public IEnumerable Response { get; } + + public IReadOnlyDictionary Values { get; } + + + private IReadOnlyDictionary GetValuesFromResponse() + { + var result = new Dictionary(); + + foreach (var line in Response) + { + var match = LinePattern.Match(line); + if (match.Success) + { + var mpdKey = match.Result("${key}"); + if (!string.IsNullOrEmpty(mpdKey)) + { + var mpdValue = match.Result("${value}"); + if (string.IsNullOrEmpty(mpdValue)) + { + result.Add(mpdKey, mpdValue); + + } + } + } + } + + return result; + } + } + + public static class CheckNotNullExtension + { + public static void CheckNotNull(this object toBeChecked) + { + if (toBeChecked == null) + { + throw new ArgumentNullException(nameof(toBeChecked)); + } + } + } +} diff --git a/LibMpc/Message/MpdResponseState.cs b/LibMpc/Message/MpdResponseState.cs new file mode 100644 index 0000000..e1043e4 --- /dev/null +++ b/LibMpc/Message/MpdResponseState.cs @@ -0,0 +1,63 @@ +using System.Text.RegularExpressions; + +namespace LibMpc +{ + public interface IMpdResponseState + { + string Status { get; } + string ErrorMessage { get; } + string MpdError { get; } + bool IsError { get; } + } + + public class MpdResponseState : IMpdResponseState + { + private static readonly Regex ErrorPattern = new Regex("^ACK \\[(?[0-9]*)@(?[0-9]*)] \\{(?[a-z]*)} (?.*)$"); + + private readonly string _endLine; + + public MpdResponseState(string endLine) + { + _endLine = endLine; + + if (!string.IsNullOrEmpty(_endLine)) + { + if (_endLine.Equals(Constants.Ok)) + { + Status = _endLine; + IsError = false; + } + else + { + ParseErrorResponse(); + } + } + } + + public bool IsError { get; private set; } = true; + public string Status { get; private set; } = "UNKNOWN"; + public string ErrorMessage { get; private set; } = string.Empty; + public string MpdError { get; private set; } = string.Empty; + + private void ParseErrorResponse() + { + Status = "ERROR"; + MpdError = _endLine; + + var match = ErrorPattern.Match(_endLine); + + if (match.Groups.Count != 5) + { + ErrorMessage = "Unexpected response from server."; + } + else + { + var errorCode = match.Result("${code}"); + var commandListItem = match.Result("${nr}"); + var commandFailed = match.Result("${command}"); + var errorMessage = match.Result("${message}"); + ErrorMessage = $"ErrorCode: { errorCode }, CommandListItem: { commandListItem }, CommandFailed: { commandFailed }, ErrorMessage: { errorMessage }"; + } + } + } +} \ No newline at end of file diff --git a/LibMpc/MpcConnection.cs b/LibMpc/MpcConnection.cs index 5210fa1..6740dd0 100644 --- a/LibMpc/MpcConnection.cs +++ b/LibMpc/MpcConnection.cs @@ -1,11 +1,9 @@ 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 @@ -17,12 +15,6 @@ namespace LibMpc /// 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; @@ -61,12 +53,12 @@ namespace LibMpc _writer = new StreamWriter(_networkStream, Encoding.UTF8) { NewLine = "\n" }; var firstLine = _reader.ReadLine(); - if (!firstLine.StartsWith(FIRST_LINE_PREFIX)) + if (!firstLine.StartsWith(Constants.FirstLinePrefix)) { await DisconnectAsync(); - throw new InvalidDataException("Response of mpd does not start with \"" + FIRST_LINE_PREFIX + "\"." ); + throw new InvalidDataException("Response of mpd does not start with \"" + Constants.FirstLinePrefix + "\"." ); } - _version = firstLine.Substring(FIRST_LINE_PREFIX.Length); + _version = firstLine.Substring(Constants.FirstLinePrefix.Length); await _writer.WriteLineAsync(); _writer.Flush(); @@ -194,23 +186,16 @@ namespace LibMpc 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))) + string responseLine; + do { - response.Add(currentLine); - currentLine = await _reader.ReadLineAsync(); - } + responseLine = await _reader.ReadLineAsync(); + response.Add(responseLine); + } while (!(responseLine.Equals(Constants.Ok) || responseLine.StartsWith(Constants.Ack) || string.IsNullOrEmpty(responseLine))); - if (currentLine.Equals(OK)) - { - return new MpdResponse(new ReadOnlyCollection(response)); - } - else - { - return AcknowledgeResponse.Parse(currentLine, response); - } + return new MpdResponse(response); } private void ClearConnectionFields() @@ -222,24 +207,4 @@ namespace LibMpc _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)); - } - } } diff --git a/LibMpc/MpdResponse.cs b/LibMpc/MpdResponse.cs deleted file mode 100644 index 0b1ad21..0000000 --- a/LibMpc/MpdResponse.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Text; -using System.Text.RegularExpressions; - -namespace LibMpc -{ - /// - /// The MpdResponse class parses the response to an MPD command in it's most - /// basic structure. - /// - public class MpdResponse : IEnumerable> - { - private const string OK = "OK"; - private const string ACK = "ACK"; - - private static readonly Regex LINE_REGEX = new Regex("^(?[A-Za-z]*):[ ]{0,1}(?.*)$"); - - private readonly bool isError; - private readonly int errorCode; - private readonly int commandListNum; - private readonly string currentCommand; - private readonly string errorMessage; - private readonly ReadOnlyCollection message; - - private Dictionary dictionary = null; - /// - /// If the response denotes an error in the last command. - /// - public bool IsError { get { return this.isError; } } - /// - /// The error code if an error occured. - /// - public int ErrorCode { get { return this.errorCode; } } - /// - /// If an error occured the index of the invalid command in a command list. - /// - public int CommandListNum { get { return this.commandListNum; } } - /// - /// The command executed. - /// - public string CurrentCommand { get { return this.currentCommand; } } - /// - /// The description of the error, if occured. - /// - public string ErrorMessage { get { return this.errorMessage; } } - /// - /// The lines of the response message. - /// - public ReadOnlyCollection Message { get { return this.message; } } - /// - /// The value of an attribute in the MPD response. - /// - /// The name of the attribute. - /// The value of the attribute - public string this[string key] - { - get - { - if (this.dictionary == null) - { - this.dictionary = new Dictionary(); - - foreach (string line in this.message) - { - Match match = LINE_REGEX.Match(line); - if (match.Success) - this.dictionary[match.Result("$key")] = match.Result("$value"); - } - } - - return this.dictionary[key]; - } - } - /// - /// The number of lines in the response message. - /// - public int Count { get { return this.message.Count; } } - /// - /// A line in the MPD response as KeyValuePair. If the message cannot be separated - /// into key and value according to the MPD protocol spec, a KeyValuePair is returned - /// with the key null and the value the whole text of the line. - /// - /// The index of the line. - /// The requested line as KeyValuePair. - public KeyValuePair this[int line] - { - get - { - Match match = LINE_REGEX.Match(this.message[line]); - if (match.Success) - return new KeyValuePair(match.Result("${key}"), match.Result("${value}")); - else - return new KeyValuePair(null, this.message[line]); - } - } - /// - /// Creates a new MpdResponse from a list of lines in case no error occured. - /// - /// The response to an MPD command. - public MpdResponse( ReadOnlyCollection message ) - { - if (message == null) - throw new ArgumentNullException("message"); - - this.isError = false; - this.errorCode = -1; - this.commandListNum = 0; - this.currentCommand = null; - this.errorMessage = null; - this.message = message; - } - /// - /// Creates a new MpdResponse in case an error occured. - /// - /// The code of the error. - /// The index of the command which raised the error. - /// The command that raised the error. - /// The message describing the error. - /// The text of the standard MPD response. - public MpdResponse( int errorCode, int commandListNum, string currentCommand, string errorMessage, ReadOnlyCollection message) - { - if (currentCommand == null) - throw new ArgumentNullException("currentCommand"); - if (errorMessage == null) - throw new ArgumentNullException("errorMessage"); - if (message == null) - throw new ArgumentNullException("message"); - - this.isError = true; - this.errorCode = errorCode; - this.commandListNum = commandListNum; - this.currentCommand = currentCommand; - this.errorMessage = errorMessage; - this.message = message; - } - /// - /// Returns the values in all lines with the given attribute. - /// - /// The attribute who's values are reguested. - /// The values in all lines with the given attribute. - public List getAttributeValueList(string attribute) - { - List ret = new List(); - - foreach (string line in this.message) - { - Match match = LINE_REGEX.Match(line); - if (match.Success) - { - string key = match.Result("${key}"); - if( ( key != null ) && key.Equals( attribute ) ) - { - string value = match.Result("${value}"); - if( value != null ) - ret.Add( value ); - } - } - } - - return ret; - } - /// - /// Returns only the value parts in all key/value pairs in the response. - /// - /// The list of values in all key/value pairs in the response. - public List getValueList() - { - List ret = new List(); - - foreach (string line in this.message) - { - Match match = LINE_REGEX.Match(line); - if (match.Success) - { - string value = match.Result("${value}"); - if (value != null) - ret.Add(value); - } - } - - return ret; - } - /// - /// Builds the response text of the MPD server from the object. - /// - /// The response text of the MPD server from the object. - public override string ToString() - { - StringBuilder builder = new StringBuilder(); - foreach (string line in this.message) - builder.AppendLine(line); - - if (this.isError) - { - builder.Append(ACK); - builder.Append(" ["); - builder.Append(this.errorMessage); - builder.Append('@'); - builder.Append(this.commandListNum); - builder.Append("] {"); - builder.Append(this.currentCommand); - builder.Append("} "); - builder.Append(this.errorMessage); - //ACK [50@1] {play} song doesn't exist: "10240" - } - else - builder.Append(OK); - - return builder.ToString(); - } - - #region IEnumerable> Members - /// - /// Returns an enumerator for all KeyValuePairs in the MPD response. - /// - /// An enumerator for all KeyValuePairs in the MPD response. - IEnumerator> IEnumerable>.GetEnumerator() - { - return new MpdResponseEnumerator(this); - } - - #endregion - - #region IEnumerable Members - /// - /// Returns an enumerator for all KeyValuePairs in the MPD response. - /// - /// An enumerator for all KeyValuePairs in the MPD response. - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return new MpdResponseEnumerator(this); - } - - #endregion - } - /// - /// A class for enumerating over the KeyValuePairs in the response. - /// - public class MpdResponseEnumerator :IEnumerator> - { - private readonly MpdResponse response; - private int position = -1; - private KeyValuePair current; - /// - /// Creates a new MpdResponseEnumerator. - /// - /// The response to enumerate over. - protected internal MpdResponseEnumerator(MpdResponse response) - { - this.response = response; - } - - #region IEnumerator> Members - /// - /// Returns the current element of the enumerator. - /// - KeyValuePair IEnumerator>.Current - { - get { return this.current; } - } - - #endregion - - #region IDisposable Members - - void IDisposable.Dispose() - { - this.position = -1; - } - - #endregion - - #region IEnumerator Members - /// - /// Returns the current element of the enumerator. - /// - object System.Collections.IEnumerator.Current - { - get { return this.current; } - } - /// - /// Moves the enumerator to the next KeyValuePair in the MPD response. - /// - /// If the enumerator has any values left. - bool System.Collections.IEnumerator.MoveNext() - { - this.position++; - if (this.position < this.response.Count) - { - this.current = this.response[this.position]; - return true; - } - else - return false; - } - /// - /// Sets the enumerator to it's initial state. - /// - void System.Collections.IEnumerator.Reset() - { - this.position = -1; - } - - #endregion - } -}