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 { } } } }