Skip to content

Commit

Permalink
Default view filters (#4962)
Browse files Browse the repository at this point in the history
* Merge/adapt from yoshnopa:defaultDetails
* Deprecate and remove default filter calls
* Fix weird behaviour when clicking set as default
* Update deprecated get/set default filter resolvers
* Add config migration
---------
Co-authored-by: yoshnopa <usingusenet@protonmail.com>
  • Loading branch information
WithoutPants authored Jun 18, 2024
1 parent 4be6031 commit f9a624b
Show file tree
Hide file tree
Showing 59 changed files with 611 additions and 403 deletions.
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

0 comments on commit f9a624b

Please sign in to comment.