1
0
mirror of https://github.com/ZetaKebab/MpcNET.git synced 2024-09-16 05:30:09 +00:00
MpcNET/LibMpc/MpcConnection.cs

286 lines
9.3 KiB
C#
Raw Normal View History

2016-10-30 22:54:45 +00:00
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;
namespace Libmpc
{
/// <summary>
/// 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="Libmpc.Mpc"/>
/// class.
/// </summary>
public class MpcConnection
{
/// <summary>
/// Is fired when a connection to a MPD server is established.
/// </summary>
2016-11-30 11:59:26 +00:00
public event EventHandler Connected;
2016-10-30 22:54:45 +00:00
/// <summary>
/// Is fired when the connection to the MPD server is closed.
/// </summary>
2016-11-30 11:59:26 +00:00
public event EventHandler Disconnected;
2016-10-30 22:54:45 +00:00
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 \\[(?<code>[0-9]*)@(?<nr>[0-9]*)] \\{(?<command>[a-z]*)} (?<message>.*)$");
2016-11-01 09:04:31 +00:00
private IPEndPoint _ipEndPoint;
2016-10-30 22:54:45 +00:00
2016-11-01 09:04:31 +00:00
private TcpClient _tcpClient;
private NetworkStream _networkStream;
2016-10-30 22:54:45 +00:00
2016-11-01 09:04:31 +00:00
private StreamReader _reader;
private StreamWriter _writer;
2016-10-30 22:54:45 +00:00
2016-11-01 09:04:31 +00:00
private string _version;
2016-10-30 22:54:45 +00:00
/// <summary>
/// If the connection to the MPD is connected.
/// </summary>
2016-11-30 11:59:26 +00:00
public bool IsConnected { get { return (_tcpClient != null) && _tcpClient.Connected; } }
2016-10-30 22:54:45 +00:00
/// <summary>
/// The version of the MPD.
/// </summary>
2016-11-01 09:04:31 +00:00
public string Version { get { return _version; } }
2016-10-30 22:54:45 +00:00
2016-11-01 09:04:31 +00:00
private bool _autoConnect = false;
2016-10-30 22:54:45 +00:00
/// <summary>
/// If a connection should be established when a command is to be
/// executed in disconnected state.
/// </summary>
public bool AutoConnect
{
2016-11-01 09:04:31 +00:00
get{ return _autoConnect; }
set { _autoConnect = value; }
2016-10-30 22:54:45 +00:00
}
2016-11-01 09:04:31 +00:00
2016-10-30 22:54:45 +00:00
/// <summary>
/// Creates a new MpdConnection.
/// </summary>
/// <param name="server">The IPEndPoint of the MPD server.</param>
2016-11-01 09:04:31 +00:00
public MpcConnection(IPEndPoint server)
{
2016-11-30 11:59:26 +00:00
Server = server;
2016-11-01 09:04:31 +00:00
}
2016-10-30 22:54:45 +00:00
/// <summary>
/// The IPEndPoint of the MPD server.
/// </summary>
/// <exception cref="AlreadyConnectedException">When a conenction to a MPD server is already established.</exception>
public IPEndPoint Server
{
2016-11-01 09:04:31 +00:00
get { return _ipEndPoint; }
2016-10-30 22:54:45 +00:00
set
{
2016-11-30 11:59:26 +00:00
if (IsConnected)
2016-10-30 22:54:45 +00:00
throw new AlreadyConnectedException();
2016-11-01 09:04:31 +00:00
_ipEndPoint = value;
2016-10-30 22:54:45 +00:00
2016-11-01 09:04:31 +00:00
ClearConnectionFields();
2016-10-30 22:54:45 +00:00
}
}
2016-11-30 11:59:26 +00:00
2016-10-30 22:54:45 +00:00
/// <summary>
/// Connects to the MPD server who's IPEndPoint was set in the Server property.
/// </summary>
/// <exception cref="InvalidOperationException">If no IPEndPoint was set to the Server property.</exception>
public void Connect()
{
2016-11-01 09:04:31 +00:00
if (_ipEndPoint == null)
2016-10-30 22:54:45 +00:00
throw new InvalidOperationException("Server IPEndPoint not set.");
2016-11-30 11:59:26 +00:00
if (IsConnected)
2016-10-30 22:54:45 +00:00
throw new AlreadyConnectedException();
2016-11-01 09:04:31 +00:00
_tcpClient = new TcpClient();
var connection = _tcpClient.ConnectAsync(_ipEndPoint.Address, _ipEndPoint.Port);
connection.Wait();
_networkStream = _tcpClient.GetStream();
_reader = new StreamReader(_networkStream, Encoding.UTF8);
_writer = new StreamWriter(_networkStream, Encoding.UTF8);
_writer.NewLine = "\n";
2016-10-30 22:54:45 +00:00
2016-11-01 09:04:31 +00:00
string firstLine = _reader.ReadLine();
2016-10-30 22:54:45 +00:00
if( !firstLine.StartsWith( FIRST_LINE_PREFIX ) )
{
2016-11-01 09:04:31 +00:00
Disconnect();
2016-10-30 22:54:45 +00:00
throw new InvalidDataException("Response of mpd does not start with \"" + FIRST_LINE_PREFIX + "\"." );
}
2016-11-01 09:04:31 +00:00
_version = firstLine.Substring(FIRST_LINE_PREFIX.Length);
2016-10-30 22:54:45 +00:00
2016-11-01 09:04:31 +00:00
_writer.WriteLine();
_writer.Flush();
2016-10-30 22:54:45 +00:00
2016-11-01 09:04:31 +00:00
ReadResponse();
2016-10-30 22:54:45 +00:00
2016-11-30 11:59:26 +00:00
Connected?.Invoke(this, EventArgs.Empty);
2016-10-30 22:54:45 +00:00
}
/// <summary>
/// Disconnects from the current MPD server.
/// </summary>
public void Disconnect()
{
2016-11-01 09:04:31 +00:00
if (_tcpClient == null)
2016-10-30 22:54:45 +00:00
return;
2016-11-01 09:04:31 +00:00
_networkStream.Dispose();
2016-10-30 22:54:45 +00:00
2016-11-01 09:04:31 +00:00
ClearConnectionFields();
2016-10-30 22:54:45 +00:00
2016-11-30 11:59:26 +00:00
Disconnected?.Invoke(this, EventArgs.Empty);
2016-10-30 22:54:45 +00:00
}
/// <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 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");
2016-11-01 09:04:31 +00:00
CheckConnected();
2016-10-30 22:54:45 +00:00
try
{
2016-11-01 09:04:31 +00:00
_writer.WriteLine(command);
_writer.Flush();
2016-10-30 22:54:45 +00:00
2016-11-01 09:04:31 +00:00
return ReadResponse();
2016-10-30 22:54:45 +00:00
}
catch (Exception)
{
2016-11-01 09:04:31 +00:00
try { Disconnect(); }
2016-10-30 22:54:45 +00:00
catch (Exception) { }
2016-11-30 11:59:26 +00:00
return null; // TODO: Create Null Object for MpdResponse
2016-10-30 22:54:45 +00:00
}
}
/// <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 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");
}
2016-11-01 09:04:31 +00:00
CheckConnected();
2016-10-30 22:54:45 +00:00
try
{
2016-11-01 09:04:31 +00:00
_writer.Write(command);
2016-10-30 22:54:45 +00:00
foreach (string arg in argument)
{
2016-11-01 09:04:31 +00:00
_writer.Write(' ');
WriteToken(arg);
2016-10-30 22:54:45 +00:00
}
2016-11-01 09:04:31 +00:00
_writer.WriteLine();
_writer.Flush();
2016-10-30 22:54:45 +00:00
2016-11-01 09:04:31 +00:00
return ReadResponse();
2016-10-30 22:54:45 +00:00
}
catch (Exception)
{
2016-11-01 09:04:31 +00:00
try { Disconnect(); } catch (Exception) { }
2016-10-30 22:54:45 +00:00
throw;
}
}
private void CheckConnected()
{
2016-11-30 11:59:26 +00:00
if (!IsConnected)
2016-10-30 22:54:45 +00:00
{
2016-11-01 09:04:31 +00:00
if (_autoConnect)
Connect();
2016-10-30 22:54:45 +00:00
else
throw new NotConnectedException();
}
}
private void WriteToken(string token)
{
if (token.Contains(" "))
{
2016-11-01 09:04:31 +00:00
_writer.Write("\"");
2016-10-30 22:54:45 +00:00
foreach (char chr in token)
if (chr == '"')
2016-11-01 09:04:31 +00:00
_writer.Write("\\\"");
2016-10-30 22:54:45 +00:00
else
2016-11-01 09:04:31 +00:00
_writer.Write(chr);
2016-10-30 22:54:45 +00:00
}
else
2016-11-01 09:04:31 +00:00
_writer.Write(token);
2016-10-30 22:54:45 +00:00
}
2016-11-01 09:04:31 +00:00
private MpdResponse ReadResponse()
2016-10-30 22:54:45 +00:00
{
List<string> ret = new List<string>();
2016-11-01 09:04:31 +00:00
string line = _reader.ReadLine();
2016-10-30 22:54:45 +00:00
while (!(line.Equals(OK) || line.StartsWith(ACK)))
{
ret.Add(line);
2016-11-01 09:04:31 +00:00
line = _reader.ReadLine();
2016-10-30 22:54:45 +00:00
}
if (line.Equals(OK))
return new MpdResponse(new ReadOnlyCollection<string>(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<string>(ret)
);
}
}
private void ClearConnectionFields()
{
2016-11-01 09:04:31 +00:00
_tcpClient?.Dispose();
_networkStream?.Dispose();
_reader?.Dispose();
_writer?.Dispose();
_version = string.Empty;
2016-10-30 22:54:45 +00:00
}
}
}