Work done so far

This commit is contained in:
minster586
2026-08-11 22:42:01 -04:00
parent da5fb64400
commit 02bd8ffd5e
10 changed files with 1321 additions and 256 deletions
+242 -9
View File
@@ -13,6 +13,13 @@ namespace PainoBar_Helper
private StreamWriter? _inputWriter;
private bool _isConnected;
private readonly object _lockObject = new object();
private PianobarEventMonitor? _eventMonitor;
private bool _receivedStdOut;
private bool _receivedStdErr;
private readonly string _ioLogPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"PianobarHelper",
"pianobar-io.log");
public event EventHandler<SongChangedEventArgs>? SongChanged;
public event EventHandler<string>? StationChanged;
@@ -52,32 +59,68 @@ namespace PainoBar_Helper
public string? CurrentStation { get; private set; }
public bool IsPlaying { get; private set; } = true;
public void Connect(string pianobarPath, string? configPath = null)
public void Connect(string pianobarPath, string? configPath = null, string? eventFilePath = null)
{
if (IsConnected)
throw new InvalidOperationException("Already connected to Pianobar.");
// Check if pianobar is already running
_receivedStdOut = false;
_receivedStdErr = false;
// Check if pianobar is already running (check both with and without .exe)
var existingProcesses = Process.GetProcessesByName("pianobar");
if (existingProcesses.Length == 0)
{
// Also try without extension
string processName = Path.GetFileNameWithoutExtension(pianobarPath);
existingProcesses = Process.GetProcessesByName(processName);
}
if (existingProcesses.Length > 0)
{
foreach (var proc in existingProcesses)
proc.Dispose();
throw new InvalidOperationException("Pianobar is already running. Please close existing instances.");
throw new InvalidOperationException("Pianobar is already running. Please close existing instances before connecting.");
}
// Validate the pianobar path exists
string fullPath = pianobarPath;
// If it's just a filename or not found, try to find it in PATH
if (!File.Exists(fullPath))
{
string? foundPath = FindInPath(pianobarPath);
if (!string.IsNullOrEmpty(foundPath))
{
fullPath = foundPath;
}
else
{
throw new FileNotFoundException(
$"Pianobar executable not found.\n\n" +
$"Path checked: {pianobarPath}\n" +
$"Full path: {Path.GetFullPath(pianobarPath)}\n\n" +
$"Please verify:\n" +
$"1. The path in Settings is correct\n" +
$"2. Pianobar is installed\n" +
$"3. The file exists at the specified location");
}
}
try
{
var startInfo = new ProcessStartInfo
{
FileName = pianobarPath,
FileName = fullPath,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
CreateNoWindow = true, // Run in background without terminal window
WindowStyle = ProcessWindowStyle.Hidden,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
StandardErrorEncoding = Encoding.UTF8,
WorkingDirectory = Path.GetDirectoryName(fullPath) ?? Environment.CurrentDirectory
};
if (!string.IsNullOrEmpty(configPath))
@@ -91,13 +134,35 @@ namespace PainoBar_Helper
_process.Exited += Process_Exited;
_process.EnableRaisingEvents = true;
_process.Start();
bool started = _process.Start();
if (!started)
{
throw new InvalidOperationException("Process.Start() returned false. The process could not be started.");
}
// Verify the process actually started
if (_process.HasExited)
{
throw new InvalidOperationException($"Process started but immediately exited with code: {_process.ExitCode}");
}
_inputWriter = _process.StandardInput;
_process.BeginOutputReadLine();
_process.BeginErrorReadLine();
IsConnected = true;
RaiseEvent("Connected to Pianobar");
// Start monitoring event file if provided
if (!string.IsNullOrEmpty(eventFilePath))
{
_eventMonitor = new PianobarEventMonitor();
_eventMonitor.EventReceived += OnEventReceived;
_eventMonitor.StartMonitoring(eventFilePath);
}
RaiseEvent($"Pianobar started successfully (PID: {_process.Id})");
RaiseEvent($"I/O log: {_ioLogPath}");
}
catch (Exception ex)
{
@@ -106,6 +171,59 @@ namespace PainoBar_Helper
}
}
private string? FindInPath(string command)
{
// Check if the command is available in PATH
var pathEnv = Environment.GetEnvironmentVariable("PATH");
if (string.IsNullOrEmpty(pathEnv))
return null;
var paths = pathEnv.Split(Path.PathSeparator);
var extensions = new[] { ".exe", ".cmd", ".bat", "" };
// Remove .exe if already present in command
string baseCommand = Path.GetFileNameWithoutExtension(command);
string commandExt = Path.GetExtension(command);
foreach (var path in paths)
{
try
{
if (!Directory.Exists(path))
continue;
foreach (var ext in extensions)
{
// If command already has extension, use it as-is
if (!string.IsNullOrEmpty(commandExt))
{
var fullPath = Path.Combine(path, command);
if (File.Exists(fullPath))
return fullPath;
}
else
{
var fullPath = Path.Combine(path, baseCommand + ext);
if (File.Exists(fullPath))
return fullPath;
}
}
}
catch
{
// Skip paths that cause errors
continue;
}
}
return null;
}
private bool IsCommandInPath(string command)
{
return !string.IsNullOrEmpty(FindInPath(command));
}
public void Disconnect()
{
if (!IsConnected)
@@ -113,7 +231,33 @@ namespace PainoBar_Helper
Cleanup();
IsConnected = false;
RaiseEvent("Disconnected from Pianobar");
RaiseEvent("Disconnected from Pianobar (process detached)");
}
public void DisconnectAndKill()
{
if (!IsConnected)
return;
try
{
if (_process != null && !_process.HasExited)
{
int pid = _process.Id;
_process.Kill();
_process.WaitForExit(5000); // Wait up to 5 seconds for graceful exit
RaiseEvent($"Pianobar process killed (PID: {pid})");
}
}
catch (Exception ex)
{
RaiseEvent($"Error killing process: {ex.Message}");
}
finally
{
Cleanup();
IsConnected = false;
}
}
public void SendCommand(char command)
@@ -172,6 +316,13 @@ namespace PainoBar_Helper
return;
string line = e.Data;
if (!_receivedStdOut)
{
_receivedStdOut = true;
RaiseEvent("Received output on STDOUT");
}
LogIoLine("STDOUT", line);
OutputReceived?.Invoke(this, line);
ParseOutput(line);
@@ -180,15 +331,44 @@ namespace PainoBar_Helper
private void Process_ErrorDataReceived(object? sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Data))
{
if (!_receivedStdErr)
{
_receivedStdErr = true;
RaiseEvent("Received output on STDERR");
}
LogIoLine("STDERR", e.Data);
OutputReceived?.Invoke(this, e.Data);
ParseOutput(e.Data);
}
}
private void Process_Exited(object? sender, EventArgs e)
{
if (!_receivedStdOut)
RaiseEvent("No STDOUT data received before process exit");
IsConnected = false;
RaiseEvent("Pianobar process exited");
}
private void LogIoLine(string source, string line)
{
try
{
var logDir = Path.GetDirectoryName(_ioLogPath);
if (!string.IsNullOrEmpty(logDir))
Directory.CreateDirectory(logDir);
File.AppendAllText(_ioLogPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] [{source}] {line}{Environment.NewLine}");
}
catch
{
// Ignore logging errors
}
}
private void ParseOutput(string line)
{
try
@@ -278,6 +458,51 @@ namespace PainoBar_Helper
}
}
private void OnEventReceived(object? sender, PianobarEventData eventData)
{
// Update metadata from event file
if (!string.IsNullOrEmpty(eventData.Title))
CurrentSong = eventData.Title;
if (!string.IsNullOrEmpty(eventData.Artist))
CurrentArtist = eventData.Artist;
if (!string.IsNullOrEmpty(eventData.Album))
CurrentAlbum = eventData.Album;
if (!string.IsNullOrEmpty(eventData.CoverArt))
CurrentAlbumArtUrl = eventData.CoverArt;
if (!string.IsNullOrEmpty(eventData.StationName))
{
CurrentStation = eventData.StationName;
StationChanged?.Invoke(this, CurrentStation);
}
// Raise event based on event type
if (!string.IsNullOrEmpty(eventData.Event))
{
switch (eventData.Event.ToLower())
{
case "songstart":
IsPlaying = true;
TriggerSongChanged();
break;
case "songfinish":
break;
case "songlove":
RaiseEvent("Song Loved");
break;
case "songban":
RaiseEvent("Song Banned");
break;
default:
RaiseEvent($"Event: {eventData.Event}");
break;
}
}
}
private void RaiseEvent(string eventText)
{
EventOccurred?.Invoke(this, new PianobarEventArgs { EventText = eventText });
@@ -287,6 +512,14 @@ namespace PainoBar_Helper
{
try
{
// Stop event monitoring
if (_eventMonitor != null)
{
_eventMonitor.EventReceived -= OnEventReceived;
_eventMonitor.Dispose();
_eventMonitor = null;
}
if (_process != null)
{
_process.OutputDataReceived -= Process_OutputDataReceived;