first test
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PainoBar_Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the Pianobar process lifecycle and communication
|
||||
/// </summary>
|
||||
public class PianobarManager : IDisposable
|
||||
{
|
||||
private Process? _process;
|
||||
private StreamWriter? _inputWriter;
|
||||
private bool _isConnected;
|
||||
private readonly object _lockObject = new object();
|
||||
|
||||
public event EventHandler<SongChangedEventArgs>? SongChanged;
|
||||
public event EventHandler<string>? StationChanged;
|
||||
public event EventHandler<PianobarEventArgs>? EventOccurred;
|
||||
public event EventHandler? Connected;
|
||||
public event EventHandler? Disconnected;
|
||||
public event EventHandler<string>? OutputReceived;
|
||||
|
||||
public bool IsConnected
|
||||
{
|
||||
get { lock (_lockObject) return _isConnected; }
|
||||
private set
|
||||
{
|
||||
bool changed = false;
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_isConnected != value)
|
||||
{
|
||||
_isConnected = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed)
|
||||
{
|
||||
if (value)
|
||||
Connected?.Invoke(this, EventArgs.Empty);
|
||||
else
|
||||
Disconnected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string? CurrentSong { get; private set; }
|
||||
public string? CurrentArtist { get; private set; }
|
||||
public string? CurrentAlbum { get; private set; }
|
||||
public string? CurrentAlbumArtUrl { get; private set; }
|
||||
public string? CurrentStation { get; private set; }
|
||||
public bool IsPlaying { get; private set; } = true;
|
||||
|
||||
public void Connect(string pianobarPath, string? configPath = null)
|
||||
{
|
||||
if (IsConnected)
|
||||
throw new InvalidOperationException("Already connected to Pianobar.");
|
||||
|
||||
// Check if pianobar is already running
|
||||
var existingProcesses = Process.GetProcessesByName("pianobar");
|
||||
if (existingProcesses.Length > 0)
|
||||
{
|
||||
foreach (var proc in existingProcesses)
|
||||
proc.Dispose();
|
||||
throw new InvalidOperationException("Pianobar is already running. Please close existing instances.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = pianobarPath,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(configPath))
|
||||
{
|
||||
startInfo.Arguments = $"-c \"{configPath}\"";
|
||||
}
|
||||
|
||||
_process = new Process { StartInfo = startInfo };
|
||||
_process.OutputDataReceived += Process_OutputDataReceived;
|
||||
_process.ErrorDataReceived += Process_ErrorDataReceived;
|
||||
_process.Exited += Process_Exited;
|
||||
_process.EnableRaisingEvents = true;
|
||||
|
||||
_process.Start();
|
||||
_inputWriter = _process.StandardInput;
|
||||
_process.BeginOutputReadLine();
|
||||
_process.BeginErrorReadLine();
|
||||
|
||||
IsConnected = true;
|
||||
RaiseEvent("Connected to Pianobar");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Cleanup();
|
||||
throw new InvalidOperationException($"Failed to start Pianobar: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
if (!IsConnected)
|
||||
return;
|
||||
|
||||
Cleanup();
|
||||
IsConnected = false;
|
||||
RaiseEvent("Disconnected from Pianobar");
|
||||
}
|
||||
|
||||
public void SendCommand(char command)
|
||||
{
|
||||
if (!IsConnected || _inputWriter == null)
|
||||
throw new InvalidOperationException("Not connected to Pianobar.");
|
||||
|
||||
try
|
||||
{
|
||||
_inputWriter.WriteLine(command);
|
||||
_inputWriter.Flush();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to send command: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendCommand(string command)
|
||||
{
|
||||
if (!IsConnected || _inputWriter == null)
|
||||
throw new InvalidOperationException("Not connected to Pianobar.");
|
||||
|
||||
try
|
||||
{
|
||||
_inputWriter.WriteLine(command);
|
||||
_inputWriter.Flush();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to send command: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayPause()
|
||||
{
|
||||
SendCommand('p');
|
||||
IsPlaying = !IsPlaying;
|
||||
RaiseEvent(IsPlaying ? "Resumed" : "Paused");
|
||||
}
|
||||
|
||||
public void Skip()
|
||||
{
|
||||
SendCommand('n');
|
||||
RaiseEvent("Skipped Track");
|
||||
}
|
||||
|
||||
public void ShowStations()
|
||||
{
|
||||
SendCommand('s');
|
||||
}
|
||||
|
||||
private void Process_OutputDataReceived(object? sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(e.Data))
|
||||
return;
|
||||
|
||||
string line = e.Data;
|
||||
OutputReceived?.Invoke(this, line);
|
||||
|
||||
ParseOutput(line);
|
||||
}
|
||||
|
||||
private void Process_ErrorDataReceived(object? sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
OutputReceived?.Invoke(this, e.Data);
|
||||
}
|
||||
|
||||
private void Process_Exited(object? sender, EventArgs e)
|
||||
{
|
||||
IsConnected = false;
|
||||
RaiseEvent("Pianobar process exited");
|
||||
}
|
||||
|
||||
private void ParseOutput(string line)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse song information
|
||||
if (line.Contains("|>"))
|
||||
{
|
||||
// Song is playing
|
||||
IsPlaying = true;
|
||||
ParseSongLine(line);
|
||||
}
|
||||
else if (line.Contains("||"))
|
||||
{
|
||||
// Song is paused
|
||||
IsPlaying = false;
|
||||
}
|
||||
|
||||
// Parse station selection
|
||||
var stationMatch = Regex.Match(line, @"\|\s*Station\s+""(.+?)""");
|
||||
if (stationMatch.Success)
|
||||
{
|
||||
CurrentStation = stationMatch.Groups[1].Value;
|
||||
StationChanged?.Invoke(this, CurrentStation);
|
||||
RaiseEvent("Station Selected");
|
||||
}
|
||||
|
||||
// Parse song metadata
|
||||
if (line.Contains("Title:"))
|
||||
{
|
||||
var match = Regex.Match(line, @"Title:\s*(.+)");
|
||||
if (match.Success)
|
||||
CurrentSong = match.Groups[1].Value.Trim();
|
||||
}
|
||||
else if (line.Contains("Artist:"))
|
||||
{
|
||||
var match = Regex.Match(line, @"Artist:\s*(.+)");
|
||||
if (match.Success)
|
||||
CurrentArtist = match.Groups[1].Value.Trim();
|
||||
}
|
||||
else if (line.Contains("Album:"))
|
||||
{
|
||||
var match = Regex.Match(line, @"Album:\s*(.+)");
|
||||
if (match.Success)
|
||||
CurrentAlbum = match.Groups[1].Value.Trim();
|
||||
}
|
||||
else if (line.Contains("coverArt"))
|
||||
{
|
||||
var match = Regex.Match(line, @"coverArt:\s*(.+)");
|
||||
if (match.Success)
|
||||
{
|
||||
CurrentAlbumArtUrl = match.Groups[1].Value.Trim();
|
||||
TriggerSongChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore parsing errors
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseSongLine(string line)
|
||||
{
|
||||
// Parse format: |> "Song Title" by "Artist" on "Album"
|
||||
var match = Regex.Match(line, @"\|>\s+""(.+?)""\s+by\s+""(.+?)""\s+on\s+""(.+?)""");
|
||||
if (match.Success)
|
||||
{
|
||||
CurrentSong = match.Groups[1].Value;
|
||||
CurrentArtist = match.Groups[2].Value;
|
||||
CurrentAlbum = match.Groups[3].Value;
|
||||
TriggerSongChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void TriggerSongChanged()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(CurrentSong) && !string.IsNullOrEmpty(CurrentArtist))
|
||||
{
|
||||
SongChanged?.Invoke(this, new SongChangedEventArgs
|
||||
{
|
||||
Title = CurrentSong,
|
||||
Artist = CurrentArtist,
|
||||
Album = CurrentAlbum,
|
||||
AlbumArtUrl = CurrentAlbumArtUrl
|
||||
});
|
||||
RaiseEvent("Song Changed");
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseEvent(string eventText)
|
||||
{
|
||||
EventOccurred?.Invoke(this, new PianobarEventArgs { EventText = eventText });
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_process != null)
|
||||
{
|
||||
_process.OutputDataReceived -= Process_OutputDataReceived;
|
||||
_process.ErrorDataReceived -= Process_ErrorDataReceived;
|
||||
_process.Exited -= Process_Exited;
|
||||
|
||||
_inputWriter?.Close();
|
||||
_inputWriter = null;
|
||||
|
||||
// Don't kill the process, just detach
|
||||
_process.Close();
|
||||
_process = null;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public class SongChangedEventArgs : EventArgs
|
||||
{
|
||||
public string? Title { get; set; }
|
||||
public string? Artist { get; set; }
|
||||
public string? Album { get; set; }
|
||||
public string? AlbumArtUrl { get; set; }
|
||||
}
|
||||
|
||||
public class PianobarEventArgs : EventArgs
|
||||
{
|
||||
public string EventText { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user