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 }