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  }