Maintain saved filters in full export/import (#5465)

* Remove ellipsis from full export button
This commit is contained in:
WithoutPants
2024-11-12 16:59:28 +11:00
committed by GitHub
parent 41d1b45fb9
commit a18c538c1f
11 changed files with 484 additions and 9 deletions

View File

@@ -0,0 +1,31 @@
package jsonschema
import (
"fmt"
"os"
jsoniter "github.com/json-iterator/go"
)
func loadFile[T any](filePath string) (*T, error) {
var ret T
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
var json = jsoniter.ConfigCompatibleWithStandardLibrary
jsonParser := json.NewDecoder(file)
err = jsonParser.Decode(&ret)
if err != nil {
return nil, err
}
return &ret, nil
}
func saveFile[T any](filePath string, obj *T) error {
if obj == nil {
return fmt.Errorf("object must not be nil")
}
return marshalToFile(filePath, obj)
}

View File

@@ -0,0 +1,27 @@
package jsonschema
import (
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/models"
)
type SavedFilter struct {
Mode models.FilterMode `db:"mode" json:"mode"`
Name string `db:"name" json:"name"`
FindFilter *models.FindFilterType `json:"find_filter"`
ObjectFilter map[string]interface{} `json:"object_filter"`
UIOptions map[string]interface{} `json:"ui_options"`
}
func (s SavedFilter) Filename() string {
ret := fsutil.SanitiseBasename(s.Name + "_" + s.Mode.String())
return ret + ".json"
}
func LoadSavedFilterFile(filePath string) (*SavedFilter, error) {
return loadFile[SavedFilter](filePath)
}
func SaveSavedFilterFile(filePath string, image *SavedFilter) error {
return saveFile[SavedFilter](filePath, image)
}

View File

@@ -12,14 +12,15 @@ type JSONPaths struct {
ScrapedFile string
Performers string
Scenes string
Images string
Galleries string
Studios string
Tags string
Groups string
Files string
Performers string
Scenes string
Images string
Galleries string
Studios string
Tags string
Groups string
Files string
SavedFilters string
}
func newJSONPaths(baseDir string) *JSONPaths {
@@ -34,6 +35,7 @@ func newJSONPaths(baseDir string) *JSONPaths {
jp.Groups = filepath.Join(baseDir, "movies")
jp.Tags = filepath.Join(baseDir, "tags")
jp.Files = filepath.Join(baseDir, "files")
jp.SavedFilters = filepath.Join(baseDir, "saved_filters")
return &jp
}
@@ -52,6 +54,7 @@ func EmptyJSONDirs(baseDir string) {
_ = fsutil.EmptyDir(jsonPaths.Groups)
_ = fsutil.EmptyDir(jsonPaths.Tags)
_ = fsutil.EmptyDir(jsonPaths.Files)
_ = fsutil.EmptyDir(jsonPaths.SavedFilters)
}
func EnsureJSONDirs(baseDir string) {
@@ -83,4 +86,7 @@ func EnsureJSONDirs(baseDir string) {
if err := fsutil.EnsureDir(jsonPaths.Files); err != nil {
logger.Warnf("couldn't create directories for Files: %v", err)
}
if err := fsutil.EnsureDir(jsonPaths.SavedFilters); err != nil {
logger.Warnf("couldn't create directories for Saved Filters: %v", err)
}
}

19
pkg/savedfilter/export.go Normal file
View File

@@ -0,0 +1,19 @@
package savedfilter
import (
"context"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
)
// ToJSON converts a SavedFilter object into its JSON equivalent.
func ToJSON(ctx context.Context, filter *models.SavedFilter) (*jsonschema.SavedFilter, error) {
return &jsonschema.SavedFilter{
Name: filter.Name,
Mode: filter.Mode,
FindFilter: filter.FindFilter,
ObjectFilter: filter.ObjectFilter,
UIOptions: filter.UIOptions,
}, nil
}

View File

@@ -0,0 +1,91 @@
package savedfilter
import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/models/mocks"
"github.com/stretchr/testify/assert"
"testing"
)
const (
savedFilterID = 1
noImageID = 2
errImageID = 3
errAliasID = 4
withParentsID = 5
errParentsID = 6
)
const (
filterName = "testFilter"
mode = models.FilterModeGalleries
)
var (
findFilter = models.FindFilterType{}
objectFilter = make(map[string]interface{})
uiOptions = make(map[string]interface{})
)
func createSavedFilter(id int) models.SavedFilter {
return models.SavedFilter{
ID: id,
Name: filterName,
Mode: mode,
FindFilter: &findFilter,
ObjectFilter: objectFilter,
UIOptions: uiOptions,
}
}
func createJSONSavedFilter() *jsonschema.SavedFilter {
return &jsonschema.SavedFilter{
Name: filterName,
Mode: mode,
FindFilter: &findFilter,
ObjectFilter: objectFilter,
UIOptions: uiOptions,
}
}
type testScenario struct {
savedFilter models.SavedFilter
expected *jsonschema.SavedFilter
err bool
}
var scenarios []testScenario
func initTestTable() {
scenarios = []testScenario{
{
createSavedFilter(savedFilterID),
createJSONSavedFilter(),
false,
},
}
}
func TestToJSON(t *testing.T) {
initTestTable()
db := mocks.NewDatabase()
for i, s := range scenarios {
savedFilter := s.savedFilter
json, err := ToJSON(testCtx, &savedFilter)
switch {
case !s.err && err != nil:
t.Errorf("[%d] unexpected error: %s", i, err.Error())
case s.err && err == nil:
t.Errorf("[%d] expected error not returned", i)
default:
assert.Equal(t, s.expected, json, "[%d]", i)
}
}
db.AssertExpectations(t)
}

60
pkg/savedfilter/import.go Normal file
View File

@@ -0,0 +1,60 @@
package savedfilter
import (
"context"
"fmt"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
)
type ImporterReaderWriter interface {
models.SavedFilterWriter
}
type Importer struct {
ReaderWriter ImporterReaderWriter
Input jsonschema.SavedFilter
MissingRefBehaviour models.ImportMissingRefEnum
savedFilter models.SavedFilter
}
func (i *Importer) PreImport(ctx context.Context) error {
i.savedFilter = models.SavedFilter{
Name: i.Input.Name,
Mode: i.Input.Mode,
FindFilter: i.Input.FindFilter,
ObjectFilter: i.Input.ObjectFilter,
UIOptions: i.Input.UIOptions,
}
return nil
}
func (i *Importer) PostImport(ctx context.Context, id int) error {
return nil
}
func (i *Importer) Name() string {
return i.Input.Name
}
func (i *Importer) FindExistingID(ctx context.Context) (*int, error) {
// for now, assume this is only imported in full, so we don't support updating existing filters
return nil, nil
}
func (i *Importer) Create(ctx context.Context) (*int, error) {
err := i.ReaderWriter.Create(ctx, &i.savedFilter)
if err != nil {
return nil, fmt.Errorf("error creating saved filter: %v", err)
}
id := i.savedFilter.ID
return &id, nil
}
func (i *Importer) Update(ctx context.Context, id int) error {
return fmt.Errorf("updating existing saved filters is not supported")
}

View File

@@ -0,0 +1,124 @@
package savedfilter
import (
"context"
"errors"
"testing"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/models/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
const (
savedFilterNameErr = "savedFilterNameErr"
existingSavedFilterName = "existingSavedFilterName"
existingFilterID = 100
)
var testCtx = context.Background()
func TestImporterName(t *testing.T) {
i := Importer{
Input: jsonschema.SavedFilter{
Name: filterName,
},
}
assert.Equal(t, filterName, i.Name())
}
func TestImporterPreImport(t *testing.T) {
i := Importer{
Input: jsonschema.SavedFilter{
Name: filterName,
},
}
err := i.PreImport(testCtx)
assert.Nil(t, err)
}
func TestImporterPostImport(t *testing.T) {
db := mocks.NewDatabase()
i := Importer{
ReaderWriter: db.SavedFilter,
Input: jsonschema.SavedFilter{},
}
err := i.PostImport(testCtx, savedFilterID)
assert.Nil(t, err)
db.AssertExpectations(t)
}
func TestImporterFindExistingID(t *testing.T) {
db := mocks.NewDatabase()
i := Importer{
ReaderWriter: db.SavedFilter,
Input: jsonschema.SavedFilter{
Name: filterName,
},
}
id, err := i.FindExistingID(testCtx)
assert.Nil(t, id)
assert.Nil(t, err)
}
func TestCreate(t *testing.T) {
db := mocks.NewDatabase()
savedFilter := models.SavedFilter{
Name: filterName,
}
savedFilterErr := models.SavedFilter{
Name: savedFilterNameErr,
}
i := Importer{
ReaderWriter: db.SavedFilter,
savedFilter: savedFilter,
}
errCreate := errors.New("Create error")
db.SavedFilter.On("Create", testCtx, &savedFilter).Run(func(args mock.Arguments) {
t := args.Get(1).(*models.SavedFilter)
t.ID = savedFilterID
}).Return(nil).Once()
db.SavedFilter.On("Create", testCtx, &savedFilterErr).Return(errCreate).Once()
id, err := i.Create(testCtx)
assert.Equal(t, savedFilterID, *id)
assert.Nil(t, err)
i.savedFilter = savedFilterErr
id, err = i.Create(testCtx)
assert.Nil(t, id)
assert.NotNil(t, err)
db.AssertExpectations(t)
}
func TestUpdate(t *testing.T) {
db := mocks.NewDatabase()
savedFilterErr := models.SavedFilter{
Name: savedFilterNameErr,
}
i := Importer{
ReaderWriter: db.SavedFilter,
savedFilter: savedFilterErr,
}
// Update is not currently supported
err := i.Update(testCtx, existingFilterID)
assert.NotNil(t, err)
}