From 34c6a2e03d3dc17dd7a2b0a95d5b659864660476 Mon Sep 17 00:00:00 2001 From: "kayos@tcp.direct" Date: Sun, 20 Aug 2023 21:36:37 -0700 Subject: [PATCH] better dump command and prolly other stuff (???) --- go.mod | 1 + go.sum | 2 + internal/cli/cli.go | 4 +- internal/cli/commands.go | 233 ++++++++++++++++++++++++++++------ internal/cli/completer.go | 1 + internal/cli/set.go | 17 +-- internal/ssh/serve.go | 5 +- internal/ziggy/multiplexor.go | 52 +++++++- 8 files changed, 259 insertions(+), 56 deletions(-) diff --git a/go.mod b/go.mod index 8ed6061..45a0d85 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( ) require ( + dario.cat/mergo v1.0.0 // indirect dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037 // indirect gioui.org v0.0.0-20230404150518-c0d3f67b0468 // indirect gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect diff --git a/go.sum b/go.sum index 7942388..44c342a 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037 h1:+PdD6GLKejR9DizMAKT5DpSAkKswvZrurk1/eEt9+pw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 48f91f0..25f4f77 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -28,7 +28,7 @@ var ( var noHist = map[string]bool{"clear": true, "exit": true, "quit": true} -// Interpret is where we will actuall define our Commands +// Executor executes commands func Executor(cmd string) { if log == nil { log = config.StartLogger() @@ -94,6 +94,8 @@ func Executor(cmd string) { } if newlevel, ok := levelsdebug[args[1]]; ok { zerolog.SetGlobalLevel(newlevel) + nl := log.Level(newlevel) + log = &nl return } if args[1] == "debugcli" || args[1] == "cli" { diff --git a/internal/cli/commands.go b/internal/cli/commands.go index ceff67e..81fc157 100644 --- a/internal/cli/commands.go +++ b/internal/cli/commands.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "dario.cat/mergo" "github.com/davecgh/go-spew/spew" "github.com/yunginnanet/huego" @@ -53,6 +54,15 @@ func newZiggsCommand(react reactor, desc string, requires int, aliases ...string var Commands = make(map[string]*ziggsCommand) +func cmdRefresh(br *ziggy.Bridge, args []string) error { + ziggy.NeedsUpdate() + ziggy.GetGroupMap() + ziggy.GetLightMap() + ziggy.GetSceneMap() + ziggy.GetSensorMap() + return nil +} + func cmdList(br *ziggy.Bridge, args []string) error { var runs = []reactor{cmdLights, cmdGroups, cmdScenes, cmdSensors} var cont = false @@ -78,12 +88,33 @@ func cmdList(br *ziggy.Bridge, args []string) error { } func cmdScenes(br *ziggy.Bridge, args []string) error { + var targGroup *ziggy.HueGroup + if len(args) > 0 { + targGroup = ziggy.GetGroupMap()[args[0]] + } scenes, err := br.GetScenes() if err != nil { return err } for _, scene := range scenes { - log.Info().Str("caller", strings.Split(br.Host, "://")[1]). + scGrNum, numErr := strconv.Atoi(scene.Group) + if numErr != nil { + continue + } + grp, gerr := br.GetGroup(scGrNum) + if gerr == nil { + scene.Group = grp.Name + } + if gerr == nil && targGroup != nil { + if targGroup.ID != scGrNum { + continue + } + } + /*for _, lstate := range scene.LightStates { + // lstate. + }*/ + + log.Info().Str("caller", scene.Group). Str("ID", scene.ID).Msgf("Scene: %s", scene.Name) log.Trace().Caller().Msgf("%v", spew.Sprint(scene)) } @@ -175,6 +206,9 @@ func cmdDelete(br *ziggy.Bridge, args []string) error { if len(args) < 2 { return errors.New("not enough arguments") } + + defer ziggy.NeedsUpdate() + confirm := func() bool { log.Info().Msgf("Are you sure you want to delete the %s identified as %s? [y/N]", args[0], args[1]) var input string @@ -231,6 +265,9 @@ func cmdCp(br *ziggy.Bridge, args []string) error { if len(args) < 2 { return errors.New("not enough arguments") } + + defer ziggy.NeedsUpdate() + var ( targetLight *ziggy.HueLight targetGroup *ziggy.HueGroup @@ -267,6 +304,9 @@ func cmdRename(br *ziggy.Bridge, args []string) error { target renameable err error ) + + defer ziggy.NeedsUpdate() + switch args[0] { case "light", "l": target, err = br.FindLight(args[1]) @@ -287,6 +327,19 @@ func cmdRename(br *ziggy.Bridge, args []string) error { return target.Rename(args[2]) } +type dumpTarget struct { + Name string + Object any + ParentFolder string +} + +func newTarget(name string, obj any) dumpTarget { + return dumpTarget{ + Name: name, + Object: obj, + } +} + // cmdDump exports a target object to a JSON file func cmdDump(br *ziggy.Bridge, args []string) error { if len(args) < 2 && args[0] != "all" && args[0] != "conf" && args[0] != "groups" && @@ -296,12 +349,13 @@ func cmdDump(br *ziggy.Bridge, args []string) error { return errors.New("not enough arguments") } var ( - target interface{} - name string - err error + targets []dumpTarget + err error ) switch args[0] { case "light", "l": + var name string + var target any target, err = br.FindLight(args[1]) if err != nil { return err @@ -312,65 +366,162 @@ func cmdDump(br *ziggy.Bridge, args []string) error { } else { name = lght.Name } + targets = append(targets, newTarget(name, target)) case "group", "g": - target, err = br.FindGroup(args[1]) + var target any + target, err = br.GetGroups() if err != nil { return err } - name = target.(*ziggy.HueGroup).Name + for _, g := range target.([]huego.Group) { + if !strings.EqualFold(g.Name, args[1]) && !strings.EqualFold(strconv.Itoa(g.ID), args[1]) { + continue + } + targets = append(targets, newTarget(g.Name, g)) + } + case "groups": + var target any + target, err = br.GetGroups() + if err != nil { + return err + } + for _, g := range target.([]huego.Group) { + targets = append(targets, newTarget(g.Name, g)) + } case "schedule": - return errors.New("not implemented") - case "rule": - return errors.New("not implemented") - case "rules": - target, err = br.GetRules() - if err != nil { - return err - } - case "scenes": - target, err = br.GetScenes() - if err != nil { - return err - } - case "schedules": + var target any target, err = br.GetSchedules() if err != nil { return err } + for _, s := range target.([]huego.Schedule) { + if !strings.EqualFold(s.Name, args[1]) && !strings.EqualFold(strconv.Itoa(s.ID), args[1]) { + continue + } + name := s.Name + targets = append(targets, newTarget(name, s)) + break + } + case "rule": + var target any + target, err = br.GetRules() + if err != nil { + return err + } + for _, r := range target.([]huego.Rule) { + if !strings.EqualFold(r.Name, args[1]) && !strings.EqualFold(strconv.Itoa(r.ID), args[1]) { + continue + } + name := r.Name + targets = append(targets, newTarget(name, r)) + break + } + case "rules": + var target any + target, err = br.GetRules() + if err != nil { + return err + } + name := "rules" + targets = append(targets, newTarget(name, target)) + case "scenes": + var scenes []huego.Scene + scenes, err = br.GetScenes() + if err != nil { + return err + } + for _, s := range scenes { + var scene *huego.Scene + if scene, err = br.GetScene(s.ID); err != nil { + return err + } + var group *huego.Group + var num int + if num, err = strconv.Atoi(scene.Group); err != nil { + group = nil + } + group, err = br.GetGroup(num) + if err != nil { + group = nil + } + + sc := newTarget(scene.Name, scene) + if group != nil { + sc.ParentFolder = group.Name + } + + if mergo.Merge(&sc.Object, s); err != nil { + return err + } + targets = append(targets, sc) + } + case "schedules": + var target any + target, err = br.GetSchedules() + if err != nil { + return err + } + for _, s := range target.([]huego.Schedule) { + name := s.Name + targets = append(targets, newTarget(name, s)) + } case "sensor": - return errors.New("not implemented") + var target any + target, err = br.GetSensors() + if err != nil { + return err + } + for _, sensor := range target.([]huego.Sensor) { + name := sensor.Name + targets = append(targets, newTarget(name, sensor)) + } case "bridge", "all": - target = br - name = br.Info.Name + targets = append(targets, newTarget("bridge", br)) case "config": var conf *huego.Config conf, err = br.GetConfig() if err != nil { return err } - target = conf - name = br.Info.BridgeID + name := br.Info.BridgeID + targets = append(targets, newTarget(name, conf)) default: return errors.New("invalid target type") } - js, err := json.Marshal(target) - if err != nil { - return err + for _, target := range targets { + var wd string + wd, err = os.Getwd() + if err != nil { + panic(err) + } + wd = filepath.Join(wd, "dump", args[0]) + parentFolder := "" + if target.ParentFolder != "" { + targetDir := filepath.Join(wd, target.ParentFolder) + if err = os.MkdirAll(targetDir, 0o755); err != nil { // #nosec + return err + } + log.Info().Msgf("created folder: %s", targetDir) + parentFolder = target.ParentFolder + } + var js []byte + if js, err = json.Marshal(target.Object); err != nil { + return err + } + fpath := filepath.Join(wd, target.Name+".json") + if parentFolder != "" { + fpath = filepath.Join(wd, parentFolder, target.Name+".json") + } + if err = os.WriteFile(fpath, js, 0o666); err != nil { + return err + } + // get current working directory + + log.Info().Msgf("dumped to: %s", fpath) } - name = name + ".json" - if err := os.WriteFile(name, js, 0o666); err != nil { - return err - } - // get current working directory - wd, err := os.Getwd() - if err != nil { - // we can't get the current working directory, but the file was written successfully (in theory). - // so just return nil without printing the path - return nil - } - log.Info().Msgf("dumped to: %s", filepath.Join(wd, name)) return nil + } func cmdGetFullState(br *ziggy.Bridge, args []string) error { @@ -482,6 +633,7 @@ func cmdLoad(br *ziggy.Bridge, args []string) error { } func cmdAdopt(br *ziggy.Bridge, args []string) error { + defer ziggy.NeedsUpdate() resp, err := br.FindLights() if err != nil { return err @@ -507,6 +659,7 @@ func cmdAdopt(br *ziggy.Bridge, args []string) error { } func cmdReboot(br *ziggy.Bridge, args []string) error { + defer ziggy.NeedsUpdate() resp, err := br.UpdateConfig(&huego.Config{Reboot: true}) if err != nil { return err diff --git a/internal/cli/completer.go b/internal/cli/completer.go index f218f3d..56f4f1f 100644 --- a/internal/cli/completer.go +++ b/internal/cli/completer.go @@ -89,6 +89,7 @@ var ( ) func init() { + Commands["refresh"] = newZiggsCommand(cmdRefresh, "refresh cached bridge data", 0) Commands["ls"] = newZiggsCommand(cmdList, "list all lights, groups, scenes, rules, and schedules", 0) Commands["schedules"] = newZiggsCommand(cmdSchedules, "list schedules", 0, "lssched", "crontab") diff --git a/internal/cli/set.go b/internal/cli/set.go index dbac4ec..3319ba0 100644 --- a/internal/cli/set.go +++ b/internal/cli/set.go @@ -232,16 +232,17 @@ func cmdSet(bridge *ziggy.Bridge, args []string) error { return ErrNotEnoughArguments } targetScene := strings.TrimSpace(args[argHead]) + log.Debug().Msgf("target scene: %s", targetScene) actions = append(actions, func() error { - err := target.Scene(targetScene) - if err != nil { - targetScene = ziggy.GetSceneMap()[targetScene].ID - err = target.Scene(targetScene) - if err != nil { - err = fmt.Errorf("failed to set scene: %w", err) - } + zhg := target.(*ziggy.HueGroup) + if zhg == nil { + return errors.New("target is not a group") } - return err + if ts := ziggy.GetSceneMap()[targetScene]; ts != nil { + ts.Recall(zhg.ID) + return nil + } + return fmt.Errorf("scene %s not found", targetScene) }) default: diff --git a/internal/ssh/serve.go b/internal/ssh/serve.go index e1c2d48..90ff89b 100644 --- a/internal/ssh/serve.go +++ b/internal/ssh/serve.go @@ -11,7 +11,6 @@ import ( "git.tcp.direct/kayos/common/squish" "github.com/rs/zerolog" - "git.tcp.direct/kayos/ziggs/internal/cli" "git.tcp.direct/kayos/ziggs/internal/config" "github.com/charmbracelet/ssh" @@ -47,7 +46,8 @@ func (s *Server) checkAuth(h ssh.Handler) ssh.Handler { func (s *Server) ziggssh(h ssh.Handler) ssh.Handler { return func(ss ssh.Session) { - cli.StartCLI() + ss. + cli.StartCLI() h(ss) } } @@ -64,7 +64,6 @@ func (s *Server) ListenAndServe() error { wish.WithMiddleware( logging.Middleware(), s.checkAuth, - ), ) if err != nil { diff --git a/internal/ziggy/multiplexor.go b/internal/ziggy/multiplexor.go index 7b069d7..e64e5dd 100644 --- a/internal/ziggy/multiplexor.go +++ b/internal/ziggy/multiplexor.go @@ -8,8 +8,28 @@ type Multiplex struct { bridges []*Bridge } +var ( + lightMap map[string]*HueLight + groupMap map[string]*HueGroup + sensorMap map[string]*HueSensor + sceneMap map[string]*HueScene + needsUpdate = 4 +) + +func NeedsUpdate() { + needsUpdate = 4 +} + func GetLightMap() map[string]*HueLight { - var lightMap = make(map[string]*HueLight) + if needsUpdate == 0 { + return lightMap + } + + defer func() { + needsUpdate-- + }() + + lightMap = make(map[string]*HueLight) for _, c := range Lucifer.Bridges { ls, err := c.GetLights() if err != nil { @@ -33,7 +53,15 @@ func GetLightMap() map[string]*HueLight { } func GetGroupMap() map[string]*HueGroup { - var groupMap = make(map[string]*HueGroup) + if needsUpdate == 0 { + return groupMap + } + + defer func() { + needsUpdate-- + }() + + groupMap = make(map[string]*HueGroup) for _, c := range Lucifer.Bridges { gs, err := c.GetGroups() if err != nil { @@ -59,7 +87,15 @@ func GetGroupMap() map[string]*HueGroup { } func GetSensorMap() map[string]*HueSensor { - var sensorMap = make(map[string]*HueSensor) + if needsUpdate == 0 { + return sensorMap + } + + defer func() { + needsUpdate-- + }() + + sensorMap = make(map[string]*HueSensor) for _, c := range Lucifer.Bridges { ss, err := c.GetSensors() if err != nil { @@ -83,7 +119,15 @@ func GetSensorMap() map[string]*HueSensor { } func GetSceneMap() map[string]*HueScene { - var sceneMap = make(map[string]*HueScene) + if needsUpdate == 0 { + return sceneMap + } + + defer func() { + needsUpdate-- + }() + + sceneMap = make(map[string]*HueScene) for _, c := range Lucifer.Bridges { scs, err := c.GetScenes() if err != nil {