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
this is a demonstration of how to
introspection is optional including exporting manager's introspection, counter's introspections and re-exporting manager's introspection after having created new objects. you can still use these objects without any issue but some tools like d-feet may introspect object recursively from / and list all objects found on the path. if you do not update the parent objectpath, your child object will not show up in these tools.
这是一小段在dbus中
以提供服务的例子。
内省(introspection),包括发布父路径的内省接口、子路径的内省接口或者重新发布父路径的内省接口,这都是可选不必要的。无论有没有内省,dbus都可以找到你的对象、调用接口,其他的dbus连接也都可以请求到你对象上的函数、监听到事件,不会有任何问题。但是有一些工具,举个例子d-feet,他们会从根目录/一层层检查内省接口,以此枚举对象,不用内省的最主要的问题就是这些工具无法去很方便的去展示有什么东西。类似的,增加对象后,如果不更新父路径,最主要的问题也就是工具上面无论怎么刷新都不会有新的对象,但是实际上还是可以在代码中访问到对象的