github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/automation/trigger_manager.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 automation
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"sync"
    25  
    26  	"github.com/e154/smart-home/adaptors"
    27  	"github.com/e154/smart-home/common/apperr"
    28  	"github.com/e154/smart-home/common/events"
    29  	m "github.com/e154/smart-home/models"
    30  	"github.com/e154/smart-home/plugins/triggers"
    31  	"github.com/e154/smart-home/system/bus"
    32  	"github.com/e154/smart-home/system/scripts"
    33  	"github.com/e154/smart-home/system/supervisor"
    34  	"github.com/pkg/errors"
    35  	"go.uber.org/atomic"
    36  )
    37  
    38  type triggerManager struct {
    39  	eventBus       bus.Bus
    40  	scriptService  scripts.ScriptService
    41  	supervisor     supervisor.Supervisor
    42  	adaptors       *adaptors.Adaptors
    43  	isStarted      *atomic.Bool
    44  	rawPlugin      triggers.IGetTrigger
    45  	triggerCounter *atomic.Uint64
    46  	sync.Mutex
    47  	triggers map[int64]*Trigger
    48  }
    49  
    50  func NewTriggerManager(eventBus bus.Bus,
    51  	scriptService scripts.ScriptService,
    52  	sup supervisor.Supervisor,
    53  	adaptors *adaptors.Adaptors) (manager *triggerManager) {
    54  	manager = &triggerManager{
    55  		eventBus:       eventBus,
    56  		scriptService:  scriptService,
    57  		supervisor:     sup,
    58  		adaptors:       adaptors,
    59  		isStarted:      atomic.NewBool(false),
    60  		triggers:       make(map[int64]*Trigger),
    61  		triggerCounter: atomic.NewUint64(0),
    62  	}
    63  	return
    64  }
    65  
    66  // Start ...
    67  func (a *triggerManager) Start() {
    68  
    69  	a.load()
    70  	_ = a.eventBus.Subscribe("system/automation/triggers/+", a.eventHandler, false)
    71  	_ = a.eventBus.Subscribe("system/models/triggers/+", a.eventHandler, false)
    72  	a.isStarted.Store(true)
    73  
    74  	log.Info("Started")
    75  }
    76  
    77  // Shutdown ...
    78  func (a *triggerManager) Shutdown() {
    79  
    80  	a.unload()
    81  	_ = a.eventBus.Unsubscribe("system/automation/triggers/+", a.eventHandler)
    82  	_ = a.eventBus.Unsubscribe("system/models/triggers/+", a.eventHandler)
    83  
    84  	log.Info("Shutdown")
    85  }
    86  
    87  func (a *triggerManager) eventHandler(_ string, msg interface{}) {
    88  
    89  	switch v := msg.(type) {
    90  	case events.CommandEnableTrigger:
    91  		go a.updateTrigger(v.Id)
    92  	case events.CommandDisableTrigger:
    93  		go a.removeTrigger(v.Id)
    94  
    95  	case events.EventUpdatedTriggerModel:
    96  		go a.updateTrigger(v.Id)
    97  	case events.EventCreatedTriggerModel:
    98  		go a.updateTrigger(v.Id)
    99  	case events.EventRemovedTriggerModel:
   100  		go a.removeTrigger(v.Id)
   101  	}
   102  }
   103  
   104  func (a *triggerManager) load() {
   105  	if a.isStarted.Load() {
   106  		return
   107  	}
   108  
   109  	// load triggers plugin
   110  	plugin, err := a.supervisor.GetPlugin(triggers.Name)
   111  	if err != nil {
   112  		log.Error(err.Error())
   113  		return
   114  	}
   115  
   116  	if rawPlugin, ok := plugin.(triggers.IGetTrigger); ok {
   117  		a.rawPlugin = rawPlugin
   118  	} else {
   119  		log.Fatal("bad static cast triggers.IGetTrigger")
   120  	}
   121  
   122  	const perPage int64 = 500
   123  	var page int64 = 0
   124  LOOP:
   125  	triggers, _, err := a.adaptors.Trigger.List(context.Background(), perPage, page*perPage, "", "", true)
   126  	if err != nil {
   127  		log.Error(err.Error())
   128  		return
   129  	}
   130  	for _, trigger := range triggers {
   131  		if err = a.addTrigger(trigger); err != nil {
   132  			log.Warn(err.Error())
   133  		}
   134  	}
   135  	if len(triggers) != 0 {
   136  		page++
   137  		goto LOOP
   138  	}
   139  
   140  	log.Info("Loaded ...")
   141  }
   142  
   143  func (a *triggerManager) unload() {
   144  	if !a.isStarted.Load() {
   145  		return
   146  	}
   147  
   148  	for id := range a.triggers {
   149  		a.removeTrigger(id)
   150  	}
   151  	a.isStarted.Store(false)
   152  
   153  	log.Info("Unloaded ...")
   154  }
   155  
   156  // addTrigger ...
   157  func (a *triggerManager) addTrigger(model *m.Trigger) (err error) {
   158  
   159  	defer func() {
   160  		if err == nil {
   161  			a.triggerCounter.Inc()
   162  		}
   163  	}()
   164  
   165  	if _, ok := a.triggers[model.Id]; ok {
   166  		err = errors.Wrap(apperr.ErrInternal, fmt.Sprintf("trigger %s exist", model.Name))
   167  		return
   168  	}
   169  
   170  	if !model.Enabled {
   171  		return
   172  	}
   173  
   174  	var trigger *Trigger
   175  	if trigger, err = NewTrigger(a.eventBus, a.scriptService, model, a.rawPlugin); err != nil {
   176  		log.Error(err.Error())
   177  		return
   178  	}
   179  
   180  	a.Lock()
   181  	a.triggers[model.Id] = trigger
   182  	a.Unlock()
   183  
   184  	trigger.Start()
   185  
   186  	return
   187  }
   188  
   189  // removeTrigger ...
   190  func (a *triggerManager) removeTrigger(id int64) {
   191  	a.Lock()
   192  	defer a.Unlock()
   193  	//log.Infof("remove trigger id:%d", id)
   194  
   195  	trigger, ok := a.triggers[id]
   196  	if !ok {
   197  		return
   198  	}
   199  	trigger.Stop()
   200  	delete(a.triggers, id)
   201  
   202  	a.triggerCounter.Dec()
   203  }
   204  
   205  // updateTrigger ...
   206  func (a *triggerManager) updateTrigger(id int64) {
   207  	//log.Infof("reload trigger id:%d", id)
   208  	a.removeTrigger(id)
   209  
   210  	trigger, err := a.adaptors.Trigger.GetById(context.Background(), id)
   211  	if err != nil {
   212  		return
   213  	}
   214  
   215  	a.addTrigger(trigger)
   216  }
   217  
   218  func (a *triggerManager) IsLoaded(id int64) (loaded bool) {
   219  	a.Lock()
   220  	_, loaded = a.triggers[id]
   221  	a.Unlock()
   222  	return
   223  }