joshuar-go-hass-agent/pkg/linux/dbusx/props.go

144 lines
4.1 KiB
Go

// Copyright (c) 2024 Joshua Rich <joshua.rich@gmail.com>
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
package dbusx
import (
"fmt"
"log/slog"
"github.com/godbus/dbus/v5"
)
//revive:disable:struct-tag
type Property[P any] struct {
bus *Bus `validate:"required"`
path string `validate:"required"`
intr string `validate:"required"`
name string `validate:"required"`
}
// Get retrieves the value of the property from D-Bus. If the property cannot be
// retrieved, a non-nil error is returned.
func (p *Property[P]) Get() (P, error) {
var value P
if err := valid(p); err != nil {
return value, fmt.Errorf("invalid property: %w", err)
}
p.bus.traceLog("Requesting property.", slog.String("path", p.path), slog.String("dest", p.intr), slog.String("property", p.name))
obj := p.bus.getObject(p.intr, p.path)
res, err := obj.GetProperty(p.name)
if err != nil {
return value, fmt.Errorf("%s: unable to retrieve property %s from %s: %w", p.bus.busType.String(), p.name, p.intr, err)
}
value, err = VariantToValue[P](res)
if err != nil {
return value, fmt.Errorf("%s: unable to retrieve property %s from %s: %w", p.bus.busType.String(), p.name, p.intr, err)
}
return value, nil
}
// Set sets the property to the specified value.
func (p *Property[P]) Set(value P) error {
if err := valid(p); err != nil {
return fmt.Errorf("invalid property: %w", err)
}
p.bus.traceLog("Setting property.",
slog.String("path", p.path), slog.String("dest", p.intr), slog.String("property", p.name), slog.Any("value", value))
v := dbus.MakeVariant(value)
obj := p.bus.getObject(p.intr, p.path)
err := obj.SetProperty(p.name, v)
if err != nil {
return fmt.Errorf("%s: unable to set property %s (%s) to %v: %w", p.bus.busType.String(), p.name, p.intr, value, err)
}
return nil
}
func NewProperty[P any](bus *Bus, path, intr, name string) *Property[P] {
return &Property[P]{
bus: bus,
intr: intr,
path: path,
name: name,
}
}
// Properties represents a signal that matches the canonical
// org.freedesktop.DBus.PropertiesChanged signature. These will have an
// interface name together with a list of changed properties (and their values)
// and invalidated property names.
type Properties struct {
Interface string
Changed map[string]dbus.Variant
Invalidated []string
}
// ParsePropertiesChanged treats the given signal body as matching the canonical
// org.freedesktop.DBus.PropertiesChanged signature and will parse it into a
// Properties structure that is easier to use. If the signal body cannot be
// parsed an error will be returned with details of the problem. Adapted from
// https://github.com/godbus/dbus/issues/201
//
//nolint:mnd
func ParsePropertiesChanged(propsChanged []any) (*Properties, error) {
props := &Properties{}
var ok bool
if len(propsChanged) != 3 {
return nil, ErrNotPropChanged
}
props.Interface, ok = propsChanged[0].(string)
if !ok {
return nil, ErrParseInterface
}
props.Changed, ok = propsChanged[1].(map[string]dbus.Variant)
if !ok {
return nil, ErrParseNewProps
}
props.Invalidated, ok = propsChanged[2].([]string)
if !ok {
return nil, ErrParseOldProps
}
return props, nil
}
// HasPropertyChanged checks if the given property has been changed in the given
// signal. The given signal should match the canonical
// org.freedesktop.DBus.PropertiesChanged signature. Returned values will be a
// boolean indicating whether the property changed and the new value (or the
// default value) of the specified type for the property. If any errors
// occurred, a non-nil error will be the third return value.
func HasPropertyChanged[T any](event []any, property string) (changed bool, value T, err error) {
props, err := ParsePropertiesChanged(event)
if err != nil {
return false, value, fmt.Errorf("cannot parse event: %w", err)
}
if variant, changed := props.Changed[property]; changed {
if value, err = VariantToValue[T](variant); err != nil {
return true, value, fmt.Errorf("cannot convert variant to requested value: %w", err)
}
return true, value, nil
}
return false, value, nil
}