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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
new file mode 100644
index 0000000..b94895d
--- /dev/null
+++ b/MainWindow.xaml.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.ComponentModel;
+using System.Diagnostics;
+
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Threading;
+
+using MPDCtrl.Models;
+
+namespace unison
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ private readonly MPC _mpd = new();
+ private bool _connected = false;
+ private int _currentVolume;
+ private bool _currentRandom;
+ private bool _currentRepeat;
+ private bool _currentSingle;
+ private bool _currentConsume;
+ private double _currentElapsed;
+
+ private readonly Process _snapcast = new Process();
+ private bool _snapcastStarted = false;
+
+ private string _snapcastVersion = "snapclient_0.25.0-1_win64";
+ private string _mpdHost = "192.168.1.13";
+ private int _mpdPort = 6600;
+ private string _mpdPassword = null;
+
+ public MainWindow()
+ {
+ InitializeComponent();
+ ConnectToMPD();
+ DispatcherTimer timer = new DispatcherTimer();
+ timer.Interval = TimeSpan.FromSeconds(0.2);
+ timer.Tick += timer_Tick;
+ timer.Start();
+ }
+
+ public async void ConnectToMPD()
+ {
+ Trace.WriteLine("Trying to connect...");
+ _connected = await _mpd.MpdCommandConnectionStart(_mpdHost, _mpdPort, _mpdPassword);
+ if (_connected)
+ {
+ await _mpd.MpdQueryStatus();
+ await Task.Delay(5);
+ _currentVolume = _mpd.MpdStatus.MpdVolume;
+ _currentRandom = _mpd.MpdStatus.MpdRandom;
+ _currentRepeat = _mpd.MpdStatus.MpdRepeat;
+ _currentSingle = _mpd.MpdStatus.MpdSingle;
+ _currentConsume = _mpd.MpdStatus.MpdConsume;
+ _currentElapsed = _mpd.MpdStatus.MpdSongElapsed;
+ }
+ }
+
+ void timer_Tick(object sender, EventArgs e)
+ {
+ LoopMPD();
+ UpdateInterface();
+ }
+
+ public void CheckStatus(ref T a, T b)
+ {
+ if (Comparer.Default.Compare(a, b) != 0)
+ a = b;
+ }
+
+ public async void LoopMPD()
+ {
+ if (!_connected)
+ return;
+
+ var status = await _mpd.MpdQueryStatus();
+ //Trace.WriteLine(status.ResultText);
+ await Task.Delay(5);
+
+ if (status != null)
+ {
+ CheckStatus(ref _currentVolume, _mpd.MpdStatus.MpdVolume);
+ CheckStatus(ref _currentRandom, _mpd.MpdStatus.MpdRandom);
+ CheckStatus(ref _currentRepeat, _mpd.MpdStatus.MpdRepeat);
+ CheckStatus(ref _currentSingle, _mpd.MpdStatus.MpdSingle);
+ CheckStatus(ref _currentConsume, _mpd.MpdStatus.MpdConsume);
+ CheckStatus(ref _currentElapsed, _mpd.MpdStatus.MpdSongElapsed);
+ }
+
+ await _mpd.MpdQueryCurrentSong();
+ await Task.Delay(5);
+ }
+
+ public void UpdateButton(ref Button button, bool b)
+ {
+ if (b)
+ button.Foreground = System.Windows.SystemColors.GradientActiveCaptionBrush;
+ else
+ button.Foreground = System.Windows.SystemColors.DesktopBrush;
+ }
+
+ public string FormatSeconds(double time)
+ {
+ var timespan = TimeSpan.FromSeconds(time);
+ return timespan.ToString(@"mm\:ss");
+ }
+
+ public void UpdateInterface()
+ {
+ if (_mpd.MpdCurrentSong != null)
+ {
+ SongTitle.Text = _mpd.MpdCurrentSong.Title;
+ SongTitle.ToolTip = _mpd.MpdCurrentSong.File;
+ SongArtist.Text = _mpd.MpdCurrentSong.Artist;
+ SongAlbum.Text = _mpd.MpdCurrentSong.Album + " (" + _mpd.MpdCurrentSong.Date + ")";
+ Bitrate.Text = _mpd.MpdCurrentSong.File.Substring(_mpd.MpdCurrentSong.File.LastIndexOf(".") + 1);
+ Bitrate.Text += " – ";
+ Bitrate.Text += _mpd.MpdStatus.MpdBitrate + "kbps";
+
+ CurrentTime.Text = FormatSeconds(_currentElapsed);
+ EndTime.Text = FormatSeconds(_mpd.MpdStatus.MpdSongTime);
+
+ TimeSlider.Value = _currentElapsed / _mpd.MpdCurrentSong.TimeSort * 100;
+ }
+
+ if (VolumeSlider.Value != _currentVolume)
+ {
+ VolumeSlider.Value = _currentVolume;
+ VolumeSlider.ToolTip = _currentVolume;
+ }
+
+ if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play)
+ PauseButton.Content = "⏸";
+ else if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Pause)
+ PauseButton.Content = "▶️";
+
+ if (_snapcastStarted)
+ Snapcast.Content = "Stop Snapcast";
+ else
+ Snapcast.Content = "Start Snapcast";
+
+ DebugText.Text = _mpd.MpdHost + ":" + _mpd.MpdPort;
+
+ UpdateButton(ref Random, _currentRandom);
+ UpdateButton(ref Repeat, _currentRepeat);
+ UpdateButton(ref Single, _currentSingle);
+ UpdateButton(ref Consume, _currentConsume);
+ }
+
+ private async void Pause_Clicked(object sender, RoutedEventArgs e)
+ {
+ if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play)
+ await _mpd.MpdPlaybackPause();
+ else if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Pause)
+ await _mpd.MpdPlaybackPlay(_currentVolume);
+ }
+
+ private async void Previous_Clicked(object sender, RoutedEventArgs e)
+ {
+ await _mpd.MpdPlaybackPrev(_currentVolume);
+ }
+
+ private async void Next_Clicked(object sender, RoutedEventArgs e)
+ {
+ await _mpd.MpdPlaybackNext(_currentVolume);
+ }
+
+ private async void Random_Clicked(object sender, RoutedEventArgs e)
+ {
+ await _mpd.MpdSetRandom(!_currentRandom);
+ }
+
+ private async void Repeat_Clicked(object sender, RoutedEventArgs e)
+ {
+ await _mpd.MpdSetRepeat(!_currentRepeat);
+ }
+
+ private async void Single_Clicked(object sender, RoutedEventArgs e)
+ {
+ await _mpd.MpdSetSingle(!_currentSingle);
+ }
+
+ private async void Consume_Clicked(object sender, RoutedEventArgs e)
+ {
+ await _mpd.MpdSetConsume(!_currentConsume);
+ }
+
+ private void Snapcast_Clicked(object sender, RoutedEventArgs e)
+ {
+ if (!_snapcastStarted)
+ {
+ _snapcast.StartInfo.FileName = _snapcastVersion + @"\snapclient.exe";
+ _snapcast.StartInfo.Arguments = "--host " + _mpd.MpdHost;
+ _snapcast.StartInfo.CreateNoWindow = true;
+ _snapcast.Start();
+ _snapcastStarted = true;
+ }
+ else
+ {
+ _snapcast.Kill();
+ _snapcastStarted = false;
+ }
+ }
+
+ private void Window_Closing(object sender, CancelEventArgs e)
+ {
+ if (_snapcastStarted)
+ _snapcast.Kill();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Properties/PublishProfiles/FolderProfile.pubxml b/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 0000000..306e129
--- /dev/null
+++ b/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,14 @@
+
+
+
+
+ Release
+ Any CPU
+ publish\
+ FileSystem
+ net5.0-windows
+ false
+
+
\ No newline at end of file
diff --git a/images/nocover.png b/images/nocover.png
new file mode 100644
index 0000000..3d4d203
Binary files /dev/null and b/images/nocover.png differ
diff --git a/snapclient_0.25.0-1_win64/FLAC.dll b/snapclient_0.25.0-1_win64/FLAC.dll
new file mode 100644
index 0000000..710fb87
Binary files /dev/null and b/snapclient_0.25.0-1_win64/FLAC.dll differ
diff --git a/snapclient_0.25.0-1_win64/ogg.dll b/snapclient_0.25.0-1_win64/ogg.dll
new file mode 100644
index 0000000..49cbb82
Binary files /dev/null and b/snapclient_0.25.0-1_win64/ogg.dll differ
diff --git a/snapclient_0.25.0-1_win64/opus.dll b/snapclient_0.25.0-1_win64/opus.dll
new file mode 100644
index 0000000..232ecbc
Binary files /dev/null and b/snapclient_0.25.0-1_win64/opus.dll differ
diff --git a/snapclient_0.25.0-1_win64/snapclient.exe b/snapclient_0.25.0-1_win64/snapclient.exe
new file mode 100644
index 0000000..bc33863
Binary files /dev/null and b/snapclient_0.25.0-1_win64/snapclient.exe differ
diff --git a/snapclient_0.25.0-1_win64/soxr.dll b/snapclient_0.25.0-1_win64/soxr.dll
new file mode 100644
index 0000000..5a7008b
Binary files /dev/null and b/snapclient_0.25.0-1_win64/soxr.dll differ
diff --git a/snapclient_0.25.0-1_win64/vorbis.dll b/snapclient_0.25.0-1_win64/vorbis.dll
new file mode 100644
index 0000000..b6b3f4e
Binary files /dev/null and b/snapclient_0.25.0-1_win64/vorbis.dll differ
diff --git a/unison.csproj b/unison.csproj
new file mode 100644
index 0000000..7976011
--- /dev/null
+++ b/unison.csproj
@@ -0,0 +1,47 @@
+
+
+
+ WinExe
+ net5.0-windows
+ true
+ unison.ico
+
+ unison.App
+ 0.0.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/unison.ico b/unison.ico
new file mode 100644
index 0000000..8bf76f8
Binary files /dev/null and b/unison.ico differ
diff --git a/unison.sln b/unison.sln
new file mode 100644
index 0000000..56338a3
--- /dev/null
+++ b/unison.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31515.178
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "unison", "unison.csproj", "{489048C4-3FCA-4573-B34C-943D03F94D04}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {489048C4-3FCA-4573-B34C-943D03F94D04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {489048C4-3FCA-4573-B34C-943D03F94D04}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {489048C4-3FCA-4573-B34C-943D03F94D04}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {489048C4-3FCA-4573-B34C-943D03F94D04}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {12DA9EE9-38C6-4BBE-8133-6F7F30AF3A67}
+ EndGlobalSection
+EndGlobal