Initial commit: Go driver for the Lian Li Galahad II LCD
Streams H.264 to the Galahad II LCD via USB (gousb) with: - ffmpeg CLI transcoding (no fragile FFmpeg C-ABI bindings) - pure-Go Annex-B access-unit splitting - framerate pacing with graceful shutdown and USB reconnect - TOML config, cobra CLI, structured slog logging, unit tests
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
// Package service manages the galahad2lcd systemd unit.
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Service represents the installed systemd unit.
|
||||
type Service struct {
|
||||
name string
|
||||
// runCmd is the command constructor; overridable for tests.
|
||||
runCmd func(ctx context.Context, name string, arg ...string) *exec.Cmd
|
||||
}
|
||||
|
||||
// New returns a Service wrapper for the given unit name.
|
||||
func New(name string) *Service {
|
||||
return &Service{name: name, runCmd: exec.CommandContext}
|
||||
}
|
||||
|
||||
// Restart tells systemd to restart the service. It fails early when the
|
||||
// caller lacks privileges.
|
||||
func (s *Service) Restart(ctx context.Context) error {
|
||||
cmd := s.runCmd(ctx, "systemctl", "restart", s.name)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if len(out) > 0 {
|
||||
return fmt.Errorf("systemctl restart %s: %w (%s)", s.name, err, string(out))
|
||||
}
|
||||
return fmt.Errorf("systemctl restart %s: %w", s.name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// fakeSystemctl installs a fake systemctl on PATH and returns the path to its
|
||||
// directory.
|
||||
func fakeSystemctl(t *testing.T, script string) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "systemctl")
|
||||
if err := os.WriteFile(path, []byte("#!/bin/sh\n"+script), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
return path
|
||||
}
|
||||
|
||||
func TestRestartSuccess(t *testing.T) {
|
||||
fakeSystemctl(t, "exit 0\n")
|
||||
if err := New("galahad2lcd").Restart(context.Background()); err != nil {
|
||||
t.Fatalf("Restart error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartFailure(t *testing.T) {
|
||||
fakeSystemctl(t, "echo 'permission denied' >&2; exit 1\n")
|
||||
err := New("galahad2lcd").Restart(context.Background())
|
||||
if err == nil {
|
||||
t.Fatal("Restart returned nil error, want failure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartCancellation(t *testing.T) {
|
||||
fakeSystemctl(t, "sleep 5\n")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
err := New("galahad2lcd").Restart(ctx)
|
||||
if err == nil {
|
||||
t.Fatal("Restart returned nil error for cancelled context")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the default constructor wires up the real exec.CommandContext.
|
||||
func TestNewDefaultRunCmd(t *testing.T) {
|
||||
s := New("x")
|
||||
if s.runCmd == nil {
|
||||
t.Fatal("runCmd not initialised")
|
||||
}
|
||||
cmd := s.runCmd(context.Background(), "true")
|
||||
if cmd == nil {
|
||||
t.Fatal("runCmd returned nil command")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user