github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/cmd/cli/commands/generate/plugin/generator.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2016-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 plugin
    20  
    21  import (
    22  	"os"
    23  	"path"
    24  	"strings"
    25  	"text/template"
    26  
    27  	"github.com/e154/smart-home/common/logger"
    28  
    29  	"github.com/e154/smart-home/cmd/cli/commands/generate"
    30  	"github.com/e154/smart-home/common"
    31  	"github.com/spf13/cobra"
    32  )
    33  
    34  var (
    35  	log = logger.MustGetLogger("plugin")
    36  )
    37  
    38  var actorTpl = `//CODE GENERATED AUTOMATICALLY
    39  
    40  package {{.Package}}
    41  
    42  import (
    43  	"fmt"
    44  	"sync"
    45  
    46  	"{{.Dir}}/common"
    47  	m "{{.Dir}}/models"
    48  	"{{.Dir}}/system/bus"
    49  	"{{.Dir}}/system/supervisor"
    50  )
    51  
    52  type Actor struct {
    53  	entity_manager.BaseActor
    54  	eventBus bus.Bus
    55  }
    56  
    57  func NewActor(entity *m.Entity,
    58  	visor supervisor.Supervisor,
    59  	eventBus bus.Bus) *Actor {
    60  
    61  	name := entity.Id.Name()
    62  
    63  	actor := &Actor{
    64  		BaseActor: supervisor.BaseActor{
    65  			Id:          common.EntityId(fmt.Sprintf("%s.%s", Entity{{.PluginName}}, name)),
    66  			Name:        name,
    67  			Description: "{{.PluginName}} plugin",
    68  			EntityType:  Entity{{.PluginName}},
    69  			AttrMu:      &sync.RWMutex{},
    70  			Attrs:       entity.Attributes,
    71  			Setts:       entity.Settings,
    72  			Supervisor:  visor,
    73  			Items:      NewStates(),
    74  			Actions:     NewActions(),
    75  		},
    76  		eventBus: eventBus,
    77  	}
    78  
    79  	if actor.Setts == nil {
    80  		actor.Setts = NewSettings()
    81  	}
    82  
    83  	return actor
    84  }
    85  
    86  func (e *Actor) Spawn() entity_manager.PluginActor {
    87  	e.UpdateEntity()
    88  	return e
    89  }
    90  
    91  func (e *Actor) UpdateEntity() {
    92  
    93  }
    94  
    95  `
    96  
    97  var pluginTpl = `//CODE GENERATED AUTOMATICALLY
    98  
    99  package {{.Package}}
   100  
   101  import (
   102  	"fmt"
   103  	"sync"
   104  	"time"
   105  
   106  	"github.com/pkg/errors"
   107  
   108  	"{{.Dir}}/common"
   109  	"{{.Dir}}/common/apperr"
   110  	"{{.Dir}}/common/logger"
   111  	m "{{.Dir}}/models"
   112  	"{{.Dir}}/system/entity_manager"
   113  	"{{.Dir}}/system/plugins"
   114  )
   115  
   116  var (
   117  	log = logger.MustGetLogger("plugins.{{.Package}}")
   118  )
   119  
   120  var _ plugins.Plugable = (*plugin)(nil)
   121  
   122  func init() {
   123  	plugins.RegisterPlugin(Name, New)
   124  }
   125  
   126  type plugin struct {
   127  	*plugins.Plugin
   128  	actorsLock *sync.Mutex
   129  	actors     map[common.EntityId]*Actor
   130  	quit       chan struct{}
   131  	pause      time.Duration
   132  }
   133  
   134  func New() plugins.Plugable {
   135  	return &plugin{
   136  		Plugin:     plugins.NewPlugin(),
   137  		actorsLock: &sync.Mutex{},
   138  		actors:     make(map[common.EntityId]*Actor),
   139  		pause:      240,
   140  	}
   141  }
   142  
   143  func (p *plugin) Load(service plugins.Service) (err error) {
   144  	if err = p.Plugin.Load(service); err != nil {
   145  		return
   146  	}
   147  
   148  	p.quit = make(chan struct{})
   149  
   150  	go func() {
   151  		ticker := time.NewTicker(time.Second * p.pause)
   152  
   153  		defer func() {
   154  			ticker.Stop()
   155  			close(p.quit)
   156  		}()
   157  
   158  		for {
   159  			select {
   160  			case <-p.quit:
   161  				return
   162  			case <-ticker.C:
   163  				p.updateForAll()
   164  			}
   165  		}
   166  	}()
   167  
   168  	return nil
   169  }
   170  
   171  func (p *plugin) Unload() (err error) {
   172  	if err = p.Plugin.Unload(); err != nil {
   173  		return
   174  	}
   175  
   176  	p.quit <- struct{}{}
   177  	return nil
   178  }
   179  
   180  func (p *plugin) Name() string {
   181  	return Name
   182  }
   183  
   184  func (p *plugin) AddOrUpdateActor(entity *m.Entity) (err error) {
   185  	p.actorsLock.Lock()
   186  	defer p.actorsLock.Unlock()
   187  
   188  	if _, ok := p.actors[entity.Id]; ok {
   189  		p.actors[entity.Id].UpdateEntity()
   190  		return
   191  	}
   192  
   193  	p.actors[entity.Id] = NewActor(entity, p.Supervisor, p.EventBus)
   194  	p.Supervisor.Spawn(p.actors[entity.Id].Spawn)
   195  
   196  	return
   197  }
   198  
   199  func (p *plugin) RemoveActor(entityId common.EntityId) error {
   200  	return p.removeEntity(entityId)
   201  }
   202  
   203  func (p *plugin) removeEntity(entityId common.EntityId) (err error) {
   204  	p.actorsLock.Lock()
   205  	defer p.actorsLock.Unlock()
   206  
   207  	if _, ok := p.actors[entityId]; !ok {
   208  		err = errors.Wrap(apperr.ErrNotFound, fmt.Sprintf("failed remove \"%s\"", entityId.Name()))
   209  		return
   210  	}
   211  
   212  	delete(p.actors, entityId)
   213  
   214  	return
   215  }
   216  
   217  func (p *plugin) updateForAll() {
   218  	p.actorsLock.Lock()
   219  	defer p.actorsLock.Unlock()
   220  
   221  	for _, actor := range p.actors {
   222  		actor.UpdateEntity()
   223  	}
   224  }
   225  
   226  func (p *plugin) Type() plugins.PluginType {
   227  	return plugins.PluginBuiltIn
   228  }
   229  
   230  func (p *plugin) Depends() []string {
   231  	return nil
   232  }
   233  
   234  func (p *plugin) Version() string {
   235  	return "0.0.1"
   236  }
   237  
   238  func (p *plugin) Options() m.PluginOptions {
   239  	return m.PluginOptions{
   240  		Actors:       true,
   241  		ActorAttrs:   NewAttr(),
   242  		ActorSetts:   NewSettings(),
   243  		ActorActions: entity_manager.ToEntityActionShort(NewActions()),
   244  		ActorStates:  entity_manager.ToEntityStateShort(NewStates()),
   245  	}
   246  }
   247  
   248  `
   249  
   250  var typesTpl = `//CODE GENERATED AUTOMATICALLY
   251  
   252  package {{.Package}}
   253  
   254  import (
   255  	"{{.Dir}}/common"
   256  	m "{{.Dir}}/models"
   257  	"{{.Dir}}/system/entity_manager"
   258  )
   259  
   260  const (
   261  	Name        = "{{.PluginName}}"
   262  	Entity{{.PluginName}} = string("{{.PluginName}}")
   263  )
   264  
   265  const (
   266  	SettingParam1 = "param1"
   267  	SettingParam2 = "param2"
   268  
   269  	AttrPhase = "phase"
   270  
   271  	StateEnabled  = "enabled"
   272  	StateDisabled = "disabled"
   273  
   274  	ActionEnabled = "enable"
   275  	ActionDisable = "disable"
   276  )
   277  
   278  // store entity status in this struct
   279  func NewAttr() m.Attributes {
   280  	return m.Attributes{
   281  		AttrPhase: {
   282  			Name: AttrPhase,
   283  			Type: common.AttributeString,
   284  		},
   285  	}
   286  }
   287  
   288  // entity settings
   289  func NewSettings() m.Attributes {
   290  	return m.Attributes{
   291  		SettingParam1: {
   292  			Name: SettingParam1,
   293  			Type: common.AttributeString,
   294  		},
   295  		SettingParam2: {
   296  			Name: SettingParam2,
   297  			Type: common.AttributeString,
   298  		},
   299  	}
   300  }
   301  
   302  // state list entity
   303  func NewStates() (states map[string]entity_manager.ActorState) {
   304  
   305  	states = map[string]entity_manager.ActorState{
   306  		StateEnabled: {
   307  			Name:        StateEnabled,
   308  			Description: "Enabled",
   309  		},
   310  		StateDisabled: {
   311  			Name:        StateDisabled,
   312  			Description: "disabled",
   313  		},
   314  	}
   315  
   316  	return
   317  }
   318  
   319  // entity action list
   320  func NewActions() map[string]entity_manager.ActorAction {
   321  	return map[string]entity_manager.ActorAction{
   322  		ActionEnabled: {
   323  			Name:        ActionEnabled,
   324  			Description: "enable",
   325  		},
   326  		ActionDisable: {
   327  			Name:        ActionDisable,
   328  			Description: "disable",
   329  		},
   330  	}
   331  }
   332  
   333  `
   334  
   335  var (
   336  	controllerCmd = &cobra.Command{
   337  		Use:   "p",
   338  		Short: "plugin generator",
   339  		Long:  "$ cli g p [pluginName]",
   340  	}
   341  	endpointName string
   342  )
   343  
   344  func init() {
   345  	generate.Generate.AddCommand(controllerCmd)
   346  	controllerCmd.Flags().StringVarP(&endpointName, "endpoint", "e", "EndpointName", "EndpointName")
   347  	controllerCmd.Run = func(cmd *cobra.Command, args []string) {
   348  
   349  		if len(args) == 0 {
   350  			log.Error("Wrong number of arguments. Run: cli help generate")
   351  			return
   352  		}
   353  
   354  		currpath, _ := os.Getwd()
   355  
   356  		g := Generator{}
   357  		g.Generate(args[0], currpath)
   358  	}
   359  }
   360  
   361  // Generator ...
   362  type Generator struct{}
   363  
   364  // Generate ...
   365  func (e Generator) Generate(pluginName, currpath string) {
   366  
   367  	log.Infof("Using '%s' as controller name", pluginName)
   368  
   369  	fp := path.Join(currpath, "plugins", strings.ToLower(pluginName))
   370  
   371  	e.addPLugin(fp, pluginName)
   372  }
   373  
   374  func createFile(fp, tpl, fileName, pluginName string) {
   375  
   376  	if _, err := os.Stat(fp); os.IsNotExist(err) {
   377  		if err := os.MkdirAll(fp, 0777); err != nil {
   378  			log.Errorf("Could not create plugin directory: %s", err.Error())
   379  			return
   380  		}
   381  	}
   382  
   383  	templateData := struct {
   384  		Package    string
   385  		PluginName string
   386  		Dir        string
   387  	}{
   388  		Dir:        common.Dir(),
   389  		Package:    strings.ToLower(pluginName),
   390  		PluginName: pluginName,
   391  	}
   392  
   393  	fpath := path.Join(fp, strings.ToLower(fileName)+".go")
   394  	f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666)
   395  	if err != nil {
   396  		log.Errorf("Could not create file: %s", err.Error())
   397  		return
   398  	}
   399  	defer f.Close()
   400  
   401  	t := template.Must(template.New("controller").Parse(tpl))
   402  
   403  	if t.Execute(f, templateData) != nil {
   404  		log.Error(err.Error())
   405  	}
   406  
   407  	common.FormatSourceCode(fpath)
   408  }
   409  
   410  func (e Generator) addPLugin(fp, pluginName string) {
   411  	createFile(fp, actorTpl, "actor", pluginName)
   412  	createFile(fp, pluginTpl, "plugin", pluginName)
   413  	createFile(fp, typesTpl, "types", pluginName)
   414  }