stash/pkg/sqlite/setup_test.go

1937 lines
47 KiB
Go

//go:build integration
// +build integration
package sqlite_test
import (
"context"
"database/sql"
"errors"
"fmt"
"os"
"path/filepath"
"slices"
"strconv"
"testing"
"time"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sqlite"
"github.com/stashapp/stash/pkg/txn"
// necessary to register custom migrations
_ "github.com/stashapp/stash/pkg/sqlite/migrations"
)
const (
spacedSceneTitle = "zzz yyy xxx"
)
const (
folderIdxWithSubFolder = iota
folderIdxWithParentFolder
folderIdxWithFiles
folderIdxInZip
folderIdxForObjectFiles
folderIdxWithImageFiles
folderIdxWithGalleryFiles
folderIdxWithSceneFiles
totalFolders
)
const (
fileIdxZip = iota
fileIdxInZip
fileIdxStartVideoFiles
fileIdxStartImageFiles
fileIdxStartGalleryFiles
totalFiles
)
const (
sceneIdxWithGroup = iota
sceneIdxWithGallery
sceneIdxWithPerformer
sceneIdx1WithPerformer
sceneIdx2WithPerformer
sceneIdxWithTwoPerformers
sceneIdxWithThreePerformers
sceneIdxWithTag
sceneIdxWithTwoTags
sceneIdxWithThreeTags
sceneIdxWithMarkerAndTag
sceneIdxWithMarkerTwoTags
sceneIdxWithStudio
sceneIdx1WithStudio
sceneIdx2WithStudio
sceneIdxWithMarkers
sceneIdxWithPerformerTag
sceneIdxWithTwoPerformerTag
sceneIdxWithPerformerTwoTags
sceneIdxWithSpacedName
sceneIdxWithStudioPerformer
sceneIdxWithGrandChildStudio
sceneIdxMissingPhash
sceneIdxWithPerformerParentTag
sceneIdxWithGroupWithParent
// new indexes above
lastSceneIdx
totalScenes = lastSceneIdx + 3
)
const dupeScenePhashes = 2
const (
imageIdxWithGallery = iota
imageIdx1WithGallery
imageIdx2WithGallery
imageIdxWithTwoGalleries
imageIdxWithPerformer
imageIdx1WithPerformer
imageIdx2WithPerformer
imageIdxWithTwoPerformers
imageIdxWithThreePerformers
imageIdxWithTag
imageIdxWithTwoTags
imageIdxWithThreeTags
imageIdxWithStudio
imageIdx1WithStudio
imageIdx2WithStudio
imageIdxWithStudioPerformer
imageIdxInZip
imageIdxWithPerformerTag
imageIdxWithTwoPerformerTag
imageIdxWithPerformerTwoTags
imageIdxWithGrandChildStudio
imageIdxWithPerformerParentTag
// new indexes above
totalImages
)
const (
performerIdxWithScene = iota
performerIdx1WithScene
performerIdx2WithScene
performerIdx3WithScene
performerIdxWithTwoScenes
performerIdxWithImage
performerIdxWithTwoImages
performerIdx1WithImage
performerIdx2WithImage
performerIdx3WithImage
performerIdxWithTag
performerIdx2WithTag
performerIdxWithTwoTags
performerIdxWithGallery
performerIdxWithTwoGalleries
performerIdx1WithGallery
performerIdx2WithGallery
performerIdx3WithGallery
performerIdxWithSceneStudio
performerIdxWithImageStudio
performerIdxWithGalleryStudio
performerIdxWithParentTag
// new indexes above
// performers with dup names start from the end
performerIdx1WithDupName
performerIdxWithDupName
performersNameCase = performerIdx1WithDupName
performersNameNoCase = 2
totalPerformers = performersNameCase + performersNameNoCase
)
const (
groupIdxWithScene = iota
groupIdxWithStudio
groupIdxWithTag
groupIdxWithTwoTags
groupIdxWithThreeTags
groupIdxWithGrandChild
groupIdxWithChild
groupIdxWithParentAndChild
groupIdxWithParent
groupIdxWithGrandParent
groupIdxWithParentAndScene
groupIdxWithChildWithScene
// groups with dup names start from the end
groupIdxWithDupName
groupsNameCase = groupIdxWithDupName
groupsNameNoCase = 1
)
const (
galleryIdxWithScene = iota
galleryIdxWithChapters
galleryIdxWithImage
galleryIdx1WithImage
galleryIdx2WithImage
galleryIdxWithTwoImages
galleryIdxWithPerformer
galleryIdx1WithPerformer
galleryIdx2WithPerformer
galleryIdxWithTwoPerformers
galleryIdxWithThreePerformers
galleryIdxWithTag
galleryIdxWithTwoTags
galleryIdxWithThreeTags
galleryIdxWithStudio
galleryIdx1WithStudio
galleryIdx2WithStudio
galleryIdxWithPerformerTag
galleryIdxWithTwoPerformerTag
galleryIdxWithPerformerTwoTags
galleryIdxWithStudioPerformer
galleryIdxWithGrandChildStudio
galleryIdxWithoutFile
galleryIdxWithPerformerParentTag
// new indexes above
lastGalleryIdx
totalGalleries = lastGalleryIdx + 1
)
const (
tagIdxWithScene = iota
tagIdx1WithScene
tagIdx2WithScene
tagIdx3WithScene
tagIdxWithPrimaryMarkers
tagIdxWithMarkers
tagIdxWithCoverImage
tagIdxWithImage
tagIdx1WithImage
tagIdx2WithImage
tagIdx3WithImage
tagIdxWithPerformer
tagIdx1WithPerformer
tagIdx2WithPerformer
tagIdxWithStudio
tagIdx1WithStudio
tagIdx2WithStudio
tagIdxWithGallery
tagIdx1WithGallery
tagIdx2WithGallery
tagIdx3WithGallery
tagIdxWithChildTag
tagIdxWithParentTag
tagIdxWithGrandChild
tagIdxWithParentAndChild
tagIdxWithGrandParent
tagIdx2WithMarkers
tagIdxWithGroup
tagIdx1WithGroup
tagIdx2WithGroup
tagIdx3WithGroup
// new indexes above
// tags with dup names start from the end
tagIdx1WithDupName
tagIdxWithDupName
tagsNameNoCase = 2
tagsNameCase = tagIdx1WithDupName
totalTags = tagsNameCase + tagsNameNoCase
)
const (
studioIdxWithScene = iota
studioIdxWithTwoScenes
studioIdxWithGroup
studioIdxWithChildStudio
studioIdxWithParentStudio
studioIdxWithImage
studioIdxWithTwoImages
studioIdxWithGallery
studioIdxWithTwoGalleries
studioIdxWithScenePerformer
studioIdxWithImagePerformer
studioIdxWithGalleryPerformer
studioIdxWithTag
studioIdx2WithTag
studioIdxWithTwoTags
studioIdxWithParentTag
studioIdxWithGrandChild
studioIdxWithParentAndChild
studioIdxWithGrandParent
// new indexes above
// studios with dup names start from the end
studioIdxWithDupName
studiosNameCase = studioIdxWithDupName
studiosNameNoCase = 1
totalStudios = studiosNameCase + studiosNameNoCase
)
const (
markerIdxWithScene = iota
markerIdxWithTag
markerIdxWithSceneTag
totalMarkers
)
const (
chapterIdxWithGallery = iota
totalChapters
)
const (
savedFilterIdxScene = iota
savedFilterIdxImage
// new indexes above
totalSavedFilters
)
const (
pathField = "Path"
checksumField = "Checksum"
titleField = "Title"
urlField = "URL"
zipPath = "zipPath.zip"
firstSavedFilterName = "firstSavedFilterName"
)
var (
folderIDs []models.FolderID
fileIDs []models.FileID
sceneFileIDs []models.FileID
imageFileIDs []models.FileID
galleryFileIDs []models.FileID
chapterIDs []int
sceneIDs []int
imageIDs []int
performerIDs []int
groupIDs []int
galleryIDs []int
tagIDs []int
studioIDs []int
markerIDs []int
savedFilterIDs []int
folderPaths []string
tagNames []string
studioNames []string
groupNames []string
performerNames []string
)
type idAssociation struct {
first int
second int
}
type linkMap map[int][]int
func (m linkMap) reverseLookup(idx int) []int {
var result []int
for k, v := range m {
for _, vv := range v {
if vv == idx {
result = append(result, k)
}
}
}
return result
}
var (
folderParentFolders = map[int]int{
folderIdxWithParentFolder: folderIdxWithSubFolder,
folderIdxWithSceneFiles: folderIdxForObjectFiles,
folderIdxWithImageFiles: folderIdxForObjectFiles,
folderIdxWithGalleryFiles: folderIdxForObjectFiles,
}
fileFolders = map[int]int{
fileIdxZip: folderIdxWithFiles,
fileIdxInZip: folderIdxInZip,
}
folderZipFiles = map[int]int{
folderIdxInZip: fileIdxZip,
}
fileZipFiles = map[int]int{
fileIdxInZip: fileIdxZip,
}
)
var (
sceneTags = linkMap{
sceneIdxWithTag: {tagIdxWithScene},
sceneIdxWithTwoTags: {tagIdx1WithScene, tagIdx2WithScene},
sceneIdxWithThreeTags: {tagIdx1WithScene, tagIdx2WithScene, tagIdx3WithScene},
sceneIdxWithMarkerAndTag: {tagIdx3WithScene},
sceneIdxWithMarkerTwoTags: {tagIdx2WithScene, tagIdx3WithScene},
}
scenePerformers = linkMap{
sceneIdxWithPerformer: {performerIdxWithScene},
sceneIdxWithTwoPerformers: {performerIdx1WithScene, performerIdx2WithScene},
sceneIdxWithThreePerformers: {performerIdx1WithScene, performerIdx2WithScene, performerIdx3WithScene},
sceneIdxWithPerformerTag: {performerIdxWithTag},
sceneIdxWithTwoPerformerTag: {performerIdxWithTag, performerIdx2WithTag},
sceneIdxWithPerformerTwoTags: {performerIdxWithTwoTags},
sceneIdx1WithPerformer: {performerIdxWithTwoScenes},
sceneIdx2WithPerformer: {performerIdxWithTwoScenes},
sceneIdxWithStudioPerformer: {performerIdxWithSceneStudio},
sceneIdxWithPerformerParentTag: {performerIdxWithParentTag},
}
sceneGalleries = linkMap{
sceneIdxWithGallery: {galleryIdxWithScene},
}
sceneGroups = linkMap{
sceneIdxWithGroup: {groupIdxWithScene},
sceneIdxWithGroupWithParent: {groupIdxWithParentAndScene},
}
sceneStudios = map[int]int{
sceneIdxWithStudio: studioIdxWithScene,
sceneIdx1WithStudio: studioIdxWithTwoScenes,
sceneIdx2WithStudio: studioIdxWithTwoScenes,
sceneIdxWithStudioPerformer: studioIdxWithScenePerformer,
sceneIdxWithGrandChildStudio: studioIdxWithGrandParent,
}
)
type markerSpec struct {
sceneIdx int
primaryTagIdx int
tagIdxs []int
}
var (
// indexed by marker
markerSpecs = []markerSpec{
{sceneIdxWithMarkers, tagIdxWithPrimaryMarkers, nil},
{sceneIdxWithMarkers, tagIdxWithPrimaryMarkers, []int{tagIdxWithMarkers}},
{sceneIdxWithMarkers, tagIdxWithPrimaryMarkers, []int{tagIdx2WithMarkers}},
{sceneIdxWithMarkers, tagIdxWithPrimaryMarkers, []int{tagIdxWithMarkers, tagIdx2WithMarkers}},
{sceneIdxWithMarkerAndTag, tagIdxWithPrimaryMarkers, nil},
{sceneIdxWithMarkerTwoTags, tagIdxWithPrimaryMarkers, nil},
}
)
type chapterSpec struct {
galleryIdx int
title string
imageIndex int
}
var (
// indexed by chapter
chapterSpecs = []chapterSpec{
{galleryIdxWithChapters, "Test1", 10},
}
)
var (
imageGalleries = linkMap{
imageIdxWithGallery: {galleryIdxWithImage},
imageIdx1WithGallery: {galleryIdxWithTwoImages},
imageIdx2WithGallery: {galleryIdxWithTwoImages},
imageIdxWithTwoGalleries: {galleryIdx1WithImage, galleryIdx2WithImage},
}
imageStudios = map[int]int{
imageIdxWithStudio: studioIdxWithImage,
imageIdx1WithStudio: studioIdxWithTwoImages,
imageIdx2WithStudio: studioIdxWithTwoImages,
imageIdxWithStudioPerformer: studioIdxWithImagePerformer,
imageIdxWithGrandChildStudio: studioIdxWithGrandParent,
}
imageTags = linkMap{
imageIdxWithTag: {tagIdxWithImage},
imageIdxWithTwoTags: {tagIdx1WithImage, tagIdx2WithImage},
imageIdxWithThreeTags: {tagIdx1WithImage, tagIdx2WithImage, tagIdx3WithImage},
}
imagePerformers = linkMap{
imageIdxWithPerformer: {performerIdxWithImage},
imageIdxWithTwoPerformers: {performerIdx1WithImage, performerIdx2WithImage},
imageIdxWithThreePerformers: {performerIdx1WithImage, performerIdx2WithImage, performerIdx3WithImage},
imageIdxWithPerformerTag: {performerIdxWithTag},
imageIdxWithTwoPerformerTag: {performerIdxWithTag, performerIdx2WithTag},
imageIdxWithPerformerTwoTags: {performerIdxWithTwoTags},
imageIdx1WithPerformer: {performerIdxWithTwoImages},
imageIdx2WithPerformer: {performerIdxWithTwoImages},
imageIdxWithStudioPerformer: {performerIdxWithImageStudio},
imageIdxWithPerformerParentTag: {performerIdxWithParentTag},
}
)
var (
galleryPerformers = linkMap{
galleryIdxWithPerformer: {performerIdxWithGallery},
galleryIdxWithTwoPerformers: {performerIdx1WithGallery, performerIdx2WithGallery},
galleryIdxWithThreePerformers: {performerIdx1WithGallery, performerIdx2WithGallery, performerIdx3WithGallery},
galleryIdxWithPerformerTag: {performerIdxWithTag},
galleryIdxWithTwoPerformerTag: {performerIdxWithTag, performerIdx2WithTag},
galleryIdxWithPerformerTwoTags: {performerIdxWithTwoTags},
galleryIdx1WithPerformer: {performerIdxWithTwoGalleries},
galleryIdx2WithPerformer: {performerIdxWithTwoGalleries},
galleryIdxWithStudioPerformer: {performerIdxWithGalleryStudio},
galleryIdxWithPerformerParentTag: {performerIdxWithParentTag},
}
galleryStudios = map[int]int{
galleryIdxWithStudio: studioIdxWithGallery,
galleryIdx1WithStudio: studioIdxWithTwoGalleries,
galleryIdx2WithStudio: studioIdxWithTwoGalleries,
galleryIdxWithStudioPerformer: studioIdxWithGalleryPerformer,
galleryIdxWithGrandChildStudio: studioIdxWithGrandParent,
}
galleryTags = linkMap{
galleryIdxWithTag: {tagIdxWithGallery},
galleryIdxWithTwoTags: {tagIdx1WithGallery, tagIdx2WithGallery},
galleryIdxWithThreeTags: {tagIdx1WithGallery, tagIdx2WithGallery, tagIdx3WithGallery},
}
)
var (
groupStudioLinks = [][2]int{
{groupIdxWithStudio, studioIdxWithGroup},
}
groupTags = linkMap{
groupIdxWithTag: {tagIdxWithGroup},
groupIdxWithTwoTags: {tagIdx1WithGroup, tagIdx2WithGroup},
groupIdxWithThreeTags: {tagIdx1WithGroup, tagIdx2WithGroup, tagIdx3WithGroup},
}
)
var (
studioParentLinks = [][2]int{
{studioIdxWithChildStudio, studioIdxWithParentStudio},
{studioIdxWithGrandChild, studioIdxWithParentAndChild},
{studioIdxWithParentAndChild, studioIdxWithGrandParent},
}
)
var (
studioTags = linkMap{
studioIdxWithTag: {tagIdxWithStudio},
studioIdx2WithTag: {tagIdx2WithStudio},
studioIdxWithTwoTags: {tagIdx1WithStudio, tagIdx2WithStudio},
studioIdxWithParentTag: {tagIdxWithParentAndChild},
}
)
var (
performerTags = linkMap{
performerIdxWithTag: {tagIdxWithPerformer},
performerIdx2WithTag: {tagIdx2WithPerformer},
performerIdxWithTwoTags: {tagIdx1WithPerformer, tagIdx2WithPerformer},
performerIdxWithParentTag: {tagIdxWithParentAndChild},
}
)
var (
tagParentLinks = [][2]int{
{tagIdxWithChildTag, tagIdxWithParentTag},
{tagIdxWithGrandChild, tagIdxWithParentAndChild},
{tagIdxWithParentAndChild, tagIdxWithGrandParent},
}
)
var (
groupParentLinks = [][2]int{
{groupIdxWithChild, groupIdxWithParent},
{groupIdxWithGrandChild, groupIdxWithParentAndChild},
{groupIdxWithParentAndChild, groupIdxWithGrandParent},
{groupIdxWithChildWithScene, groupIdxWithParentAndScene},
}
)
func indexesToIDs(ids []int, indexes []int) []int {
ret := make([]int, len(indexes))
for i, idx := range indexes {
ret[i] = indexToID(ids, idx)
}
return ret
}
func indexToID(ids []int, idx int) int {
if idx < 0 {
return invalidID
}
return ids[idx]
}
func indexFromID(ids []int, id int) int {
for i, v := range ids {
if v == id {
return i
}
}
return -1
}
var db *sqlite.Database
func TestMain(m *testing.M) {
// initialise empty config - needed by some migrations
_ = config.InitializeEmpty()
ret := runTests(m)
os.Exit(ret)
}
func withTxn(f func(ctx context.Context) error) error {
return txn.WithTxn(context.Background(), db, f)
}
func withRollbackTxn(f func(ctx context.Context) error) error {
var ret error
withTxn(func(ctx context.Context) error {
ret = f(ctx)
return errors.New("fake error for rollback")
})
return ret
}
func runWithRollbackTxn(t *testing.T, name string, f func(t *testing.T, ctx context.Context)) {
withRollbackTxn(func(ctx context.Context) error {
t.Run(name, func(t *testing.T) {
f(t, ctx)
})
return nil
})
}
func testTeardown(databaseFile string) {
err := db.Close()
if err != nil {
panic(err)
}
err = os.Remove(databaseFile)
if err != nil {
panic(err)
}
}
func runTests(m *testing.M) int {
// create the database file
f, err := os.CreateTemp("", "*.sqlite")
if err != nil {
panic(fmt.Sprintf("Could not create temporary file: %s", err.Error()))
}
f.Close()
databaseFile := f.Name()
db = sqlite.NewDatabase()
db.SetBlobStoreOptions(sqlite.BlobStoreOptions{
UseDatabase: true,
// don't use filesystem
})
if err := db.Open(databaseFile); err != nil {
panic(fmt.Sprintf("Could not initialize database: %s", err.Error()))
}
// defer close and delete the database
defer testTeardown(databaseFile)
err = populateDB()
if err != nil {
panic(fmt.Sprintf("Could not populate database: %s", err.Error()))
}
// run the tests
return m.Run()
}
func populateDB() error {
if err := withTxn(func(ctx context.Context) error {
if err := createFolders(ctx); err != nil {
return fmt.Errorf("creating folders: %w", err)
}
if err := createFiles(ctx); err != nil {
return fmt.Errorf("creating files: %w", err)
}
// TODO - link folders to zip files
if err := createTags(ctx, db.Tag, tagsNameCase, tagsNameNoCase); err != nil {
return fmt.Errorf("error creating tags: %s", err.Error())
}
if err := createGroups(ctx, db.Group, groupsNameCase, groupsNameNoCase); err != nil {
return fmt.Errorf("error creating groups: %s", err.Error())
}
if err := createPerformers(ctx, performersNameCase, performersNameNoCase); err != nil {
return fmt.Errorf("error creating performers: %s", err.Error())
}
if err := createStudios(ctx, studiosNameCase, studiosNameNoCase); err != nil {
return fmt.Errorf("error creating studios: %s", err.Error())
}
if err := createGalleries(ctx, totalGalleries); err != nil {
return fmt.Errorf("error creating galleries: %s", err.Error())
}
if err := createScenes(ctx, totalScenes); err != nil {
return fmt.Errorf("error creating scenes: %s", err.Error())
}
if err := createImages(ctx, totalImages); err != nil {
return fmt.Errorf("error creating images: %s", err.Error())
}
if err := addTagImage(ctx, db.Tag, tagIdxWithCoverImage); err != nil {
return fmt.Errorf("error adding tag image: %s", err.Error())
}
if err := createSavedFilters(ctx, db.SavedFilter, totalSavedFilters); err != nil {
return fmt.Errorf("error creating saved filters: %s", err.Error())
}
if err := linkGroupStudios(ctx, db.Group); err != nil {
return fmt.Errorf("error linking group studios: %s", err.Error())
}
if err := linkStudiosParent(ctx); err != nil {
return fmt.Errorf("error linking studios parent: %s", err.Error())
}
if err := linkTagsParent(ctx, db.Tag); err != nil {
return fmt.Errorf("error linking tags parent: %s", err.Error())
}
if err := linkGroupsParent(ctx, db.Group); err != nil {
return fmt.Errorf("error linking tags parent: %s", err.Error())
}
for _, ms := range markerSpecs {
if err := createMarker(ctx, db.SceneMarker, ms); err != nil {
return fmt.Errorf("error creating scene marker: %s", err.Error())
}
}
for _, cs := range chapterSpecs {
if err := createChapter(ctx, db.GalleryChapter, cs); err != nil {
return fmt.Errorf("error creating gallery chapter: %s", err.Error())
}
}
return nil
}); err != nil {
return err
}
return nil
}
func getFolderPath(index int, parentFolderIdx *int) string {
path := getPrefixedStringValue("folder", index, pathField)
if parentFolderIdx != nil {
return filepath.Join(folderPaths[*parentFolderIdx], path)
}
return path
}
func getFolderModTime(index int) time.Time {
return time.Date(2000, 1, (index%10)+1, 0, 0, 0, 0, time.UTC)
}
func makeFolder(i int) models.Folder {
var folderID *models.FolderID
var folderIdx *int
if pidx, ok := folderParentFolders[i]; ok {
folderIdx = &pidx
v := folderIDs[pidx]
folderID = &v
}
return models.Folder{
ParentFolderID: folderID,
DirEntry: models.DirEntry{
// zip files have to be added after creating files
ModTime: getFolderModTime(i),
},
Path: getFolderPath(i, folderIdx),
}
}
func createFolders(ctx context.Context) error {
qb := db.Folder
for i := 0; i < totalFolders; i++ {
folder := makeFolder(i)
if err := qb.Create(ctx, &folder); err != nil {
return fmt.Errorf("Error creating folder [%d] %v+: %s", i, folder, err.Error())
}
folderIDs = append(folderIDs, folder.ID)
folderPaths = append(folderPaths, folder.Path)
}
return nil
}
func getFileBaseName(index int) string {
return getPrefixedStringValue("file", index, "basename")
}
func getFileStringValue(index int, field string) string {
return getPrefixedStringValue("file", index, field)
}
func getFileModTime(index int) time.Time {
return getFolderModTime(index)
}
func getFileFingerprints(index int) []models.Fingerprint {
return []models.Fingerprint{
{
Type: "MD5",
Fingerprint: getPrefixedStringValue("file", index, "md5"),
},
{
Type: "OSHASH",
Fingerprint: getPrefixedStringValue("file", index, "oshash"),
},
}
}
func getFileSize(index int) int64 {
return int64(index) * 10
}
func getFileDuration(index int) float64 {
duration := (index % 4) + 1
duration = duration * 100
return float64(duration) + 0.432
}
func makeFile(i int) models.File {
folderID := folderIDs[fileFolders[i]]
if folderID == 0 {
folderID = folderIDs[folderIdxWithFiles]
}
var zipFileID *models.FileID
if zipFileIndex, found := fileZipFiles[i]; found {
zipFileID = &fileIDs[zipFileIndex]
}
var ret models.File
baseFile := &models.BaseFile{
Basename: getFileBaseName(i),
ParentFolderID: folderID,
DirEntry: models.DirEntry{
// zip files have to be added after creating files
ModTime: getFileModTime(i),
ZipFileID: zipFileID,
},
Fingerprints: getFileFingerprints(i),
Size: getFileSize(i),
}
ret = baseFile
if i >= fileIdxStartVideoFiles && i < fileIdxStartImageFiles {
ret = &models.VideoFile{
BaseFile: baseFile,
Format: getFileStringValue(i, "format"),
Width: getWidth(i),
Height: getHeight(i),
Duration: getFileDuration(i),
VideoCodec: getFileStringValue(i, "videoCodec"),
AudioCodec: getFileStringValue(i, "audioCodec"),
FrameRate: getFileDuration(i) * 2,
BitRate: int64(getFileDuration(i)) * 3,
}
} else if i >= fileIdxStartImageFiles && i < fileIdxStartGalleryFiles {
ret = &models.ImageFile{
BaseFile: baseFile,
Format: getFileStringValue(i, "format"),
Width: getWidth(i),
Height: getHeight(i),
}
}
return ret
}
func createFiles(ctx context.Context) error {
qb := db.File
for i := 0; i < totalFiles; i++ {
file := makeFile(i)
if err := qb.Create(ctx, file); err != nil {
return fmt.Errorf("Error creating file [%d] %v+: %s", i, file, err.Error())
}
fileIDs = append(fileIDs, file.Base().ID)
}
return nil
}
func getPrefixedStringValue(prefix string, index int, field string) string {
return fmt.Sprintf("%s_%04d_%s", prefix, index, field)
}
func getPrefixedNullStringValue(prefix string, index int, field string) sql.NullString {
if index > 0 && index%5 == 0 {
return sql.NullString{}
}
if index > 0 && index%6 == 0 {
return sql.NullString{
String: "",
Valid: true,
}
}
return sql.NullString{
String: getPrefixedStringValue(prefix, index, field),
Valid: true,
}
}
func getSceneStringValue(index int, field string) string {
return getPrefixedStringValue("scene", index, field)
}
func getScenePhash(index int, field string) int64 {
return int64(index % (totalScenes - dupeScenePhashes) * 1234)
}
func getSceneStringPtr(index int, field string) *string {
v := getPrefixedStringValue("scene", index, field)
return &v
}
func getSceneNullStringPtr(index int, field string) *string {
return getStringPtrFromNullString(getPrefixedNullStringValue("scene", index, field))
}
func getSceneEmptyString(index int, field string) string {
v := getSceneNullStringPtr(index, field)
if v == nil {
return ""
}
return *v
}
func getSceneTitle(index int) string {
switch index {
case sceneIdxWithSpacedName:
return spacedSceneTitle
default:
return getSceneStringValue(index, titleField)
}
}
func getRating(index int) sql.NullInt64 {
rating := index % 6
return sql.NullInt64{Int64: int64(rating * 20), Valid: rating > 0}
}
func getIntPtr(r sql.NullInt64) *int {
if !r.Valid {
return nil
}
v := int(r.Int64)
return &v
}
func getStringPtrFromNullString(r sql.NullString) *string {
if !r.Valid || r.String == "" {
return nil
}
v := r.String
return &v
}
func getStringPtr(r string) *string {
if r == "" {
return nil
}
return &r
}
func getEmptyStringFromPtr(v *string) string {
if v == nil {
return ""
}
return *v
}
func getOCounter(index int) int {
return index % 3
}
func getSceneDuration(index int) float64 {
duration := index + 1
duration = duration * 100
return float64(duration) + 0.432
}
func getHeight(index int) int {
heights := []int{200, 240, 300, 480, 700, 720, 800, 1080, 1500, 2160, 3000}
height := heights[index%len(heights)]
return height
}
func getWidth(index int) int {
height := getHeight(index)
return height * 2
}
func getObjectDate(index int) *models.Date {
dates := []string{"null", "2000-01-01", "0001-01-01", "2001-02-03"}
date := dates[index%len(dates)]
if date == "null" {
return nil
}
ret, _ := models.ParseDate(date)
return &ret
}
func sceneStashID(i int) models.StashID {
return models.StashID{
StashID: getSceneStringValue(i, "stashid"),
Endpoint: getSceneStringValue(i, "endpoint"),
}
}
func getSceneBasename(index int) string {
return getSceneStringValue(index, pathField)
}
func makeSceneFile(i int) *models.VideoFile {
fp := []models.Fingerprint{
{
Type: models.FingerprintTypeMD5,
Fingerprint: getSceneStringValue(i, checksumField),
},
{
Type: models.FingerprintTypeOshash,
Fingerprint: getSceneStringValue(i, "oshash"),
},
}
if i != sceneIdxMissingPhash {
fp = append(fp, models.Fingerprint{
Type: models.FingerprintTypePhash,
Fingerprint: getScenePhash(i, "phash"),
})
}
return &models.VideoFile{
BaseFile: &models.BaseFile{
Path: getFilePath(folderIdxWithSceneFiles, getSceneBasename(i)),
Basename: getSceneBasename(i),
ParentFolderID: folderIDs[folderIdxWithSceneFiles],
Fingerprints: fp,
},
Duration: getSceneDuration(i),
Height: getHeight(i),
Width: getWidth(i),
}
}
func getScenePlayDuration(index int) float64 {
if index%5 == 0 {
return 0
}
return float64(index%5) * 123.4
}
func getSceneResumeTime(index int) float64 {
if index%5 == 0 {
return 0
}
return float64(index%5) * 1.2
}
func makeScene(i int) *models.Scene {
title := getSceneTitle(i)
details := getSceneStringValue(i, "Details")
var studioID *int
if _, ok := sceneStudios[i]; ok {
v := studioIDs[sceneStudios[i]]
studioID = &v
}
gids := indexesToIDs(galleryIDs, sceneGalleries[i])
pids := indexesToIDs(performerIDs, scenePerformers[i])
tids := indexesToIDs(tagIDs, sceneTags[i])
mids := indexesToIDs(groupIDs, sceneGroups[i])
groups := make([]models.GroupsScenes, len(mids))
for i, m := range mids {
groups[i] = models.GroupsScenes{
GroupID: m,
}
}
rating := getRating(i)
return &models.Scene{
Title: title,
Details: details,
URLs: models.NewRelatedStrings([]string{
getSceneEmptyString(i, urlField),
}),
Rating: getIntPtr(rating),
Date: getObjectDate(i),
StudioID: studioID,
GalleryIDs: models.NewRelatedIDs(gids),
PerformerIDs: models.NewRelatedIDs(pids),
TagIDs: models.NewRelatedIDs(tids),
Groups: models.NewRelatedGroups(groups),
StashIDs: models.NewRelatedStashIDs([]models.StashID{
sceneStashID(i),
}),
PlayDuration: getScenePlayDuration(i),
ResumeTime: getSceneResumeTime(i),
}
}
func createScenes(ctx context.Context, n int) error {
sqb := db.Scene
fqb := db.File
for i := 0; i < n; i++ {
f := makeSceneFile(i)
if err := fqb.Create(ctx, f); err != nil {
return fmt.Errorf("creating scene file: %w", err)
}
sceneFileIDs = append(sceneFileIDs, f.ID)
scene := makeScene(i)
if err := sqb.Create(ctx, scene, []models.FileID{f.ID}); err != nil {
return fmt.Errorf("Error creating scene %v+: %s", scene, err.Error())
}
sceneIDs = append(sceneIDs, scene.ID)
}
return nil
}
func getImageStringValue(index int, field string) string {
return fmt.Sprintf("image_%04d_%s", index, field)
}
func getImageNullStringPtr(index int, field string) *string {
return getStringPtrFromNullString(getPrefixedNullStringValue("image", index, field))
}
func getImageEmptyString(index int, field string) string {
v := getImageNullStringPtr(index, field)
if v == nil {
return ""
}
return *v
}
func getImageBasename(index int) string {
return getImageStringValue(index, pathField)
}
func makeImageFile(i int) *models.ImageFile {
return &models.ImageFile{
BaseFile: &models.BaseFile{
Path: getFilePath(folderIdxWithImageFiles, getImageBasename(i)),
Basename: getImageBasename(i),
ParentFolderID: folderIDs[folderIdxWithImageFiles],
Fingerprints: []models.Fingerprint{
{
Type: models.FingerprintTypeMD5,
Fingerprint: getImageStringValue(i, checksumField),
},
},
},
Height: getHeight(i),
Width: getWidth(i),
}
}
func makeImage(i int) *models.Image {
title := getImageStringValue(i, titleField)
var studioID *int
if _, ok := imageStudios[i]; ok {
v := studioIDs[imageStudios[i]]
studioID = &v
}
gids := indexesToIDs(galleryIDs, imageGalleries[i])
pids := indexesToIDs(performerIDs, imagePerformers[i])
tids := indexesToIDs(tagIDs, imageTags[i])
return &models.Image{
Title: title,
Rating: getIntPtr(getRating(i)),
Date: getObjectDate(i),
URLs: models.NewRelatedStrings([]string{
getImageEmptyString(i, urlField),
}),
OCounter: getOCounter(i),
StudioID: studioID,
GalleryIDs: models.NewRelatedIDs(gids),
PerformerIDs: models.NewRelatedIDs(pids),
TagIDs: models.NewRelatedIDs(tids),
}
}
func createImages(ctx context.Context, n int) error {
qb := db.Image
fqb := db.File
for i := 0; i < n; i++ {
f := makeImageFile(i)
if i == imageIdxInZip {
f.ZipFileID = &fileIDs[fileIdxZip]
}
if err := fqb.Create(ctx, f); err != nil {
return fmt.Errorf("creating image file: %w", err)
}
imageFileIDs = append(imageFileIDs, f.ID)
image := makeImage(i)
err := qb.Create(ctx, image, []models.FileID{f.ID})
if err != nil {
return fmt.Errorf("Error creating image %v+: %s", image, err.Error())
}
imageIDs = append(imageIDs, image.ID)
}
return nil
}
func getGalleryStringValue(index int, field string) string {
return getPrefixedStringValue("gallery", index, field)
}
func getGalleryNullStringValue(index int, field string) sql.NullString {
return getPrefixedNullStringValue("gallery", index, field)
}
func getGalleryNullStringPtr(index int, field string) *string {
return getStringPtrFromNullString(getPrefixedNullStringValue("gallery", index, field))
}
func getGalleryEmptyString(index int, field string) string {
v := getGalleryNullStringPtr(index, field)
if v == nil {
return ""
}
return *v
}
func getGalleryBasename(index int) string {
return getGalleryStringValue(index, pathField)
}
func makeGalleryFile(i int) *models.BaseFile {
return &models.BaseFile{
Path: getFilePath(folderIdxWithGalleryFiles, getGalleryBasename(i)),
Basename: getGalleryBasename(i),
ParentFolderID: folderIDs[folderIdxWithGalleryFiles],
Fingerprints: []models.Fingerprint{
{
Type: models.FingerprintTypeMD5,
Fingerprint: getGalleryStringValue(i, checksumField),
},
},
}
}
func makeGallery(i int, includeScenes bool) *models.Gallery {
var studioID *int
if _, ok := galleryStudios[i]; ok {
v := studioIDs[galleryStudios[i]]
studioID = &v
}
pids := indexesToIDs(performerIDs, galleryPerformers[i])
tids := indexesToIDs(tagIDs, galleryTags[i])
ret := &models.Gallery{
Title: getGalleryStringValue(i, titleField),
URLs: models.NewRelatedStrings([]string{
getGalleryEmptyString(i, urlField),
}),
Rating: getIntPtr(getRating(i)),
Date: getObjectDate(i),
StudioID: studioID,
PerformerIDs: models.NewRelatedIDs(pids),
TagIDs: models.NewRelatedIDs(tids),
}
if includeScenes {
ret.SceneIDs = models.NewRelatedIDs(indexesToIDs(sceneIDs, sceneGalleries.reverseLookup(i)))
}
return ret
}
func createGalleries(ctx context.Context, n int) error {
gqb := db.Gallery
fqb := db.File
for i := 0; i < n; i++ {
var fileIDs []models.FileID
if i != galleryIdxWithoutFile {
f := makeGalleryFile(i)
if err := fqb.Create(ctx, f); err != nil {
return fmt.Errorf("creating gallery file: %w", err)
}
galleryFileIDs = append(galleryFileIDs, f.ID)
fileIDs = []models.FileID{f.ID}
} else {
galleryFileIDs = append(galleryFileIDs, 0)
}
// gallery relationship will be created with galleries
const includeScenes = false
gallery := makeGallery(i, includeScenes)
err := gqb.Create(ctx, gallery, fileIDs)
if err != nil {
return fmt.Errorf("Error creating gallery %v+: %s", gallery, err.Error())
}
galleryIDs = append(galleryIDs, gallery.ID)
}
return nil
}
func getGroupStringValue(index int, field string) string {
return getPrefixedStringValue("group", index, field)
}
func getGroupNullStringValue(index int, field string) string {
ret := getPrefixedNullStringValue("group", index, field)
return ret.String
}
func getGroupEmptyString(index int, field string) string {
v := getPrefixedNullStringValue("group", index, field)
if !v.Valid {
return ""
}
return v.String
}
// createGroups creates n groups with plain Name and o groups with camel cased NaMe included
func createGroups(ctx context.Context, mqb models.GroupReaderWriter, n int, o int) error {
const namePlain = "Name"
const nameNoCase = "NaMe"
for i := 0; i < n+o; i++ {
index := i
name := namePlain
tids := indexesToIDs(tagIDs, groupTags[i])
if i >= n { // i<n tags get normal names
name = nameNoCase // i>=n groups get dup names if case is not checked
index = n + o - (i + 1) // for the name to be the same the number (index) must be the same also
} // so count backwards to 0 as needed
// groups [ i ] and [ n + o - i - 1 ] should have similar names with only the Name!=NaMe part different
name = getGroupStringValue(index, name)
group := models.Group{
Name: name,
URLs: models.NewRelatedStrings([]string{
getGroupEmptyString(i, urlField),
}),
TagIDs: models.NewRelatedIDs(tids),
}
err := mqb.Create(ctx, &group)
if err != nil {
return fmt.Errorf("Error creating group [%d] %v+: %s", i, group, err.Error())
}
groupIDs = append(groupIDs, group.ID)
groupNames = append(groupNames, group.Name)
}
return nil
}
func getPerformerStringValue(index int, field string) string {
return getPrefixedStringValue("performer", index, field)
}
func getPerformerNullStringValue(index int, field string) string {
ret := getPrefixedNullStringValue("performer", index, field)
return ret.String
}
func getPerformerEmptyString(index int, field string) string {
v := getPrefixedNullStringValue("performer", index, field)
if !v.Valid {
return ""
}
return v.String
}
func getPerformerBoolValue(index int) bool {
index = index % 2
return index == 1
}
func getPerformerBirthdate(index int) *models.Date {
const minAge = 18
birthdate := time.Now()
birthdate = birthdate.AddDate(-minAge-index, -1, -1)
ret := models.Date{
Time: birthdate,
}
return &ret
}
func getPerformerDeathDate(index int) *models.Date {
if index != 5 {
return nil
}
deathDate := time.Now()
deathDate = deathDate.AddDate(-index+1, -1, -1)
ret := models.Date{
Time: deathDate,
}
return &ret
}
func getPerformerCareerLength(index int) *string {
if index%5 == 0 {
return nil
}
ret := fmt.Sprintf("20%2d", index)
return &ret
}
func getPerformerPenisLength(index int) *float64 {
if index%5 == 0 {
return nil
}
ret := float64(index)
return &ret
}
func getPerformerCircumcised(index int) *models.CircumisedEnum {
var ret models.CircumisedEnum
switch {
case index%3 == 0:
return nil
case index%3 == 1:
ret = models.CircumisedEnumCut
default:
ret = models.CircumisedEnumUncut
}
return &ret
}
func getIgnoreAutoTag(index int) bool {
return index%5 == 0
}
func performerStashID(i int) models.StashID {
return models.StashID{
StashID: getPerformerStringValue(i, "stashid"),
Endpoint: getPerformerStringValue(i, "endpoint"),
}
}
func performerAliases(i int) []string {
if i%5 == 0 {
return []string{}
}
return []string{getPerformerStringValue(i, "alias")}
}
// createPerformers creates n performers with plain Name and o performers with camel cased NaMe included
func createPerformers(ctx context.Context, n int, o int) error {
pqb := db.Performer
const namePlain = "Name"
const nameNoCase = "NaMe"
name := namePlain
for i := 0; i < n+o; i++ {
index := i
if i >= n { // i<n tags get normal names
name = nameNoCase // i>=n performers get dup names if case is not checked
index = n + o - (i + 1) // for the name to be the same the number (index) must be the same also
} // so count backwards to 0 as needed
// performers [ i ] and [ n + o - i - 1 ] should have similar names with only the Name!=NaMe part different
tids := indexesToIDs(tagIDs, performerTags[i])
performer := models.Performer{
Name: getPerformerStringValue(index, name),
Disambiguation: getPerformerStringValue(index, "disambiguation"),
Aliases: models.NewRelatedStrings(performerAliases(index)),
URLs: models.NewRelatedStrings([]string{
getPerformerEmptyString(i, urlField),
}),
Favorite: getPerformerBoolValue(i),
Birthdate: getPerformerBirthdate(i),
DeathDate: getPerformerDeathDate(i),
Details: getPerformerStringValue(i, "Details"),
Ethnicity: getPerformerStringValue(i, "Ethnicity"),
PenisLength: getPerformerPenisLength(i),
Circumcised: getPerformerCircumcised(i),
Rating: getIntPtr(getRating(i)),
IgnoreAutoTag: getIgnoreAutoTag(i),
TagIDs: models.NewRelatedIDs(tids),
}
careerLength := getPerformerCareerLength(i)
if careerLength != nil {
performer.CareerLength = *careerLength
}
if (index+1)%5 != 0 {
performer.StashIDs = models.NewRelatedStashIDs([]models.StashID{
performerStashID(i),
})
}
err := pqb.Create(ctx, &performer)
if err != nil {
return fmt.Errorf("Error creating performer %v+: %s", performer, err.Error())
}
performerIDs = append(performerIDs, performer.ID)
performerNames = append(performerNames, performer.Name)
}
return nil
}
func getTagBoolValue(index int) bool {
index = index % 2
return index == 1
}
func getTagStringValue(index int, field string) string {
return "tag_" + strconv.FormatInt(int64(index), 10) + "_" + field
}
func getTagSceneCount(id int) int {
idx := indexFromID(tagIDs, id)
return len(sceneTags.reverseLookup(idx))
}
func getTagMarkerCount(id int) int {
count := 0
idx := indexFromID(tagIDs, id)
for _, s := range markerSpecs {
if s.primaryTagIdx == idx || slices.Contains(s.tagIdxs, idx) {
count++
}
}
return count
}
func getTagImageCount(id int) int {
idx := indexFromID(tagIDs, id)
return len(imageTags.reverseLookup(idx))
}
func getTagGalleryCount(id int) int {
idx := indexFromID(tagIDs, id)
return len(galleryTags.reverseLookup(idx))
}
func getTagPerformerCount(id int) int {
idx := indexFromID(tagIDs, id)
return len(performerTags.reverseLookup(idx))
}
func getTagStudioCount(id int) int {
idx := indexFromID(tagIDs, id)
return len(studioTags.reverseLookup(idx))
}
func getTagParentCount(id int) int {
if id == tagIDs[tagIdxWithParentTag] || id == tagIDs[tagIdxWithGrandParent] || id == tagIDs[tagIdxWithParentAndChild] {
return 1
}
return 0
}
func getTagChildCount(id int) int {
if id == tagIDs[tagIdxWithChildTag] || id == tagIDs[tagIdxWithGrandChild] || id == tagIDs[tagIdxWithParentAndChild] {
return 1
}
return 0
}
// createTags creates n tags with plain Name and o tags with camel cased NaMe included
func createTags(ctx context.Context, tqb models.TagReaderWriter, n int, o int) error {
const namePlain = "Name"
const nameNoCase = "NaMe"
name := namePlain
for i := 0; i < n+o; i++ {
index := i
if i >= n { // i<n tags get normal names
name = nameNoCase // i>=n tags get dup names if case is not checked
index = n + o - (i + 1) // for the name to be the same the number (index) must be the same also
} // so count backwards to 0 as needed
// tags [ i ] and [ n + o - i - 1 ] should have similar names with only the Name!=NaMe part different
tag := models.Tag{
Name: getTagStringValue(index, name),
IgnoreAutoTag: getIgnoreAutoTag(i),
}
err := tqb.Create(ctx, &tag)
if err != nil {
return fmt.Errorf("Error creating tag %v+: %s", tag, err.Error())
}
// add alias
alias := getTagStringValue(i, "Alias")
if err := tqb.UpdateAliases(ctx, tag.ID, []string{alias}); err != nil {
return fmt.Errorf("error setting tag alias: %s", err.Error())
}
tagIDs = append(tagIDs, tag.ID)
tagNames = append(tagNames, tag.Name)
}
return nil
}
func getStudioStringValue(index int, field string) string {
return getPrefixedStringValue("studio", index, field)
}
func getStudioNullStringValue(index int, field string) string {
ret := getPrefixedNullStringValue("studio", index, field)
return ret.String
}
func createStudio(ctx context.Context, sqb *sqlite.StudioStore, name string, parentID *int) (*models.Studio, error) {
studio := models.Studio{
Name: name,
}
if parentID != nil {
studio.ParentID = parentID
}
err := createStudioFromModel(ctx, sqb, &studio)
if err != nil {
return nil, err
}
return &studio, nil
}
func createStudioFromModel(ctx context.Context, sqb *sqlite.StudioStore, studio *models.Studio) error {
err := sqb.Create(ctx, studio)
if err != nil {
return fmt.Errorf("Error creating studio %v+: %s", studio, err.Error())
}
return nil
}
func getStudioBoolValue(index int) bool {
index = index % 2
return index == 1
}
// createStudios creates n studios with plain Name and o studios with camel cased NaMe included
func createStudios(ctx context.Context, n int, o int) error {
sqb := db.Studio
const namePlain = "Name"
const nameNoCase = "NaMe"
for i := 0; i < n+o; i++ {
index := i
name := namePlain
if i >= n { // i<n studios get normal names
name = nameNoCase // i>=n studios get dup names if case is not checked
index = n + o - (i + 1) // for the name to be the same the number (index) must be the same also
} // so count backwards to 0 as needed
// studios [ i ] and [ n + o - i - 1 ] should have similar names with only the Name!=NaMe part different
name = getStudioStringValue(index, name)
tids := indexesToIDs(tagIDs, studioTags[i])
studio := models.Studio{
Name: name,
URL: getStudioStringValue(index, urlField),
Favorite: getStudioBoolValue(index),
IgnoreAutoTag: getIgnoreAutoTag(i),
TagIDs: models.NewRelatedIDs(tids),
}
// only add aliases for some scenes
if i == studioIdxWithGroup || i%5 == 0 {
alias := getStudioStringValue(i, "Alias")
studio.Aliases = models.NewRelatedStrings([]string{alias})
}
err := createStudioFromModel(ctx, sqb, &studio)
if err != nil {
return err
}
studioIDs = append(studioIDs, studio.ID)
studioNames = append(studioNames, studio.Name)
}
return nil
}
func createMarker(ctx context.Context, mqb models.SceneMarkerReaderWriter, markerSpec markerSpec) error {
marker := models.SceneMarker{
SceneID: sceneIDs[markerSpec.sceneIdx],
PrimaryTagID: tagIDs[markerSpec.primaryTagIdx],
}
err := mqb.Create(ctx, &marker)
if err != nil {
return fmt.Errorf("error creating marker %v+: %w", marker, err)
}
markerIDs = append(markerIDs, marker.ID)
if len(markerSpec.tagIdxs) > 0 {
newTagIDs := []int{}
for _, tagIdx := range markerSpec.tagIdxs {
newTagIDs = append(newTagIDs, tagIDs[tagIdx])
}
if err := mqb.UpdateTags(ctx, marker.ID, newTagIDs); err != nil {
return fmt.Errorf("error creating marker/tag join: %w", err)
}
}
return nil
}
func createChapter(ctx context.Context, mqb models.GalleryChapterReaderWriter, chapterSpec chapterSpec) error {
chapter := models.GalleryChapter{
GalleryID: sceneIDs[chapterSpec.galleryIdx],
Title: chapterSpec.title,
ImageIndex: chapterSpec.imageIndex,
}
err := mqb.Create(ctx, &chapter)
if err != nil {
return fmt.Errorf("error creating chapter %v+: %w", chapter, err)
}
chapterIDs = append(chapterIDs, chapter.ID)
return nil
}
func getSavedFilterMode(index int) models.FilterMode {
switch index {
case savedFilterIdxScene:
return models.FilterModeScenes
case savedFilterIdxImage:
return models.FilterModeImages
default:
return models.FilterModeScenes
}
}
func getSavedFilterName(index int) string {
if index <= savedFilterIdxImage {
// use the same name for the first two - should be possible
return firstSavedFilterName
}
return getPrefixedStringValue("savedFilter", index, "Name")
}
func createSavedFilters(ctx context.Context, qb models.SavedFilterReaderWriter, n int) error {
for i := 0; i < n; i++ {
filterQ := ""
filterPage := i
filterPerPage := i * 40
filterSort := "date"
filterDirection := models.SortDirectionEnumAsc
findFilter := models.FindFilterType{
Q: &filterQ,
Page: &filterPage,
PerPage: &filterPerPage,
Sort: &filterSort,
Direction: &filterDirection,
}
savedFilter := models.SavedFilter{
Mode: getSavedFilterMode(i),
Name: getSavedFilterName(i),
FindFilter: &findFilter,
ObjectFilter: map[string]interface{}{
"test": "object",
},
UIOptions: map[string]interface{}{
"display_mode": 1,
"zoom_index": 1,
},
}
err := qb.Create(ctx, &savedFilter)
if err != nil {
return fmt.Errorf("Error creating saved filter %v+: %s", savedFilter, err.Error())
}
savedFilterIDs = append(savedFilterIDs, savedFilter.ID)
}
return nil
}
func doLinks(links [][2]int, fn func(idx1, idx2 int) error) error {
for _, l := range links {
if err := fn(l[0], l[1]); err != nil {
return err
}
}
return nil
}
func linkGroupStudios(ctx context.Context, mqb models.GroupWriter) error {
return doLinks(groupStudioLinks, func(groupIndex, studioIndex int) error {
group := models.GroupPartial{
StudioID: models.NewOptionalInt(studioIDs[studioIndex]),
}
_, err := mqb.UpdatePartial(ctx, groupIDs[groupIndex], group)
return err
})
}
func linkStudiosParent(ctx context.Context) error {
qb := db.Studio
return doLinks(studioParentLinks, func(parentIndex, childIndex int) error {
input := &models.StudioPartial{
ID: studioIDs[childIndex],
ParentID: models.NewOptionalInt(studioIDs[parentIndex]),
}
_, err := qb.UpdatePartial(ctx, *input)
return err
})
}
func linkTagsParent(ctx context.Context, qb models.TagReaderWriter) error {
return doLinks(tagParentLinks, func(parentIndex, childIndex int) error {
tagID := tagIDs[childIndex]
parentTags, err := qb.FindByChildTagID(ctx, tagID)
if err != nil {
return err
}
var parentIDs []int
for _, parentTag := range parentTags {
parentIDs = append(parentIDs, parentTag.ID)
}
parentIDs = append(parentIDs, tagIDs[parentIndex])
return qb.UpdateParentTags(ctx, tagID, parentIDs)
})
}
func linkGroupsParent(ctx context.Context, qb models.GroupReaderWriter) error {
return doLinks(groupParentLinks, func(parentIndex, childIndex int) error {
groupID := groupIDs[childIndex]
p := models.GroupPartial{
ContainingGroups: &models.UpdateGroupDescriptions{
Groups: []models.GroupIDDescription{
{GroupID: groupIDs[parentIndex]},
},
Mode: models.RelationshipUpdateModeAdd,
},
}
_, err := qb.UpdatePartial(ctx, groupID, p)
return err
})
}
func addTagImage(ctx context.Context, qb models.TagWriter, tagIndex int) error {
return qb.UpdateImage(ctx, tagIDs[tagIndex], []byte("image"))
}