Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default view filters #4962

Merged
merged 10 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ require (
github.com/knadh/koanf v1.5.0
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-sqlite3 v1.14.22
github.com/mitchellh/mapstructure v1.5.0
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/remeh/sizedwaitgroup v1.0.0
Expand Down Expand Up @@ -88,7 +89,6 @@ require (
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
Expand Down
2 changes: 2 additions & 0 deletions graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type Query {
findSavedFilter(id: ID!): SavedFilter
findSavedFilters(mode: FilterMode): [SavedFilter!]!
findDefaultFilter(mode: FilterMode!): SavedFilter
@deprecated(reason: "default filter now stored in UI config")

"Find a scene by ID or Checksum"
findScene(id: ID, checksum: String): Scene
Expand Down Expand Up @@ -345,6 +346,7 @@ type Mutation {
saveFilter(input: SaveFilterInput!): SavedFilter!
destroySavedFilter(input: DestroyFilterInput!): Boolean!
setDefaultFilter(input: SetDefaultFilterInput!): Boolean!
@deprecated(reason: "now uses UI config")

"Change general configuration options"
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
Expand Down
59 changes: 40 additions & 19 deletions internal/api/resolver_mutation_saved_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import (
"strconv"
"strings"

"github.com/mitchellh/mapstructure"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)

func (r *mutationResolver) SaveFilter(ctx context.Context, input SaveFilterInput) (ret *models.SavedFilter, err error) {
Expand Down Expand Up @@ -67,30 +70,48 @@ func (r *mutationResolver) DestroySavedFilter(ctx context.Context, input Destroy
}

func (r *mutationResolver) SetDefaultFilter(ctx context.Context, input SetDefaultFilterInput) (bool, error) {
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.SavedFilter
// deprecated - write to the config in the meantime
config := config.GetInstance()

uiConfig := config.GetUIConfiguration()
if uiConfig == nil {
uiConfig = make(map[string]interface{})
}

if input.FindFilter == nil && input.ObjectFilter == nil && input.UIOptions == nil {
// clearing
def, err := qb.FindDefault(ctx, input.Mode)
if err != nil {
return err
}
m := utils.NestedMap(uiConfig)

if def != nil {
return qb.Destroy(ctx, def.ID)
}
if input.FindFilter == nil && input.ObjectFilter == nil && input.UIOptions == nil {
// clearing
m.Delete("defaultFilters." + strings.ToLower(input.Mode.String()))
config.SetUIConfiguration(m)

return nil
if err := config.Write(); err != nil {
return false, err
}

return qb.SetDefault(ctx, &models.SavedFilter{
Mode: input.Mode,
FindFilter: input.FindFilter,
ObjectFilter: input.ObjectFilter,
UIOptions: input.UIOptions,
})
}); err != nil {
return true, nil
}

subMap := make(map[string]interface{})
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
TagName: "json",
WeaklyTypedInput: true,
Result: &subMap,
})

if err != nil {
return false, err
}

if err := d.Decode(input); err != nil {
return false, err
}

m.Set("defaultFilters."+strings.ToLower(input.Mode.String()), subMap)

config.SetUIConfiguration(m)

if err := config.Write(); err != nil {
return false, err
}

Expand Down
38 changes: 33 additions & 5 deletions internal/api/resolver_query_find_saved_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ package api
import (
"context"
"strconv"
"strings"

"github.com/mitchellh/mapstructure"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)

func (r *queryResolver) FindSavedFilter(ctx context.Context, id string) (ret *models.SavedFilter, err error) {
Expand Down Expand Up @@ -37,11 +41,35 @@ func (r *queryResolver) FindSavedFilters(ctx context.Context, mode *models.Filte
}

func (r *queryResolver) FindDefaultFilter(ctx context.Context, mode models.FilterMode) (ret *models.SavedFilter, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.SavedFilter.FindDefault(ctx, mode)
return err
}); err != nil {
// deprecated - read from the config in the meantime
config := config.GetInstance()

uiConfig := config.GetUIConfiguration()
if uiConfig == nil {
return nil, nil
}

m := utils.NestedMap(uiConfig)
filterRaw, _ := m.Get("defaultFilters." + strings.ToLower(mode.String()))

if filterRaw == nil {
return nil, nil
}

ret = &models.SavedFilter{}
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
TagName: "json",
WeaklyTypedInput: true,
Result: ret,
})

if err != nil {
return nil, err
}
return ret, err

if err := d.Decode(filterRaw); err != nil {
return nil, err
}

return ret, nil
}
2 changes: 0 additions & 2 deletions pkg/models/saved_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ type SavedFilterReader interface {
Find(ctx context.Context, id int) (*SavedFilter, error)
FindMany(ctx context.Context, ids []int, ignoreNotFound bool) ([]*SavedFilter, error)
FindByMode(ctx context.Context, mode FilterMode) ([]*SavedFilter, error)
FindDefault(ctx context.Context, mode FilterMode) (*SavedFilter, error)
}

type SavedFilterWriter interface {
Create(ctx context.Context, obj *SavedFilter) error
Update(ctx context.Context, obj *SavedFilter) error
SetDefault(ctx context.Context, obj *SavedFilter) error
Destroy(ctx context.Context, id int) error
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/sqlite/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const (
dbConnTimeout = 30
)

var appSchemaVersion uint = 59
var appSchemaVersion uint = 60

//go:embed migrations/*.sql
var migrationsBox embed.FS
Expand Down
2 changes: 2 additions & 0 deletions pkg/sqlite/migrations/60_default_filter_move.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- no schema changes
-- default filters will be removed in post-migration
176 changes: 176 additions & 0 deletions pkg/sqlite/migrations/60_postmigrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package migrations

import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/sqlite"
)

type schema60Migrator struct {
migrator
}

func post60(ctx context.Context, db *sqlx.DB) error {
logger.Info("Running post-migration for schema version 60")

m := schema60Migrator{
migrator: migrator{
db: db,
},
}

return m.migrate(ctx)
}

func (m *schema60Migrator) decodeJSON(s string, v interface{}) {
if s == "" {
return
}

if err := json.Unmarshal([]byte(s), v); err != nil {
logger.Errorf("error decoding json %q: %v", s, err)
}
}

type schema60DefaultFilters map[string]interface{}

func (m *schema60Migrator) migrate(ctx context.Context) error {

// save default filters into the UI config
if err := m.withTxn(ctx, func(tx *sqlx.Tx) error {
query := "SELECT id, mode, find_filter, object_filter, ui_options FROM `saved_filters` WHERE `name` = ''"

rows, err := m.db.Query(query)
if err != nil {
return err
}
defer rows.Close()

defaultFilters := make(schema60DefaultFilters)

for rows.Next() {
var (
id int
mode string
findFilterStr string
objectFilterStr string
uiOptionsStr string
)

if err := rows.Scan(&id, &mode, &findFilterStr, &objectFilterStr, &uiOptionsStr); err != nil {
return err
}

// convert the filters to the correct format
findFilter := make(map[string]interface{})
objectFilter := make(map[string]interface{})
uiOptions := make(map[string]interface{})

m.decodeJSON(findFilterStr, &findFilter)
m.decodeJSON(objectFilterStr, &objectFilter)
m.decodeJSON(uiOptionsStr, &uiOptions)

o := map[string]interface{}{
"mode": mode,
"find_filter": findFilter,
"object_filter": objectFilter,
"ui_options": uiOptions,
}

defaultFilters[strings.ToLower(mode)] = o
}

if err := rows.Err(); err != nil {
return err
}

if err := m.saveDefaultFilters(defaultFilters); err != nil {
return fmt.Errorf("saving default filters: %w", err)
}

// remove the default filters from the database
query = "DELETE FROM `saved_filters` WHERE `name` = ''"
if _, err := m.db.Exec(query); err != nil {
return fmt.Errorf("deleting default filters: %w", err)
}

return nil
}); err != nil {
return err
}

return nil
}

func (m *schema60Migrator) saveDefaultFilters(defaultFilters schema60DefaultFilters) error {
if len(defaultFilters) == 0 {
logger.Debugf("no default filters to save")
return nil
}

// save the default filters into the UI config
config := config.GetInstance()

orgPath := config.GetConfigFile()

if orgPath == "" {
// no config file to migrate (usually in a test or new system)
logger.Debugf("no config file to migrate")
return nil
}

uiConfig := config.GetUIConfiguration()
if uiConfig == nil {
uiConfig = make(map[string]interface{})
}

// if the defaultFilters key already exists, don't overwrite them
if _, found := uiConfig["defaultFilters"]; found {
logger.Warn("defaultFilters already exists in the UI config, skipping migration")
return nil
}

if err := m.backupConfig(orgPath); err != nil {
return fmt.Errorf("backing up config: %w", err)
}

uiConfig["defaultFilters"] = map[string]interface{}(defaultFilters)
config.SetUIConfiguration(uiConfig)

if err := config.Write(); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}

return nil
}

func (m *schema60Migrator) backupConfig(orgPath string) error {
c := config.GetInstance()

// save a backup of the original config file
backupPath := fmt.Sprintf("%s.59.%s", orgPath, time.Now().Format("20060102_150405"))

data, err := c.Marshal()
if err != nil {
return fmt.Errorf("failed to marshal backup config: %w", err)
}

logger.Infof("Backing up config to %s", backupPath)
if err := os.WriteFile(backupPath, data, 0644); err != nil {
return fmt.Errorf("failed to write backup config: %w", err)
}

return nil
}

func init() {
sqlite.RegisterPostMigration(60, post60)
}
Loading
Loading