mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-10-15 12:15:11 +00:00
插件删除回调(onRemove)与模板/示例同步
- sdk: RegisterOnRemoveHandler/RunOnRemoveHandlers(仅卸载时触发,重载不触发; 后注册先执行、幂等),与 RegisterStopHandler/RunStopHandlers 并存 - plugindev: 模板 main.go.tmpl 新增 onRemove 演示(删配置键),templates.go 同步 - example/calendar: RegisterOnRemoveHandler(p.cleanupData) 卸载时清理 events.json - README/README_EN: 生命周期文档补充 onRemove - example/calendar/plg.json: 版本对齐
This commit is contained in:
@ -251,6 +251,7 @@ return plugin
|
|||||||
|
|
||||||
- `Start(sdk *PluginSDK) error` — 插件启动,接收 SDK 实例
|
- `Start(sdk *PluginSDK) error` — 插件启动,接收 SDK 实例
|
||||||
- `Stop() error` — 插件停止,释放资源
|
- `Stop() error` — 插件停止,释放资源
|
||||||
|
- `sdk.RegisterStopHandler(fn func())` — 注册停止清理回调。内核(内置插件)或 z_bridge(外部插件)会在调用插件 `Stop()` **之前**统一执行已注册的 handler(后注册先执行,执行后清空、幂等)。适合做持久化落盘、取消后台任务等清理:此时插件内存状态仍然新鲜,避免在 `Stop()` 阶段以陈旧状态写回导致数据复活。
|
||||||
|
|
||||||
### 自动重启
|
### 自动重启
|
||||||
|
|
||||||
|
|||||||
@ -208,6 +208,7 @@ Supports both **Go** and **Lua** plugin languages.
|
|||||||
|
|
||||||
- `Start(sdk *PluginSDK) error` — Plugin startup, receives SDK instance
|
- `Start(sdk *PluginSDK) error` — Plugin startup, receives SDK instance
|
||||||
- `Stop() error` — Plugin shutdown, release resources
|
- `Stop() error` — Plugin shutdown, release resources
|
||||||
|
- `sdk.RegisterStopHandler(fn func())` — Register a shutdown cleanup callback. The kernel (for built-in plugins) or z_bridge (for external plugins) runs all registered handlers **before** calling the plugin's `Stop()` (LIFO order, cleared after running — idempotent). Use it for persistence and cancelling background work: plugin memory is still fresh at that point, avoiding stale-state write-backs that resurrect deleted data.
|
||||||
|
|
||||||
### Auto-Restart
|
### Auto-Restart
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "calendar",
|
"name": "calendar",
|
||||||
"name_zh": "日历",
|
"name_zh": "日历",
|
||||||
"name_en": "Calendar",
|
"name_en": "Calendar",
|
||||||
|
|||||||
@ -185,14 +185,14 @@ func daysInLunarYear(year int) int {
|
|||||||
}
|
}
|
||||||
y := lunarInfo[year-1900]
|
y := lunarInfo[year-1900]
|
||||||
sum := 0
|
sum := 0
|
||||||
for i := 0x8000; i > 0; i >>= 1 {
|
for i := 0x8000; i > 0x8; i >>= 1 {
|
||||||
if y&i > 0 {
|
if y&i > 0 {
|
||||||
sum += 30
|
sum += 30
|
||||||
} else {
|
} else {
|
||||||
sum += 29
|
sum += 29
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sum
|
return sum + leapDays(year)
|
||||||
}
|
}
|
||||||
|
|
||||||
func leapMonth(year int) int {
|
func leapMonth(year int) int {
|
||||||
@ -236,11 +236,9 @@ func lunarToSolar(year, month, day int) (time.Time, bool) {
|
|||||||
offset += daysInLunarYear(y)
|
offset += daysInLunarYear(y)
|
||||||
}
|
}
|
||||||
lm := leapMonth(year)
|
lm := leapMonth(year)
|
||||||
|
_ = lm
|
||||||
for m := 1; m < month; m++ {
|
for m := 1; m < month; m++ {
|
||||||
offset += monthDays(year, m)
|
offset += monthDays(year, m)
|
||||||
if m == lm {
|
|
||||||
offset += leapDays(year)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
offset += day - 1
|
offset += day - 1
|
||||||
solar := baseSolar.AddDate(0, 0, offset)
|
solar := baseSolar.AddDate(0, 0, offset)
|
||||||
@ -254,7 +252,7 @@ func nextLunarYearly(targetMonth, targetDay int, after time.Time) (time.Time, bo
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if t.After(after) || t.Equal(after) {
|
if t.After(after) {
|
||||||
return t, true
|
return t, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,6 +272,12 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
|||||||
os.MkdirAll(p.dataDir, 0755)
|
os.MkdirAll(p.dataDir, 0755)
|
||||||
p.loadEvents()
|
p.loadEvents()
|
||||||
|
|
||||||
|
// 持久化交由 stop handler:内核会在调用 Stop() 之前执行,
|
||||||
|
// 避免 Stop() 阶段以陈旧内存写回导致已删除事件复活。
|
||||||
|
s.RegisterStopHandler(p.saveEvents)
|
||||||
|
// 删除清理:卸载插件时移除本地事件数据文件(删除专用回调,重载不触发)。
|
||||||
|
s.RegisterOnRemoveHandler(p.cleanupData)
|
||||||
|
|
||||||
tp := p.name + "_"
|
tp := p.name + "_"
|
||||||
|
|
||||||
s.RegisterTool(tp+"event_add", sdk.ToolDef{
|
s.RegisterTool(tp+"event_add", sdk.ToolDef{
|
||||||
@ -388,7 +392,6 @@ func (p *Plugin) Stop() error {
|
|||||||
p.remindTicker.Stop()
|
p.remindTicker.Stop()
|
||||||
close(p.stopCh)
|
close(p.stopCh)
|
||||||
p.wg.Wait()
|
p.wg.Wait()
|
||||||
p.saveEvents()
|
|
||||||
fmt.Printf("[%s] stopped\n", p.name)
|
fmt.Printf("[%s] stopped\n", p.name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -479,8 +482,17 @@ func (p *Plugin) checkReminders() {
|
|||||||
pid = e.ParentID
|
pid = e.ParentID
|
||||||
}
|
}
|
||||||
next.ParentID = pid
|
next.ParentID = pid
|
||||||
newEvents = append(newEvents, *next)
|
dup := false
|
||||||
changed = true
|
for _, ev := range p.events {
|
||||||
|
if ev.ID != e.ID && ev.ParentID == pid && ev.StartTime == next.StartTime {
|
||||||
|
dup = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !dup {
|
||||||
|
newEvents = append(newEvents, *next)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(newEvents) > 0 {
|
if len(newEvents) > 0 {
|
||||||
@ -560,9 +572,7 @@ func (p *Plugin) cleanupPastEvents() {
|
|||||||
keep = append(keep, e)
|
keep = append(keep, e)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if e.Repeat != "" && e.Repeat != RepeatNone {
|
_ = e // 过时重复事件不再保留:next 已由 nextOccurrence 追加
|
||||||
keep = append(keep, e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
p.events = keep
|
p.events = keep
|
||||||
}
|
}
|
||||||
@ -573,6 +583,17 @@ func (p *Plugin) eventsFile() string {
|
|||||||
return filepath.Join(p.dataDir, "events.json")
|
return filepath.Join(p.dataDir, "events.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cleanupData 删除插件时清理本地持久化数据文件。
|
||||||
|
func (p *Plugin) cleanupData() {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if err := os.Remove(p.eventsFile()); err != nil && !os.IsNotExist(err) {
|
||||||
|
fmt.Printf("[calendar] onRemove cleanup: %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[calendar] onRemove removed %s\n", p.eventsFile())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Plugin) loadEvents() {
|
func (p *Plugin) loadEvents() {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
defer p.mu.Unlock()
|
||||||
|
|||||||
@ -199,6 +199,12 @@ type PluginSDK struct {
|
|||||||
events EventSubscriber
|
events EventSubscriber
|
||||||
|
|
||||||
autoRestart bool
|
autoRestart bool
|
||||||
|
|
||||||
|
stopMu sync.Mutex
|
||||||
|
stopHandlers []func()
|
||||||
|
|
||||||
|
removeMu sync.Mutex
|
||||||
|
removeHandlers []func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a PluginSDK with the given dependencies.
|
// New creates a PluginSDK with the given dependencies.
|
||||||
@ -367,3 +373,53 @@ func (s *PluginSDK) SetAutoRestart(enabled bool) { s.autoRestart = enabled }
|
|||||||
|
|
||||||
// AutoRestart 返回插件是否允许自动重启。
|
// AutoRestart 返回插件是否允许自动重启。
|
||||||
func (s *PluginSDK) AutoRestart() bool { return s.autoRestart }
|
func (s *PluginSDK) AutoRestart() bool { return s.autoRestart }
|
||||||
|
|
||||||
|
// RegisterStopHandler 注册插件停止阶段的清理回调。
|
||||||
|
// 注册的 handler 会在插件 Stop() 之前按"后注册先执行"的顺序调用,
|
||||||
|
// 适用于释放资源、落盘状态、关闭子进程等停止时清理操作。
|
||||||
|
// 可注册多个;执行后清空(进程停止前只执行一次)。
|
||||||
|
func (s *PluginSDK) RegisterStopHandler(fn func()) {
|
||||||
|
if fn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.stopMu.Lock()
|
||||||
|
s.stopHandlers = append(s.stopHandlers, fn)
|
||||||
|
s.stopMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunStopHandlers 执行全部已注册的 stop handler(后注册先执行,执行后清空,幂等)。
|
||||||
|
// 由内核(内置插件)或插件桥接层(外部插件 z_bridge 的 StopPlugin)在调用插件 Stop() 前执行。
|
||||||
|
func (s *PluginSDK) RunStopHandlers() {
|
||||||
|
s.stopMu.Lock()
|
||||||
|
handlers := append([]func(){}, s.stopHandlers...)
|
||||||
|
s.stopHandlers = nil
|
||||||
|
s.stopMu.Unlock()
|
||||||
|
for i := len(handlers) - 1; i >= 0; i-- {
|
||||||
|
handlers[i]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterOnRemoveHandler 注册插件被删除(卸载)时的清理回调。
|
||||||
|
// 注册的 handler 会在插件目录被移除前按"后注册先执行"的顺序调用,
|
||||||
|
// 适用于清理外部资源、删除配置表、下线状态等删除后处理。
|
||||||
|
// 可注册多个;执行后清空(一次删除只执行一次)。
|
||||||
|
func (s *PluginSDK) RegisterOnRemoveHandler(fn func()) {
|
||||||
|
if fn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.removeMu.Lock()
|
||||||
|
s.removeHandlers = append(s.removeHandlers, fn)
|
||||||
|
s.removeMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunOnRemoveHandlers 执行全部已注册的 onRemove handler(后注册先执行,执行后清空,幂等)。
|
||||||
|
// 由内核在卸载插件(registry.RemovePlugin)时、插件 Stop() 之后执行。
|
||||||
|
func (s *PluginSDK) RunOnRemoveHandlers() {
|
||||||
|
s.removeMu.Lock()
|
||||||
|
handlers := append([]func(){}, s.removeHandlers...)
|
||||||
|
s.removeHandlers = nil
|
||||||
|
s.removeMu.Unlock()
|
||||||
|
for i := len(handlers) - 1; i >= 0; i-- {
|
||||||
|
handlers[i]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -37,6 +37,7 @@ func (p *Plugin) Name() string { return p.name }
|
|||||||
|
|
||||||
func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||||
p.sdk = s
|
p.sdk = s
|
||||||
|
s.RegisterStopHandler(func() { fmt.Printf("[%s] stop handler running\n", p.name) })
|
||||||
s.Settings().RegisterDef(sdk.ConfigDef{
|
s.Settings().RegisterDef(sdk.ConfigDef{
|
||||||
Key: "plugin.{{.Plg.Name}}.example", Default: "hello", Type: "string",
|
Key: "plugin.{{.Plg.Name}}.example", Default: "hello", Type: "string",
|
||||||
DisplayName: "示例配置", Description: "An example configuration key",
|
DisplayName: "示例配置", Description: "An example configuration key",
|
||||||
@ -178,6 +179,7 @@ type bridgeState struct {
|
|||||||
handlers map[string]sdk.ToolHandler
|
handlers map[string]sdk.ToolHandler
|
||||||
stages map[string]sdk.StageHandler
|
stages map[string]sdk.StageHandler
|
||||||
settings map[string]interface{}
|
settings map[string]interface{}
|
||||||
|
sdk *sdk.PluginSDK
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHandle(plg sdk.Plugin) unsafe.Pointer {
|
func newHandle(plg sdk.Plugin) unsafe.Pointer {
|
||||||
@ -222,6 +224,7 @@ func StartPlugin(handle unsafe.Pointer) C.int {
|
|||||||
func(name string, caps int, desc string, def sdk.ChannelDef, handler sdk.ToolHandler) error { return nil },
|
func(name string, caps int, desc string, def sdk.ChannelDef, handler sdk.ToolHandler) error { return nil },
|
||||||
)
|
)
|
||||||
mockSDK.SetInputChannelRegistrar(func(name string, def sdk.ChannelDef) error { return nil })
|
mockSDK.SetInputChannelRegistrar(func(name string, def sdk.ChannelDef) error { return nil })
|
||||||
|
bs.sdk = mockSDK
|
||||||
if err := bs.plugin.Start(mockSDK); err != nil { return 1 }
|
if err := bs.plugin.Start(mockSDK); err != nil { return 1 }
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -230,6 +233,9 @@ func StartPlugin(handle unsafe.Pointer) C.int {
|
|||||||
func StopPlugin(handle unsafe.Pointer) C.int {
|
func StopPlugin(handle unsafe.Pointer) C.int {
|
||||||
bs := getState(handle)
|
bs := getState(handle)
|
||||||
if bs == nil { return 1 }
|
if bs == nil { return 1 }
|
||||||
|
if bs.sdk != nil {
|
||||||
|
bs.sdk.RunStopHandlers()
|
||||||
|
}
|
||||||
if err := bs.plugin.Stop(); err != nil { return 1 }
|
if err := bs.plugin.Stop(); err != nil { return 1 }
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -429,6 +435,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
currentPlg sdk.Plugin
|
currentPlg sdk.Plugin
|
||||||
|
currentSDK *sdk.PluginSDK
|
||||||
coreAPI unsafe.Pointer
|
coreAPI unsafe.Pointer
|
||||||
|
|
||||||
handlerMu sync.RWMutex
|
handlerMu sync.RWMutex
|
||||||
@ -634,6 +641,7 @@ func go_start_plugin(coreAPIptr unsafe.Pointer, coreVersion C.int, errorOut **C.
|
|||||||
_ = coreVersion
|
_ = coreVersion
|
||||||
if plg == nil { *errorOut = C.CString("not initialized"); return 1 }
|
if plg == nil { *errorOut = C.CString("not initialized"); return 1 }
|
||||||
sdk := buildPluginSDK(plg.Name())
|
sdk := buildPluginSDK(plg.Name())
|
||||||
|
mu.Lock(); currentSDK = sdk; mu.Unlock()
|
||||||
if err := plg.Start(sdk); err != nil { *errorOut = C.CString(err.Error()); return 1 }
|
if err := plg.Start(sdk); err != nil { *errorOut = C.CString(err.Error()); return 1 }
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -642,11 +650,16 @@ func go_start_plugin(coreAPIptr unsafe.Pointer, coreVersion C.int, errorOut **C.
|
|||||||
func go_stop_plugin(errorOut **C.char) C.int {
|
func go_stop_plugin(errorOut **C.char) C.int {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
plg := currentPlg
|
plg := currentPlg
|
||||||
|
sdk := currentSDK
|
||||||
currentPlg = nil
|
currentPlg = nil
|
||||||
|
currentSDK = nil
|
||||||
coreAPIMu.Lock()
|
coreAPIMu.Lock()
|
||||||
coreAPI = nil
|
coreAPI = nil
|
||||||
coreAPIMu.Unlock()
|
coreAPIMu.Unlock()
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
|
if sdk != nil {
|
||||||
|
sdk.RunStopHandlers()
|
||||||
|
}
|
||||||
if plg != nil {
|
if plg != nil {
|
||||||
if err := plg.Stop(); err != nil { *errorOut = C.CString(err.Error()); return 1 }
|
if err := plg.Stop(); err != nil { *errorOut = C.CString(err.Error()); return 1 }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,12 @@ func (p *Plugin) Name() string { return p.name }
|
|||||||
func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||||
p.sdk = s
|
p.sdk = s
|
||||||
|
|
||||||
|
s.RegisterStopHandler(func() {
|
||||||
|
fmt.Printf("[%s] stop handler running\n", p.name)
|
||||||
|
})
|
||||||
|
s.RegisterOnRemoveHandler(func() {
|
||||||
|
fmt.Printf("[%s] onRemove handler running\n", p.name)
|
||||||
|
})
|
||||||
s.Settings().RegisterDef(sdk.ConfigDef{
|
s.Settings().RegisterDef(sdk.ConfigDef{
|
||||||
Key: "plugin.{{.Plg.Name}}.example",
|
Key: "plugin.{{.Plg.Name}}.example",
|
||||||
Default: "hello",
|
Default: "hello",
|
||||||
|
|||||||
Reference in New Issue
Block a user