github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/scripts/engine_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/e154/smart-home/common"
    26  	"github.com/e154/smart-home/common/events"
    27  	m "github.com/e154/smart-home/models"
    28  	"github.com/e154/smart-home/system/bus"
    29  )
    30  
    31  type EngineWatcher struct {
    32  	eventBus      bus.Bus
    33  	scriptService *scriptService
    34  	f             func(engine *Engine)
    35  	fBefore       func(engine *Engine)
    36  	structures    *Pull
    37  	functions     *Pull
    38  	mx            *sync.RWMutex
    39  	script        *m.Script
    40  	engine        *Engine
    41  }
    42  
    43  func NewEngineWatcher(script *m.Script, s *scriptService, eventBus bus.Bus) *EngineWatcher {
    44  	w := &EngineWatcher{
    45  		eventBus:      eventBus,
    46  		scriptService: s,
    47  		mx:            &sync.RWMutex{},
    48  		script:        script,
    49  		structures:    NewPull(),
    50  		functions:     NewPull(),
    51  	}
    52  
    53  	w.engine, _ = w.scriptService.NewEngine(nil)
    54  	w.structures.Range(func(key, value interface{}) bool {
    55  		w.engine.PushStruct(key.(string), value)
    56  		return true
    57  	})
    58  	w.functions.Range(func(key, value interface{}) bool {
    59  		w.engine.PushFunction(key.(string), value)
    60  		return true
    61  	})
    62  
    63  	if script.Id != 0 {
    64  		_ = eventBus.Subscribe(fmt.Sprintf("system/models/scripts/%d", script.Id), w.eventHandler)
    65  	}
    66  
    67  	return w
    68  }
    69  
    70  func (w *EngineWatcher) Stop() {
    71  	if w.engine.model != nil && w.engine.model.Id != 0 {
    72  		_ = w.eventBus.Unsubscribe(fmt.Sprintf("system/models/scripts/%d", w.engine.model.Id), w.eventHandler)
    73  	}
    74  }
    75  
    76  func (w *EngineWatcher) Spawn(f func(engine *Engine)) {
    77  	w.mx.RLock()
    78  	defer w.mx.RUnlock()
    79  
    80  	w.engine, _ = w.scriptService.NewEngine(&m.Script{
    81  		Id:   w.script.Id,
    82  		Lang: common.ScriptLangJavascript,
    83  	})
    84  	w.structures.Range(func(key, value interface{}) bool {
    85  		w.engine.PushStruct(key.(string), value)
    86  		return true
    87  	})
    88  	w.functions.Range(func(key, value interface{}) bool {
    89  		w.engine.PushFunction(key.(string), value)
    90  		return true
    91  	})
    92  
    93  	if w.fBefore != nil {
    94  		w.fBefore(w.engine)
    95  	}
    96  
    97  	if _, err := w.engine.EvalScript(w.script); err != nil {
    98  		if w.script.Id != 0 {
    99  			log.Errorf("script id: %d, %s", w.script.Id, err.Error())
   100  		} else {
   101  			log.Error(err.Error())
   102  		}
   103  	}
   104  
   105  	if f != nil {
   106  		w.f = f
   107  		w.f(w.engine)
   108  	}
   109  }
   110  
   111  func (w *EngineWatcher) BeforeSpawn(f func(engine *Engine)) {
   112  	if f == nil {
   113  		return
   114  	}
   115  	w.mx.Lock()
   116  	defer w.mx.Unlock()
   117  	w.fBefore = f
   118  }
   119  
   120  func (w *EngineWatcher) Engine() *Engine {
   121  	w.mx.RLock()
   122  	defer w.mx.RUnlock()
   123  	return w.engine
   124  }
   125  
   126  // eventHandler ...
   127  func (w *EngineWatcher) eventHandler(_ string, message interface{}) {
   128  
   129  	switch msg := message.(type) {
   130  	case events.EventUpdatedScriptModel:
   131  		go w.eventUpdatedScript(msg)
   132  	case events.EventRemovedScriptModel:
   133  		go w.eventScriptDeleted(msg)
   134  	}
   135  }
   136  
   137  func (w *EngineWatcher) eventUpdatedScript(msg events.EventUpdatedScriptModel) {
   138  
   139  	if msg.Script == nil {
   140  		return
   141  	}
   142  
   143  	w.script = msg.Script
   144  
   145  	w.Spawn(w.f)
   146  
   147  	log.Infof("script '%s' (%d) updated", msg.Script.Name, msg.ScriptId)
   148  }
   149  
   150  func (w *EngineWatcher) eventScriptDeleted(msg events.EventRemovedScriptModel) {
   151  	if w.engine.model != nil {
   152  		_ = w.eventBus.Unsubscribe(fmt.Sprintf("system/models/scripts/%d", w.script.Id), w.eventHandler)
   153  	}
   154  
   155  	var err error
   156  	if w.engine, err = w.scriptService.NewEngine(nil); err != nil {
   157  		log.Error(err.Error())
   158  		return
   159  	}
   160  }
   161  
   162  func (w *EngineWatcher) PushStruct(name string, str interface{}) {
   163  	w.structures.Push(name, str)
   164  	if w.engine != nil {
   165  		w.engine.PushStruct(name, str)
   166  	}
   167  }
   168  
   169  func (w *EngineWatcher) PopStruct(name string) {
   170  	w.structures.Pop(name)
   171  }
   172  
   173  func (w *EngineWatcher) PushFunction(name string, f interface{}) {
   174  	w.functions.Push(name, f)
   175  	if w.engine != nil {
   176  		w.engine.PushFunction(name, f)
   177  	}
   178  }
   179  
   180  func (w *EngineWatcher) PopFunction(name string) {
   181  	w.functions.Pop(name)
   182  }