From 1bcf2e262cce701506bf35bfcc1d67abd3074501 Mon Sep 17 00:00:00 2001 From: stashcoder42 Date: Tue, 1 Jul 2025 09:35:17 -0400 Subject: [PATCH 1/2] Fix make generate-dataloaders and the extra time import --- internal/api/loaders/dataloaders.go | 11 +++-- .../api/loaders/scenelastplayedloader_gen.go | 38 +++++++++--------- .../api/loaders/sceneohistoryloader_gen.go | 40 +++++++++---------- .../api/loaders/sceneplayhistoryloader_gen.go | 40 +++++++++---------- 4 files changed, 67 insertions(+), 62 deletions(-) diff --git a/internal/api/loaders/dataloaders.go b/internal/api/loaders/dataloaders.go index 38f72b0a1..1d9416417 100644 --- a/internal/api/loaders/dataloaders.go +++ b/internal/api/loaders/dataloaders.go @@ -17,9 +17,9 @@ //go:generate go run github.com/vektah/dataloaden CustomFieldsLoader int github.com/stashapp/stash/pkg/models.CustomFieldMap //go:generate go run github.com/vektah/dataloaden SceneOCountLoader int int //go:generate go run github.com/vektah/dataloaden ScenePlayCountLoader int int -//go:generate go run github.com/vektah/dataloaden SceneOHistoryLoader int []time.Time -//go:generate go run github.com/vektah/dataloaden ScenePlayHistoryLoader int []time.Time -//go:generate go run github.com/vektah/dataloaden SceneLastPlayedLoader int *time.Time +//go:generate go run github.com/vektah/dataloaden SceneOHistoryLoader int []Time +//go:generate go run github.com/vektah/dataloaden ScenePlayHistoryLoader int []Time +//go:generate go run github.com/vektah/dataloaden SceneLastPlayedLoader int *Time package loaders import ( @@ -30,6 +30,11 @@ import ( "github.com/stashapp/stash/pkg/models" ) +// Work around bug in dataloaden https://github.com/vektah/dataloaden/issues/54 +type Time struct { + time.Time +} + type contextKey struct{ name string } var ( diff --git a/internal/api/loaders/scenelastplayedloader_gen.go b/internal/api/loaders/scenelastplayedloader_gen.go index 3017660b6..eb8c49ac8 100644 --- a/internal/api/loaders/scenelastplayedloader_gen.go +++ b/internal/api/loaders/scenelastplayedloader_gen.go @@ -10,7 +10,7 @@ import ( // SceneLastPlayedLoaderConfig captures the config to create a new SceneLastPlayedLoader type SceneLastPlayedLoaderConfig struct { // Fetch is a method that provides the data for the loader - Fetch func(keys []int) ([]*time.Time, []error) + Fetch func(keys []int) ([]*Time, []error) // Wait is how long wait before sending a batch Wait time.Duration @@ -31,7 +31,7 @@ func NewSceneLastPlayedLoader(config SceneLastPlayedLoaderConfig) *SceneLastPlay // SceneLastPlayedLoader batches and caches requests type SceneLastPlayedLoader struct { // this method provides the data for the loader - fetch func(keys []int) ([]*time.Time, []error) + fetch func(keys []int) ([]*Time, []error) // how long to done before sending a batch wait time.Duration @@ -42,7 +42,7 @@ type SceneLastPlayedLoader struct { // INTERNAL // lazily created cache - cache map[int]*time.Time + cache map[int]*Time // the current batch. keys will continue to be collected until timeout is hit, // then everything will be sent to the fetch method and out to the listeners @@ -54,25 +54,25 @@ type SceneLastPlayedLoader struct { type sceneLastPlayedLoaderBatch struct { keys []int - data []*time.Time + data []*Time error []error closing bool done chan struct{} } // Load a Time by key, batching and caching will be applied automatically -func (l *SceneLastPlayedLoader) Load(key int) (*time.Time, error) { +func (l *SceneLastPlayedLoader) Load(key int) (*Time, error) { return l.LoadThunk(key)() } // LoadThunk returns a function that when called will block waiting for a Time. // This method should be used if you want one goroutine to make requests to many // different data loaders without blocking until the thunk is called. -func (l *SceneLastPlayedLoader) LoadThunk(key int) func() (*time.Time, error) { +func (l *SceneLastPlayedLoader) LoadThunk(key int) func() (*Time, error) { l.mu.Lock() if it, ok := l.cache[key]; ok { l.mu.Unlock() - return func() (*time.Time, error) { + return func() (*Time, error) { return it, nil } } @@ -83,10 +83,10 @@ func (l *SceneLastPlayedLoader) LoadThunk(key int) func() (*time.Time, error) { pos := batch.keyIndex(l, key) l.mu.Unlock() - return func() (*time.Time, error) { + return func() (*Time, error) { <-batch.done - var data *time.Time + var data *Time if pos < len(batch.data) { data = batch.data[pos] } @@ -111,14 +111,14 @@ func (l *SceneLastPlayedLoader) LoadThunk(key int) func() (*time.Time, error) { // LoadAll fetches many keys at once. It will be broken into appropriate sized // sub batches depending on how the loader is configured -func (l *SceneLastPlayedLoader) LoadAll(keys []int) ([]*time.Time, []error) { - results := make([]func() (*time.Time, error), len(keys)) +func (l *SceneLastPlayedLoader) LoadAll(keys []int) ([]*Time, []error) { + results := make([]func() (*Time, error), len(keys)) for i, key := range keys { results[i] = l.LoadThunk(key) } - times := make([]*time.Time, len(keys)) + times := make([]*Time, len(keys)) errors := make([]error, len(keys)) for i, thunk := range results { times[i], errors[i] = thunk() @@ -129,13 +129,13 @@ func (l *SceneLastPlayedLoader) LoadAll(keys []int) ([]*time.Time, []error) { // LoadAllThunk returns a function that when called will block waiting for a Times. // This method should be used if you want one goroutine to make requests to many // different data loaders without blocking until the thunk is called. -func (l *SceneLastPlayedLoader) LoadAllThunk(keys []int) func() ([]*time.Time, []error) { - results := make([]func() (*time.Time, error), len(keys)) +func (l *SceneLastPlayedLoader) LoadAllThunk(keys []int) func() ([]*Time, []error) { + results := make([]func() (*Time, error), len(keys)) for i, key := range keys { results[i] = l.LoadThunk(key) } - return func() ([]*time.Time, []error) { - times := make([]*time.Time, len(keys)) + return func() ([]*Time, []error) { + times := make([]*Time, len(keys)) errors := make([]error, len(keys)) for i, thunk := range results { times[i], errors[i] = thunk() @@ -147,7 +147,7 @@ func (l *SceneLastPlayedLoader) LoadAllThunk(keys []int) func() ([]*time.Time, [ // Prime the cache with the provided key and value. If the key already exists, no change is made // and false is returned. // (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).) -func (l *SceneLastPlayedLoader) Prime(key int, value *time.Time) bool { +func (l *SceneLastPlayedLoader) Prime(key int, value *Time) bool { l.mu.Lock() var found bool if _, found = l.cache[key]; !found { @@ -167,9 +167,9 @@ func (l *SceneLastPlayedLoader) Clear(key int) { l.mu.Unlock() } -func (l *SceneLastPlayedLoader) unsafeSet(key int, value *time.Time) { +func (l *SceneLastPlayedLoader) unsafeSet(key int, value *Time) { if l.cache == nil { - l.cache = map[int]*time.Time{} + l.cache = map[int]*Time{} } l.cache[key] = value } diff --git a/internal/api/loaders/sceneohistoryloader_gen.go b/internal/api/loaders/sceneohistoryloader_gen.go index a13dfbbd6..af5cd326d 100644 --- a/internal/api/loaders/sceneohistoryloader_gen.go +++ b/internal/api/loaders/sceneohistoryloader_gen.go @@ -10,7 +10,7 @@ import ( // SceneOHistoryLoaderConfig captures the config to create a new SceneOHistoryLoader type SceneOHistoryLoaderConfig struct { // Fetch is a method that provides the data for the loader - Fetch func(keys []int) ([][]time.Time, []error) + Fetch func(keys []int) ([][]Time, []error) // Wait is how long wait before sending a batch Wait time.Duration @@ -31,7 +31,7 @@ func NewSceneOHistoryLoader(config SceneOHistoryLoaderConfig) *SceneOHistoryLoad // SceneOHistoryLoader batches and caches requests type SceneOHistoryLoader struct { // this method provides the data for the loader - fetch func(keys []int) ([][]time.Time, []error) + fetch func(keys []int) ([][]Time, []error) // how long to done before sending a batch wait time.Duration @@ -42,7 +42,7 @@ type SceneOHistoryLoader struct { // INTERNAL // lazily created cache - cache map[int][]time.Time + cache map[int][]Time // the current batch. keys will continue to be collected until timeout is hit, // then everything will be sent to the fetch method and out to the listeners @@ -54,25 +54,25 @@ type SceneOHistoryLoader struct { type sceneOHistoryLoaderBatch struct { keys []int - data [][]time.Time + data [][]Time error []error closing bool done chan struct{} } // Load a Time by key, batching and caching will be applied automatically -func (l *SceneOHistoryLoader) Load(key int) ([]time.Time, error) { +func (l *SceneOHistoryLoader) Load(key int) ([]Time, error) { return l.LoadThunk(key)() } // LoadThunk returns a function that when called will block waiting for a Time. // This method should be used if you want one goroutine to make requests to many // different data loaders without blocking until the thunk is called. -func (l *SceneOHistoryLoader) LoadThunk(key int) func() ([]time.Time, error) { +func (l *SceneOHistoryLoader) LoadThunk(key int) func() ([]Time, error) { l.mu.Lock() if it, ok := l.cache[key]; ok { l.mu.Unlock() - return func() ([]time.Time, error) { + return func() ([]Time, error) { return it, nil } } @@ -83,10 +83,10 @@ func (l *SceneOHistoryLoader) LoadThunk(key int) func() ([]time.Time, error) { pos := batch.keyIndex(l, key) l.mu.Unlock() - return func() ([]time.Time, error) { + return func() ([]Time, error) { <-batch.done - var data []time.Time + var data []Time if pos < len(batch.data) { data = batch.data[pos] } @@ -111,14 +111,14 @@ func (l *SceneOHistoryLoader) LoadThunk(key int) func() ([]time.Time, error) { // LoadAll fetches many keys at once. It will be broken into appropriate sized // sub batches depending on how the loader is configured -func (l *SceneOHistoryLoader) LoadAll(keys []int) ([][]time.Time, []error) { - results := make([]func() ([]time.Time, error), len(keys)) +func (l *SceneOHistoryLoader) LoadAll(keys []int) ([][]Time, []error) { + results := make([]func() ([]Time, error), len(keys)) for i, key := range keys { results[i] = l.LoadThunk(key) } - times := make([][]time.Time, len(keys)) + times := make([][]Time, len(keys)) errors := make([]error, len(keys)) for i, thunk := range results { times[i], errors[i] = thunk() @@ -129,13 +129,13 @@ func (l *SceneOHistoryLoader) LoadAll(keys []int) ([][]time.Time, []error) { // LoadAllThunk returns a function that when called will block waiting for a Times. // This method should be used if you want one goroutine to make requests to many // different data loaders without blocking until the thunk is called. -func (l *SceneOHistoryLoader) LoadAllThunk(keys []int) func() ([][]time.Time, []error) { - results := make([]func() ([]time.Time, error), len(keys)) +func (l *SceneOHistoryLoader) LoadAllThunk(keys []int) func() ([][]Time, []error) { + results := make([]func() ([]Time, error), len(keys)) for i, key := range keys { results[i] = l.LoadThunk(key) } - return func() ([][]time.Time, []error) { - times := make([][]time.Time, len(keys)) + return func() ([][]Time, []error) { + times := make([][]Time, len(keys)) errors := make([]error, len(keys)) for i, thunk := range results { times[i], errors[i] = thunk() @@ -147,13 +147,13 @@ func (l *SceneOHistoryLoader) LoadAllThunk(keys []int) func() ([][]time.Time, [] // Prime the cache with the provided key and value. If the key already exists, no change is made // and false is returned. // (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).) -func (l *SceneOHistoryLoader) Prime(key int, value []time.Time) bool { +func (l *SceneOHistoryLoader) Prime(key int, value []Time) bool { l.mu.Lock() var found bool if _, found = l.cache[key]; !found { // make a copy when writing to the cache, its easy to pass a pointer in from a loop var // and end up with the whole cache pointing to the same value. - cpy := make([]time.Time, len(value)) + cpy := make([]Time, len(value)) copy(cpy, value) l.unsafeSet(key, cpy) } @@ -168,9 +168,9 @@ func (l *SceneOHistoryLoader) Clear(key int) { l.mu.Unlock() } -func (l *SceneOHistoryLoader) unsafeSet(key int, value []time.Time) { +func (l *SceneOHistoryLoader) unsafeSet(key int, value []Time) { if l.cache == nil { - l.cache = map[int][]time.Time{} + l.cache = map[int][]Time{} } l.cache[key] = value } diff --git a/internal/api/loaders/sceneplayhistoryloader_gen.go b/internal/api/loaders/sceneplayhistoryloader_gen.go index dfbea84dc..a204ee3aa 100644 --- a/internal/api/loaders/sceneplayhistoryloader_gen.go +++ b/internal/api/loaders/sceneplayhistoryloader_gen.go @@ -10,7 +10,7 @@ import ( // ScenePlayHistoryLoaderConfig captures the config to create a new ScenePlayHistoryLoader type ScenePlayHistoryLoaderConfig struct { // Fetch is a method that provides the data for the loader - Fetch func(keys []int) ([][]time.Time, []error) + Fetch func(keys []int) ([][]Time, []error) // Wait is how long wait before sending a batch Wait time.Duration @@ -31,7 +31,7 @@ func NewScenePlayHistoryLoader(config ScenePlayHistoryLoaderConfig) *ScenePlayHi // ScenePlayHistoryLoader batches and caches requests type ScenePlayHistoryLoader struct { // this method provides the data for the loader - fetch func(keys []int) ([][]time.Time, []error) + fetch func(keys []int) ([][]Time, []error) // how long to done before sending a batch wait time.Duration @@ -42,7 +42,7 @@ type ScenePlayHistoryLoader struct { // INTERNAL // lazily created cache - cache map[int][]time.Time + cache map[int][]Time // the current batch. keys will continue to be collected until timeout is hit, // then everything will be sent to the fetch method and out to the listeners @@ -54,25 +54,25 @@ type ScenePlayHistoryLoader struct { type scenePlayHistoryLoaderBatch struct { keys []int - data [][]time.Time + data [][]Time error []error closing bool done chan struct{} } // Load a Time by key, batching and caching will be applied automatically -func (l *ScenePlayHistoryLoader) Load(key int) ([]time.Time, error) { +func (l *ScenePlayHistoryLoader) Load(key int) ([]Time, error) { return l.LoadThunk(key)() } // LoadThunk returns a function that when called will block waiting for a Time. // This method should be used if you want one goroutine to make requests to many // different data loaders without blocking until the thunk is called. -func (l *ScenePlayHistoryLoader) LoadThunk(key int) func() ([]time.Time, error) { +func (l *ScenePlayHistoryLoader) LoadThunk(key int) func() ([]Time, error) { l.mu.Lock() if it, ok := l.cache[key]; ok { l.mu.Unlock() - return func() ([]time.Time, error) { + return func() ([]Time, error) { return it, nil } } @@ -83,10 +83,10 @@ func (l *ScenePlayHistoryLoader) LoadThunk(key int) func() ([]time.Time, error) pos := batch.keyIndex(l, key) l.mu.Unlock() - return func() ([]time.Time, error) { + return func() ([]Time, error) { <-batch.done - var data []time.Time + var data []Time if pos < len(batch.data) { data = batch.data[pos] } @@ -111,14 +111,14 @@ func (l *ScenePlayHistoryLoader) LoadThunk(key int) func() ([]time.Time, error) // LoadAll fetches many keys at once. It will be broken into appropriate sized // sub batches depending on how the loader is configured -func (l *ScenePlayHistoryLoader) LoadAll(keys []int) ([][]time.Time, []error) { - results := make([]func() ([]time.Time, error), len(keys)) +func (l *ScenePlayHistoryLoader) LoadAll(keys []int) ([][]Time, []error) { + results := make([]func() ([]Time, error), len(keys)) for i, key := range keys { results[i] = l.LoadThunk(key) } - times := make([][]time.Time, len(keys)) + times := make([][]Time, len(keys)) errors := make([]error, len(keys)) for i, thunk := range results { times[i], errors[i] = thunk() @@ -129,13 +129,13 @@ func (l *ScenePlayHistoryLoader) LoadAll(keys []int) ([][]time.Time, []error) { // LoadAllThunk returns a function that when called will block waiting for a Times. // This method should be used if you want one goroutine to make requests to many // different data loaders without blocking until the thunk is called. -func (l *ScenePlayHistoryLoader) LoadAllThunk(keys []int) func() ([][]time.Time, []error) { - results := make([]func() ([]time.Time, error), len(keys)) +func (l *ScenePlayHistoryLoader) LoadAllThunk(keys []int) func() ([][]Time, []error) { + results := make([]func() ([]Time, error), len(keys)) for i, key := range keys { results[i] = l.LoadThunk(key) } - return func() ([][]time.Time, []error) { - times := make([][]time.Time, len(keys)) + return func() ([][]Time, []error) { + times := make([][]Time, len(keys)) errors := make([]error, len(keys)) for i, thunk := range results { times[i], errors[i] = thunk() @@ -147,13 +147,13 @@ func (l *ScenePlayHistoryLoader) LoadAllThunk(keys []int) func() ([][]time.Time, // Prime the cache with the provided key and value. If the key already exists, no change is made // and false is returned. // (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).) -func (l *ScenePlayHistoryLoader) Prime(key int, value []time.Time) bool { +func (l *ScenePlayHistoryLoader) Prime(key int, value []Time) bool { l.mu.Lock() var found bool if _, found = l.cache[key]; !found { // make a copy when writing to the cache, its easy to pass a pointer in from a loop var // and end up with the whole cache pointing to the same value. - cpy := make([]time.Time, len(value)) + cpy := make([]Time, len(value)) copy(cpy, value) l.unsafeSet(key, cpy) } @@ -168,9 +168,9 @@ func (l *ScenePlayHistoryLoader) Clear(key int) { l.mu.Unlock() } -func (l *ScenePlayHistoryLoader) unsafeSet(key int, value []time.Time) { +func (l *ScenePlayHistoryLoader) unsafeSet(key int, value []Time) { if l.cache == nil { - l.cache = map[int][]time.Time{} + l.cache = map[int][]Time{} } l.cache[key] = value } From 682f15e63d7f7c6b44b80897ccafa81dd70375bd Mon Sep 17 00:00:00 2001 From: stashcoder42 Date: Tue, 1 Jul 2025 09:35:17 -0400 Subject: [PATCH 2/2] Fix make generate-dataloaders and the extra time import --- internal/api/loaders/dataloaders.go | 56 +++++++++++++++++++++++----- internal/api/resolver_model_scene.go | 16 ++++---- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/internal/api/loaders/dataloaders.go b/internal/api/loaders/dataloaders.go index 1d9416417..e8edfe1df 100644 --- a/internal/api/loaders/dataloaders.go +++ b/internal/api/loaders/dataloaders.go @@ -32,7 +32,34 @@ import ( // Work around bug in dataloaden https://github.com/vektah/dataloaden/issues/54 type Time struct { - time.Time + time.Time +} + +// Helper functions for time conversion +func timeToTime(t *time.Time) *Time { + if t == nil { + return nil + } + return &Time{*t} +} + +func timesToTimes(times []*time.Time) []*Time { + result := make([]*Time, len(times)) + for i, t := range times { + result[i] = timeToTime(t) + } + return result +} + +func timeSlicesToTimeSlices(timeSlices [][]time.Time) [][]Time { + result := make([][]Time, len(timeSlices)) + for i, slice := range timeSlices { + result[i] = make([]Time, len(slice)) + for j, t := range slice { + result[i][j] = Time{t} + } + } + return result } type contextKey struct{ name string } @@ -357,35 +384,44 @@ func (m Middleware) fetchScenesPlayCount(ctx context.Context) func(keys []int) ( } } -func (m Middleware) fetchScenesOHistory(ctx context.Context) func(keys []int) ([][]time.Time, []error) { - return func(keys []int) (ret [][]time.Time, errs []error) { +func (m Middleware) fetchScenesOHistory(ctx context.Context) func(keys []int) ([][]Time, []error) { + return func(keys []int) (ret [][]Time, errs []error) { + var timeRet [][]time.Time err := m.Repository.WithDB(ctx, func(ctx context.Context) error { var err error - ret, err = m.Repository.Scene.GetManyODates(ctx, keys) + timeRet, err = m.Repository.Scene.GetManyODates(ctx, keys) return err }) + + ret = timeSlicesToTimeSlices(timeRet) return ret, toErrorSlice(err) } } -func (m Middleware) fetchScenesPlayHistory(ctx context.Context) func(keys []int) ([][]time.Time, []error) { - return func(keys []int) (ret [][]time.Time, errs []error) { +func (m Middleware) fetchScenesPlayHistory(ctx context.Context) func(keys []int) ([][]Time, []error) { + return func(keys []int) (ret [][]Time, errs []error) { + var timeRet [][]time.Time err := m.Repository.WithDB(ctx, func(ctx context.Context) error { var err error - ret, err = m.Repository.Scene.GetManyViewDates(ctx, keys) + timeRet, err = m.Repository.Scene.GetManyViewDates(ctx, keys) return err }) + + ret = timeSlicesToTimeSlices(timeRet) return ret, toErrorSlice(err) } } -func (m Middleware) fetchScenesLastPlayed(ctx context.Context) func(keys []int) ([]*time.Time, []error) { - return func(keys []int) (ret []*time.Time, errs []error) { +func (m Middleware) fetchScenesLastPlayed(ctx context.Context) func(keys []int) ([]*Time, []error) { + return func(keys []int) (ret []*Time, errs []error) { + var timeRet []*time.Time err := m.Repository.WithDB(ctx, func(ctx context.Context) error { var err error - ret, err = m.Repository.Scene.GetManyLastViewed(ctx, keys) + timeRet, err = m.Repository.Scene.GetManyLastViewed(ctx, keys) return err }) + + ret = timesToTimes(timeRet) return ret, toErrorSlice(err) } } diff --git a/internal/api/resolver_model_scene.go b/internal/api/resolver_model_scene.go index 2600c9538..6b6931e1c 100644 --- a/internal/api/resolver_model_scene.go +++ b/internal/api/resolver_model_scene.go @@ -367,7 +367,11 @@ func (r *sceneResolver) LastPlayedAt(ctx context.Context, obj *models.Scene) (*t return nil, err } - return ret, nil + if ret == nil { + return nil, nil + } + + return &ret.Time, nil } func (r *sceneResolver) PlayCount(ctx context.Context, obj *models.Scene) (*int, error) { @@ -385,11 +389,10 @@ func (r *sceneResolver) PlayHistory(ctx context.Context, obj *models.Scene) ([]* return nil, err } - // convert to pointer slice + // convert to pointer slice of time.Time ptrRet := make([]*time.Time, len(ret)) for i, t := range ret { - tt := t - ptrRet[i] = &tt + ptrRet[i] = &t.Time } return ptrRet, nil @@ -401,11 +404,10 @@ func (r *sceneResolver) OHistory(ctx context.Context, obj *models.Scene) ([]*tim return nil, err } - // convert to pointer slice + // convert to pointer slice of time.Time ptrRet := make([]*time.Time, len(ret)) for i, t := range ret { - tt := t - ptrRet[i] = &tt + ptrRet[i] = &t.Time } return ptrRet, nil