Last active
November 28, 2020 10:52
-
-
Save jeffguorg/8741334573e5e2aa6087119d75d7c3a4 to your computer and use it in GitHub Desktop.
dbus counter demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
又更新了一下,写了篇博客
https://blog.jeffthecoder.xyz/posts/linux-ipc-dbus-golang/
如果其中有比较大的问题,请各位大神不吝赐教