|
|
@ -3,143 +3,105 @@ package services |
|
|
|
import ( |
|
|
|
lucifer3 "git.aiterp.net/lucifer3/server" |
|
|
|
"git.aiterp.net/lucifer3/server/commands" |
|
|
|
"git.aiterp.net/lucifer3/server/device" |
|
|
|
"git.aiterp.net/lucifer3/server/events" |
|
|
|
"git.aiterp.net/lucifer3/server/internal/gentools" |
|
|
|
"reflect" |
|
|
|
"strings" |
|
|
|
"github.com/gobwas/glob" |
|
|
|
"sync" |
|
|
|
) |
|
|
|
|
|
|
|
func Resolver() lucifer3.Service { |
|
|
|
return &resolver{ |
|
|
|
tags: make(map[string][]string), |
|
|
|
names: make(map[string][]string), |
|
|
|
func NewResolver() *Resolver { |
|
|
|
return &Resolver{ |
|
|
|
pointers: make([]*device.Pointer, 0, 128), |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
type resolver struct { |
|
|
|
tags map[string][]string |
|
|
|
names map[string][]string |
|
|
|
// Resolver implements both device.Resolver and lucifer3.Service. There should be only one of them as it keeps its
|
|
|
|
// state from event-sourcery.
|
|
|
|
type Resolver struct { |
|
|
|
mu sync.Mutex |
|
|
|
pointers []*device.Pointer |
|
|
|
} |
|
|
|
|
|
|
|
func (r *resolver) Active() bool { |
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
func (r *resolver) HandleEvent(_ *lucifer3.EventBus, event lucifer3.Event) { |
|
|
|
switch event := event.(type) { |
|
|
|
case events.HardwareState: |
|
|
|
// On HardwareState, use the internal name if (and only if) it is not already named.
|
|
|
|
|
|
|
|
if event.InternalName != "" { |
|
|
|
found := false |
|
|
|
for _, ids := range r.names { |
|
|
|
i := gentools.IndexOf(ids, event.ID) |
|
|
|
if i != -1 { |
|
|
|
found = true |
|
|
|
break |
|
|
|
func (r *Resolver) ensure(id string) *device.Pointer { |
|
|
|
for _, ptr := range r.pointers { |
|
|
|
if ptr.ID == id { |
|
|
|
return ptr |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if !found { |
|
|
|
r.names[event.InternalName] = append(r.names[event.InternalName], event.ID) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
r.pointers = append(r.pointers, &device.Pointer{ |
|
|
|
ID: id, |
|
|
|
}) |
|
|
|
|
|
|
|
return r.pointers[len(r.pointers)-1] |
|
|
|
} |
|
|
|
|
|
|
|
func (r *resolver) rerun(bus *lucifer3.EventBus, resolveList *[]string, command interface{}) { |
|
|
|
newList := make([]string, 0, 16) |
|
|
|
|
|
|
|
for _, id := range *resolveList { |
|
|
|
switch { |
|
|
|
case strings.HasPrefix(id, "lucifer:tag:"): |
|
|
|
tag := id[12:] |
|
|
|
if len(newList) == 0 { |
|
|
|
newList = append(newList, r.tags[tag]...) |
|
|
|
} else { |
|
|
|
gentools.AddUniques(&newList, r.tags[tag]...) |
|
|
|
} |
|
|
|
case strings.HasPrefix(id, "lucifer:name:"): |
|
|
|
name := id[13:] |
|
|
|
if strings.HasSuffix(name, "*") { |
|
|
|
prefix := name[:len(name)-1] |
|
|
|
for name, ids := range r.names { |
|
|
|
if strings.HasPrefix(name, prefix) { |
|
|
|
gentools.AddUniques(&newList, ids...) |
|
|
|
} |
|
|
|
} |
|
|
|
} else if strings.HasPrefix(name, "*") { |
|
|
|
suffix := name[1:] |
|
|
|
for name, ids := range r.names { |
|
|
|
if strings.HasSuffix(name, suffix) { |
|
|
|
gentools.AddUniques(&newList, ids...) |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
gentools.AddUniques(&newList, r.names[name]...) |
|
|
|
func (r *Resolver) resolve(pattern string) []*device.Pointer { |
|
|
|
g, err := glob.Compile(pattern, ':') |
|
|
|
if err != nil { |
|
|
|
return []*device.Pointer{} |
|
|
|
} |
|
|
|
|
|
|
|
res := make([]*device.Pointer, 0, 32) |
|
|
|
for _, ptr := range r.pointers { |
|
|
|
if ptr.Matches(g) { |
|
|
|
res = append(res, ptr) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if len(newList) > 0 { |
|
|
|
*resolveList = newList |
|
|
|
return res |
|
|
|
} |
|
|
|
|
|
|
|
bus.RunCommand(reflect.Indirect(reflect.ValueOf(command)).Interface().(lucifer3.Command)) |
|
|
|
func (r *Resolver) Resolve(pattern string) []device.Pointer { |
|
|
|
g, err := glob.Compile(pattern, ':') |
|
|
|
if err != nil { |
|
|
|
return []device.Pointer{} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (r *resolver) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) { |
|
|
|
r.mu.Lock() |
|
|
|
defer r.mu.Unlock() |
|
|
|
|
|
|
|
switch command := command.(type) { |
|
|
|
case commands.Assign: |
|
|
|
r.rerun(bus, &command.IDs, &command) |
|
|
|
case commands.ReplaceScene: |
|
|
|
r.rerun(bus, &command.IDs, &command) |
|
|
|
case commands.ClearScene: |
|
|
|
r.rerun(bus, &command.IDs, &command) |
|
|
|
case commands.SetName: |
|
|
|
if !strings.HasPrefix(command.ID, "lucifer:") { |
|
|
|
for name := range r.names { |
|
|
|
i := gentools.IndexOf(r.names[name], command.ID) |
|
|
|
if i != -1 { |
|
|
|
r.names[name] = append(r.names[name][:i], r.names[name][i+1:]...) |
|
|
|
} |
|
|
|
} |
|
|
|
r.names[command.Name] = append(r.names[command.Name], command.ID) |
|
|
|
res := make([]device.Pointer, 0, 32) |
|
|
|
for _, ptr := range r.pointers { |
|
|
|
if ptr.Matches(g) { |
|
|
|
res = append(res, *ptr) |
|
|
|
} |
|
|
|
case commands.AddTag: |
|
|
|
for _, id := range command.IDs { |
|
|
|
if strings.HasPrefix(id, "lucifer:") { |
|
|
|
continue |
|
|
|
} |
|
|
|
|
|
|
|
found := false |
|
|
|
for _, id2 := range r.tags[command.Tag] { |
|
|
|
if id == id2 { |
|
|
|
found = true |
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
|
if !found { |
|
|
|
r.tags[command.Tag] = append(r.tags[command.Tag], id) |
|
|
|
} |
|
|
|
} |
|
|
|
return res |
|
|
|
} |
|
|
|
|
|
|
|
r.rerun(bus, &command.IDs, &command) |
|
|
|
case commands.RemoveTag: |
|
|
|
for _, id := range command.IDs { |
|
|
|
if strings.HasPrefix(id, "lucifer:") { |
|
|
|
continue |
|
|
|
} |
|
|
|
func (r *Resolver) Active() bool { |
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
for i, id2 := range r.tags[command.Tag] { |
|
|
|
if id == id2 { |
|
|
|
r.tags[command.Tag] = append(r.tags[command.Tag][:i], r.tags[command.Tag][i+1:]...) |
|
|
|
break |
|
|
|
func (r *Resolver) HandleEvent(_ *lucifer3.EventBus, event lucifer3.Event) { |
|
|
|
switch event := event.(type) { |
|
|
|
// On HardwareState, use the internal name if (and only if) it is not already named.
|
|
|
|
case events.HardwareState: |
|
|
|
r.mu.Lock() |
|
|
|
ptr := r.ensure(event.ID) |
|
|
|
if event.InternalName != "" && ptr.Name() == "" { |
|
|
|
ptr.AddAlias("lucifer:name:" + event.InternalName) |
|
|
|
} |
|
|
|
r.mu.Unlock() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (r *Resolver) HandleCommand(_ *lucifer3.EventBus, command lucifer3.Command) { |
|
|
|
switch command := command.(type) { |
|
|
|
case commands.AddAlias: |
|
|
|
r.mu.Lock() |
|
|
|
for _, ptr := range r.resolve(command.Match) { |
|
|
|
ptr.AddAlias(command.Alias) |
|
|
|
} |
|
|
|
r.mu.Unlock() |
|
|
|
|
|
|
|
r.rerun(bus, &command.IDs, &command) |
|
|
|
case commands.RemoveAlias: |
|
|
|
r.mu.Lock() |
|
|
|
for _, ptr := range r.resolve(command.Match) { |
|
|
|
ptr.AddAlias(command.Alias) |
|
|
|
} |
|
|
|
r.mu.Unlock() |
|
|
|
} |
|
|
|
} |
xxxxxxxxxx