mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2024-12-09 07:00:46 +01:00
343 lines
7.5 KiB
Go
343 lines
7.5 KiB
Go
|
package dbus
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
)
|
||
|
|
||
|
func newIntrospectIntf(h *defaultHandler) *exportedIntf {
|
||
|
methods := make(map[string]Method)
|
||
|
methods["Introspect"] = exportedMethod{
|
||
|
reflect.ValueOf(func(msg Message) (string, *Error) {
|
||
|
path := msg.Headers[FieldPath].value.(ObjectPath)
|
||
|
return h.introspectPath(path), nil
|
||
|
}),
|
||
|
}
|
||
|
return newExportedIntf(methods, true)
|
||
|
}
|
||
|
|
||
|
//NewDefaultHandler returns an instance of the default
|
||
|
//call handler. This is useful if you want to implement only
|
||
|
//one of the two handlers but not both.
|
||
|
//
|
||
|
// Deprecated: this is the default value, don't use it, it will be unexported.
|
||
|
func NewDefaultHandler() *defaultHandler {
|
||
|
h := &defaultHandler{
|
||
|
objects: make(map[ObjectPath]*exportedObj),
|
||
|
defaultIntf: make(map[string]*exportedIntf),
|
||
|
}
|
||
|
h.defaultIntf["org.freedesktop.DBus.Introspectable"] = newIntrospectIntf(h)
|
||
|
return h
|
||
|
}
|
||
|
|
||
|
type defaultHandler struct {
|
||
|
sync.RWMutex
|
||
|
objects map[ObjectPath]*exportedObj
|
||
|
defaultIntf map[string]*exportedIntf
|
||
|
}
|
||
|
|
||
|
func (h *defaultHandler) PathExists(path ObjectPath) bool {
|
||
|
_, ok := h.objects[path]
|
||
|
return ok
|
||
|
}
|
||
|
|
||
|
func (h *defaultHandler) introspectPath(path ObjectPath) string {
|
||
|
subpath := make(map[string]struct{})
|
||
|
var xml bytes.Buffer
|
||
|
xml.WriteString("<node>")
|
||
|
for obj := range h.objects {
|
||
|
p := string(path)
|
||
|
if p != "/" {
|
||
|
p += "/"
|
||
|
}
|
||
|
if strings.HasPrefix(string(obj), p) {
|
||
|
node_name := strings.Split(string(obj[len(p):]), "/")[0]
|
||
|
subpath[node_name] = struct{}{}
|
||
|
}
|
||
|
}
|
||
|
for s := range subpath {
|
||
|
xml.WriteString("\n\t<node name=\"" + s + "\"/>")
|
||
|
}
|
||
|
xml.WriteString("\n</node>")
|
||
|
return xml.String()
|
||
|
}
|
||
|
|
||
|
func (h *defaultHandler) LookupObject(path ObjectPath) (ServerObject, bool) {
|
||
|
h.RLock()
|
||
|
defer h.RUnlock()
|
||
|
object, ok := h.objects[path]
|
||
|
if ok {
|
||
|
return object, ok
|
||
|
}
|
||
|
|
||
|
// If an object wasn't found for this exact path,
|
||
|
// look for a matching subtree registration
|
||
|
subtreeObject := newExportedObject()
|
||
|
path = path[:strings.LastIndex(string(path), "/")]
|
||
|
for len(path) > 0 {
|
||
|
object, ok = h.objects[path]
|
||
|
if ok {
|
||
|
for name, iface := range object.interfaces {
|
||
|
// Only include this handler if it registered for the subtree
|
||
|
if iface.isFallbackInterface() {
|
||
|
subtreeObject.interfaces[name] = iface
|
||
|
}
|
||
|
}
|
||
|
break
|
||
|
}
|
||
|
|
||
|
path = path[:strings.LastIndex(string(path), "/")]
|
||
|
}
|
||
|
|
||
|
for name, intf := range h.defaultIntf {
|
||
|
if _, exists := subtreeObject.interfaces[name]; exists {
|
||
|
continue
|
||
|
}
|
||
|
subtreeObject.interfaces[name] = intf
|
||
|
}
|
||
|
|
||
|
return subtreeObject, true
|
||
|
}
|
||
|
|
||
|
func (h *defaultHandler) AddObject(path ObjectPath, object *exportedObj) {
|
||
|
h.Lock()
|
||
|
h.objects[path] = object
|
||
|
h.Unlock()
|
||
|
}
|
||
|
|
||
|
func (h *defaultHandler) DeleteObject(path ObjectPath) {
|
||
|
h.Lock()
|
||
|
delete(h.objects, path)
|
||
|
h.Unlock()
|
||
|
}
|
||
|
|
||
|
type exportedMethod struct {
|
||
|
reflect.Value
|
||
|
}
|
||
|
|
||
|
func (m exportedMethod) Call(args ...interface{}) ([]interface{}, error) {
|
||
|
t := m.Type()
|
||
|
|
||
|
params := make([]reflect.Value, len(args))
|
||
|
for i := 0; i < len(args); i++ {
|
||
|
params[i] = reflect.ValueOf(args[i]).Elem()
|
||
|
}
|
||
|
|
||
|
ret := m.Value.Call(params)
|
||
|
var err error
|
||
|
nilErr := false // The reflection will find almost-nils, let's only pass back clean ones!
|
||
|
if t.NumOut() > 0 {
|
||
|
if e, ok := ret[t.NumOut()-1].Interface().(*Error); ok { // godbus *Error
|
||
|
nilErr = ret[t.NumOut()-1].IsNil()
|
||
|
ret = ret[:t.NumOut()-1]
|
||
|
err = e
|
||
|
} else if ret[t.NumOut()-1].Type().Implements(errType) { // Go error
|
||
|
i := ret[t.NumOut()-1].Interface()
|
||
|
if i == nil {
|
||
|
nilErr = ret[t.NumOut()-1].IsNil()
|
||
|
} else {
|
||
|
err = i.(error)
|
||
|
}
|
||
|
ret = ret[:t.NumOut()-1]
|
||
|
}
|
||
|
}
|
||
|
out := make([]interface{}, len(ret))
|
||
|
for i, val := range ret {
|
||
|
out[i] = val.Interface()
|
||
|
}
|
||
|
if nilErr || err == nil {
|
||
|
//concrete type to interface nil is a special case
|
||
|
return out, nil
|
||
|
}
|
||
|
return out, err
|
||
|
}
|
||
|
|
||
|
func (m exportedMethod) NumArguments() int {
|
||
|
return m.Value.Type().NumIn()
|
||
|
}
|
||
|
|
||
|
func (m exportedMethod) ArgumentValue(i int) interface{} {
|
||
|
return reflect.Zero(m.Type().In(i)).Interface()
|
||
|
}
|
||
|
|
||
|
func (m exportedMethod) NumReturns() int {
|
||
|
return m.Value.Type().NumOut()
|
||
|
}
|
||
|
|
||
|
func (m exportedMethod) ReturnValue(i int) interface{} {
|
||
|
return reflect.Zero(m.Type().Out(i)).Interface()
|
||
|
}
|
||
|
|
||
|
func newExportedObject() *exportedObj {
|
||
|
return &exportedObj{
|
||
|
interfaces: make(map[string]*exportedIntf),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type exportedObj struct {
|
||
|
mu sync.RWMutex
|
||
|
interfaces map[string]*exportedIntf
|
||
|
}
|
||
|
|
||
|
func (obj *exportedObj) LookupInterface(name string) (Interface, bool) {
|
||
|
if name == "" {
|
||
|
return obj, true
|
||
|
}
|
||
|
obj.mu.RLock()
|
||
|
defer obj.mu.RUnlock()
|
||
|
intf, exists := obj.interfaces[name]
|
||
|
return intf, exists
|
||
|
}
|
||
|
|
||
|
func (obj *exportedObj) AddInterface(name string, iface *exportedIntf) {
|
||
|
obj.mu.Lock()
|
||
|
defer obj.mu.Unlock()
|
||
|
obj.interfaces[name] = iface
|
||
|
}
|
||
|
|
||
|
func (obj *exportedObj) DeleteInterface(name string) {
|
||
|
obj.mu.Lock()
|
||
|
defer obj.mu.Unlock()
|
||
|
delete(obj.interfaces, name)
|
||
|
}
|
||
|
|
||
|
func (obj *exportedObj) LookupMethod(name string) (Method, bool) {
|
||
|
obj.mu.RLock()
|
||
|
defer obj.mu.RUnlock()
|
||
|
for _, intf := range obj.interfaces {
|
||
|
method, exists := intf.LookupMethod(name)
|
||
|
if exists {
|
||
|
return method, exists
|
||
|
}
|
||
|
}
|
||
|
return nil, false
|
||
|
}
|
||
|
|
||
|
func (obj *exportedObj) isFallbackInterface() bool {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func newExportedIntf(methods map[string]Method, includeSubtree bool) *exportedIntf {
|
||
|
return &exportedIntf{
|
||
|
methods: methods,
|
||
|
includeSubtree: includeSubtree,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type exportedIntf struct {
|
||
|
methods map[string]Method
|
||
|
|
||
|
// Whether or not this export is for the entire subtree
|
||
|
includeSubtree bool
|
||
|
}
|
||
|
|
||
|
func (obj *exportedIntf) LookupMethod(name string) (Method, bool) {
|
||
|
out, exists := obj.methods[name]
|
||
|
return out, exists
|
||
|
}
|
||
|
|
||
|
func (obj *exportedIntf) isFallbackInterface() bool {
|
||
|
return obj.includeSubtree
|
||
|
}
|
||
|
|
||
|
//NewDefaultSignalHandler returns an instance of the default
|
||
|
//signal handler. This is useful if you want to implement only
|
||
|
//one of the two handlers but not both.
|
||
|
//
|
||
|
// Deprecated: this is the default value, don't use it, it will be unexported.
|
||
|
func NewDefaultSignalHandler() *defaultSignalHandler {
|
||
|
return &defaultSignalHandler{}
|
||
|
}
|
||
|
|
||
|
type defaultSignalHandler struct {
|
||
|
mu sync.RWMutex
|
||
|
closed bool
|
||
|
signals []*signalChannelData
|
||
|
}
|
||
|
|
||
|
func (sh *defaultSignalHandler) DeliverSignal(intf, name string, signal *Signal) {
|
||
|
sh.mu.RLock()
|
||
|
defer sh.mu.RUnlock()
|
||
|
if sh.closed {
|
||
|
return
|
||
|
}
|
||
|
for _, scd := range sh.signals {
|
||
|
scd.deliver(signal)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (sh *defaultSignalHandler) Terminate() {
|
||
|
sh.mu.Lock()
|
||
|
defer sh.mu.Unlock()
|
||
|
if sh.closed {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
for _, scd := range sh.signals {
|
||
|
scd.close()
|
||
|
close(scd.ch)
|
||
|
}
|
||
|
sh.closed = true
|
||
|
sh.signals = nil
|
||
|
}
|
||
|
|
||
|
func (sh *defaultSignalHandler) AddSignal(ch chan<- *Signal) {
|
||
|
sh.mu.Lock()
|
||
|
defer sh.mu.Unlock()
|
||
|
if sh.closed {
|
||
|
return
|
||
|
}
|
||
|
sh.signals = append(sh.signals, &signalChannelData{
|
||
|
ch: ch,
|
||
|
done: make(chan struct{}),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (sh *defaultSignalHandler) RemoveSignal(ch chan<- *Signal) {
|
||
|
sh.mu.Lock()
|
||
|
defer sh.mu.Unlock()
|
||
|
if sh.closed {
|
||
|
return
|
||
|
}
|
||
|
for i := len(sh.signals) - 1; i >= 0; i-- {
|
||
|
if ch == sh.signals[i].ch {
|
||
|
sh.signals[i].close()
|
||
|
copy(sh.signals[i:], sh.signals[i+1:])
|
||
|
sh.signals[len(sh.signals)-1] = nil
|
||
|
sh.signals = sh.signals[:len(sh.signals)-1]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type signalChannelData struct {
|
||
|
wg sync.WaitGroup
|
||
|
ch chan<- *Signal
|
||
|
done chan struct{}
|
||
|
}
|
||
|
|
||
|
func (scd *signalChannelData) deliver(signal *Signal) {
|
||
|
select {
|
||
|
case scd.ch <- signal:
|
||
|
case <-scd.done:
|
||
|
return
|
||
|
default:
|
||
|
scd.wg.Add(1)
|
||
|
go scd.deferredDeliver(signal)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (scd *signalChannelData) deferredDeliver(signal *Signal) {
|
||
|
select {
|
||
|
case scd.ch <- signal:
|
||
|
case <-scd.done:
|
||
|
}
|
||
|
scd.wg.Done()
|
||
|
}
|
||
|
|
||
|
func (scd *signalChannelData) close() {
|
||
|
close(scd.done)
|
||
|
scd.wg.Wait() // wait until all spawned goroutines return
|
||
|
}
|