Feature: Tag StashID support (#6255)

This commit is contained in:
Gykes
2025-11-12 19:24:09 -08:00
committed by GitHub
parent a08d2e258a
commit c99825a453
30 changed files with 387 additions and 34 deletions

View File

@@ -102,6 +102,7 @@ func (db *Anonymiser) deleteStashIDs() error {
func() error { return db.truncateTable("scene_stash_ids") },
func() error { return db.truncateTable("studio_stash_ids") },
func() error { return db.truncateTable("performer_stash_ids") },
func() error { return db.truncateTable("tag_stash_ids") },
})
}

View File

@@ -34,7 +34,7 @@ const (
cacheSizeEnv = "STASH_SQLITE_CACHE_SIZE"
)
var appSchemaVersion uint = 73
var appSchemaVersion uint = 74
//go:embed migrations/*.sql
var migrationsBox embed.FS

View File

@@ -0,0 +1,7 @@
CREATE TABLE `tag_stash_ids` (
`tag_id` integer,
`endpoint` varchar(255),
`stash_id` varchar(36),
`updated_at` datetime not null default '1970-01-01T00:00:00Z',
foreign key(`tag_id`) references `tags`(`id`) on delete CASCADE
);

View File

@@ -27,8 +27,9 @@ func testStashIDReaderWriter(ctx context.Context, t *testing.T, r stashIDReaderW
const stashIDStr = "stashID"
const endpoint = "endpoint"
stashID := models.StashID{
StashID: stashIDStr,
Endpoint: endpoint,
StashID: stashIDStr,
Endpoint: endpoint,
UpdatedAt: epochTime,
}
// update stash ids and ensure was updated

View File

@@ -47,6 +47,7 @@ var (
tagsAliasesJoinTable = goqu.T(tagAliasesTable)
tagRelationsJoinTable = goqu.T(tagRelationsTable)
tagsStashIDsJoinTable = goqu.T("tag_stash_ids")
)
var (
@@ -375,6 +376,13 @@ var (
}
tagsChildTagsTableMgr = *tagsParentTagsTableMgr.invert()
tagsStashIDsTableMgr = &stashIDTable{
table: table{
table: tagsStashIDsJoinTable,
idColumn: tagsStashIDsJoinTable.Col(tagIDColumn),
},
}
)
var (

View File

@@ -101,7 +101,8 @@ func (r *tagRowRecord) fromPartial(o models.TagPartial) {
type tagRepositoryType struct {
repository
aliases stringRepository
aliases stringRepository
stashIDs stashIDRepository
scenes joinRepository
images joinRepository
@@ -121,6 +122,12 @@ var (
},
stringColumn: tagAliasColumn,
},
stashIDs: stashIDRepository{
repository{
tableName: "tag_stash_ids",
idColumn: tagIDColumn,
},
},
scenes: joinRepository{
repository: repository{
tableName: scenesTagsTable,
@@ -199,6 +206,12 @@ func (qb *TagStore) Create(ctx context.Context, newObject *models.Tag) error {
}
}
if newObject.StashIDs.Loaded() {
if err := tagsStashIDsTableMgr.insertJoins(ctx, id, newObject.StashIDs.List()); err != nil {
return err
}
}
updated, err := qb.find(ctx, id)
if err != nil {
return fmt.Errorf("finding after create: %w", err)
@@ -242,6 +255,12 @@ func (qb *TagStore) UpdatePartial(ctx context.Context, id int, partial models.Ta
}
}
if partial.StashIDs != nil {
if err := tagsStashIDsTableMgr.modifyJoins(ctx, id, partial.StashIDs.StashIDs, partial.StashIDs.Mode); err != nil {
return nil, err
}
}
return qb.find(ctx, id)
}
@@ -271,6 +290,12 @@ func (qb *TagStore) Update(ctx context.Context, updatedObject *models.Tag) error
}
}
if updatedObject.StashIDs.Loaded() {
if err := tagsStashIDsTableMgr.replaceJoins(ctx, updatedObject.ID, updatedObject.StashIDs.List()); err != nil {
return err
}
}
return nil
}
@@ -509,6 +534,24 @@ func (qb *TagStore) FindByNames(ctx context.Context, names []string, nocase bool
return ret, nil
}
func (qb *TagStore) FindByStashID(ctx context.Context, stashID models.StashID) ([]*models.Tag, error) {
sq := dialect.From(tagsStashIDsJoinTable).Select(tagsStashIDsJoinTable.Col(tagIDColumn)).Where(
tagsStashIDsJoinTable.Col("stash_id").Eq(stashID.StashID),
tagsStashIDsJoinTable.Col("endpoint").Eq(stashID.Endpoint),
)
idsQuery := qb.selectDataset().Where(
qb.table().Col(idColumn).In(sq),
)
ret, err := qb.getMany(ctx, idsQuery)
if err != nil {
return nil, fmt.Errorf("getting tags for stash ID %s: %w", stashID.StashID, err)
}
return ret, nil
}
func (qb *TagStore) GetParentIDs(ctx context.Context, relatedID int) ([]int, error) {
return tagsParentTagsTableMgr.get(ctx, relatedID)
}
@@ -779,6 +822,14 @@ func (qb *TagStore) UpdateAliases(ctx context.Context, tagID int, aliases []stri
return tagRepository.aliases.replace(ctx, tagID, aliases)
}
func (qb *TagStore) GetStashIDs(ctx context.Context, tagID int) ([]models.StashID, error) {
return tagsStashIDsTableMgr.get(ctx, tagID)
}
func (qb *TagStore) UpdateStashIDs(ctx context.Context, tagID int, stashIDs []models.StashID) error {
return tagsStashIDsTableMgr.replaceJoins(ctx, tagID, stashIDs)
}
func (qb *TagStore) Merge(ctx context.Context, source []int, destination int) error {
if len(source) == 0 {
return nil
@@ -840,6 +891,19 @@ AND NOT EXISTS(SELECT 1 FROM `+table+` o WHERE o.`+idColumn+` = `+table+`.`+idCo
return err
}
// Merge StashIDs - move all source StashIDs to destination (ignoring duplicates)
_, err = dbWrapper.Exec(ctx, `UPDATE OR IGNORE `+"tag_stash_ids"+`
SET tag_id = ?
WHERE tag_id IN `+inBinding, args...)
if err != nil {
return err
}
// Delete remaining source StashIDs that couldn't be moved (duplicates)
if _, err := dbWrapper.Exec(ctx, `DELETE FROM tag_stash_ids WHERE tag_id IN `+inBinding, srcArgs...); err != nil {
return err
}
for _, id := range source {
err = qb.Destroy(ctx, id)
if err != nil {

View File

@@ -900,6 +900,66 @@ func TestTagUpdateAlias(t *testing.T) {
}
}
func TestTagStashIDs(t *testing.T) {
if err := withTxn(func(ctx context.Context) error {
qb := db.Tag
// create tag to test against
const name = "TestTagStashIDs"
tag := models.Tag{
Name: name,
}
err := qb.Create(ctx, &tag)
if err != nil {
return fmt.Errorf("Error creating tag: %s", err.Error())
}
testStashIDReaderWriter(ctx, t, qb, tag.ID)
return nil
}); err != nil {
t.Error(err.Error())
}
}
func TestTagFindByStashID(t *testing.T) {
withTxn(func(ctx context.Context) error {
qb := db.Tag
// create tag to test against
const name = "TestTagFindByStashID"
const stashID = "stashid"
const endpoint = "endpoint"
tag := models.Tag{
Name: name,
StashIDs: models.NewRelatedStashIDs([]models.StashID{{StashID: stashID, Endpoint: endpoint}}),
}
err := qb.Create(ctx, &tag)
if err != nil {
return fmt.Errorf("Error creating tag: %s", err.Error())
}
// find by stash ID
tags, err := qb.FindByStashID(ctx, models.StashID{StashID: stashID, Endpoint: endpoint})
if err != nil {
return fmt.Errorf("Error finding by stash ID: %s", err.Error())
}
assert.Len(t, tags, 1)
assert.Equal(t, tag.ID, tags[0].ID)
// find by non-existent stash ID
tags, err = qb.FindByStashID(ctx, models.StashID{StashID: "nonexistent", Endpoint: endpoint})
if err != nil {
return fmt.Errorf("Error finding by stash ID: %s", err.Error())
}
assert.Len(t, tags, 0)
return nil
})
}
func TestTagMerge(t *testing.T) {
assert := assert.New(t)