diff --git a/.gitignore b/.gitignore index 9f99927..2fad962 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ obj/ bin/ publish .vs -*.csproj.user \ No newline at end of file +*.csproj.user +*.user diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..0043d95 --- /dev/null +++ b/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..e47d817 --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace unison +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs new file mode 100644 index 0000000..8b5504e --- /dev/null +++ b/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/MPDCtrl/BinaryDownloader.cs b/MPDCtrl/BinaryDownloader.cs new file mode 100644 index 0000000..c9a3e31 --- /dev/null +++ b/MPDCtrl/BinaryDownloader.cs @@ -0,0 +1,1415 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; + +namespace MPDCtrl.Models +{ + public class BinaryDownloader + { + private static TcpClient _binaryConnection = new(); + private StreamReader _binaryReader; + private StreamWriter _binaryWriter; + + private AlbumImage _albumCover = new(); + public AlbumImage AlbumCover { get => _albumCover; } + + private string _host; + private int _port; + //private string _password; + + private string MpdVersion { get; set; } + + public BinaryDownloader() + { + + } + + public async Task MpdBinaryConnectionStart(string host, int port, string password) + { + ConnectionResult r = await MpdBinaryConnect(host, port); + + if (r.IsSuccess) + { + CommandResult d = await MpdBinarySendPassword(password); + + if (d.IsSuccess) + { + // ここでIdleにして、以降はnoidle + cmd + idleの組み合わせでやる。 + // ただし、実際にはidleのあとReadしていないからタイムアウトで切断されてしまう模様。 + + // awaitが必要だった。 + //d = await MpdSendIdle(); + + return d.IsSuccess; + } + } + + return false; + } + + private async Task MpdBinaryConnect(string host, int port) + { + ConnectionResult result = new(); + + //IsMpdCommandConnected = false; + + _binaryConnection = new TcpClient(); + + _host = host; + _port = port; + + //DebugCommandOutput?.Invoke(this, "TCP Command Connection: Connecting." + "\n" + "\n"); + + //ConnectionState = ConnectionStatus.Connecting; + + //Debug.WriteLine("TCP Binary Connection: Connecting. " + host + " " + port.ToString()); + + try + { + await _binaryConnection.ConnectAsync(_host, _port); + + if (_binaryConnection.Client == null) + { + Debug.WriteLine("_binaryConnection.Client == null. " + host + " " + port.ToString()); + + result.ErrorMessage = "_binaryConnection.Client == null"; + + return result; + } + + if (_binaryConnection.Client.Connected) + { + //DebugCommandOutput?.Invoke(this, "TCP Command Connection: Connection established." + "\n" + "\n"); + + //ConnectionState = ConnectionStatus.Connected; + + var tcpStream = _binaryConnection.GetStream(); + //tcpStream.ReadTimeout = System.Threading.Timeout.Infinite; + // + tcpStream.ReadTimeout = 3000; + + _binaryReader = new(tcpStream); + _binaryWriter = new(tcpStream); + _binaryWriter.AutoFlush = true; + + string response = await _binaryReader.ReadLineAsync(); + + if (response.StartsWith("OK MPD ")) + { + MpdVersion = response.Replace("OK MPD ", string.Empty).Trim(); + + //Debug.WriteLine("TCP Binary Connection: Connected. MPD " + VerText); + + //DebugCommandOutput?.Invoke(this, "<<<<" + response.Trim() + "\n" + "\n"); + + //IsMpdCommandConnected = true; + + result.IsSuccess = true; + + // Done for now. + } + else + { + Debug.WriteLine("**** TCP Binary Connection: MPD did not respond with proper respose@MpdBinaryConnect"); + + //DebugCommandOutput?.Invoke(this, "TCP Command Connection: MPD did not respond with proper respose." + "\n" + "\n"); + + //ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + //ConnectionError?.Invoke(this, "TCP connection error: MPD did not respond with proper respose."); + } + } + else + { + //? + + Debug.WriteLine("**** !client.Client.Connected@MpdBinaryConnect"); + + //DebugCommandOutput?.Invoke(this, "TCP Command Connection: FAIL to established... Client not connected." + "\n" + "\n"); + + //ConnectionState = ConnectionStatus.NeverConnected; + + //ConnectionError?.Invoke(this, "TCP Command Connection: FAIL to established... Client not connected."); + } + } + catch (Exception e) + { + Debug.WriteLine("**** Exception@MpdBinaryConnect: " + e.Message); + //DebugCommandOutput?.Invoke(this, "TCP Command Connection: Error while connecting. Fail to connect (Exception): " + e.Message + "\n" + "\n"); + + //ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + //ConnectionError?.Invoke(this, "TCP connection error: " + e.Message); + } + + return result; + } + + private async Task MpdBinarySendPassword(string password = "") + { + //_password = password; + + CommandResult ret = new(); + + if (string.IsNullOrEmpty(password)) + { + ret.IsSuccess = true; + ret.ResultText = "OK";//Or OK + ret.ErrorMessage = ""; + + return ret; + } + + string cmd = "password " + password + "\n"; + + return await MpdBinarySendCommand(cmd); + + } + + private async Task MpdBinarySendCommand(string cmd, bool isAutoIdling = false) + { + CommandResult ret = new(); + + if (_binaryConnection.Client == null) + { + return ret; + } + + if ((_binaryWriter == null) || (_binaryReader == null)) + { + Debug.WriteLine("@MpdBinarySendCommand: " + "_commandWriter or _commandReader is null"); + + //ret.IsSuccess = false; + //ret.ErrorMessage = "_commandWriter or _commandReader is null"; + + //DebugCommandOutput?.Invoke(this, string.Format("################ Error :@{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdSendCommand", "_commandWriter or _commandReader is null", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return ret; + } + + if (!_binaryConnection.Client.Connected) + { + Debug.WriteLine("@MpdBinarySendCommand: " + "NOT IsMpdCommandConnected"); + + //ret.IsSuccess = false; + //ret.ErrorMessage = "NOT IsMpdCommandConnected"; + + //DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdSendCommand", "!CommandConnection.Client.Connected", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return ret; + } + + // WriteAsync + try + { + //IsBusy?.Invoke(this, true); + + if (cmd.Trim().StartsWith("idle")) + { + //DebugCommandOutput?.Invoke(this, ">>>>" + cmd.Trim() + "\n" + "\n"); + + await _binaryWriter.WriteAsync(cmd.Trim() + "\n"); + + if (!isAutoIdling) + { + ret.IsSuccess = true; + + //IsBusy?.Invoke(this, false); + return ret; + } + } + else + { + string cmdDummy = cmd; + if (cmd.StartsWith("password ")) + cmdDummy = "password ****"; + + cmdDummy = cmdDummy.Trim().Replace("\n", "\n" + ">>>>"); + + //if (isAutoIdling) + //DebugCommandOutput?.Invoke(this, ">>>>" + "noidle\n>>>>" + cmdDummy.Trim() + "\n>>>>idle player" + "\n" + "\n"); + //else + //DebugCommandOutput?.Invoke(this, ">>>>" + cmdDummy.Trim() + "\n" + "\n"); + + if (isAutoIdling) + await _binaryWriter.WriteAsync("noidle\n" + cmd.Trim() + "\n" + "idle player\n"); + else + await _binaryWriter.WriteAsync(cmd.Trim() + "\n"); + } + } + catch (System.IO.IOException e) + { + // IOException : Unable to write data to the transport connection: 確立された接続がホスト コンピューターのソウトウェアによって中止されました。 + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + /* + // Could be application shutdopwn. + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("################ Error@{0}, Reason:{1}, Data:{2}, {3} Exception: {4} {5}", "WriteAsync@MpdSendCommand", "IOException", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + // タイムアウトしていたらここで「も」エラーになる模様。 + + IsMpdCommandConnected = false; + + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting... " + Environment.NewLine + Environment.NewLine)); + + try + { + _commandConnection.Client.Shutdown(SocketShutdown.Both); + _commandConnection.Close(); + } + catch { } + + ConnectionResult newCon = await MpdCommandConnect(_host, _port); + + if (newCon.IsSuccess) + { + CommandResult d = await MpdCommandSendPassword(_password); + + if (d.IsSuccess) + { + d = await MpdSendCommand("idle player"); + + if (d.IsSuccess) + { + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Success. @IOExceptionOfWriteAsync" + Environment.NewLine + Environment.NewLine)); + + ret = await MpdSendCommand(cmd, isAutoIdling); + } + } + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Failed. " + Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (command) has been terminated (IOException): " + e.Message); + } + } + */ + + //IsBusy?.Invoke(this, false); + return ret; + } + catch (Exception e) + { + Debug.WriteLine("Exception@MpdBinarySendCommand: " + cmd.Trim() + " WriteAsync " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + /* + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "WriteAsync@MpdSendCommand", "Exception", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + //ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + //ConnectionError?.Invoke(this, "The connection (command) has been terminated (Exception): " + e.Message); + } + */ + //IsBusy?.Invoke(this, false); + return ret; + } + + // ReadLineAsync + try + { + //IsBusy?.Invoke(this, true); + + StringBuilder stringBuilder = new(); + + bool isDoubleOk = false; + //bool isAck = false; + string ackText = ""; + bool isNullReturn = false; + + while (true) + { + string line = await _binaryReader.ReadLineAsync(); + + if (line != null) + { + if (line.StartsWith("ACK")) + { + Debug.WriteLine("ACK line @MpdBinarySendCommand: " + cmd.Trim() + " and " + line); + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + ret.ErrorMessage = line; + ackText = line; + //isAck = true; + + break; + } + else if (line.StartsWith("OK")) + { + if (isAutoIdling) + { + if (isDoubleOk) + { + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + ret.IsSuccess = true; + + break; + } + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + isDoubleOk = true; + + } + else + { + ret.IsSuccess = true; + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + break; + } + } + else if (line.StartsWith("changed: ")) + { + // noidleでついてくるかもしれないchanged. idleConnectionで見ているからここでは無視したいが・・・。 + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + } + else + { + if (!string.IsNullOrEmpty(line)) + { + stringBuilder.Append(line + "\n"); // << has to be \n + } + } + } + else + { + isNullReturn = true; + + break; + } + } + + if (isNullReturn) + { + Debug.WriteLine("@MpdBinarySendCommand ReadLineAsync isNullReturn"); + /* + DebugCommandOutput?.Invoke(this, string.Format("################ Error @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdSendCommand", "ReadLineAsync received null data", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + ret.ResultText = stringBuilder.ToString(); + ret.ErrorMessage = "ReadLineAsync@MpdSendCommand received null data"; + + // タイムアウトしていたらここで「も」エラーになる模様。 + + IsMpdCommandConnected = false; + + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting... " + Environment.NewLine + Environment.NewLine)); + + try + { + _commandConnection.Client.Shutdown(SocketShutdown.Both); + _commandConnection.Close(); + } + catch { } + + ConnectionResult newCon = await MpdCommandConnect(_host, _port); + + if (newCon.IsSuccess) + { + CommandResult d = await MpdCommandSendPassword(_password); + + if (d.IsSuccess) + { + d = await MpdSendCommand("idle player"); + + if (d.IsSuccess) + { + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Success. @isNullReturn" + Environment.NewLine + Environment.NewLine)); + + ret = await MpdSendCommand(cmd, isAutoIdling); + } + } + } + else + { + // + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Failed. " + Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (command) has been terminated (null return)."); + } + */ + //IsBusy?.Invoke(this, false); + return ret; + } + else + { + //DebugCommandOutput?.Invoke(this, "<<<<" + stringBuilder.ToString().Trim().Replace("\n", "\n" + "<<<<") + "\n" + "\n"); + + //if (isAck) + // MpdAckError?.Invoke(this, ackText + " (@MSC)"); + + ret.ResultText = stringBuilder.ToString(); + + //IsBusy?.Invoke(this, false); + return ret; + } + + } + catch (System.InvalidOperationException e) + { + // The stream is currently in use by a previous operation on the stream. + + Debug.WriteLine("InvalidOperationException@MpdBinarySendCommand: " + cmd.Trim() + " ReadLineAsync ---- " + e.Message); + + //DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdSendCommand", "InvalidOperationException (Most likely the connection is overloaded)", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + //IsBusy?.Invoke(this, false); + return ret; + } + catch (System.IO.IOException e) + { + // IOException : Unable to write data to the transport connection: 確立された接続がホスト コンピューターのソウトウェアによって中止されました。 + + Debug.WriteLine("IOException@MpdBinarySendCommand: " + cmd.Trim() + " ReadLineAsync ---- " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + /* + // Could be application shutdopwn. + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdSendCommand", "IOException", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + // タイムアウトしていたらここで「も」エラーになる模様。 + + IsMpdCommandConnected = false; + + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting... " + Environment.NewLine + Environment.NewLine)); + + try + { + _commandConnection.Client.Shutdown(SocketShutdown.Both); + _commandConnection.Close(); + } + catch { } + + ConnectionResult newCon = await MpdCommandConnect(_host, _port); + + if (newCon.IsSuccess) + { + CommandResult d = await MpdCommandSendPassword(_password); + + if (d.IsSuccess) + { + d = await MpdSendCommand("idle player"); + + if (d.IsSuccess) + { + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Success. @IOExceptionOfReadLineAsync" + Environment.NewLine + Environment.NewLine)); + + ret = await MpdSendCommand(cmd, isAutoIdling); + } + } + } + else + { + // Unable to read data from the transport connection: 既に接続済みのソケットに対して接続を要求しました。. + + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Failed. " + Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (command) has been terminated (IOException): " + e.Message); + } + } + */ + //IsBusy?.Invoke(this, false); + return ret; + } + catch (Exception e) + { + Debug.WriteLine("Exception@MpdBinarySendCommand: " + cmd.Trim() + " ReadLineAsync ---- " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + //DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdSendCommand", "Exception", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + //IsBusy?.Invoke(this, false); + return ret; + } + + } + + private async Task MpdBinarySendBinaryCommand(string cmd, bool isAutoIdling = false) + { + CommandBinaryResult ret = new(); + + if (_binaryConnection.Client == null) + { + Debug.WriteLine("@MpdBinarySendBinaryCommand: " + "TcpClient.Client == null"); + + ret.IsSuccess = false; + ret.ErrorMessage = "TcpClient.Client == null"; + + //DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdCommandGetBinary", "TcpClient.Client == null", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return ret; + } + + if ((_binaryWriter == null) || (_binaryReader == null)) + { + Debug.WriteLine("@MpdBinarySendBinaryCommand: " + "_binaryWriter or _binaryReader is null"); + + ret.IsSuccess = false; + ret.ErrorMessage = "_binaryWriter or _binaryReader is null"; + + //DebugCommandOutput?.Invoke(this, string.Format("################ Error :@{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdCommandGetBinary", "_commandWriter or _commandReader is null", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return ret; + } + + if (!_binaryConnection.Client.Connected) + { + Debug.WriteLine("@MpdBinarySendBinaryCommand: " + "NOT IsMpdCommandConnected"); + + ret.IsSuccess = false; + ret.ErrorMessage = "NOT IsMpdCommandConnected"; + + //DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdCommandGetBinary", "!CommandConnection.Client.Connected", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return ret; + } + + // WriteAsync + try + { + if (cmd.Trim().StartsWith("idle")) + { + //DebugCommandOutput?.Invoke(this, ">>>>" + cmd.Trim() + "\n" + "\n"); + + await _binaryWriter.WriteAsync(cmd.Trim() + "\n"); + + if (!isAutoIdling) + return ret; + } + else + { + string cmdDummy = cmd; + if (cmd.StartsWith("password ")) + cmdDummy = "password ****"; + + cmdDummy = cmdDummy.Trim().Replace("\n", "\n" + ">>>>"); + + //if (isAutoIdling) + // DebugCommandOutput?.Invoke(this, ">>>>" + "noidle\n>>>>" + cmdDummy.Trim() + "\n>>>>idle player" + "\n" + "\n"); + //else + // DebugCommandOutput?.Invoke(this, ">>>>" + cmdDummy.Trim() + "\n" + "\n"); + + if (isAutoIdling) + await _binaryWriter.WriteAsync("noidle\n" + cmd.Trim() + "\n" + "idle player\n"); + else + await _binaryWriter.WriteAsync(cmd.Trim() + "\n"); + } + } + catch (System.IO.IOException e) + { + // IOException : Unable to write data to the transport connection: 確立された接続がホスト コンピューターのソウトウェアによって中止されました。 + + Debug.WriteLine("Exception@MpdBinarySendBinaryCommand: " + cmd.Trim() + " WriteAsync " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + /* + // Could be application shutdopwn. + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("################ Error@{0}, Reason:{1}, Data:{2}, {3} Exception: {4} {5}", "WriteAsync@MpdCommandGetBinary", "IOException", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + // タイムアウトしていたらここで「も」エラーになる模様。 + + IsMpdCommandConnected = false; + + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting... " + Environment.NewLine + Environment.NewLine)); + + _commandConnection.Close(); + + ConnectionResult newCon = await MpdCommandConnect(_host, _port); + + if (newCon.IsSuccess) + { + CommandResult d = await MpdCommandSendPassword(_password); + + if (d.IsSuccess) + { + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Success. " + Environment.NewLine + Environment.NewLine)); + + ret = await MpdCommandGetBinary(cmd, isAutoIdling); + } + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Failed. " + Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (command) has been terminated (IOException): " + e.Message); + } + } + */ + return ret; + } + catch (Exception e) + { + Debug.WriteLine("Exception@MpdBinarySendBinaryCommand: " + cmd.Trim() + " WriteAsync " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + /* + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "WriteAsync@MpdCommandGetBinary", "Exception", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + //ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + //ConnectionError?.Invoke(this, "The connection (command) has been terminated (Exception): " + e.Message); + } + */ + return ret; + } + + // ReadAsync + try + { + StringBuilder stringBuilder = new(); + + byte[] bin = Array.Empty(); + + bool isDoubleOk = false; + bool isAck = false; + string ackText = ""; + bool isNullReturn = false; + bool isBinaryFound = false; + + bool isWaitForOK = true; + + while (isWaitForOK) + { + + int readSize = 0; + int bufferSize = 5000; + byte[] buffer = new byte[bufferSize]; + byte[] bindata = Array.Empty(); + + using (MemoryStream ms = new()) + { + while ((readSize = await _binaryReader.BaseStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + ms.Write(buffer, 0, readSize); + + if (readSize < bufferSize) + { + break; + } + } + + bindata = ms.ToArray(); + } + + if (bindata.Length == 0) + { + isNullReturn = true; + + isWaitForOK = false; + } + else + { + + bin = CombineByteArray(bin, bindata); + + //Debug.WriteLine("Done .Write:" + bin.Length.ToString()); + + string res = Encoding.Default.GetString(bindata, 0, bindata.Length); + + List values = res.Split("\n").ToList(); + + foreach (var line in values) + { + if (line != null) + { + if (line.StartsWith("ACK")) + { + //Debug.WriteLine("ACK line @MpdCommandGetBinary: " + cmd.Trim() + " and " + line); + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + ret.ErrorMessage = line; + ackText = line; + isAck = true; + + isWaitForOK = false; + + break; + } + else if (line.StartsWith("changed: ")) + { + // noidleでついてくるかもしれないchanged. idleConnectionで見ているからここでは無視したいが・・・。 + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + } + else if (line.StartsWith("size: ")) + { + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + List s = line.Split(':').ToList(); + if (s.Count > 1) + { + if (Int32.TryParse(s[1], out int i)) + { + ret.WholeSize = i; + } + } + } + else if (line.StartsWith("type: ")) + { + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + List s = line.Split(':').ToList(); + if (s.Count > 1) + { + ret.Type = s[1].Trim(); + } + } + else if (line.StartsWith("binary: ")) + { + isBinaryFound = true; + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + stringBuilder.Append("{binary data}" + "\n"); + + List s = line.Split(':').ToList(); + if (s.Count > 1) + { + if (Int32.TryParse(s[1], out int i)) + { + ret.ChunkSize = i; + } + } + + } + else if (line == "OK") // == + { + if (isAutoIdling) + { + if (isDoubleOk) + { + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + ret.IsSuccess = true; + + isWaitForOK = false; + break; + } + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + isDoubleOk = true; + + } + else + { + ret.IsSuccess = true; + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + isWaitForOK = false; + break; + } + } + else + { + // This should be binary data if not reading correctly above. + + //Debug.WriteLine(line); + } + } + else + { + isNullReturn = true; + + isWaitForOK = false; + break; + } + } + + } + } + + if (isNullReturn) + { + Debug.WriteLine("@MpdBinarySendBinaryCommand ReadAsync isNullReturn"); + + //DebugCommandOutput?.Invoke(this, string.Format("################ Error @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadAsync@MpdCommandGetBinary", "ReadLineAsync received null data", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + ret.ErrorMessage = "ReadAsync@MpdBinarySendBinaryCommand received null data"; + + return ret; + } + else + { + //DebugCommandOutput?.Invoke(this, "<<<<" + stringBuilder.ToString().Trim().Replace("\n", "\n" + "<<<<") + "\n" + "\n"); + + if (isAck) + { + // とりあえず今の所、アルバムカバーのfile not existsは無視するようにしている。 + //MpdAckError?.Invoke(this, ackText + " (@MCGB)"); + + ret.ErrorMessage = ackText + " (@MpdBinarySendBinaryCommand)"; + + return ret; + } + else if (isBinaryFound) + { + //return ret; + //return ParseAlbumArtResponse(bin); + return ParseAlbumImageData(bin); + } + else + { + //Debug.WriteLine("No binary data(size) found. Could be a readpicture command?"); + + // TODO: + + //DebugCommandOutput?.Invoke(this, "No binary data(size) found. Could be a readpicture command?" + "\n" + "\n"); + + ret.ErrorMessage = "No binary data(size) found. Could be a readpicture command? (@MpdBinarySendBinaryCommand)"; + + return ret; + } + } + } + catch (System.InvalidOperationException e) + { + // The stream is currently in use by a previous operation on the stream. + + Debug.WriteLine("InvalidOperationException@MpdBinarySendBinaryCommand: " + cmd.Trim() + " ReadAsync ---- " + e.Message); + + //DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadAsync@MpdCommandGetBinary", "InvalidOperationException (Most likely the connection is overloaded)", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + return ret; + } + catch (System.IO.IOException e) + { + // IOException : Unable to write data to the transport connection: 確立された接続がホスト コンピューターのソウトウェアによって中止されました。 + + Debug.WriteLine("IOException@MpdBinarySendBinaryCommand: " + cmd.Trim() + " ReadAsync ---- " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + /* + // Could be application shutdopwn. + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadAsync@MpdCommandGetBinary", "IOException", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + // タイムアウトしていたらここで「も」エラーになる模様。 + + IsMpdCommandConnected = false; + + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting... " + Environment.NewLine + Environment.NewLine)); + + _commandConnection.Close(); + + ConnectionResult newCon = await MpdCommandConnect(_host, _port); + + if (newCon.IsSuccess) + { + CommandResult d = await MpdCommandSendPassword(_password); + + if (d.IsSuccess) + { + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Success. " + Environment.NewLine + Environment.NewLine)); + + ret = await MpdCommandGetBinary(cmd, isAutoIdling); + } + } + else + { + // Unable to read data from the transport connection: 既に接続済みのソケットに対して接続を要求しました。. + + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Failed. " + Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (command) has been terminated (IOException): " + e.Message); + } + } + */ + return ret; + } + catch (Exception e) + { + Debug.WriteLine("Exception@MpdBinarySendBinaryCommand: " + cmd.Trim() + " ReadAsync ---- " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + //DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadAsync@MpdCommandGetBinary", "Exception", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + return ret; + } + + } + + private CommandBinaryResult ParseAlbumImageData(byte[] data) + { + CommandBinaryResult r = new(); + + //if (MpdStop) return r; + + if (data.Length > 1000000) //2000000000 + { + Debug.WriteLine("**ParseAlbumImageData: binary file size too big: " + data.Length.ToString()); + + _albumCover.IsDownloading = false; + return r; + } + + if (string.IsNullOrEmpty(_albumCover.SongFilePath)) + { + Debug.WriteLine("**ParseAlbumImageData: File path is not set."); + + _albumCover.IsDownloading = false; + return r; + } + + if (!_albumCover.IsDownloading) + { + Debug.WriteLine("**ParseAlbumImageData: IsDownloading = false. Downloading canceld? ."); + + _albumCover.IsDownloading = false; + return r; + } + + try + { + //int gabStart = gabPre; + //int gabEnd = gabAfter; + int gabStart = 0; + int gabEnd = 0; + + string res = Encoding.Default.GetString(data, 0, data.Length); + + int binSize = 0; + int binResSize = 0; + + List values = res.Split('\n').ToList(); + + bool found = false; + foreach (var val in values) + { + if (val.StartsWith("size: ")) + { + found = true; + + gabStart = gabStart + val.Length + 1; + + List s = val.Split(':').ToList(); + if (s.Count > 1) + { + if (Int32.TryParse(s[1], out int i)) + { + binSize = i; + + r.WholeSize = i; + } + } + + } + else if (val.StartsWith("type: ")) + { + gabStart = gabStart + val.Length + 1; + + List s = val.Split(':').ToList(); + if (s.Count > 1) + { + r.Type = s[1]; + } + + } + else if (val.StartsWith("binary: ")) + { + gabStart = gabStart + val.Length + 1; + + List s = val.Split(':').ToList(); + if (s.Count > 1) + { + if (Int32.TryParse(s[1], out int i)) + { + binResSize = i; + + r.ChunkSize = binResSize; + } + } + + } + else if (val == "OK") // == + { + //gabEnd = gabEnd + val.Length + 1; + if (found) + { + gabEnd = gabEnd + val.Length + 1; + //System.Diagnostics.Debug.WriteLine("OK:after " + val); + } + else + { + gabStart = gabStart + val.Length + 1; + //System.Diagnostics.Debug.WriteLine("OK:before " + val); + } + + } + else if (val.StartsWith("ACK")) + { + // ACK 応答はここまで到達しないはず。 + + if (found) + { + gabEnd = gabEnd + val.Length + 1; + //System.Diagnostics.Debug.WriteLine("changed:after " + val); + } + else + { + gabStart = gabStart + val.Length + 1; + //System.Diagnostics.Debug.WriteLine("changed:before " + val); + } + } + else if (val.StartsWith("changed:")) + { + // Song is changed...so should skip ?? + //DataReceived_ParseData(val); + + if (found) + { + gabEnd = gabEnd + val.Length + 1; + //System.Diagnostics.Debug.WriteLine("changed:after " + val); + } + else + { + gabStart = gabStart + val.Length + 1; + //System.Diagnostics.Debug.WriteLine("changed:before " + val); + } + + } + else + { + // should be binary... + } + } + + gabEnd++; // + + // test + //gabEnd = 4; // \n O K \n + + + if (binSize > 1000000) + { + Debug.WriteLine("binary file too big: " + binSize.ToString()); + + //DebugCommandOutput?.Invoke(this, "binary file too big: " + binSize.ToString() + "\n" + "\n"); + + _albumCover.IsDownloading = false; + + return r; + } + + if ((binSize == 0)) + { + Debug.WriteLine("binary file size is Zero: " + binSize.ToString() + ", " + binResSize.ToString() + ", " + data.Length.ToString()); + + _albumCover.IsDownloading = false; + + return r; + } + + if (binResSize != ((data.Length - gabStart) - gabEnd)) + { + Debug.WriteLine("binary file size mismatch: " + binSize.ToString() + ", [" + binResSize.ToString() + ", " + (data.Length - gabStart - gabEnd) + "], " + data.Length.ToString()); + + //DebugCommandOutput?.Invoke(this, "binary file size mismatch." + "\n" + "\n"); + + //DebugCommandOutput?.Invoke(this, "raw text data:\n" + res + "\n" + "\n"); + + _albumCover.IsDownloading = false; + + return r; + } + + r.WholeSize = binSize; + r.ChunkSize = binResSize; + + // 今回受け取ったバイナリ用にバイトアレイをイニシャライズ + byte[] resBinary = new byte[data.Length - gabStart - gabEnd]; + // 今回受け取ったバイナリをresBinaryへコピー + Array.Copy(data, gabStart, resBinary, 0, resBinary.Length); + + r.BinaryData = resBinary; + + r.IsSuccess = true; + + return r; + + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine("Error@ParseAlbumImageData (l): " + ex.Message); + + _albumCover.IsDownloading = false; + return r; + } + } + + private static byte[] CombineByteArray(byte[] first, byte[] second) + { + byte[] ret = new byte[first.Length + second.Length]; + Buffer.BlockCopy(first, 0, ret, 0, first.Length); + Buffer.BlockCopy(second, 0, ret, first.Length, second.Length); + return ret; + } + + public async Task MpdQueryAlbumArt(string uri, bool isUsingReadpicture) + { + if (string.IsNullOrEmpty(uri)) + { + CommandResult f = new(); + f.ErrorMessage = "IsNullOrEmpty(uri)"; + f.IsSuccess = false; + return f; + } + + + /* + if (songId != MpdStatus.MpdSongID) + { + // probably you clicked on "Next" too farst or double clicked. + Debug.WriteLine("Error@MpdQueryAlbumArt: songId != MpdStatus.MpdSongID. Ignoring."); + + CommandResult f = new CommandResult(); + f.IsSuccess = false; + return f; + } + */ + + //Debug.WriteLine("Downloading..." + uri); + + _albumCover.SongFilePath = uri; + _albumCover.IsDownloading = true; + /* + Application.Current.Dispatcher.Invoke(() => + { + _albumCover = new AlbumImage(); + _albumCover.IsDownloading = true; + _albumCover.SongFilePath = uri; + _albumCover.AlbumImageSource = null; + _albumCover.BinaryData = new byte[0]; + _albumCover.BinarySize = 0; + }); + */ + + uri = Regex.Escape(uri); + + string cmd = "albumart \"" + uri + "\" 0" + "\n"; + if (isUsingReadpicture) + if (CompareVersionString(MpdVersion,"0.22.0") >= 0) + cmd = "readpicture \"" + uri + "\" 0" + "\n"; + + CommandBinaryResult result = await MpdBinarySendBinaryCommand(cmd, false); + + bool r = false; + + if (result.IsSuccess) + { + _albumCover.BinaryData = result.BinaryData; + + if ((result.WholeSize != 0) && (result.WholeSize == result.ChunkSize)) + { + Application.Current.Dispatcher.Invoke(() => + { + _albumCover.AlbumImageSource = BitmaSourceFromByteArray(_albumCover.BinaryData); + _albumCover.IsSuccess = true; + _albumCover.IsDownloading = false; + + //MpdAlbumArtChanged?.Invoke(this); + }); + + r = true; + //Debug.WriteLine("一発できた"); + } + else + { + //Debug.WriteLine("何回かみにいくみたい"); + + if ((result.WholeSize != 0) && (result.WholeSize > result.ChunkSize)) + { + while ((result.WholeSize > _albumCover.BinaryData.Length) && result.IsSuccess) + { + result = await MpdReQueryAlbumArt(_albumCover.SongFilePath, _albumCover.BinaryData.Length, isUsingReadpicture); + + if (result.IsSuccess && (result.BinaryData != null)) + _albumCover.BinaryData = CombineByteArray(_albumCover.BinaryData, result.BinaryData); + } + + //Debug.WriteLine("何回かできた"); + + if (result.IsSuccess && (_albumCover.BinaryData != null)) + { + Application.Current.Dispatcher.Invoke(() => + { + _albumCover.AlbumImageSource = BitmaSourceFromByteArray(_albumCover.BinaryData); + _albumCover.IsSuccess = true; + _albumCover.IsDownloading = false; + + //MpdAlbumArtChanged?.Invoke(this); + }); + + r = true; + } + } + } + } + + CommandResult b = new(); + b.IsSuccess = r; + if (!r) + { + b.ErrorMessage = result.ErrorMessage; + } + + return b; + } + + private async Task MpdReQueryAlbumArt(string uri, int offset, bool isUsingReadpicture) + { + if (string.IsNullOrEmpty(uri)) + { + CommandBinaryResult f = new(); + f.ErrorMessage = "IsNullOrEmpty(uri)"; + f.IsSuccess = false; + return f; + } + + if (!_albumCover.IsDownloading) + { + Debug.WriteLine("Error@MpdQueryAlbumArt: _albumCover.IsDownloading == false. Ignoring."); + + CommandBinaryResult f = new(); + f.IsSuccess = false; + return f; + } + + if (_albumCover.SongFilePath != uri) + { + Debug.WriteLine("Error@MpdQueryAlbumArt: _albumCover.SongFilePath != uri. Ignoring."); + + _albumCover.IsDownloading = false; + + CommandBinaryResult f = new(); + f.IsSuccess = false; + return f; + } + + uri = Regex.Escape(uri); + + string cmd = "albumart \"" + uri + "\" " + offset.ToString() + "\n"; + if (isUsingReadpicture) + if (CompareVersionString(MpdVersion, "0.22.0") >= 0) + cmd = "readpicture \"" + uri + "\" " + offset.ToString() + "\n"; + + return await MpdBinarySendBinaryCommand(cmd, false); + } + + private static int CompareVersionString(string a, string b) + { + return (new System.Version(a)).CompareTo(new System.Version(b)); + } + + private static BitmapSource BitmaSourceFromByteArray(byte[] buffer) + { + using var stream = new MemoryStream(buffer); + return BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + } + + public void MpdBinaryConnectionDisconnect() + { + //Debug.WriteLine("TCP Binary Connection: Disconnecting."); + + try + { + if (_binaryConnection.Client != null) + _binaryConnection.Client.Shutdown(SocketShutdown.Both); + _binaryConnection.Close(); + } + catch { } + + } + + } +} diff --git a/MPDCtrl/Classes/AlbumImage.cs b/MPDCtrl/Classes/AlbumImage.cs new file mode 100644 index 0000000..53dfb6a --- /dev/null +++ b/MPDCtrl/Classes/AlbumImage.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace MPDCtrl.Models +{ + /// + /// AlbumCover class. + /// + public class AlbumImage + { + public bool IsDownloading { get; set; } + + public bool IsSuccess { get; set; } + + public string SongFilePath { get; set; } + + public byte[] BinaryData { get; set; } = Array.Empty(); + + public int BinarySize { get; set; } + + public ImageSource AlbumImageSource { get; set; } + + } +} diff --git a/MPDCtrl/Classes/Playlist.cs b/MPDCtrl/Classes/Playlist.cs new file mode 100644 index 0000000..d9f0921 --- /dev/null +++ b/MPDCtrl/Classes/Playlist.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MPDCtrl.Models +{ + public class Playlist + { + public string Name { get; set; } = ""; + + private string _lastModified; + public string LastModified + { + get + { + return _lastModified; + } + set + { + if (_lastModified == value) + return; + + _lastModified = value; + } + } + + public string LastModifiedFormated + { + get + { + DateTime _lastModifiedDateTime = default; //new DateTime(1998,04,30) + + if (!string.IsNullOrEmpty(_lastModified)) + { + try + { + _lastModifiedDateTime = DateTime.Parse(_lastModified, null, System.Globalization.DateTimeStyles.RoundtripKind); + } + catch + { + System.Diagnostics.Debug.WriteLine("Wrong LastModified timestamp format. " + _lastModified); + } + } + + var culture = System.Globalization.CultureInfo.CurrentCulture; + return _lastModifiedDateTime.ToString(culture); + } + } + + } +} diff --git a/MPDCtrl/Classes/Result.cs b/MPDCtrl/Classes/Result.cs new file mode 100644 index 0000000..d235d0d --- /dev/null +++ b/MPDCtrl/Classes/Result.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MPDCtrl.Models +{ + public class Result + { + public bool IsSuccess; + public string ErrorMessage; + } + + public class ConnectionResult: Result + { + + } + + // generic + public class CommandResult : Result + { + public string ResultText; + } + + public class CommandBinaryResult : Result + { + public int WholeSize; + public int ChunkSize; + public string Type; + public byte[] BinaryData; + } + + // for commands that return playlist songs. + public class CommandPlaylistResult : CommandResult + { + public ObservableCollection PlaylistSongs; + } + + // for commands that return search result. + public class CommandSearchResult : CommandResult + { + public ObservableCollection SearchResult; + } + + // TODO: Not used? + public class IdleResult : CommandResult + { + + } +} diff --git a/MPDCtrl/Classes/Song.cs b/MPDCtrl/Classes/Song.cs new file mode 100644 index 0000000..5e1c447 --- /dev/null +++ b/MPDCtrl/Classes/Song.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +//using MPDCtrl.Common; + +namespace MPDCtrl.Models +{ + // SongFile > SongInfo > SongInfoEx + + /// + /// Generic song file class. (for listall) + /// + public class SongFile// : ViewModelBase + { + public string File { get; set; } = ""; + + } + + /// + /// SongInfo class. (for playlist or search result) + /// + public class SongInfo : SongFile + { + public string Title { get; set; } = ""; + public string Track { get; set; } = ""; + public string Disc { get; set; } = ""; + public string Time { get; set; } = ""; + public string TimeFormated + { + get + { + string _timeFormatted = ""; + try + { + if (!string.IsNullOrEmpty(Time)) + { + int sec, min, hour, s; + + double dtime = double.Parse(Time); + sec = Convert.ToInt32(dtime); + + //sec = Int32.Parse(_time); + min = sec / 60; + s = sec % 60; + hour = min / 60; + min %= 60; + + if ((hour == 0) && min == 0) + { + _timeFormatted = String.Format("{0}", s); + } + else if ((hour == 0) && (min != 0)) + { + _timeFormatted = String.Format("{0}:{1:00}", min, s); + } + else if ((hour != 0) && (min != 0)) + { + _timeFormatted = String.Format("{0}:{1:00}:{2:00}", hour, min, s); + } + else if (hour != 0) + { + _timeFormatted = String.Format("{0}:{1:00}:{2:00}", hour, min, s); + } + else + { + System.Diagnostics.Debug.WriteLine("Oops@TimeFormated: " + Time + " : " + hour.ToString() + " " + min.ToString() + " " + s.ToString()); + } + } + } + catch (FormatException e) + { + // Ignore. + // System.Diagnostics.Debug.WriteLine(e.Message); + System.Diagnostics.Debug.WriteLine("Wrong Time format. " + Time + " " + e.Message); + } + + return _timeFormatted; + } + + } + public double TimeSort + { + get + { + double dtime = double.NaN; + try + { + dtime = double.Parse(Time); + } + catch { } + return dtime; + } + } + public string Duration { get; set; } = ""; + public string Artist { get; set; } = ""; + public string Album { get; set; } = ""; + public string AlbumArtist { get; set; } = ""; + public string Composer { get; set; } = ""; + public string Date { get; set; } = ""; + public string Genre { get; set; } = ""; + + private string _lastModified; + public string LastModified + { + get + { + return _lastModified; + } + set + { + if (_lastModified == value) + return; + + _lastModified = value; + } + } + + public string LastModifiedFormated + { + get + { + DateTime _lastModifiedDateTime = default; //new DateTime(1998,04,30) + + if (!string.IsNullOrEmpty(_lastModified)) + { + try + { + _lastModifiedDateTime = DateTime.Parse(_lastModified, null, System.Globalization.DateTimeStyles.RoundtripKind); + } + catch + { + System.Diagnostics.Debug.WriteLine("Wrong LastModified timestamp format. " + _lastModified); + } + } + + var culture = System.Globalization.CultureInfo.CurrentCulture; + return _lastModifiedDateTime.ToString(culture); + } + } + + // for sorting and (playlist pos) + private int _index; + public int Index + { + get + { + return _index; + } + set + { + if (_index == value) + return; + + _index = value; + //this.NotifyPropertyChanged(nameof(Index)); + } + } + + private bool _isSelected; + public bool IsSelected + { + get + { + return _isSelected; + } + set + { + if (_isSelected == value) + return; + + _isSelected = value; + + //NotifyPropertyChanged("IsSelected"); + } + } + + public int IndexPlusOne + { + get + { + return _index+1; + } + } + } + + /// + /// song class with some extra info. (for queue) + /// + public class SongInfoEx : SongInfo + { + // Queue specific + + public string Id { get; set; } + + private string _pos; + public string Pos + { + get + { + return _pos; + } + set + { + if (_pos == value) + return; + + _pos = value; + //this.NotifyPropertyChanged(nameof(Pos)); + } + } + + private bool _isPlaying; + public bool IsPlaying + { + get + { + return _isPlaying; + } + set + { + if (_isPlaying == value) + return; + + _isPlaying = value; + //this.NotifyPropertyChanged(nameof(IsPlaying)); + } + } + } +} diff --git a/MPDCtrl/Classes/Status.cs b/MPDCtrl/Classes/Status.cs new file mode 100644 index 0000000..e14099e --- /dev/null +++ b/MPDCtrl/Classes/Status.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MPDCtrl.Models +{ + /// + /// MPD "status" class. (for "status" command result) + /// + public class Status + { + public enum MpdPlayState + { + Play, Pause, Stop + }; + + private MpdPlayState _ps; + private string _bitrate; + private int _volume = 50; + private bool _volumeIsSet; + private bool _repeat; + private bool _random; + private bool _consume; + private bool _single; + private string _songID = ""; + private double _songTime = 0; + private double _songElapsed = 0; + private string _error = ""; + + public MpdPlayState MpdState + { + get { return _ps; } + set { _ps = value; } + } + + public string MpdBitrate + { + get { return _bitrate; } + set + { + _bitrate = value; + } + } + + public int MpdVolume + { + get { return _volume; } + set + { + _volume = value; + } + } + + public bool MpdVolumeIsSet + { + get { return _volumeIsSet; } + set + { + _volumeIsSet = value; + } + } + + public bool MpdRepeat + { + get { return _repeat; } + set + { + _repeat = value; + } + } + + public bool MpdRandom + { + get { return _random; } + set + { + _random = value; + } + } + public bool MpdConsume + { + get { return _consume; } + set + { + _consume = value; + } + } + + public bool MpdSingle + { + get { return _single; } + set + { + _single = value; + } + } + + public string MpdSongID + { + get { return _songID; } + set + { + _songID = value; + } + } + + public double MpdSongTime + { + get { return _songTime; } + set + { + _songTime = value; + } + } + + public double MpdSongElapsed + { + get { return _songElapsed; } + set + { + _songElapsed = value; + } + } + public string MpdError + { + get { return _error; } + set + { + _error = value; + } + } + + public void Reset() + { + _volume = 50; + _volumeIsSet = false; + _repeat = false; + _random = false; + _consume = false; + _songID = ""; + _songTime = 0; + _songElapsed = 0; + _error = ""; + } + + } +} diff --git a/MPDCtrl/MPC.cs b/MPDCtrl/MPC.cs new file mode 100644 index 0000000..184515a --- /dev/null +++ b/MPDCtrl/MPC.cs @@ -0,0 +1,3722 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; + +namespace MPDCtrl.Models +{ + /// + /// MPD client class. + /// + public class MPC + { + #region == Consts, Properties, etc == + + private string _host; + public string MpdHost + { + get { return _host; } + } + + private int _port; + public int MpdPort + { + get { return _port; } + } + + private string _password; + public string MpdPassword + { + get { return _password; } + } + + private string _mpdVerText; + public string MpdVerText + { + get { return _mpdVerText; } + set + { + if (value == _mpdVerText) + return; + + _mpdVerText = value; + + } + } + + private readonly Status _status = new(); + public Status MpdStatus + { + get { return _status; } + } + + public bool MpdStop { get; set; } + + private SongInfoEx _currentSong; + public SongInfoEx MpdCurrentSong + { + // You need to either get "status" and "queue" before hand, or "currentsong". + get + { + return _currentSong; + } + } + + private ObservableCollection _queue = new(); + public ObservableCollection CurrentQueue + { + get { return _queue; } + } + + private ObservableCollection _playlists = new(); + public ObservableCollection Playlists + { + get { return _playlists; } + } + + private readonly ObservableCollection _localFiles = new(); + public ObservableCollection LocalFiles + { + get { return _localFiles; } + } + + private readonly ObservableCollection _localDirectories = new(); + public ObservableCollection LocalDirectories + { + get { return _localDirectories; } + } + + private readonly ObservableCollection _searchResult = new(); + public ObservableCollection SearchResult + { + get { return _searchResult; } + } + + // TODO: + private AlbumImage _albumCover = new(); + public AlbumImage AlbumCover + { + get { return _albumCover; } + } + + #endregion + + #region == Connections == + + private static TcpClient _commandConnection = new(); + private StreamReader _commandReader; + private StreamWriter _commandWriter; + + private static TcpClient _idleConnection = new(); + private static StreamReader _idleReader; + private static StreamWriter _idleWriter; + + public enum ConnectionStatus + { + NeverConnected, + Connecting, + Connected, + DisconnectedByUser, + DisconnectedByHost, + ConnectFail_Timeout, + ReceiveFail_Timeout, + SendFail_Timeout, + SendFail_NotConnected, + Disconnecting, + Disconnected, + SeeConnectionErrorEvent + } + + private ConnectionStatus _connectionState = ConnectionStatus.NeverConnected; + public ConnectionStatus ConnectionState + { + get + { + return _connectionState; + } + private set + { + if (value == _connectionState) + return; + + _connectionState = value; + + ConnectionStatusChanged?.Invoke(this, _connectionState); + } + } + + // TODO:ちゃんと使っていないので利用するか削除すべきか。 + public bool IsMpdCommandConnected { get; set; } + public bool IsMpdIdleConnected { get; set; } + + #endregion + + #region == Events == + + public delegate void IsBusyEvent(MPC sender, bool on); + public event IsBusyEvent IsBusy; + + public delegate void DebugCommandOutputEvent(MPC sender, string data); + public event DebugCommandOutputEvent DebugCommandOutput; + + public delegate void DebugIdleOutputEvent(MPC sender, string data); + public event DebugIdleOutputEvent DebugIdleOutput; + + public delegate void ConnectionStatusChangedEvent(MPC sender, ConnectionStatus status); + public event ConnectionStatusChangedEvent ConnectionStatusChanged; + + public delegate void ConnectionErrorEvent(MPC sender, string data); + public event ConnectionErrorEvent ConnectionError; + + public delegate void IsMpdIdleConnectedEvent(MPC sender); + public event IsMpdIdleConnectedEvent MpdIdleConnected; + + public delegate void MpdAckErrorEvent(MPC sender, string data); + public event MpdAckErrorEvent MpdAckError; + + public delegate void MpdPlayerStatusChangedEvent(MPC sender); + public event MpdPlayerStatusChangedEvent MpdPlayerStatusChanged; + + public delegate void MpdCurrentQueueChangedEvent(MPC sender); + public event MpdCurrentQueueChangedEvent MpdCurrentQueueChanged; + + public delegate void MpdPlaylistsChangedEvent(MPC sender); + public event MpdPlaylistsChangedEvent MpdPlaylistsChanged; + + public delegate void MpdAlbumArtChangedEvent(MPC sender); + public event MpdAlbumArtChangedEvent MpdAlbumArtChanged; + + public delegate void MpcProgressEvent(MPC sender, string msg); + public event MpcProgressEvent MpcProgress; + + #endregion + + public MPC() + { + + } + + #region == Idle Connection == + + public async Task MpdIdleConnectionStart(string host, int port, string password) + { + // TODO: not used for now. + + ConnectionResult r = await MpdIdleConnect(host, port); + + bool ret = false; + + if (r.IsSuccess) + { + CommandResult d = await MpdIdleSendPassword(password); + + if (d.IsSuccess) + { + ret = true; + + } + } + + return ret; + } + + public async Task MpdIdleConnect(string host, int port) + { + ConnectionResult result = new(); + + IsMpdIdleConnected = false; + + _idleConnection = new TcpClient(); + + _host = host; + _port = port; + + DebugIdleOutput?.Invoke(this, "TCP Idle Connection: Connecting." + "\n" + "\n"); + MpcProgress?.Invoke(this, "Connecting..."); + + ConnectionState = ConnectionStatus.Connecting; + + try + { + IsBusy?.Invoke(this, true); + + await _idleConnection.ConnectAsync(_host, _port); + + // TODO: + if (_idleConnection.Client == null) + { + Debug.WriteLine("_idleConnection.Client == null. " + host + " " + port.ToString()); + + result.ErrorMessage = "_idleConnection.Client == null"; + + DebugIdleOutput?.Invoke(this, "TCP Idle Connection: Error while connecting. Fail to connect... "+ "\n" + "\n"); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "TCP connection error..."); + + IsBusy?.Invoke(this, false); + return result; + } + + if (_idleConnection.Client.Connected) + { + DebugIdleOutput?.Invoke(this, "TCP Idle Connection: Connection established." + "\n" + "\n"); + MpcProgress?.Invoke(this, "Connection established...."); + + ConnectionState = ConnectionStatus.Connected; + + var tcpStream = _idleConnection.GetStream(); + tcpStream.ReadTimeout = System.Threading.Timeout.Infinite; + + _idleReader = new StreamReader(tcpStream); + _idleWriter = new StreamWriter(tcpStream) + { + AutoFlush = true + }; + + string response = await _idleReader.ReadLineAsync(); + + if (response.StartsWith("OK MPD ")) + { + MpdVerText = response.Replace("OK MPD ", string.Empty).Trim(); + + DebugIdleOutput?.Invoke(this, "<<<<" + response.Trim() + "\n" + "\n"); + MpcProgress?.Invoke(this, response.Trim()); + + IsMpdIdleConnected = true; + + result.IsSuccess = true; + + MpdIdleConnected?.Invoke(this); + + // Done for now. + } + else + { + DebugIdleOutput?.Invoke(this, "TCP Idle Connection: MPD did not respond with proper respose." + "\n" + "\n"); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "TCP connection error: MPD did not respond with proper respose."); + } + } + else + { + //? + + Debug.WriteLine("**** !client.Client.Connected"); + + DebugIdleOutput?.Invoke(this, "TCP Idle Connection: FAIL to established... Client not connected." + "\n" + "\n"); + + ConnectionState = ConnectionStatus.NeverConnected; + + ConnectionError?.Invoke(this, "TCP Idle Connection: FAIL to established... Client not connected."); + } + } + catch (SocketException e) + { + // TODO: Test. + + //e.SocketErrorCode + + DebugIdleOutput?.Invoke(this, "TCP Idle Connection: Error while connecting. Fail to connect: " + e.Message + "\n" + "\n"); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "TCP connection error: " + e.Message); + } + catch (Exception e) + { + // TODO: Test. + + DebugIdleOutput?.Invoke(this, "TCP Idle Connection: Error while connecting. Fail to connect: " + e.Message + "\n" + "\n"); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "TCP connection error: " + e.Message); + } + + + IsBusy?.Invoke(this, false); + return result; + } + + public async Task MpdIdleSendPassword(string password = "") + { + //MpcProgress?.Invoke(this, "Sending password..."); + + _password = password; + + CommandResult ret = new(); + + if (string.IsNullOrEmpty(password)) + { + ret.IsSuccess = true; + ret.ResultText = "OK"; + ret.ErrorMessage = ""; + + return ret; + } + + string cmd = "password " + password + "\n"; + + return await MpdIdleSendCommand(cmd); + + } + + private async Task MpdIdleSendCommand(string cmd) + { + CommandResult ret = new(); + + if (_idleConnection.Client == null) + { + Debug.WriteLine("@MpdIdleSendCommand: " + "TcpClient.Client == null"); + + ret.IsSuccess = false; + ret.ErrorMessage = "TcpClient.Client == null"; + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdIdleSendCommand", "TcpClient.Client == null", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return ret; + } + + if ((_idleWriter == null) || (_idleReader == null)) + { + Debug.WriteLine("@MpdIdleSendCommand: " + "_idleWriter or _idleReader is null"); + + ret.IsSuccess = false; + ret.ErrorMessage = "_idleWriter or _idleReader is null"; + + DebugIdleOutput?.Invoke(this, string.Format("################ Error :@{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdIdleSendCommand", "_idleWriter or _idleReader is null", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return ret; + } + + if (!_idleConnection.Client.Connected) + { + Debug.WriteLine("Exception@MpdIdleSendCommand: " + "NOT IsMpdIdleConnected"); + + ret.IsSuccess = false; + ret.ErrorMessage = "NOT IsMpdIdleConnected"; + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdIdleSendCommand", "!CommandConnection.Client.Connected", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return ret; + } + + string cmdDummy = cmd; + if (cmd.StartsWith("password ")) + cmdDummy = "password ****"; + + //DebugIdleOutput?.Invoke(this, ">>>>" + cmdDummy.Trim() + "\n" + "\n"); + Task nowait = Task.Run(() => DebugIdleOutput?.Invoke(this, ">>>>" + cmdDummy.Trim() + "\n" + "\n")); + + try + { + if (cmd.Trim().StartsWith("idle")) + { + await _idleWriter.WriteAsync(cmd.Trim() + "\n"); + + return ret; + } + else + { + //await _idleWriter.WriteAsync("noidle\n" + cmd.Trim() + "\n"); + await _idleWriter.WriteAsync(cmd.Trim() + "\n"); + } + } + catch (System.IO.IOException e) + { + Debug.WriteLine("IOException@MpdIdleSendCommand: " + cmd.Trim() + " WriteAsync :" + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + // Could be application shutdopwn. + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + + } + else + { + DebugIdleOutput?.Invoke(this, string.Format("################ Error@{0}, Reason:{1}, Data:{2}, {3} Exception: {4} {5}", "WriteAsync@MpdIdleSendCommand", "IOException", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (idle) has been terminated (IOException): " + e.Message); + } + + return ret; + } + catch (Exception e) + { + Debug.WriteLine("Exception@MpdIdleSendCommand: " + cmd.Trim() + " WriteAsync " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + + } + else + { + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "WriteAsync@MpdIdleSendCommand", "Exception", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (idle) has been terminated (Exception): " + e.Message); + } + + return ret; + } + + try + { + StringBuilder stringBuilder = new(); + + bool isAck = false; + string ackText = ""; + + while (true) + { + string line = await _idleReader.ReadLineAsync(); + + if (line != null) + { + if (line.StartsWith("ACK")) + { + Debug.WriteLine("ACK line @MpdIdleSendCommand: " + cmd.Trim() + " and " + line); + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + isAck = true; + ackText = line; + ret.ErrorMessage = line; + + break; + } + else if (line.StartsWith("OK")) + { + ret.IsSuccess = true; + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + break; + } + else if (line.StartsWith("changed: ")) + { + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + } + else + { + if (!string.IsNullOrEmpty(line)) + { + stringBuilder.Append(line + "\n"); // << has to be \n + } + else + { + Debug.WriteLine("line == IsNullOrEmpty"); + + break; + } + } + } + else + { + Debug.WriteLine("@MpdIdleSendCommand ReadLineAsync line != null"); + + DebugIdleOutput?.Invoke(this, string.Format("################ Error @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdIdleSendCommand", "ReadLineAsync received null data", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (idle) has been terminated. "); + + ret.ResultText = stringBuilder.ToString(); + ret.ErrorMessage = "ReadLineAsync@MpdIdleSendCommand received null data"; + + break; + } + } + + nowait = Task.Run(() => DebugIdleOutput?.Invoke(this, "<<<<" + stringBuilder.ToString().Trim().Replace("\n", "\n" + "<<<<") + "\n" + "\n")); + + if (isAck) + nowait = Task.Run(() => MpdAckError?.Invoke(this, ackText + " (@MISC)")); + + ret.ResultText = stringBuilder.ToString(); + + return ret; + } + catch (System.InvalidOperationException e) + { + // The stream is currently in use by a previous operation on the stream. + + Debug.WriteLine("InvalidOperationException@MpdIdleSendCommand: " + cmd.Trim() + " ReadLineAsync ---- " + e.Message); + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdIdleSendCommand", "InvalidOperationException", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + //ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + //ConnectionError?.Invoke(this, "The connection (idle) has been terminated. Most likely the connection has been overloaded."); + + + //await _idleWriter.WriteAsync("noidle\n"); + //await _idleWriter.WriteAsync("idle player\n"); + //await Task.Delay(1000); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + return ret; + } + catch (Exception e) + { + Debug.WriteLine("Exception@MpdIdleSendCommand: " + cmd.Trim() + " ReadLineAsync ---- " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdIdleSendCommand", "Exception", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + return ret; + } + + } + + public async Task MpdIdleQueryStatus() + { + MpcProgress?.Invoke(this, "[Background] Querying status..."); + + CommandResult result = await MpdIdleSendCommand("status"); + if (result.IsSuccess) + { + MpcProgress?.Invoke(this, "[Background] Parsing status..."); + result.IsSuccess = await ParseStatus(result.ResultText); + + MpcProgress?.Invoke(this, "[Background] Status updated."); + } + + return result; + } + + public async Task MpdIdleQueryCurrentSong() + { + MpcProgress?.Invoke(this, "[Background] Querying current song info..."); + + CommandResult result = await MpdIdleSendCommand("currentsong"); + if (result.IsSuccess) + { + MpcProgress?.Invoke(this, "[Background] Parsing current song info..."); + result.IsSuccess = await ParseCurrentSong(result.ResultText); + + MpcProgress?.Invoke(this, "[Background] Current song info updated."); + } + + return result; + } + + public async Task MpdIdleQueryCurrentQueue() + { + MpcProgress?.Invoke(this, "[Background] Querying queue..."); + + CommandResult result = await MpdIdleSendCommand("playlistinfo"); + if (result.IsSuccess) + { + MpcProgress?.Invoke(this, "[Background] Parsing queue..."); + result.IsSuccess = await ParsePlaylistInfo(result.ResultText); + + MpcProgress?.Invoke(this, "[Background] Queue updated."); + } + + return result; + } + + public async Task MpdIdleQueryPlaylists() + { + MpcProgress?.Invoke(this, "[Background] Querying playlists..."); + + CommandResult result = await MpdIdleSendCommand("listplaylists"); + if (result.IsSuccess) + { + MpcProgress?.Invoke(this, "[Background] Parsing playlists..."); + result.IsSuccess = await ParsePlaylists(result.ResultText); + + MpcProgress?.Invoke(this, "[Background] Playlists updated."); + } + + return result; + } + + public async Task MpdIdleQueryListAll() + { + MpcProgress?.Invoke(this, "[Background] Querying files and directories..."); + + CommandResult result = await MpdIdleSendCommand("listall"); + if (result.IsSuccess) + { + MpcProgress?.Invoke(this, "[Background] Parsing files and directories..."); + result.IsSuccess = await ParseListAll(result.ResultText); + + MpcProgress?.Invoke(this, "[Background] Files and directories updated."); + } + + return result; + } + + public void MpdIdleStart() + { + //MpcProgress?.Invoke(this, ""); + + MpdIdle(); + } + + private async void MpdIdle() + { + if (MpdStop) + return; + + if (_idleConnection.Client == null) + { + Debug.WriteLine("@MpdIdle: " + "TcpClient.Client == null"); + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdIdle", "TcpClient.Client == null", "", Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return; + } + + if ((_commandWriter == null) || (_commandReader == null)) + { + Debug.WriteLine("@MpdIdle: " + "_idleWriter or _idleReader is null"); + + DebugIdleOutput?.Invoke(this, string.Format("################ Error :@{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdIdle", "_idleWriter or _idleReader is null", "", Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return; + } + + if (!_idleConnection.Client.Connected) + { + Debug.WriteLine("@MpdIdle: " + "!_idleConnection.Client.Connected"); + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdIdle", "!_idleConnection.Client.Connected", "", Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return; + } + + string cmd = "idle player mixer options playlist stored_playlist\n"; + + DebugIdleOutput?.Invoke(this, ">>>>" + cmd.Trim() + "\n" + "\n"); + + try + { + await _idleWriter.WriteAsync(cmd); + } + catch (IOException e) + { + // connection terminated by ... something. + + Debug.WriteLine("[IOException@MpdIdle] ({0} ):\n{1}", "WriteAsync", e.Message); + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "WriteAsync@MpdIdle", "IOException", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (idle) has been terminated. IOException: " + e.Message); + + return; + } + catch (Exception e) + { + Debug.WriteLine("[Exception@MpdIdle] ({0} ):\n{1}", "WriteAsync", e.Message); + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "WriteAsync@MpdIdle", "Exception", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (idle) has been terminated. Exception: " + e.Message); + + return; + } + + try + { + StringBuilder stringBuilder = new(); + + bool isAck = false; + string ackText = ""; + + while (true) + { + if (MpdStop) + break; + + string line = await _idleReader.ReadLineAsync(); + + if (line != null) + { + if (line.StartsWith("ACK")) + { + Debug.WriteLine("ACK: " + line); + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + isAck = true; + ackText = line; + + break; + } + else if (line.StartsWith("OK")) + { + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + break; + } + else if (line.StartsWith("changed: ")) + { + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + } + else + { + if (!string.IsNullOrEmpty(line)) + { + stringBuilder.Append(line + "\n"); + } + } + } + else + { + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser) || (ConnectionState == ConnectionStatus.Connecting)) + { + // nothing wrong. + } + else + { + Debug.WriteLine("TCP Idle Connection: ReadLineAsync @MpdIdle received null data."); + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdIdle", "ReadLineAsync@MpdIdle received null data", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection has been terminated."); + + break; + } + + return; + } + } + + string result = stringBuilder.ToString(); + + DebugIdleOutput?.Invoke(this, "<<<<" + result.Trim().Replace("\n", "\n" + "<<<<") + "\n" + "\n"); + + if (isAck) + { + MpdAckError?.Invoke(this, ackText + " (@idle)"); + } + else + { + await ParseSubSystemsAndRaiseChangedEvent(result); + } + } + catch (System.IO.IOException e) + { + // Could be application shutdopwn. + + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser) || (ConnectionState == ConnectionStatus.Connecting)) + { + // no problem + } + else + { + Debug.WriteLine("[IOException@MpdIdle] ({0}):\n{1}", "ReadLineAsync: " + ConnectionState.ToString(), e.Message); + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdIdle", "IOException", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (idle) has been terminated. Exception: " + e.Message); + } + } + catch (System.InvalidOperationException e) + { + // The stream is currently in use by a previous operation on the stream. + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdIdle", "InvalidOperationException", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (idle) has been terminated. Most likely the connection is overloaded."); + + } + catch (Exception e) + { + // Could be application shutdopwn. + + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + // no problem...probably. + } + else + { + Debug.WriteLine("[Exception@MpdIdle] ({0} ):\n{1}", "ReadLineAsync", e.Message); + + DebugIdleOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdIdle", "Exception", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (idle) has been terminated. Exception: " + e.Message); + } + } + } + + private async Task ParseSubSystemsAndRaiseChangedEvent(string result) + { + /* + public enum MpdSubSystems + { + //player mixer options playlist stored_playlist + //database: the song database has been modified after update. + //update: a database update has started or finished. If the database was modified during the update, the database event is also emitted. + //stored_playlist: a stored playlist has been modified, renamed, created or deleted + //playlist: the queue (i.e.the current playlist) has been modified + //player: the player has been started, stopped or seeked + //mixer: the volume has been changed + //output: an audio output has been added, removed or modified(e.g.renamed, enabled or disabled) + //options: options like repeat, random, crossfade, replay gain + //partition: a partition was added, removed or changed + //sticker: the sticker database has been modified. + //subscription: a client has subscribed or unsubscribed to a channel + //message: a message was received on a channel this client is subscribed to; this event is only emitted when the queue is empty + //neighbor: a neighbor was found or lost + //mount: the mount list has changed + + player, + mixer, + options, + playlist, + stored_playlist + } + */ + + List SubSystems = result.Split('\n').ToList(); + + try + { + bool isPlayer = false; + bool isCurrentQueue = false; + bool isStoredPlaylist = false; + + foreach (string line in SubSystems) + { + if (line.ToLower() == "changed: playlist") + { + // playlist: the queue (i.e.the current playlist) has been modified + isCurrentQueue = true; + } + if (line.ToLower() == "changed: player") + { + // player: the player has been started, stopped or seeked + isPlayer = true; + } + if (line.ToLower() == "changed: options") + { + // options: options like repeat, random, crossfade, replay gain + isPlayer = true; + } + if (line.ToLower() == "changed: mixer") + { + // mixer: the volume has been changed + isPlayer = true; + } + if (line.ToLower() == "changed: stored_playlist") + { + // stored_playlist: a stored playlist has been modified, renamed, created or deleted + isStoredPlaylist = true; + } + } + + if (isPlayer) + { + CommandResult idleResult = await MpdIdleQueryStatus(); + if (idleResult.IsSuccess) + { + MpdPlayerStatusChanged?.Invoke(this); + } + } + + if (isCurrentQueue) + { + CommandResult idleResult = await MpdIdleQueryCurrentQueue(); + if (idleResult.IsSuccess) + { + MpdCurrentQueueChanged?.Invoke(this); + } + } + + if (isStoredPlaylist) + { + CommandResult idleResult = await MpdIdleQueryPlaylists(); + if (idleResult.IsSuccess) + { + MpdPlaylistsChanged?.Invoke(this); + } + } + + //MpcProgress?.Invoke(this, ""); + + // start over. + MpdIdle(); + } + catch + { + Debug.WriteLine("**Error@ParseSubSystemsAndRaseChangedEvent: " + result); + } + + return true; + } + + #endregion + + #region == Command Connection == + + public async Task MpdCommandConnectionStart(string host, int port, string password) + { + ConnectionResult r = await MpdCommandConnect(host, port); + + if (r.IsSuccess) + { + CommandResult d = await MpdCommandSendPassword(password); + + if (d.IsSuccess) + { + // ここでIdleにして、以降はnoidle + cmd + idleの組み合わせでやる。 + // ただし、実際にはidleのあとReadしていないからタイムアウトで切断されてしまう模様。 + + d = await MpdSendIdle(); + + return d.IsSuccess; + } + } + + return false; + } + + public async Task MpdCommandConnect(string host, int port) + { + ConnectionResult result = new(); + + IsMpdCommandConnected = false; + + _commandConnection = new TcpClient(); + + _host = host; + _port = port; + + + DebugCommandOutput?.Invoke(this, "TCP Command Connection: Connecting." + "\n" + "\n"); + + ConnectionState = ConnectionStatus.Connecting; + + try + { + await _commandConnection.ConnectAsync(_host, _port); + + // TODO: + if (_commandConnection.Client == null) + { + Debug.WriteLine("_commandConnection.Client == null. " + host + " " + port.ToString()); + + result.ErrorMessage = "_commandConnection.Client == null"; + + DebugCommandOutput?.Invoke(this, "TCP Command Connection: Error while connecting. Fail to connect... " + "\n" + "\n"); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "TCP connection error..."); + + return result; + } + + if (_commandConnection.Client.Connected) + { + DebugCommandOutput?.Invoke(this, "TCP Command Connection: Connection established." + "\n" + "\n"); + + ConnectionState = ConnectionStatus.Connected; + + var tcpStream = _commandConnection.GetStream(); + //tcpStream.ReadTimeout = System.Threading.Timeout.Infinite; + // + tcpStream.ReadTimeout = 2000; + + _commandReader = new StreamReader(tcpStream); + _commandWriter = new StreamWriter(tcpStream) + { + AutoFlush = true + }; + + string response = await _commandReader.ReadLineAsync(); + + if (response.StartsWith("OK MPD ")) + { + MpdVerText = response.Replace("OK MPD ", string.Empty).Trim(); + + DebugCommandOutput?.Invoke(this, "<<<<" + response.Trim() + "\n" + "\n"); + + IsMpdCommandConnected = true; + + result.IsSuccess = true; + + // Done for now. + } + else + { + DebugCommandOutput?.Invoke(this, "TCP Command Connection: MPD did not respond with proper respose." + "\n" + "\n"); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "TCP connection error: MPD did not respond with proper respose."); + } + } + else + { + //? + + Debug.WriteLine("**** !client.Client.Connected"); + + DebugCommandOutput?.Invoke(this, "TCP Command Connection: FAIL to established... Client not connected." + "\n" + "\n"); + + ConnectionState = ConnectionStatus.NeverConnected; + + ConnectionError?.Invoke(this, "TCP Command Connection: FAIL to established... Client not connected."); + } + } + catch (SocketException e) + { + // TODO: Test. + + //e.SocketErrorCode + + DebugCommandOutput?.Invoke(this, "TCP Command Connection: Error while connecting. Fail to connect: " + e.Message + "\n" + "\n"); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "TCP connection error: " + e.Message); + } + catch (Exception e) + { + DebugCommandOutput?.Invoke(this, "TCP Command Connection: Error while connecting. Fail to connect (Exception): " + e.Message + "\n" + "\n"); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "TCP connection error: " + e.Message); + } + + return result; + } + + public async Task MpdCommandSendPassword(string password = "") + { + _password = password; + + CommandResult ret = new(); + + if (string.IsNullOrEmpty(password)) + { + ret.IsSuccess = true; + ret.ResultText = "OK"; + ret.ErrorMessage = ""; + + return ret; + } + + string cmd = "password " + password + "\n"; + + return await MpdSendCommand(cmd); + + } + + // TODO: Rename MpdSendCommand to MpdCommandSendCommand + private async Task MpdSendCommand(string cmd, bool isAutoIdling = false) + { + CommandResult ret = new(); + + if (_commandConnection.Client == null) + { + Debug.WriteLine("@MpdSendCommand: " + "TcpClient.Client == null"); + + ret.IsSuccess = false; + ret.ErrorMessage = "TcpClient.Client == null"; + + DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdSendCommand", "TcpClient.Client == null", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return ret; + } + + if ((_commandWriter == null) || (_commandReader == null)) + { + Debug.WriteLine("@MpdSendCommand: " + "_commandWriter or _commandReader is null"); + + ret.IsSuccess = false; + ret.ErrorMessage = "_commandWriter or _commandReader is null"; + + DebugCommandOutput?.Invoke(this, string.Format("################ Error :@{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdSendCommand", "_commandWriter or _commandReader is null", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return ret; + } + + if (!_commandConnection.Client.Connected) + { + Debug.WriteLine("@MpdSendCommand: " + "NOT IsMpdCommandConnected"); + + ret.IsSuccess = false; + ret.ErrorMessage = "NOT IsMpdCommandConnected"; + + DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "MpdSendCommand", "!CommandConnection.Client.Connected", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + return ret; + } + + // WriteAsync + try + { + IsBusy?.Invoke(this, true); + + if (cmd.Trim().StartsWith("idle")) + { + //DebugCommandOutput?.Invoke(this, ">>>>" + cmd.Trim() + "\n" + "\n"); + Task nowait = Task.Run(() => DebugCommandOutput?.Invoke(this, ">>>>" + cmd.Trim() + "\n" + "\n")); + + await _commandWriter.WriteAsync(cmd.Trim() + "\n"); + + if (!isAutoIdling) + { + ret.IsSuccess = true; + + IsBusy?.Invoke(this, false); + return ret; + } + } + else + { + string cmdDummy = cmd; + if (cmd.StartsWith("password ")) + cmdDummy = "password ****"; + + cmdDummy = cmdDummy.Trim().Replace("\n", "\n" + ">>>>"); + + Task nowait; + if (isAutoIdling) + //DebugCommandOutput?.Invoke(this, ">>>>" + "noidle\n>>>>" + cmdDummy.Trim() + "\n>>>>idle player" + "\n" + "\n"); + nowait = Task.Run(() => DebugCommandOutput?.Invoke(this, ">>>>" + "noidle\n>>>>" + cmdDummy.Trim() + "\n>>>>idle player" + "\n" + "\n")); + else + //DebugCommandOutput?.Invoke(this, ">>>>" + cmdDummy.Trim() + "\n" + "\n"); + nowait = Task.Run(() => DebugCommandOutput?.Invoke(this, ">>>>" + cmdDummy.Trim() + "\n" + "\n")); + + if (isAutoIdling) + await _commandWriter.WriteAsync("noidle\n" + cmd.Trim() + "\n" + "idle player\n"); + else + await _commandWriter.WriteAsync(cmd.Trim() + "\n"); + } + } + catch (System.IO.IOException e) + { + // IOException : Unable to write data to the transport connection: 確立された接続がホスト コンピューターのソウトウェアによって中止されました。 + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + // Could be application shutdopwn. + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("################ Error@{0}, Reason:{1}, Data:{2}, {3} Exception: {4} {5}", "WriteAsync@MpdSendCommand", "IOException", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + // タイムアウトしていたらここで「も」エラーになる模様。 + + IsMpdCommandConnected = false; + + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting... " + Environment.NewLine + Environment.NewLine)); + + try + { + //_commandConnection.Client.Shutdown(SocketShutdown.Both); + _commandConnection.Close(); + } + catch { } + + ConnectionResult newCon = await MpdCommandConnect(_host,_port); + + if (newCon.IsSuccess) + { + CommandResult d = await MpdCommandSendPassword(_password); + + if (d.IsSuccess) + { + d = await MpdSendCommand("idle player"); + + if (d.IsSuccess) + { + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Success. @IOExceptionOfWriteAsync" + Environment.NewLine + Environment.NewLine)); + + ret = await MpdSendCommand(cmd, isAutoIdling); + } + } + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Failed. " + Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (command) has been terminated (IOException): " + e.Message); + } + } + + IsBusy?.Invoke(this, false); + return ret; + } + catch (Exception e) + { + Debug.WriteLine("Exception@MpdSendCommand: " + cmd.Trim() + " WriteAsync " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "WriteAsync@MpdSendCommand", "Exception", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + //ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + //ConnectionError?.Invoke(this, "The connection (command) has been terminated (Exception): " + e.Message); + } + + IsBusy?.Invoke(this, false); + return ret; + } + + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + return ret; + } + + // ReadLineAsync + try + { + IsBusy?.Invoke(this, true); + + StringBuilder stringBuilder = new(); + + bool isDoubleOk = false; + bool isAck = false; + string ackText = ""; + bool isNullReturn = false; + + while (true) + { + string line = await _commandReader.ReadLineAsync(); + + if (line != null) + { + if (line.StartsWith("ACK")) + { + Debug.WriteLine("ACK line @MpdSendCommand: " + cmd.Trim() + " and " + line); + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + ret.ErrorMessage = line; + ackText = line; + isAck = true; + + break; + } + else if (line.StartsWith("OK")) + { + if (isAutoIdling) + { + if (isDoubleOk) + { + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + ret.IsSuccess = true; + + break; + } + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + isDoubleOk = true; + + } + else + { + ret.IsSuccess = true; + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + + break; + } + } + else if (line.StartsWith("changed: ")) + { + // noidleでついてくるかもしれないchanged. idleConnectionで見ているからここでは無視したいが・・・。 + + if (!string.IsNullOrEmpty(line)) + stringBuilder.Append(line + "\n"); + } + else + { + if (!string.IsNullOrEmpty(line)) + { + stringBuilder.Append(line + "\n"); // << has to be \n + } + } + } + else + { + isNullReturn = true; + + break; + } + } + + if (isNullReturn) + { + Debug.WriteLine("@MpdSendCommand ReadLineAsync isNullReturn"); + + DebugCommandOutput?.Invoke(this, string.Format("################ Error @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdSendCommand", "ReadLineAsync received null data", cmd.Trim(), Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + ret.ResultText = stringBuilder.ToString(); + ret.ErrorMessage = "ReadLineAsync@MpdSendCommand received null data"; + + // タイムアウトしていたらここで「も」エラーになる模様。 + + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + IsBusy?.Invoke(this, false); + return ret; + } + + IsMpdCommandConnected = false; + + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting... " + Environment.NewLine + Environment.NewLine)); + + try + { + //_commandConnection.Client.Shutdown(SocketShutdown.Both); + _commandConnection.Close(); + } + catch { } + + ConnectionResult newCon = await MpdCommandConnect(_host, _port); + + if (newCon.IsSuccess) + { + CommandResult d = await MpdCommandSendPassword(_password); + + if (d.IsSuccess) + { + d = await MpdSendCommand("idle player"); + + if (d.IsSuccess) + { + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Success. @isNullReturn" + Environment.NewLine + Environment.NewLine)); + + ret = await MpdSendCommand(cmd, isAutoIdling); + } + } + } + else + { + // + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Failed. " + Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (command) has been terminated (null return)."); + } + + IsBusy?.Invoke(this, false); + return ret; + } + else + { + //DebugCommandOutput?.Invoke(this, "<<<<" + stringBuilder.ToString().Trim().Replace("\n", "\n" + "<<<<") + "\n" + "\n"); + Task nowait = Task.Run(() => DebugCommandOutput?.Invoke(this, "<<<<" + stringBuilder.ToString().Trim().Replace("\n", "\n" + "<<<<") + "\n" + "\n")); + + if (isAck) + nowait = Task.Run(() => MpdAckError?.Invoke(this, ackText + " (@MSC)")); + + ret.ResultText = stringBuilder.ToString(); + + IsBusy?.Invoke(this, false); + return ret; + } + + } + catch (System.InvalidOperationException e) + { + // The stream is currently in use by a previous operation on the stream. + + Debug.WriteLine("InvalidOperationException@MpdSendCommand: " + cmd.Trim() + " ReadLineAsync ---- " + e.Message); + + DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdSendCommand", "InvalidOperationException (Most likely the connection is overloaded)", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + //ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + //ConnectionError?.Invoke(this, "The connection (command) has been terminated. Most likely the connection has been overloaded."); + + //TODO: testing. null return? + //_commandReader.DiscardBufferedData(); + + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + IsBusy?.Invoke(this, false); + return ret; + } + catch (System.IO.IOException e) + { + // IOException : Unable to write data to the transport connection: 確立された接続がホスト コンピューターのソウトウェアによって中止されました。 + Debug.WriteLine("IOException@MpdSendCommand: " + cmd.Trim() + " ReadLineAsync ---- " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + // Could be application shutdopwn. + if ((ConnectionState == ConnectionStatus.Disconnecting) || (ConnectionState == ConnectionStatus.DisconnectedByUser)) + { + + } + else + { + DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdSendCommand", "IOException", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + // タイムアウトしていたらここで「も」エラーになる模様。 + + IsMpdCommandConnected = false; + + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting... " + Environment.NewLine + Environment.NewLine)); + + try + { + //_commandConnection.Client.Shutdown(SocketShutdown.Both); + _commandConnection.Close(); + } + catch { } + + ConnectionResult newCon = await MpdCommandConnect(_host, _port); + + if (newCon.IsSuccess) + { + CommandResult d = await MpdCommandSendPassword(_password); + + if (d.IsSuccess) + { + d = await MpdSendCommand("idle player"); + + if (d.IsSuccess) + { + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Success. @IOExceptionOfReadLineAsync" + Environment.NewLine + Environment.NewLine)); + + ret = await MpdSendCommand(cmd, isAutoIdling); + } + } + } + else + { + // Unable to read data from the transport connection: 既に接続済みのソケットに対して接続を要求しました。. + + DebugCommandOutput?.Invoke(this, string.Format("Reconnecting Failed. " + Environment.NewLine + Environment.NewLine)); + + ConnectionState = ConnectionStatus.SeeConnectionErrorEvent; + + ConnectionError?.Invoke(this, "The connection (command) has been terminated (IOException): " + e.Message); + } + } + + IsBusy?.Invoke(this, false); + return ret; + } + catch (Exception e) + { + Debug.WriteLine("Exception@MpdSendCommand: " + cmd.Trim() + " ReadLineAsync ---- " + e.Message); + + ret.IsSuccess = false; + ret.ErrorMessage = e.Message; + + DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ReadLineAsync@MpdSendCommand", "Exception", cmd.Trim(), Environment.NewLine, e.Message, Environment.NewLine + Environment.NewLine)); + + IsBusy?.Invoke(this, false); + return ret; + } + + } + + #endregion + + #region == Command Connection's MPD Commands with results other than OK == + + public async Task MpdSendIdle() + { + return await MpdSendCommand("idle player"); + } + + public async Task MpdSendNoIdle() + { + return await MpdSendCommand("noidle"); + } + + public async Task MpdQueryStatus(bool autoIdling = true) + { + CommandResult result = await MpdSendCommand("status", autoIdling); + if (result.IsSuccess) + { + result.IsSuccess = await ParseStatus(result.ResultText); + } + + return result; + } + + public async Task MpdQueryCurrentSong(bool autoIdling = true) + { + CommandResult result = await MpdSendCommand("currentsong", autoIdling); + if (result.IsSuccess) + { + result.IsSuccess = await ParseCurrentSong(result.ResultText); + } + + return result; + } + + public async Task MpdQueryCurrentQueue(bool autoIdling = true) + { + CommandResult result = await MpdSendCommand("playlistinfo", autoIdling); + if (result.IsSuccess) + { + result.IsSuccess = await ParsePlaylistInfo(result.ResultText); + } + + return result; + } + + public async Task MpdQueryPlaylists(bool autoIdling = true) + { + CommandResult result = await MpdSendCommand("listplaylists", autoIdling); + if (result.IsSuccess) + { + result.IsSuccess = await ParsePlaylists(result.ResultText); + } + + return result; + } + + public async Task MpdQueryListAll(bool autoIdling = true) + { + MpcProgress?.Invoke(this, "[Background] Querying files and directories..."); + + CommandResult result = await MpdSendCommand("listall", autoIdling); + if (result.IsSuccess) + { + MpcProgress?.Invoke(this, "[Background] Parsing files and directories..."); + result.IsSuccess = await ParseListAll(result.ResultText); + MpcProgress?.Invoke(this, "[Background] Files and directories updated."); + } + + return result; + } + + public async Task MpdSearch(string queryTag, string queryShiki, string queryValue, bool autoIdling = true) + { + MpcProgress?.Invoke(this, "[Background] Searching..."); + + CommandSearchResult result = new(); + + if (Application.Current == null) { return result; } + Application.Current.Dispatcher.Invoke(() => + { + SearchResult.Clear(); + }); + + if (string.IsNullOrEmpty(queryTag) || string.IsNullOrEmpty(queryValue) || string.IsNullOrEmpty(queryShiki)) + { + result.IsSuccess = false; + return result; + } + + var expression = queryTag + " " + queryShiki + " \'" + Regex.Escape(queryValue) + "\'"; + + string cmd = "search \"(" + expression + ")\"\n"; + + CommandResult cm = await MpdSendCommand(cmd, autoIdling); + if (cm.IsSuccess) + { + MpcProgress?.Invoke(this, "[Background] Parsing search result..."); + if (await ParseSearchResult(cm.ResultText)) + { + result.IsSuccess = true; + + result.SearchResult = this.SearchResult; + + MpcProgress?.Invoke(this, "[Background] Search result updated."); + } + } + + return result; + } + + public async Task MpdQueryPlaylistSongs(string playlistName, bool autoIdling = true) + { + CommandPlaylistResult result = new(); + + if (string.IsNullOrEmpty(playlistName)) + { + result.IsSuccess = false; + return result; + } + + MpcProgress?.Invoke(this, "[Background] Querying playlist info..."); + + playlistName = Regex.Escape(playlistName); + + CommandResult cm = await MpdSendCommand("listplaylistinfo \"" + playlistName + "\"", autoIdling); + if (cm.IsSuccess) + { + MpcProgress?.Invoke(this, "[Background] Parsing playlist info..."); + + result.IsSuccess = cm.IsSuccess; + result.PlaylistSongs = ParsePlaylistSongsResult(cm.ResultText); + + MpcProgress?.Invoke(this, "[Background] Playlist info is updated."); + } + + return result; + } + + public async Task MpdQueryAlbumArt(string uri, bool isUsingReadpicture) + { + CommandResult f = new(); + if (string.IsNullOrEmpty(uri)) + { + f.IsSuccess = false; + return f; + } + + BinaryDownloader hoge = new(); + + bool asdf = await hoge.MpdBinaryConnectionStart(MpdHost,MpdPort,MpdPassword); + + CommandResult b = new(); + + if (asdf) + { + b = await hoge.MpdQueryAlbumArt(uri, isUsingReadpicture); + + if (b.IsSuccess) + { + if (Application.Current == null) { return f; } + Application.Current.Dispatcher.Invoke(() => + { + _albumCover = hoge.AlbumCover; + }); + + await Task.Delay(1300); + + //Debug.WriteLine("AlbumArt Donwloaded... "); + + MpdAlbumArtChanged?.Invoke(this); + } + else + { + //Debug.WriteLine("why... " + b.ErrorMessage); + } + } + else + { + Debug.WriteLine("damn... "); + } + + hoge.MpdBinaryConnectionDisconnect(); + + return b; + } + + #endregion + + #region == Command Connection's MPD Commands with boolean result == + + public async Task MpdSendUpdate() + { + CommandResult result = await MpdSendCommand("update", true); + + return result; + } + + public async Task MpdPlaybackPlay(int volume, string songId = "") + { + string cmd = "play"; + + if (songId != "") + { + cmd = "playid " + songId; + } + + if (MpdStatus.MpdVolumeIsSet) + { + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + else + { + string cmdList = "command_list_begin" + "\n"; + cmdList = cmdList + cmd + "\n"; + cmdList = cmdList + "setvol " + volume.ToString() + "\n"; + cmdList = cmdList + "command_list_end" + "\n"; + + CommandResult result = await MpdSendCommand(cmdList, true); + + return result; + } + } + + public async Task MpdPlaybackPause() + { + CommandResult result = await MpdSendCommand("pause 1", true); + + return result; + } + + public async Task MpdPlaybackResume(int volume) + { + if (MpdStatus.MpdVolumeIsSet) + { + CommandResult result = await MpdSendCommand("pause 0", true); + + return result; + } + else + { + string cmd = "command_list_begin" + "\n"; + cmd += "pause 0\n"; + cmd = cmd + "setvol " + volume.ToString() + "\n"; + cmd = cmd + "command_list_end" + "\n"; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + } + + public async Task MpdPlaybackStop() + { + CommandResult result = await MpdSendCommand("stop", true); + + return result; + } + + public async Task MpdPlaybackNext(int volume) + { + if (MpdStatus.MpdVolumeIsSet) + { + CommandResult result = await MpdSendCommand("next", true); + + return result; + } + else + { + string cmd = "command_list_begin" + "\n"; + cmd += "next\n"; + cmd = cmd + "setvol " + volume.ToString() + "\n"; + cmd = cmd + "command_list_end" + "\n"; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + } + + public async Task MpdPlaybackPrev(int volume) + { + if (MpdStatus.MpdVolumeIsSet) + { + CommandResult result = await MpdSendCommand("previous", true); + + return result; + } + else + { + string cmd = "command_list_begin" + "\n"; + cmd += "previous\n"; + cmd = cmd + "setvol " + volume.ToString() + "\n"; + cmd = cmd + "command_list_end" + "\n"; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + } + + public async Task MpdSetVolume(int v) + { + if (v == _status.MpdVolume) + { + CommandResult f = new() + { + IsSuccess = true + }; + return f; + } + + CommandResult result = await MpdSendCommand("setvol " + v.ToString(), true); + + return result; + } + + public async Task MpdPlaybackSeek(string songId, int seekTime) + { + if ((songId == "") || (seekTime == 0)) + { + CommandResult f = new() + { + IsSuccess = true + }; + return f; + } + + CommandResult result = await MpdSendCommand("seekid " + songId + " " + seekTime.ToString(), true); + + return result; + } + + public async Task MpdSetRepeat(bool on) + { + if (_status.MpdRepeat == on) + { + CommandResult f = new() + { + IsSuccess = true + }; + return f; + } + + string cmd; + if (on) + { + cmd = "repeat 1"; + } + else + { + cmd = "repeat 0"; + } + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + public async Task MpdSetRandom(bool on) + { + if (_status.MpdRandom == on) + { + CommandResult f = new() + { + IsSuccess = true + }; + return f; + } + + string cmd; + if (on) + { + cmd = "random 1"; + } + else + { + cmd = "random 0"; + } + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + public async Task MpdSetConsume(bool on) + { + if (_status.MpdConsume == on) + { + CommandResult f = new() + { + IsSuccess = true + }; + return f; + } + + string cmd; + if (on) + { + cmd = "consume 1"; + } + else + { + cmd = "consume 0"; + } + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + public async Task MpdSetSingle(bool on) + { + if (_status.MpdSingle == on) + { + CommandResult f = new() + { + IsSuccess = true + }; + return f; + } + + string cmd; + if (on) + { + cmd = "single 1"; + } + else + { + cmd = "single 0"; + } + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + public async Task MpdClear() + { + CommandResult result = await MpdSendCommand("clear", true); + + return result; + } + + public async Task MpdSave(string playlistName) + { + if (string.IsNullOrEmpty(playlistName)) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + playlistName = Regex.Escape(playlistName); + + CommandResult result = await MpdSendCommand("save \"" + playlistName + "\"", true); + + return result; + } + + public async Task MpdAdd(string uri) + { + if (string.IsNullOrEmpty(uri)) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + uri = Regex.Escape(uri); + + CommandResult result = await MpdSendCommand("add \"" + uri + "\"", true); + + return result; + } + + public async Task MpdAdd(List uris) + { + if (uris.Count < 1) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + string cmd = "command_list_begin" + "\n"; + foreach (var uri in uris) + { + var urie = Regex.Escape(uri); + cmd = cmd + "add \"" + urie + "\"\n"; + } + cmd = cmd + "command_list_end" + "\n"; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + public async Task MpdDeleteId(List ids) + { + if (ids.Count < 1) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + string cmd = "command_list_begin" + "\n"; + foreach (var id in ids) + { + cmd = cmd + "deleteid " + id + "\n"; + } + cmd = cmd + "command_list_end" + "\n"; + + return await MpdSendCommand(cmd, true); + } + + public async Task MpdDeleteId(string id) + { + if (string.IsNullOrEmpty(id)) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + string cmd = "deleteid " + id + "\n"; + + return await MpdSendCommand(cmd, true); + } + + public async Task MpdMoveId(Dictionary IdToNewPosPair) + { + if (IdToNewPosPair == null) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + if (IdToNewPosPair.Count < 1) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + string cmd = "command_list_begin" + "\n"; + foreach (KeyValuePair pair in IdToNewPosPair) + { + cmd = cmd + "moveid " + pair.Key + " " + pair.Value + "\n"; + } + cmd = cmd + "command_list_end" + "\n"; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + public async Task MpdChangePlaylist(string playlistName) + { + if (string.IsNullOrEmpty(playlistName)) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + playlistName = Regex.Escape(playlistName); + + string cmd = "command_list_begin" + "\n"; + cmd = cmd + "clear" + "\n"; + cmd = cmd + "load \"" + playlistName + "\"\n"; + cmd = cmd + "command_list_end" + "\n"; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + public async Task MpdLoadPlaylist(string playlistName) + { + if (string.IsNullOrEmpty(playlistName)) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + playlistName = Regex.Escape(playlistName); + + string cmd = "load \"" + playlistName + "\""; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + public async Task MpdRenamePlaylist(string playlistName, string newPlaylistName) + { + if (string.IsNullOrEmpty(playlistName) || string.IsNullOrEmpty(newPlaylistName)) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + playlistName = Regex.Escape(playlistName); + newPlaylistName = Regex.Escape(newPlaylistName); + + string cmd = "rename \"" + playlistName + "\" \"" + newPlaylistName + "\""; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + public async Task MpdRemovePlaylist(string playlistName) + { + if (string.IsNullOrEmpty(playlistName)) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + playlistName = Regex.Escape(playlistName); + + string cmd = "rm \"" + playlistName + "\""; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + public async Task MpdPlaylistAdd(string playlistName, List uris) + { + if (string.IsNullOrEmpty(playlistName)) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + if (uris == null) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + if (uris.Count < 1) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + playlistName = Regex.Escape(playlistName); + + string cmd = "command_list_begin" + "\n"; + foreach (var uri in uris) + { + var urie = Regex.Escape(uri); + cmd = cmd + "playlistadd " + "\"" + playlistName + "\"" + " " + "\"" + urie + "\"\n"; + } + cmd = cmd + "command_list_end" + "\n"; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + public async Task MpdPlaylistDelete(string playlistName, int pos) + { + if (string.IsNullOrEmpty(playlistName)) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + playlistName = Regex.Escape(playlistName); + + //playlistdelete {NAME} {SONGPOS} + string cmd = "playlistdelete \"" + playlistName + "\"" + " " + pos.ToString(); + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + /* NOT GOOD. Multiple deletion with SONGPO causes pos to shift. + public async Task MpdPlaylistDelete(string playlistName, List posList) + { + if (string.IsNullOrEmpty(playlistName)) + { + CommandResult f = new CommandResult(); + f.IsSuccess = false; + return f; + } + if (posList == null) + { + CommandResult f = new CommandResult(); + f.IsSuccess = false; + return f; + } + if (posList.Count < 1) + { + CommandResult f = new CommandResult(); + f.IsSuccess = false; + return f; + } + + playlistName = Regex.Escape(playlistName); + + string cmd = "command_list_begin" + "\n"; + foreach (var pos in posList) + { + cmd = cmd + "playlistdelete " + "\"" + playlistName + "\"" + " " + pos.ToString() + "\n"; + } + cmd = cmd + "command_list_end" + "\n"; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + */ + + public async Task MpdPlaylistClear(string playlistName) + { + if (string.IsNullOrEmpty(playlistName)) + { + CommandResult f = new() + { + IsSuccess = false + }; + return f; + } + + playlistName = Regex.Escape(playlistName); + + //playlistclear {NAME} + string cmd = "playlistclear \"" + playlistName + "\""; + + CommandResult result = await MpdSendCommand(cmd, true); + + return result; + } + + #endregion + + #region == Response parser methods == + + private Task ParseStatus(string result) + { + if (MpdStop) { return Task.FromResult(false); } + if (string.IsNullOrEmpty(result)) return Task.FromResult(false); + + if (result.Trim() == "OK") + { + DebugCommandOutput?.Invoke(this, "################(Error) " + "An empty result (OK) returened for a status command." + Environment.NewLine + Environment.NewLine); + DebugCommandOutput?.Invoke(this, string.Format("################ Error: @{0}, Reason: {1}, Data: {2}, {3} Exception: {4} {5}", "ParseStatus", "An empty result (OK) returened for a status command.", "", Environment.NewLine, "", Environment.NewLine + Environment.NewLine)); + + Debug.WriteLine("@ParseStatus: An empty result (OK) returened for a status command."); + + return Task.FromResult(false); + } + + List resultLines = result.Split('\n').ToList(); + + if (resultLines.Count == 0) return Task.FromResult(false); + + var comparer = StringComparer.OrdinalIgnoreCase; + Dictionary MpdStatusValues = new(comparer); + + try + { + IsBusy?.Invoke(this, true); + + Application.Current.Dispatcher.Invoke(() => + { + _status.Reset(); + + foreach (string line in resultLines) + { + string[] StatusValuePair = line.Split(':'); + if (StatusValuePair.Length > 1) + { + if (MpdStatusValues.ContainsKey(StatusValuePair[0].Trim())) + { + // new + + MpdStatusValues[StatusValuePair[0].Trim()] = line.Replace(StatusValuePair[0].Trim() + ": ", ""); + } + else + { + MpdStatusValues.Add(StatusValuePair[0].Trim(), line.Replace(StatusValuePair[0].Trim() + ": ", "")); + } + } + } + + // Play state + if (MpdStatusValues.ContainsKey("state")) + { + switch (MpdStatusValues["state"]) + { + case "play": + { + _status.MpdState = Status.MpdPlayState.Play; + break; + } + case "pause": + { + _status.MpdState = Status.MpdPlayState.Pause; + break; + } + case "stop": + { + _status.MpdState = Status.MpdPlayState.Stop; + break; + } + } + } + + // Volume + if (MpdStatusValues.ContainsKey("volume")) + { + try + { + _status.MpdVolume = Int32.Parse(MpdStatusValues["volume"]); + + _status.MpdVolumeIsSet = true; + } + catch (FormatException e) + { + System.Diagnostics.Debug.WriteLine(e.Message); + } + } + + if (MpdStatusValues.ContainsKey("bitrate")) + { + try + { + _status.MpdBitrate = MpdStatusValues["bitrate"]; + } + catch (FormatException e) + { + System.Diagnostics.Debug.WriteLine(e.Message); + } + } + + // songID + _status.MpdSongID = ""; + if (MpdStatusValues.ContainsKey("songid")) + { + _status.MpdSongID = MpdStatusValues["songid"]; + } + + // Repeat opt bool. + if (MpdStatusValues.ContainsKey("repeat")) + { + try + { + if (MpdStatusValues["repeat"] == "1") + { + _status.MpdRepeat = true; + } + else + { + _status.MpdRepeat = false; + } + + } + catch (FormatException e) + { + System.Diagnostics.Debug.WriteLine(e.Message); + } + } + + // Random opt bool. + if (MpdStatusValues.ContainsKey("random")) + { + try + { + if (Int32.Parse(MpdStatusValues["random"]) > 0) + { + _status.MpdRandom = true; + } + else + { + _status.MpdRandom = false; + } + + } + catch (FormatException e) + { + System.Diagnostics.Debug.WriteLine(e.Message); + } + } + + // Consume opt bool. + if (MpdStatusValues.ContainsKey("consume")) + { + try + { + if (Int32.Parse(MpdStatusValues["consume"]) > 0) + { + _status.MpdConsume = true; + } + else + { + _status.MpdConsume = false; + } + + } + catch (FormatException e) + { + System.Diagnostics.Debug.WriteLine(e.Message); + } + } + + // Single opt bool. + if (MpdStatusValues.ContainsKey("single")) + { + try + { + if (Int32.Parse(MpdStatusValues["single"]) > 0) + { + _status.MpdSingle = true; + } + else + { + _status.MpdSingle = false; + } + + } + catch (FormatException e) + { + System.Diagnostics.Debug.WriteLine(e.Message); + } + } + + // Song time. deprecated. + if (MpdStatusValues.ContainsKey("time")) + { + try + { + if (MpdStatusValues["time"].Split(':').Length > 1) + { + _status.MpdSongTime = Double.Parse(MpdStatusValues["time"].Split(':')[1].Trim()); + _status.MpdSongElapsed = Double.Parse(MpdStatusValues["time"].Split(':')[0].Trim()); + } + } + catch (FormatException e) + { + System.Diagnostics.Debug.WriteLine(e.Message); + } + } + + // Song time elapsed. + if (MpdStatusValues.ContainsKey("elapsed")) + { + try + { + _status.MpdSongElapsed = Double.Parse(MpdStatusValues["elapsed"]); + } + catch { } + } + + // Song duration. + if (MpdStatusValues.ContainsKey("duration")) + { + try + { + _status.MpdSongTime = Double.Parse(MpdStatusValues["duration"]); + } + catch { } + } + + // Error + if (MpdStatusValues.ContainsKey("error")) + { + _status.MpdError = MpdStatusValues["error"]; + } + else + { + _status.MpdError = ""; + } + + // TODO: more? + }); + + } + catch (Exception ex) + { + Debug.WriteLine("Exception@ParseStatus:" + ex.Message); + + if (Application.Current != null) + { + Application.Current.Dispatcher.Invoke(() => + { + /*App app = App.Current as App; + app.AppendErrorLog("Exception@MPC@ParseStatus", ex.Message);*/ + }); + } + + IsBusy?.Invoke(this, false); + return Task.FromResult(false); + } + finally + { + IsBusy?.Invoke(this, false); + } + + return Task.FromResult(true); + } + + private Task ParseCurrentSong(string result) + { + if (MpdStop) return Task.FromResult(false); + + bool isEmptyResult = false; + + if (string.IsNullOrEmpty(result)) + isEmptyResult = true; + + if (result.Trim() == "OK") + isEmptyResult = true; + + List resultLines = result.Split('\n').ToList(); + if (resultLines == null) + isEmptyResult = true; + if (resultLines.Count == 0) + isEmptyResult = true; + + if (isEmptyResult) + { + return Task.FromResult(true); + } + + IsBusy?.Invoke(this, true); + + try + { + var comparer = StringComparer.OrdinalIgnoreCase; + Dictionary SongValues = new(comparer); + + foreach (string value in resultLines) + { + string[] StatusValuePair = value.Trim().Split(':'); + if (StatusValuePair.Length > 1) + { + if (SongValues.ContainsKey(StatusValuePair[0].Trim())) + { + // Shouldn't be happening here except "Genre" + if (StatusValuePair[0].Trim() == "Genre") + { + SongValues["Genre"] = SongValues["Genre"] + "/" + value.Replace(StatusValuePair[0].Trim() + ": ", ""); + } + } + else + { + SongValues.Add(StatusValuePair[0].Trim(), value.Replace(StatusValuePair[0].Trim() + ": ", "")); + } + } + } + + if ((SongValues.Count > 0) && SongValues.ContainsKey("Id")) + { + SongInfoEx sng = FillSongInfoEx(SongValues, -1); + + if (sng != null) + { + if (_currentSong.Id != sng.Id) + _currentSong = sng; + } + + SongValues.Clear(); + } + } + catch (Exception ex) + { + Debug.WriteLine("Error@ParseCurrentSong: " + ex.Message); + + if (Application.Current != null) + { + Application.Current.Dispatcher.Invoke(() => + { + /*App app = App.Current as App; + app.AppendErrorLog("Exception@MPC@ParseCurrentSong", ex.Message);*/ + }); + } + + return Task.FromResult(false); + } + finally + { + IsBusy?.Invoke(this, false); + } + + return Task.FromResult(true); + } + + private Task ParsePlaylistInfo(string result) + { + if (MpdStop) return Task.FromResult(false); + + bool isEmptyResult = false; + + if (string.IsNullOrEmpty(result)) + isEmptyResult = true; + + if (result.Trim() == "OK") + isEmptyResult = true; + + List resultLines = result.Split('\n').ToList(); + if (resultLines == null) + isEmptyResult = true; + if (resultLines.Count == 0) + isEmptyResult = true; + + if (isEmptyResult) + { + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + CurrentQueue.Clear(); + }); + + return Task.FromResult(true); + } + + // aka Queue + + /* + file: Creedence Clearwater Revival/Chronicle, Vol. 1/11 Who'll Stop the Rain.mp3 + Last-Modified: 2011-08-03T16:08:06Z + Artist: Creedence Clearwater Revival + AlbumArtist: Creedence Clearwater Revival + Title: Who'll Stop the Rain + Album: Chronicle, Vol. 1 + Track: 11 + Date: 1976 + Genre: Rock + Composer: John Fogerty + Time: 149 + duration: 149.149 + Pos: 5 + Id: 22637 + */ + + /* + try + { + + resultLines.Clear(); + resultLines.Add("file: Slamhaus/Slamwave/06 The Trial.flac"); + resultLines.Add("Last-Modified: 2021-05-27T19:20:50Z"); + resultLines.Add("Title: The Trial"); + resultLines.Add("Artist: Slamhaus"); + resultLines.Add("Date: 2021"); + resultLines.Add("Comment: Visit https://slamhaus.bandcamp.com"); + resultLines.Add("Album: Slamwave"); + resultLines.Add("Track: 6"); + resultLines.Add("AlbumArtist: Slamhaus"); + resultLines.Add("Time: 340"); + resultLines.Add("duration: 339.504"); + resultLines.Add("Pos: 5"); + resultLines.Add("Id: 1438"); + resultLines.Add("file: Harris Heller/Synthwave/Sunset/01 - Zig the Zag.flac"); + resultLines.Add("Last-Modified: 2021-06-03T16:52:10Z"); + resultLines.Add("Album: Sunset"); + resultLines.Add("AlbumArtist: Harris Heller"); + resultLines.Add("Artist: Harris Heller"); + resultLines.Add("Date: 2021-03-05"); + resultLines.Add("Disc: 1"); + resultLines.Add("Genre: Electro"); + resultLines.Add("Genre: Dance"); + resultLines.Add("Title: Zig the Zag"); + resultLines.Add("Track: 1"); + resultLines.Add("Time: 126"); + resultLines.Add("duration: 126.250"); + resultLines.Add("Pos: 6"); + resultLines.Add("Id: 1439"); + } + catch (Exception ex) + { + Debug.WriteLine("Error@ParsePlaylistInfo: " + ex.Message); + } + */ + + try + { + IsBusy?.Invoke(this, true); + + ObservableCollection tmpQueue = new(); + /* + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + CurrentQueue.Clear(); + }); + */ + + var comparer = StringComparer.OrdinalIgnoreCase; + Dictionary SongValues = new(comparer); + + int i = 0; + + foreach (string value in resultLines) + { + string[] StatusValuePair = value.Trim().Split(':'); + if (StatusValuePair.Length > 1) + { + if (SongValues.ContainsKey(StatusValuePair[0].Trim())) + { + if (SongValues.ContainsKey("Id")) + { + SongInfoEx sng = FillSongInfoEx(SongValues, i); + + if (sng != null) + { + /* + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + CurrentQueue.Add(sng); + }); + */ + //CurrentQueue.Add(sng); + tmpQueue.Add(sng); + + i++; + + MpcProgress?.Invoke(this, string.Format("[Background] Parsing queue item... ({0})", i)); + + SongValues.Clear(); + } + } + /* + if (!SongValues.ContainsKey(StatusValuePair[0].Trim())) + { + SongValues.Add(StatusValuePair[0].Trim(), value.Replace(StatusValuePair[0].Trim() + ": ", "")); + } + else + { + if (StatusValuePair[0].Trim() == "Genre") + { + SongValues["Genre"] = SongValues["Genre"] + "/" + value.Replace(StatusValuePair[0].Trim() + ": ", ""); + } + } + */ + } + + if (!SongValues.ContainsKey(StatusValuePair[0].Trim())) + { + SongValues.Add(StatusValuePair[0].Trim(), value.Replace(StatusValuePair[0].Trim() + ": ", "")); + } + else + { + if (StatusValuePair[0].Trim() == "Genre") + { + SongValues["Genre"] = SongValues["Genre"] + "/" + value.Replace(StatusValuePair[0].Trim() + ": ", ""); + } + } + } + } + + if ((SongValues.Count > 0) && SongValues.ContainsKey("Id")) + { + SongInfoEx sng = FillSongInfoEx(SongValues, i); + + if (sng != null) + { + SongValues.Clear(); + /* + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + CurrentQueue.Add(sng); + }); + */ + //CurrentQueue.Add(sng); + tmpQueue.Add(sng); + + MpcProgress?.Invoke(this, string.Format("[Background] Parsing queue item... ({0})", i+1)); + } + } + + // test + MpcProgress?.Invoke(this, "[Background] Updating internal queue list..."); + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + //CurrentQueue.Clear(); + //_queue = tmpQueue; + _queue = new ObservableCollection(tmpQueue); + }); + MpcProgress?.Invoke(this, "[Background] Internal queue list is updated."); + + } + catch (Exception ex) + { + Debug.WriteLine("Exception@ParsePlaylistInfo: " + ex.Message); + + if (Application.Current != null) + { + Application.Current.Dispatcher.Invoke(() => + { + /*App app = App.Current as App; + app.AppendErrorLog("Exception@MPC@ParsePlaylistInfo", ex.Message);*/ + }); + } + + return Task.FromResult(false); + } + finally + { + IsBusy?.Invoke(this, false); + } + + return Task.FromResult(true); + } + + private SongInfoEx FillSongInfoEx(Dictionary SongValues, int i) + { + try + { + SongInfoEx sng = new(); + + if (SongValues.ContainsKey("Id")) + { + sng.Id = SongValues["Id"]; + } + + if (SongValues.ContainsKey("Title")) + { + sng.Title = SongValues["Title"]; + } + else + { + sng.Title = ""; + if (SongValues.ContainsKey("file")) + { + sng.Title = Path.GetFileName(SongValues["file"]); + } + } + + if (SongValues.ContainsKey("Artist")) + { + sng.Artist = SongValues["Artist"]; + } + else + { + if (SongValues.ContainsKey("AlbumArtist")) + { + // TODO: Should I? + sng.Artist = SongValues["AlbumArtist"]; + } + else + { + sng.Artist = ""; + } + } + + if (SongValues.ContainsKey("Last-Modified")) + { + sng.LastModified = SongValues["Last-Modified"]; + } + + if (SongValues.ContainsKey("AlbumArtist")) + { + sng.AlbumArtist = SongValues["AlbumArtist"]; + } + + if (SongValues.ContainsKey("Album")) + { + sng.Album = SongValues["Album"]; + } + + if (SongValues.ContainsKey("Track")) + { + sng.Track = SongValues["Track"]; + } + + if (SongValues.ContainsKey("Disc")) + { + sng.Disc = SongValues["Disc"]; + } + + if (SongValues.ContainsKey("Date")) + { + sng.Date = SongValues["Date"]; + } + + if (SongValues.ContainsKey("Genre")) + { + sng.Genre = SongValues["Genre"]; + } + + if (SongValues.ContainsKey("Composer")) + { + sng.Composer = SongValues["Composer"]; + } + + if (SongValues.ContainsKey("Time")) + { + sng.Time = SongValues["Time"]; + } + + if (SongValues.ContainsKey("duration")) + { + sng.Time = SongValues["duration"]; + sng.Duration = SongValues["duration"]; + } + + if (SongValues.ContainsKey("Pos")) + { + sng.Pos = SongValues["Pos"]; + } + + if (SongValues.ContainsKey("file")) + { + sng.File = SongValues["file"]; + } + + // for sorting. + sng.Index = i; + /* + if (i < 0) + { + // -1 means we are not parsing queue but parsing currentsong. + { + /*App app = App.Current as App; + app.AppendErrorLog("Exception@MPC@FillSongInfoEx", e.Message);*/ + }); + } + + return null; + } + } + + private Task ParsePlaylists(string result) + { + bool isEmptyResult = false; + + if (string.IsNullOrEmpty(result)) + isEmptyResult = true; + + List resultLines = result.Split('\n').ToList(); + + if (resultLines.Count == 0) + isEmptyResult = true; + + if (isEmptyResult) + { + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + Playlists.Clear(); + }); + + return Task.FromResult(true); + } + + try + { + IsBusy?.Invoke(this, true); + + ObservableCollection tmpPlaylists = new(); + + Playlist pl = null; + + foreach (string value in resultLines) + { + if (value.StartsWith("playlist:")) + { + + pl = new Playlist + { + Name = value.Replace("playlist: ", "") + }; + + tmpPlaylists.Add(pl); + } + else if (value.StartsWith("Last-Modified: ")) + { + if (pl != null) + pl.LastModified = value.Replace("Last-Modified: ", ""); + } + else if (value.StartsWith("OK")) + { + // Ignoring. + } + } + + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + _playlists = new ObservableCollection(tmpPlaylists); + }); + + /* + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + Playlists.Clear(); + + // Tmp list for sorting. + //List slTmp = new List(); + + Playlist pl = null; + + foreach (string value in resultLines) + { + if (value.StartsWith("playlist:")) + { + + pl = new Playlist + { + Name = value.Replace("playlist: ", "") + }; + + Playlists.Add(pl); + } + else if (value.StartsWith("Last-Modified: ")) + { + if (pl != null) + pl.LastModified = value.Replace("Last-Modified: ", ""); + } + else if (value.StartsWith("OK")) + { + // Ignoring. + } + } + }); + */ + } + catch (Exception e) + { + Debug.WriteLine("Error@ParsePlaylists: " + e.ToString()); + + if (Application.Current != null) + { + Application.Current.Dispatcher.Invoke(() => + { + /*App app = App.Current as App; + app.AppendErrorLog("Exception@MPC@ParsePlaylists", e.Message);*/ + }); + } + + IsBusy?.Invoke(this, false); + return Task.FromResult(false); + } + finally + { + IsBusy?.Invoke(this, false); + } + + return Task.FromResult(true); + } + + private Task ParseListAll(string result) + { + if (MpdStop) return Task.FromResult(false); + + if (string.IsNullOrEmpty(result)) return Task.FromResult(false); + + List resultLines = result.Split('\n').ToList(); + + if (resultLines.Count == 0) return Task.FromResult(false); + + try + { + IsBusy?.Invoke(this, true); + + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + LocalFiles.Clear(); + LocalDirectories.Clear(); + }); + + SongFile song = null; + + int i = 0; + + foreach (string value in resultLines) + { + if (value.StartsWith("directory:")) + { + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + LocalDirectories.Add(value.Replace("directory: ", "")); + }); + + //LocalDirectories.Add(value.Replace("directory: ", "")); + + MpcProgress?.Invoke(this, string.Format("[Background] Parsing files and directories ({0})...", i)); + } + else if (value.StartsWith("file:")) + { + song = new SongFile + { + File = value.Replace("file: ", "") + }; + + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + LocalFiles.Add(song); + }); + + //LocalFiles.Add(song); + + i++; + + MpcProgress?.Invoke(this, string.Format("[Background] Parsing files and directories ({0})...", i)); + } + else if ((value.StartsWith("OK"))) + { + // Ignoring. + } + else + { + //Debug.WriteLine(value); + } + } + + MpcProgress?.Invoke(this, "[Background] Parsing files and directories is done."); + + } + catch (Exception e) + { + Debug.WriteLine("Error@ParseListAll: " + e.ToString()); + + if (Application.Current != null) + { + Application.Current.Dispatcher.Invoke(() => + { + /*App app = App.Current as App; + app.AppendErrorLog("Exception@MPC@ParseListAll", e.Message);*/ + }); + } + + IsBusy?.Invoke(this, false); + return Task.FromResult(false); ; + } + finally + { + IsBusy?.Invoke(this, false); + } + + return Task.FromResult(true); ; + } + + private Task ParseSearchResult(string result) + { + if (MpdStop) return Task.FromResult(false); + + if (Application.Current == null) { return Task.FromResult(false); } + Application.Current.Dispatcher.Invoke(() => + { + SearchResult.Clear(); + }); + + if (string.IsNullOrEmpty(result)) return Task.FromResult(false); + + //if (result.Trim() == "OK") return true; + + List resultLines = result.Split('\n').ToList(); + if (resultLines == null) return Task.FromResult(true); + if (resultLines.Count == 0) return Task.FromResult(true); + + + + // "listplaylist" .. songs containd in a playlist. + /* + file: Nina Simone/Cellular Soundtrack/Sinnerman (remix).mp3 + Last-Modified: 2020-09-07T22:56:19Z + Artist: Nina Simone + Album: Cellular Soundtrack + Title: Sinnerman (remix) + Genre: Soundtrack + Time: 274 + duration: 274.364 + */ + + // "find" search result + /* + file: 2Pac/Better Dayz/17 Better Dayz (Feat. Mr. Biggs).mp3 + Last-Modified: 2011-02-27T14:20:18Z + Format: 44100:f:2 + Time: 258 + duration: 257.677 + Artist: 2Pac + Album: Better Dayz + Title: Better Dayz (Feat. Mr. Biggs) + Track: 17 + Genre: Rap + Date: 2002 + */ + + try + { + IsBusy?.Invoke(this, true); + + var comparer = StringComparer.OrdinalIgnoreCase; + Dictionary SongValues = new(comparer); + + int i = 0; + + foreach (string line in resultLines) + { + string[] ValuePair = line.Split(':'); + if (ValuePair.Length > 1) + { + if (SongValues.ContainsKey(ValuePair[0].Trim())) + { + // Contains means new one. + + // save old one and clear songvalues. + if (SongValues.ContainsKey("file")) + { + SongInfo sng = FillSongInfo(SongValues, i); + + i++; + + SongValues.Clear(); + + if (Application.Current == null) { return Task.FromResult(false);} + Application.Current.Dispatcher.Invoke(() => + { + SearchResult.Add(sng); + }); + + MpcProgress?.Invoke(this, string.Format("[Background] Parsing search result item... ({0})", i)); + } + + // start over + //SongValues.Add(ValuePair[0].Trim(), line.Replace(ValuePair[0].Trim() + ": ", "")); + } + /* + else + { + SongValues.Add(ValuePair[0].Trim(), line.Replace(ValuePair[0].Trim() + ": ", "")); + } + */ + if (!SongValues.ContainsKey(ValuePair[0].Trim())) + { + SongValues.Add(ValuePair[0].Trim(), line.Replace(ValuePair[0].Trim() + ": ", "")); + } + else + { + if (ValuePair[0].Trim() == "Genre") + { + SongValues["Genre"] = SongValues["Genre"] + "/" + line.Replace(ValuePair[0].Trim() + ": ", ""); + } + } + } + } + + // last one + if ((SongValues.Count > 0) && SongValues.ContainsKey("file")) + { + SongInfo sng = FillSongInfo(SongValues, i); + + SongValues.Clear(); + + if (Application.Current == null) { return Task.FromResult(false);} + Application.Current.Dispatcher.Invoke(() => + { + SearchResult.Add(sng); + }); + + MpcProgress?.Invoke(this, string.Format("[Background] Parsing search result item... ({0})", i+1)); + } + + } + catch (Exception ex) + { + Debug.WriteLine("Error@ParseSearchResult: " + ex.Message); + + if (Application.Current != null) + { + Application.Current.Dispatcher.Invoke(() => + { + /*App app = App.Current as App; + app.AppendErrorLog("Exception@MPC@ParseSearchResult", ex.Message);*/ + }); + } + + IsBusy?.Invoke(this, false); + return Task.FromResult(false); + } + finally + { + IsBusy?.Invoke(this, false); + + MpcProgress?.Invoke(this, "[Background] Search result loaded."); + } + + return Task.FromResult(true); + } + + private ObservableCollection ParsePlaylistSongsResult(string result) + { + ObservableCollection songList = new(); + + if (MpdStop) return songList; + + if (string.IsNullOrEmpty(result)) return songList; + + if (result.Trim() == "OK") return songList; + + List resultLines = result.Split('\n').ToList(); + if (resultLines == null) return songList; + if (resultLines.Count == 0) return songList; + + + // "listplaylist" .. songs containd in a playlist. + /* + file: Nina Simone/Cellular Soundtrack/Sinnerman (remix).mp3 + Last-Modified: 2020-09-07T22:56:19Z + Artist: Nina Simone + Album: Cellular Soundtrack + Title: Sinnerman (remix) + Genre: Soundtrack + Time: 274 + duration: 274.364 + */ + + // "find" search result + /* + file: 2Pac/Better Dayz/17 Better Dayz (Feat. Mr. Biggs).mp3 + Last-Modified: 2011-02-27T14:20:18Z + Format: 44100:f:2 + Time: 258 + duration: 257.677 + Artist: 2Pac + Album: Better Dayz + Title: Better Dayz (Feat. Mr. Biggs) + Track: 17 + Genre: Rap + Date: 2002 + */ + + try + { + var comparer = StringComparer.OrdinalIgnoreCase; + Dictionary SongValues = new(comparer); + + int i = 0; + + foreach (string line in resultLines) + { + string[] ValuePair = line.Split(':'); + if (ValuePair.Length > 1) + { + if (SongValues.ContainsKey(ValuePair[0].Trim())) + { + // Contains means new one. + + // save old one and clear songvalues. + if (SongValues.ContainsKey("file")) + { + SongInfo sng = FillSongInfo(SongValues, i); + + i++; + + SongValues.Clear(); + + //if (Application.Current == null) { return songList; } + //Application.Current.Dispatcher.Invoke(() => + //{ + songList.Add(sng); + //}); + + MpcProgress?.Invoke(this, string.Format("[Background] Parsing playlist item... ({0})", i)); + } + + // start over + //SongValues.Add(ValuePair[0].Trim(), line.Replace(ValuePair[0].Trim() + ": ", "")); + } + /* + else + { + SongValues.Add(ValuePair[0].Trim(), line.Replace(ValuePair[0].Trim() + ": ", "")); + } + */ + if (!SongValues.ContainsKey(ValuePair[0].Trim())) + { + SongValues.Add(ValuePair[0].Trim(), line.Replace(ValuePair[0].Trim() + ": ", "")); + } + else + { + if (ValuePair[0].Trim() == "Genre") + { + SongValues["Genre"] = SongValues["Genre"] + "/" + line.Replace(ValuePair[0].Trim() + ": ", ""); + } + } + } + } + + // last one + if ((SongValues.Count > 0) && SongValues.ContainsKey("file")) + { + SongInfo sng = FillSongInfo(SongValues, i); + + SongValues.Clear(); + + //if (Application.Current == null) { return songList; } + //Application.Current.Dispatcher.Invoke(() => + //{ + songList.Add(sng); + //}); + MpcProgress?.Invoke(this, string.Format("[Background] Parsing playlist item... ({0})", i+1)); + } + + } + catch (Exception ex) + { + Debug.WriteLine("Error@ParsePlaylistSongsResult: " + ex.Message); + + if (Application.Current != null) + { + Application.Current.Dispatcher.Invoke(() => + { + /*App app = App.Current as App; + app.AppendErrorLog("Exception@MPC@ParsePlaylistSongsResult", ex.Message);*/ + }); + } + + return songList; + } + + return songList; + } + + private static SongInfo FillSongInfo(Dictionary SongValues, int i) + { + + SongInfo sng = new(); + + if (SongValues.ContainsKey("Title")) + { + sng.Title = SongValues["Title"]; + } + else + { + sng.Title = ""; + if (SongValues.ContainsKey("file")) + { + sng.Title = Path.GetFileName(SongValues["file"]); + } + } + + if (SongValues.ContainsKey("Artist")) + { + sng.Artist = SongValues["Artist"]; + } + else + { + if (SongValues.ContainsKey("AlbumArtist")) + { + // TODO: Should I? + sng.Artist = SongValues["AlbumArtist"]; + } + else + { + sng.Artist = ""; + } + } + + if (SongValues.ContainsKey("Last-Modified")) + { + sng.LastModified = SongValues["Last-Modified"]; + } + + if (SongValues.ContainsKey("AlbumArtist")) + { + sng.AlbumArtist = SongValues["AlbumArtist"]; + } + + if (SongValues.ContainsKey("Album")) + { + sng.Album = SongValues["Album"]; + } + + if (SongValues.ContainsKey("Track")) + { + sng.Track = SongValues["Track"]; + } + + if (SongValues.ContainsKey("Disc")) + { + sng.Disc = SongValues["Disc"]; + } + + if (SongValues.ContainsKey("Date")) + { + sng.Date = SongValues["Date"]; + } + + if (SongValues.ContainsKey("Genre")) + { + sng.Genre = SongValues["Genre"]; + } + + if (SongValues.ContainsKey("Composer")) + { + sng.Composer = SongValues["Composer"]; + } + + if (SongValues.ContainsKey("Time")) + { + sng.Time = SongValues["Time"]; + } + + if (SongValues.ContainsKey("duration")) + { + sng.Time = SongValues["duration"]; + sng.Duration = SongValues["duration"]; + } + + if (SongValues.ContainsKey("file")) + { + sng.File = SongValues["file"]; + } + + // for sorting. (and playlist pos) + sng.Index = i; + + return sng; + } + + #endregion + + public void MpdDisconnect() + { + try + { + IsBusy?.Invoke(this, true); + + ConnectionState = ConnectionStatus.Disconnecting; + + if (_commandConnection.Client != null) + _commandConnection.Client.Shutdown(SocketShutdown.Both); + _commandConnection.Close(); + } + catch { } + finally + { + IsBusy?.Invoke(this, false); + ConnectionState = ConnectionStatus.DisconnectedByUser; + } + + try + { + IsBusy?.Invoke(this, true); + + ConnectionState = ConnectionStatus.Disconnecting; + + if (_idleConnection.Client != null) + _idleConnection.Client.Shutdown(SocketShutdown.Both); + _idleConnection.Close(); + } + catch { } + finally + { + IsBusy?.Invoke(this, false); + ConnectionState = ConnectionStatus.DisconnectedByUser; + } + + } + + } +} + diff --git a/MainWindow.xaml b/MainWindow.xaml new file mode 100644 index 0000000..47cf3b6 --- /dev/null +++ b/MainWindow.xaml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +