package devices import ( "fmt" "os" "os/exec" "strings" "sync" ) // TODO: A command device // You will be able to use shell commands to push the power button and get the status. // // Status will give an env variable: WOLBODGE_STATUS_FILE, you will be expected to write a "0" or a "1" there, depending on the state. func NewCommand(shell []string, statusCmd string, toggleStatusCmd string) Device { device := &command{ shell: shell, statusCmd: statusCmd, toggleStatusCmd: toggleStatusCmd, lock: sync.Mutex{}, } return device } type command struct { shell []string statusCmd string toggleStatusCmd string lock sync.Mutex } func (d *command) Status() (bool, error) { d.lock.Lock() defer d.lock.Unlock() file, err := os.CreateTemp("", "wolbodge-status-*") if err != nil { return false, fmt.Errorf("failed to create temporary status file: %w", err) } defer os.Remove(file.Name()) cmd := d.makeCommand(d.statusCmd) cmd.Env = append( cmd.Environ(), fmt.Sprintf("WOLBODGE_STATUS_FILE=%s", file.Name()), ) err = cmd.Run() if err != nil { return false, fmt.Errorf("failed to run command: %w", err) } data, err := os.ReadFile(file.Name()) if err != nil { return false, fmt.Errorf("failed to read status file: %w", err) } content := string(data) content = strings.TrimSpace(content) switch content { case "0": return false, nil case "1": return true, nil default: return false, fmt.Errorf("invalid status file content, expected \"0\" or \"1\", got \"%s\"", content) } } func (d *command) ToggleStatus() error { d.lock.Lock() defer d.lock.Unlock() cmd := d.makeCommand(d.toggleStatusCmd) err := cmd.Run() if err != nil { return fmt.Errorf("failed to run command: %w", err) } return nil } func (d *command) makeCommand(command string) *exec.Cmd { args := make([]string, len(d.shell)) copy(args, d.shell) for i := range args { args[i] = strings.ReplaceAll(args[i], "$COMMAND", command) } return exec.Command(args[0], args[1:]...) } func (d *command) String() string { return "command" }