mirror of https://github.com/stashapp/stash.git
1937 lines
47 KiB
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"))
|
|
}
|