node build fixed
This commit is contained in:
35
seanime-2.9.10/internal/hook/README.md
Normal file
35
seanime-2.9.10/internal/hook/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
### `Request` events
|
||||
|
||||
- Route scoped
|
||||
- A handler that does the native job is called last and can be interrupted if `e.next()` isn't called
|
||||
|
||||
|
||||
### `Requested` events
|
||||
|
||||
- Example: `onAnimeEntryRequested`
|
||||
- Called before creation of a struct
|
||||
- Native job cannot be interrupted even if `e.next()` isn't called
|
||||
- Followed by event containing the struct, e.g. `onAnimeEntry`
|
||||
|
||||
|
||||
### TODO
|
||||
|
||||
- [ ] Scanning
|
||||
- [ ] Torrent client
|
||||
- [ ] Torrent search
|
||||
- [ ] AutoDownloader
|
||||
- [ ] Torrent streaming
|
||||
- [ ] Debrid / Debrid streaming
|
||||
- [ ] PlaybackManager
|
||||
- [ ] Media Player
|
||||
- [ ] Sync / Offline
|
||||
- [ ] Online streaming
|
||||
- [ ] Metadata provider
|
||||
- [ ] Manga
|
||||
- [ ] Media streaming
|
||||
|
||||
---
|
||||
|
||||
- [ ] Command palette
|
||||
- [ ] Database
|
||||
- [ ] App Context
|
||||
200
seanime-2.9.10/internal/hook/hook.go
Normal file
200
seanime-2.9.10/internal/hook/hook.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"seanime/internal/hook_resolver"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Handler defines a single Hook handler.
|
||||
// Multiple handlers can share the same id.
|
||||
// If Id is not explicitly set it will be autogenerated by Hook.Add and Hook.AddHandler.
|
||||
type Handler[T hook_resolver.Resolver] struct {
|
||||
// Func defines the handler function to execute.
|
||||
//
|
||||
// Note that users need to call e.Next() in order to proceed with
|
||||
// the execution of the hook chain.
|
||||
Func func(T) error
|
||||
|
||||
// Id is the unique identifier of the handler.
|
||||
//
|
||||
// It could be used later to remove the handler from a hook via [Hook.Remove].
|
||||
//
|
||||
// If missing, an autogenerated value will be assigned when adding
|
||||
// the handler to a hook.
|
||||
Id string
|
||||
|
||||
// Priority allows changing the default exec priority of the handler within a hook.
|
||||
//
|
||||
// If 0, the handler will be executed in the same order it was registered.
|
||||
Priority int
|
||||
}
|
||||
|
||||
// Hook defines a generic concurrent safe structure for managing event hooks.
|
||||
// When using custom event it must embed the base [hook.Event].
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type CustomEvent struct {
|
||||
// hook.Event
|
||||
// SomeField int
|
||||
// }
|
||||
//
|
||||
// h := Hook[*CustomEvent]{}
|
||||
//
|
||||
// h.BindFunc(func(e *CustomEvent) error {
|
||||
// println(e.SomeField)
|
||||
//
|
||||
// return e.Next()
|
||||
// })
|
||||
//
|
||||
// h.Trigger(&CustomEvent{ SomeField: 123 })
|
||||
type Hook[T hook_resolver.Resolver] struct {
|
||||
handlers []*Handler[T]
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// Bind registers the provided handler to the current hooks queue.
|
||||
//
|
||||
// If handler.Id is empty it is updated with autogenerated value.
|
||||
//
|
||||
// If a handler from the current hook list has Id matching handler.Id
|
||||
// then the old handler is replaced with the new one.
|
||||
func (h *Hook[T]) Bind(handler *Handler[T]) string {
|
||||
if h == nil {
|
||||
return ""
|
||||
}
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
var exists bool
|
||||
|
||||
if handler.Id == "" {
|
||||
handler.Id = generateHookId()
|
||||
|
||||
// ensure that it doesn't exist
|
||||
DuplicateCheck:
|
||||
for _, existing := range h.handlers {
|
||||
if existing.Id == handler.Id {
|
||||
handler.Id = generateHookId()
|
||||
goto DuplicateCheck
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// replace existing
|
||||
for i, existing := range h.handlers {
|
||||
if existing.Id == handler.Id {
|
||||
h.handlers[i] = handler
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// append new
|
||||
if !exists {
|
||||
h.handlers = append(h.handlers, handler)
|
||||
}
|
||||
|
||||
// sort handlers by Priority, preserving the original order of equal items
|
||||
sort.SliceStable(h.handlers, func(i, j int) bool {
|
||||
return h.handlers[i].Priority < h.handlers[j].Priority
|
||||
})
|
||||
|
||||
return handler.Id
|
||||
}
|
||||
|
||||
// BindFunc is similar to Bind but registers a new handler from just the provided function.
|
||||
//
|
||||
// The registered handler is added with a default 0 priority and the id will be autogenerated.
|
||||
//
|
||||
// If you want to register a handler with custom priority or id use the [Hook.Bind] method.
|
||||
func (h *Hook[T]) BindFunc(fn func(e T) error) string {
|
||||
return h.Bind(&Handler[T]{Func: fn})
|
||||
}
|
||||
|
||||
// Unbind removes one or many hook handler by their id.
|
||||
func (h *Hook[T]) Unbind(idsToRemove ...string) {
|
||||
if h == nil {
|
||||
return
|
||||
}
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
for _, id := range idsToRemove {
|
||||
for i := len(h.handlers) - 1; i >= 0; i-- {
|
||||
if h.handlers[i].Id == id {
|
||||
h.handlers = append(h.handlers[:i], h.handlers[i+1:]...)
|
||||
break // for now stop on the first occurrence since we don't allow handlers with duplicated ids
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UnbindAll removes all registered handlers.
|
||||
func (h *Hook[T]) UnbindAll() {
|
||||
if h == nil {
|
||||
return
|
||||
}
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
h.handlers = nil
|
||||
}
|
||||
|
||||
// Length returns to total number of registered hook handlers.
|
||||
func (h *Hook[T]) Length() int {
|
||||
if h == nil {
|
||||
return 0
|
||||
}
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
|
||||
return len(h.handlers)
|
||||
}
|
||||
|
||||
// Trigger executes all registered hook handlers one by one
|
||||
// with the specified event as an argument.
|
||||
//
|
||||
// Optionally, this method allows also to register additional one off
|
||||
// handler funcs that will be temporary appended to the handlers queue.
|
||||
//
|
||||
// NB! Each hook handler must call event.Next() in order the hook chain to proceed.
|
||||
func (h *Hook[T]) Trigger(event T, oneOffHandlerFuncs ...func(T) error) error {
|
||||
if h == nil {
|
||||
event.SetNextFunc(nil)
|
||||
return event.Next()
|
||||
}
|
||||
h.mu.RLock()
|
||||
handlers := make([]func(T) error, 0, len(h.handlers)+len(oneOffHandlerFuncs))
|
||||
for _, handler := range h.handlers {
|
||||
handlers = append(handlers, handler.Func)
|
||||
}
|
||||
handlers = append(handlers, oneOffHandlerFuncs...)
|
||||
h.mu.RUnlock()
|
||||
|
||||
event.SetNextFunc(nil) // reset in case the event is being reused
|
||||
|
||||
for i := len(handlers) - 1; i >= 0; i-- {
|
||||
i := i
|
||||
old := event.NextFunc()
|
||||
event.SetNextFunc(func() error {
|
||||
event.SetNextFunc(old)
|
||||
handlerErr := handlers[i](event)
|
||||
if handlerErr != nil {
|
||||
log.Error().Err(handlerErr).Msg("hook: Error in handler")
|
||||
return handlerErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return event.Next()
|
||||
}
|
||||
|
||||
func generateHookId() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
52
seanime-2.9.10/internal/hook/hook_test.go
Normal file
52
seanime-2.9.10/internal/hook/hook_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"seanime/internal/hook_resolver"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHookAddHandlerAndAdd(t *testing.T) {
|
||||
calls := ""
|
||||
|
||||
h := Hook[*hook_resolver.Event]{}
|
||||
|
||||
h.BindFunc(func(e *hook_resolver.Event) error { calls += "1"; return e.Next() })
|
||||
h.BindFunc(func(e *hook_resolver.Event) error { calls += "2"; return e.Next() })
|
||||
h3Id := h.BindFunc(func(e *hook_resolver.Event) error { calls += "3"; return e.Next() })
|
||||
h.Bind(&Handler[*hook_resolver.Event]{
|
||||
Id: h3Id, // should replace 3
|
||||
Func: func(e *hook_resolver.Event) error { calls += "3'"; return e.Next() },
|
||||
})
|
||||
h.Bind(&Handler[*hook_resolver.Event]{
|
||||
Func: func(e *hook_resolver.Event) error { calls += "4"; return e.Next() },
|
||||
Priority: -2,
|
||||
})
|
||||
h.Bind(&Handler[*hook_resolver.Event]{
|
||||
Func: func(e *hook_resolver.Event) error { calls += "5"; return e.Next() },
|
||||
Priority: -1,
|
||||
})
|
||||
h.Bind(&Handler[*hook_resolver.Event]{
|
||||
Func: func(e *hook_resolver.Event) error { calls += "6"; return e.Next() },
|
||||
})
|
||||
h.Bind(&Handler[*hook_resolver.Event]{
|
||||
Func: func(e *hook_resolver.Event) error { calls += "7"; e.Next(); return errors.New("test") }, // error shouldn't stop the chain
|
||||
})
|
||||
|
||||
h.Trigger(
|
||||
&hook_resolver.Event{},
|
||||
func(e *hook_resolver.Event) error { calls += "8"; return e.Next() },
|
||||
func(e *hook_resolver.Event) error { calls += "9"; return nil }, // skip next
|
||||
func(e *hook_resolver.Event) error { calls += "10"; return e.Next() },
|
||||
)
|
||||
|
||||
if total := len(h.handlers); total != 7 {
|
||||
t.Fatalf("Expected %d handlers, found %d", 7, total)
|
||||
}
|
||||
|
||||
expectedCalls := "45123'6789"
|
||||
|
||||
if calls != expectedCalls {
|
||||
t.Fatalf("Expected calls sequence %q, got %q", expectedCalls, calls)
|
||||
}
|
||||
}
|
||||
1119
seanime-2.9.10/internal/hook/hooks.go
Normal file
1119
seanime-2.9.10/internal/hook/hooks.go
Normal file
File diff suppressed because it is too large
Load Diff
81
seanime-2.9.10/internal/hook/tagged.go
Normal file
81
seanime-2.9.10/internal/hook/tagged.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package hook
|
||||
|
||||
//
|
||||
//// Tagger defines an interface for event data structs that support tags/groups/categories/etc.
|
||||
//// Usually used together with TaggedHook.
|
||||
//type Tagger interface {
|
||||
// hook_events.Resolver
|
||||
//
|
||||
// Tags() []string
|
||||
//}
|
||||
//
|
||||
//// wrapped local Hook embedded struct to limit the public API surface.
|
||||
//type mainHook[T Tagger] struct {
|
||||
// *Hook[T]
|
||||
//}
|
||||
//
|
||||
//// NewTaggedHook creates a new TaggedHook with the provided main hook and optional tags.
|
||||
//func NewTaggedHook[T Tagger](hook *Hook[T], tags ...string) *TaggedHook[T] {
|
||||
// return &TaggedHook[T]{
|
||||
// mainHook[T]{hook},
|
||||
// tags,
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// TaggedHook defines a proxy hook which register handlers that are triggered only
|
||||
//// if the TaggedHook.tags are empty or includes at least one of the event data tag(s).
|
||||
//type TaggedHook[T Tagger] struct {
|
||||
// mainHook[T]
|
||||
//
|
||||
// tags []string
|
||||
//}
|
||||
//
|
||||
//// CanTriggerOn checks if the current TaggedHook can be triggered with
|
||||
//// the provided event data tags.
|
||||
////
|
||||
//// It returns always true if the hook doens't have any tags.
|
||||
//func (h *TaggedHook[T]) CanTriggerOn(tagsToCheck []string) bool {
|
||||
// if len(h.tags) == 0 {
|
||||
// return true // match all
|
||||
// }
|
||||
//
|
||||
// for _, t := range tagsToCheck {
|
||||
// if util.Contains(h.tags, t) {
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return false
|
||||
//}
|
||||
//
|
||||
//// Bind registers the provided handler to the current hooks queue.
|
||||
////
|
||||
//// It is similar to [Hook.Bind] with the difference that the handler
|
||||
//// function is invoked only if the event data tags satisfy h.CanTriggerOn.
|
||||
//func (h *TaggedHook[T]) Bind(handler *Handler[T]) string {
|
||||
// fn := handler.Func
|
||||
//
|
||||
// handler.Func = func(e T) error {
|
||||
// if h.CanTriggerOn(e.Tags()) {
|
||||
// return fn(e)
|
||||
// }
|
||||
//
|
||||
// return e.Next()
|
||||
// }
|
||||
//
|
||||
// return h.mainHook.Bind(handler)
|
||||
//}
|
||||
//
|
||||
//// BindFunc registers a new handler with the specified function.
|
||||
////
|
||||
//// It is similar to [Hook.Bind] with the difference that the handler
|
||||
//// function is invoked only if the event data tags satisfy h.CanTriggerOn.
|
||||
//func (h *TaggedHook[T]) BindFunc(fn func(e T) error) string {
|
||||
// return h.mainHook.BindFunc(func(e T) error {
|
||||
// if h.CanTriggerOn(e.Tags()) {
|
||||
// return fn(e)
|
||||
// }
|
||||
//
|
||||
// return e.Next()
|
||||
// })
|
||||
//}
|
||||
79
seanime-2.9.10/internal/hook/tagged_test.go
Normal file
79
seanime-2.9.10/internal/hook/tagged_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package hook
|
||||
|
||||
//type mockTagsEvent struct {
|
||||
// Event
|
||||
// tags []string
|
||||
//}
|
||||
//
|
||||
//func (m mockTagsEvent) Tags() []string {
|
||||
// return m.tags
|
||||
//}
|
||||
//
|
||||
//func TestTaggedHook(t *testing.T) {
|
||||
// calls := ""
|
||||
//
|
||||
// base := &Hook[*mockTagsEvent]{}
|
||||
// base.BindFunc(func(e *mockTagsEvent) error { calls += "f0"; return e.Next() })
|
||||
//
|
||||
// hA := NewTaggedHook(base)
|
||||
// hA.BindFunc(func(e *mockTagsEvent) error { calls += "a1"; return e.Next() })
|
||||
// hA.Bind(&Handler[*mockTagsEvent]{
|
||||
// Func: func(e *mockTagsEvent) error { calls += "a2"; return e.Next() },
|
||||
// Priority: -1,
|
||||
// })
|
||||
//
|
||||
// hB := NewTaggedHook(base, "b1", "b2")
|
||||
// hB.BindFunc(func(e *mockTagsEvent) error { calls += "b1"; return e.Next() })
|
||||
// hB.Bind(&Handler[*mockTagsEvent]{
|
||||
// Func: func(e *mockTagsEvent) error { calls += "b2"; return e.Next() },
|
||||
// Priority: -2,
|
||||
// })
|
||||
//
|
||||
// hC := NewTaggedHook(base, "c1", "c2")
|
||||
// hC.BindFunc(func(e *mockTagsEvent) error { calls += "c1"; return e.Next() })
|
||||
// hC.Bind(&Handler[*mockTagsEvent]{
|
||||
// Func: func(e *mockTagsEvent) error { calls += "c2"; return e.Next() },
|
||||
// Priority: -3,
|
||||
// })
|
||||
//
|
||||
// scenarios := []struct {
|
||||
// event *mockTagsEvent
|
||||
// expectedCalls string
|
||||
// }{
|
||||
// {
|
||||
// &mockTagsEvent{},
|
||||
// "a2f0a1",
|
||||
// },
|
||||
// {
|
||||
// &mockTagsEvent{tags: []string{"missing"}},
|
||||
// "a2f0a1",
|
||||
// },
|
||||
// {
|
||||
// &mockTagsEvent{tags: []string{"b2"}},
|
||||
// "b2a2f0a1b1",
|
||||
// },
|
||||
// {
|
||||
// &mockTagsEvent{tags: []string{"c1"}},
|
||||
// "c2a2f0a1c1",
|
||||
// },
|
||||
// {
|
||||
// &mockTagsEvent{tags: []string{"b1", "c2"}},
|
||||
// "c2b2a2f0a1b1c1",
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// for _, s := range scenarios {
|
||||
// t.Run(strings.Join(s.event.tags, "_"), func(t *testing.T) {
|
||||
// calls = "" // reset
|
||||
//
|
||||
// err := base.Trigger(s.event)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Unexpected trigger error: %v", err)
|
||||
// }
|
||||
//
|
||||
// if calls != s.expectedCalls {
|
||||
// t.Fatalf("Expected calls sequence %q, got %q", s.expectedCalls, calls)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
Reference in New Issue
Block a user