github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/scripts/engines_watcher.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package scripts 20 21 import ( 22 "fmt" 23 "sync" 24 25 "github.com/pkg/errors" 26 27 "github.com/e154/smart-home/common/events" 28 m "github.com/e154/smart-home/models" 29 "github.com/e154/smart-home/system/bus" 30 ) 31 32 type EnginesWatcher struct { 33 eventBus bus.Bus 34 scriptService *scriptService 35 f func(engine *Engine) 36 fBefore func(engine *Engine) 37 structures *Pull 38 functions *Pull 39 mx *sync.Mutex 40 engine *Engine 41 scripts []*m.Script 42 } 43 44 func NewEnginesWatcher(scripts []*m.Script, s *scriptService, eventBus bus.Bus) *EnginesWatcher { 45 w := &EnginesWatcher{ 46 eventBus: eventBus, 47 scriptService: s, 48 mx: &sync.Mutex{}, 49 scripts: scripts, 50 structures: NewPull(), 51 functions: NewPull(), 52 } 53 54 for _, script := range scripts { 55 _ = eventBus.Subscribe(fmt.Sprintf("system/models/scripts/%d", script.Id), w.eventHandler) 56 } 57 58 return w 59 } 60 61 func (w *EnginesWatcher) Stop() { 62 w.mx.Lock() 63 defer w.mx.Unlock() 64 for _, script := range w.scripts { 65 if script.Id != 0 { 66 _ = w.eventBus.Unsubscribe(fmt.Sprintf("system/models/scripts/%d", script.Id), w.eventHandler) 67 } 68 } 69 } 70 71 func (w *EnginesWatcher) Spawn(f func(engine *Engine)) { 72 w.mx.Lock() 73 defer w.mx.Unlock() 74 75 w.engine, _ = w.scriptService.NewEngine(nil) 76 w.structures.Range(func(key, value interface{}) bool { 77 w.engine.PushStruct(key.(string), value) 78 return true 79 }) 80 w.functions.Range(func(key, value interface{}) bool { 81 w.engine.PushFunction(key.(string), value) 82 return true 83 }) 84 85 if w.fBefore != nil { 86 w.fBefore(w.engine) 87 } 88 89 for _, script := range w.scripts { 90 if _, err := w.engine.EvalScript(script); err != nil { 91 if script.Id != 0 { 92 log.Errorf("script id: %d, %s", script.Id, err.Error()) 93 } 94 log.Error(err.Error()) 95 } 96 } 97 98 if f != nil { 99 w.f = f 100 w.f(w.engine) 101 } 102 } 103 104 func (w *EnginesWatcher) BeforeSpawn(f func(engine *Engine)) { 105 if f == nil { 106 return 107 } 108 w.mx.Lock() 109 defer w.mx.Unlock() 110 w.fBefore = f 111 } 112 113 func (w *EnginesWatcher) Engine() *Engine { 114 w.mx.Lock() 115 defer w.mx.Unlock() 116 return w.engine 117 } 118 119 func (w *EnginesWatcher) AssertFunction(f string, arg ...interface{}) (result string, err error) { 120 w.mx.Lock() 121 defer w.mx.Unlock() 122 if w.engine == nil { 123 return 124 } 125 result, err = w.engine.AssertFunction(f, arg...) 126 if err != nil { 127 ids := []int64{} 128 for _, script := range w.scripts { 129 ids = append(ids, script.Id) 130 } 131 err = errors.Wrapf(err, "see scripts: %v ", ids) 132 } 133 return 134 } 135 136 // eventHandler ... 137 func (w *EnginesWatcher) eventHandler(_ string, message interface{}) { 138 139 switch msg := message.(type) { 140 case events.EventUpdatedScriptModel: 141 go w.eventUpdatedScript(msg) 142 case events.EventRemovedScriptModel: 143 go w.eventScriptDeleted(msg) 144 } 145 } 146 147 func (w *EnginesWatcher) eventUpdatedScript(msg events.EventUpdatedScriptModel) { 148 149 if msg.Script == nil { 150 return 151 } 152 153 for s, script := range w.scripts { 154 if script.Id == msg.ScriptId { 155 w.scripts[s] = msg.Script 156 break 157 } 158 } 159 160 w.Spawn(w.f) 161 162 log.Infof("script '%s' (%d) updated", msg.Script.Name, msg.ScriptId) 163 } 164 165 func (w *EnginesWatcher) eventScriptDeleted(msg events.EventRemovedScriptModel) { 166 if w.engine.model != nil { 167 _ = w.eventBus.Unsubscribe(fmt.Sprintf("system/models/scripts/%d", msg.ScriptId), w.eventHandler) 168 } 169 170 var scriptName string 171 172 var indexesToDelete []int 173 174 for i, script := range w.scripts { 175 if script.Id == msg.ScriptId { 176 scriptName = script.Name 177 indexesToDelete = append(indexesToDelete, i) 178 break 179 } 180 } 181 182 // remove script 183 for i := len(indexesToDelete) - 1; i >= 0; i-- { 184 index := indexesToDelete[i] 185 w.scripts = append(w.scripts[:index], w.scripts[index+1:]...) 186 } 187 188 log.Infof("script '%s' (%d) deleted", scriptName, msg.ScriptId) 189 190 w.Spawn(w.f) 191 } 192 193 func (w *EnginesWatcher) PushStruct(name string, str interface{}) { 194 w.structures.Push(name, str) 195 if w.engine != nil { 196 w.engine.PushStruct(name, str) 197 } 198 } 199 200 func (w *EnginesWatcher) PopStruct(name string) { 201 w.structures.Pop(name) 202 } 203 204 func (w *EnginesWatcher) PushFunction(name string, f interface{}) { 205 w.functions.Push(name, f) 206 if w.engine != nil { 207 w.engine.PushFunction(name, f) 208 } 209 } 210 211 func (w *EnginesWatcher) PopFunction(name string) { 212 w.functions.Pop(name) 213 }