Skip to content

Instantly share code, notes, and snippets.

@jeffguorg
Last active November 28, 2020 10:52
Show Gist options
  • Save jeffguorg/8741334573e5e2aa6087119d75d7c3a4 to your computer and use it in GitHub Desktop.
Save jeffguorg/8741334573e5e2aa6087119d75d7c3a4 to your computer and use it in GitHub Desktop.
dbus counter demo
package main
import (
"fmt"
"log"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/introspect"
"github.com/godbus/dbus/v5/prop"
"github.com/google/uuid"
)
type Controller struct {
bus *dbus.Conn
counters map[string]*Counter
locker sync.Locker
privateCounter map[string][]string
privateLocker sync.Locker
}
func NewController(busConn *dbus.Conn) *Controller {
return &Controller{
bus: busConn,
counters: make(map[string]*Counter),
locker: &sync.Mutex{},
privateCounter: make(map[string][]string),
privateLocker: &sync.Mutex{},
}
}
func (controller *Controller) Counter(initValue int64) (string, *dbus.Error) {
id := strings.ReplaceAll(uuid.New().String(), "-", "")
path := fmt.Sprintf("/xyz/jeffthecoder/Counter/%v", id)
counter := &Counter{path: dbus.ObjectPath(path), controller: controller, value: initValue, locker: &sync.Mutex{}}
if err := controller.bus.Export(
counter,
dbus.ObjectPath(path),
"xyz.jeffthecoder.Counter",
); err != nil {
return "", &dbus.Error{}
}
if err := controller.bus.Export(introspect.NewIntrospectable(&introspect.Node{
Name: path,
Interfaces: []introspect.Interface{
{
Name: "xyz.jeffthecoder.Counter",
Methods: introspect.Methods(counter),
Properties: []introspect.Property{
{
Name: "value",
Type: "x",
Access: "read",
},
},
},
},
}), dbus.ObjectPath(path),
"org.freedesktop.DBus.Introspectable"); err != nil {
return "", &dbus.Error{}
}
if _, err := prop.Export(controller.bus, dbus.ObjectPath(path), map[string]map[string]*prop.Prop{
"xyz.jeffthecoder.Counter": {
"value": &prop.Prop{
Value: initValue,
Writable: false,
},
},
}); err != nil {
return "", &dbus.Error{}
}
controller.locker.Lock()
controller.counters[id] = counter
controller.locker.Unlock()
node := introspect.Node{
Name: "/xyz/jeffthecoder/Counter",
Interfaces: []introspect.Interface{
{
Name: "xyz.jeffthecoder.Counter.Manager",
Methods: introspect.Methods(controller),
},
},
}
for k := range controller.counters {
node.Children = append(node.Children, introspect.Node{
Name: fmt.Sprint(k),
})
}
if err := controller.bus.Export(introspect.NewIntrospectable(&node), "/xyz/jeffthecoder/Counter",
"org.freedesktop.DBus.Introspectable"); err != nil {
return "", &dbus.Error{}
}
return path, nil
}
func (controller *Controller) PrivateCounter(sender dbus.Sender, initValue int64) (string, *dbus.Error) {
id := strings.ReplaceAll(uuid.New().String(), "-", "")
path := fmt.Sprintf("/xyz/jeffthecoder/Counter/%v/%v", string(sender), id)
counter := &PrivateCounter{
Counter: Counter{path: dbus.ObjectPath(path), controller: controller, value: initValue, locker: &sync.Mutex{}},
Sender: sender,
}
if err := controller.bus.Export(
counter,
dbus.ObjectPath(path),
"xyz.jeffthecoder.Counter",
); err != nil {
return "", &dbus.Error{}
}
if err := controller.bus.Export(introspect.NewIntrospectable(&introspect.Node{
Name: path,
Interfaces: []introspect.Interface{
{
Name: "xyz.jeffthecoder.Counter",
Methods: introspect.Methods(counter),
Properties: []introspect.Property{
{
Name: "value",
Type: "x",
Access: "read",
},
},
},
},
}), dbus.ObjectPath(path),
"org.freedesktop.DBus.Introspectable"); err != nil {
return "", &dbus.Error{}
}
if _, err := prop.Export(controller.bus, dbus.ObjectPath(path), map[string]map[string]*prop.Prop{
"xyz.jeffthecoder.Counter": {
"value": &prop.Prop{
Value: initValue,
Writable: false,
},
},
}); err != nil {
return "", &dbus.Error{}
}
controller.privateLocker.Lock()
if arr, ok := controller.privateCounter[string(sender)]; ok {
controller.privateCounter[string(sender)] = append(arr, id)
} else {
controller.privateCounter[string(sender)] = []string{id}
}
controller.privateLocker.Unlock()
if err := controller.bus.AddMatchSignal(
dbus.WithMatchObjectPath("/org/freedesktop/DBus"),
dbus.WithMatchInterface("org.freedesktop.DBus"),
dbus.WithMatchSender("org.freedesktop.DBus"),
dbus.WithMatchMember("NameLost"),
dbus.WithMatchOption("arg1", string(sender)),
); err != nil {
log.Printf("warning: failed to add match signal for %v, there will be memory leak in the future: %v", sender, err)
}
return path, nil
}
func (controller *Controller) Destroy(id string) *dbus.Error {
controller.locker.Lock()
defer controller.locker.Unlock()
if _, ok := controller.counters[id]; !ok {
return nil
}
delete(controller.counters, id)
path := fmt.Sprintf("/xyz/jeffthecoder/Counter/%v", id)
// unexport
if err := controller.bus.Export(
nil,
dbus.ObjectPath(path),
"xyz.jeffthecoder.Counter",
); err != nil {
return &dbus.Error{}
}
if err := controller.bus.Export(nil, dbus.ObjectPath(path), "org.freedesktop.DBus.Introspectable"); err != nil {
return &dbus.Error{}
}
// re-export controller's introspectable interface
node := introspect.Node{
Name: "/xyz/jeffthecoder/Counter",
Interfaces: []introspect.Interface{
{
Name: "xyz.jeffthecoder.Counter.Manager",
Methods: introspect.Methods(controller),
},
},
}
for k := range controller.counters {
node.Children = append(node.Children, introspect.Node{
Name: fmt.Sprint(k),
})
}
if err := controller.bus.Export(introspect.NewIntrospectable(&node), "/xyz/jeffthecoder/Counter",
"org.freedesktop.DBus.Introspectable"); err != nil {
return &dbus.Error{}
}
return nil
}
func (controller *Controller) Run() {
busSigChan := make(chan *dbus.Signal)
controller.bus.Signal(busSigChan)
procSigChan := make(chan os.Signal)
signal.Notify(procSigChan, syscall.SIGINT)
MainLoop:
for {
select {
case sig := <-busSigChan:
log.Println("signal from dbus: ", sig)
if sig.Name == "NameOwnerChanged" && len(sig.Body) >= 1 {
if sender, ok := sig.Body[0].(string); ok {
controller.privateLocker.Lock()
if counters, ok := controller.privateCounter[sender]; ok {
for _, counter := range counters {
path := fmt.Sprintf("/xyz/jeffthecoder/Counter/%v/%v", string(sender), counter)
_ = controller.bus.Export(nil, dbus.ObjectPath(path), "xyz.jeffthecoder.Counter")
_ = controller.bus.Export(nil, dbus.ObjectPath(path), "org.freedesktop.DBus.Introspectable")
}
delete(controller.privateCounter, sender)
}
controller.privateLocker.Unlock()
_ = controller.bus.RemoveMatchSignal(
dbus.WithMatchObjectPath("/org/freedesktop/DBus"),
dbus.WithMatchInterface("org.freedesktop.DBus"),
dbus.WithMatchSender("org.freedesktop.DBus"),
dbus.WithMatchMember("NameLost"),
dbus.WithMatchOption("arg1", sender),
)
}
}
case sig := <-procSigChan:
log.Println("signal from system: ", sig)
switch sig {
case syscall.SIGINT:
break MainLoop
}
}
}
}
package main
import (
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/prop"
"sync"
)
type Counter struct {
path dbus.ObjectPath
controller *Controller
value int64
locker sync.Locker
}
func (counter *Counter) Add(delta int64) (int64, *dbus.Error) {
counter.locker.Lock()
defer counter.locker.Unlock()
counter.value += delta
if _, err := prop.Export(counter.controller.bus, counter.path, map[string]map[string]*prop.Prop{
"xyz.jeffthecoder.Counter": {
"value": &prop.Prop{
Value: counter.value,
Writable: false,
},
},
}); err != nil {
return 0, &dbus.Error{}
}
return counter.value, nil
}
type PrivateCounter struct {
Counter
Sender dbus.Sender
}
func (privateCounter *PrivateCounter) Add(sender dbus.Sender, delta int64) (int64, *dbus.Error) {
if sender == privateCounter.Sender {
return privateCounter.Counter.Add(delta)
}
return 0, &dbus.ErrMsgNoObject
}
package main
import (
"flag"
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/introspect"
"log"
)
var (
fSystemBus = flag.Bool("system", false, "connect to system bus")
fBusName = flag.String("name", "xyz.jeffthecoder.Counter", "dbus name")
)
func init() {
flag.Parse()
}
func main() {
var (
bus *dbus.Conn
err error
)
if *fSystemBus {
bus, err = dbus.SystemBus()
} else {
bus, err = dbus.SessionBus()
}
if err != nil {
log.Fatal(err)
}
if reply, err := bus.RequestName(*fBusName, dbus.NameFlagAllowReplacement|dbus.NameFlagAllowReplacement); err != nil {
log.Fatal("failure during requesting name: ", err)
} else if reply != dbus.RequestNameReplyPrimaryOwner {
log.Fatal("unexpected result when requesting name: ", reply)
}
defer bus.ReleaseName(*fBusName)
controller := NewController(bus)
if err := bus.Export(controller, "/xyz/jeffthecoder/Counter", "xyz.jeffthecoder.Counter.Manager"); err != nil {
log.Fatal("failed to export object: ", err)
}
if err := bus.Export(introspect.NewIntrospectable(&introspect.Node{
Interfaces: []introspect.Interface{
{
Name: "xyz.jeffthecoder.Counter.Manager",
Methods: introspect.Methods(controller),
},
},
}), "/xyz/jeffthecoder/Counter",
"org.freedesktop.DBus.Introspectable"); err != nil {
}
controller.Run()
}
@jeffguorg
Copy link
Author

jeffguorg commented Nov 28, 2020

又更新了一下,写了篇博客

https://blog.jeffthecoder.xyz/posts/linux-ipc-dbus-golang/

如果其中有比较大的问题,请各位大神不吝赐教

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment