diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index ffbb6150c..5d5595932 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -248,7 +248,7 @@ type Query { validateStashBoxCredentials(input: StashBoxInput!): StashBoxValidationResult! "List pending fingerprint submissions for a stash-box endpoint" - pendingFingerprintSubmissions(endpoint: String!): [FingerprintSubmission!]! + pendingFingerprintSubmissions(stash_box_endpoint: String!): [FingerprintSubmission!]! # System status systemStatus: SystemStatus! @@ -578,7 +578,7 @@ type Mutation { "Remove a fingerprint submission from the queue" removeFingerprintSubmission(input: RemoveFingerprintInput!): Boolean! "Submit all pending fingerprint submissions for a stash-box endpoint" - submitFingerprintSubmissions(endpoint: String!): Boolean! + submitFingerprintSubmissions(stash_box_endpoint: String!): Boolean! "Backup the database. Optionally returns a link to download the database file" backupDatabase(input: BackupDatabaseInput!): String diff --git a/graphql/schema/types/stash-box.graphql b/graphql/schema/types/stash-box.graphql index 8e65a0620..c551c9150 100644 --- a/graphql/schema/types/stash-box.graphql +++ b/graphql/schema/types/stash-box.graphql @@ -31,7 +31,7 @@ enum FingerprintVote { } input FingerprintSubmissionInput { - scene_id: String! + scene_id: ID! stash_box_scene_id: String! stash_box_endpoint: String! vote: FingerprintVote! diff --git a/internal/api/resolver_mutation_stash_box.go b/internal/api/resolver_mutation_stash_box.go index 6e852287f..017a79c90 100644 --- a/internal/api/resolver_mutation_stash_box.go +++ b/internal/api/resolver_mutation_stash_box.go @@ -15,12 +15,6 @@ import ( ) func (r *mutationResolver) SubmitStashBoxFingerprints(ctx context.Context, input StashBoxFingerprintSubmissionInput) (bool, error) { - // New format: use fingerprints field with explicit stash-box scene IDs and votes - if len(input.Fingerprints) > 0 { - return r.submitFingerprintsNew(ctx, input.Fingerprints) - } - - // Legacy format: use scene_ids and look up stash_ids from scenes b, err := resolveStashBox(nil, input.StashBoxEndpoint) if err != nil { return false, err @@ -45,73 +39,6 @@ func (r *mutationResolver) SubmitStashBoxFingerprints(ctx context.Context, input return client.SubmitFingerprints(ctx, scenes) } -func (r *mutationResolver) submitFingerprintsNew(ctx context.Context, submissions []*FingerprintSubmissionInput) (bool, error) { - // Group submissions by endpoint - byEndpoint := make(map[string][]*FingerprintSubmissionInput) - for _, s := range submissions { - byEndpoint[s.StashBoxEndpoint] = append(byEndpoint[s.StashBoxEndpoint], s) - } - - for endpoint, endpointSubmissions := range byEndpoint { - b, err := resolveStashBox(nil, &endpoint) - if err != nil { - return false, err - } - - // Collect all scene IDs for this endpoint - sceneIDSet := make(map[string]struct{}) - for _, s := range endpointSubmissions { - sceneIDSet[s.SceneID] = struct{}{} - } - - sceneIDs := make([]string, 0, len(sceneIDSet)) - for id := range sceneIDSet { - sceneIDs = append(sceneIDs, id) - } - - ids, err := stringslice.StringSliceToIntSlice(sceneIDs) - if err != nil { - return false, err - } - - var scenes []*models.Scene - if err := r.withReadTxn(ctx, func(ctx context.Context) error { - scenes, err = r.sceneService.FindByIDs(ctx, ids, scene.LoadFiles) - return err - }); err != nil { - return false, err - } - - // Build a map of scene ID to scene for quick lookup - sceneMap := make(map[int]*models.Scene) - for _, s := range scenes { - sceneMap[s.ID] = s - } - - client := r.newStashBoxClient(*b) - - // Submit each fingerprint with its vote - for _, sub := range endpointSubmissions { - sceneID, err := strconv.Atoi(sub.SceneID) - if err != nil { - return false, fmt.Errorf("invalid scene ID %s: %w", sub.SceneID, err) - } - - s, ok := sceneMap[sceneID] - if !ok { - return false, fmt.Errorf("scene %d not found", sceneID) - } - - vote := stashbox.FingerprintVote(sub.Vote) - if err := client.SubmitFingerprintsWithVote(ctx, s, sub.StashBoxSceneID, vote); err != nil { - return false, err - } - } - } - - return true, nil -} - func (r *mutationResolver) StashBoxBatchPerformerTag(ctx context.Context, input manager.StashBoxBatchTagInput) (string, error) { b, err := resolveStashBoxBatchTagInput(input.Endpoint, input.StashBoxEndpoint) //nolint:staticcheck if err != nil { @@ -330,8 +257,8 @@ func (r *mutationResolver) RemoveFingerprintSubmission(ctx context.Context, inpu return true, nil } -func (r *mutationResolver) SubmitFingerprintSubmissions(ctx context.Context, endpoint string) (bool, error) { - b, err := resolveStashBox(nil, &endpoint) +func (r *mutationResolver) SubmitFingerprintSubmissions(ctx context.Context, stashBoxEndpoint string) (bool, error) { + b, err := resolveStashBox(nil, &stashBoxEndpoint) if err != nil { return false, err } @@ -339,7 +266,7 @@ func (r *mutationResolver) SubmitFingerprintSubmissions(ctx context.Context, end var submissions []*models.FingerprintSubmission if err := r.withReadTxn(ctx, func(ctx context.Context) error { var err error - submissions, err = r.repository.FingerprintSubmission.FindByEndpoint(ctx, endpoint) + submissions, err = r.repository.FingerprintSubmission.FindByEndpoint(ctx, stashBoxEndpoint) return err }); err != nil { return false, err @@ -349,27 +276,20 @@ func (r *mutationResolver) SubmitFingerprintSubmissions(ctx context.Context, end return true, nil } - // Collect all scene IDs - sceneIDSet := make(map[int]struct{}) - for _, s := range submissions { - sceneIDSet[s.SceneID] = struct{}{} - } - - sceneIDs := make([]int, 0, len(sceneIDSet)) - for id := range sceneIDSet { - sceneIDs = append(sceneIDs, id) + ids := make([]int, len(submissions)) + for i, sub := range submissions { + ids[i] = sub.SceneID } var scenes []*models.Scene if err := r.withReadTxn(ctx, func(ctx context.Context) error { var err error - scenes, err = r.sceneService.FindByIDs(ctx, sceneIDs, scene.LoadFiles) + scenes, err = r.sceneService.FindByIDs(ctx, ids, scene.LoadFiles) return err }); err != nil { return false, err } - // Build a map of scene ID to scene sceneMap := make(map[int]*models.Scene) for _, s := range scenes { sceneMap[s.ID] = s @@ -377,7 +297,17 @@ func (r *mutationResolver) SubmitFingerprintSubmissions(ctx context.Context, end client := r.newStashBoxClient(*b) - // Submit each fingerprint and track successful submissions + if len(submissions) > 40 { + // Submit async to avoid timeouts for large batches + go r.submitFingerprintBatch(client, submissions, sceneMap) + } else { + r.submitFingerprintBatch(client, submissions, sceneMap) + } + + return true, nil +} + +func (r *mutationResolver) submitFingerprintBatch(client *stashbox.Client, submissions []*models.FingerprintSubmission, sceneMap map[int]*models.Scene) { var successfulSubmissions []*models.FingerprintSubmission for _, sub := range submissions { s, ok := sceneMap[sub.SceneID] @@ -386,8 +316,7 @@ func (r *mutationResolver) SubmitFingerprintSubmissions(ctx context.Context, end continue } - vote := stashbox.FingerprintVote(sub.Vote) - if err := client.SubmitFingerprintsWithVote(ctx, s, sub.StashID, vote); err != nil { + if err := client.SubmitFingerprintsWithVote(context.Background(), s, sub.StashID, sub.Vote); err != nil { logger.Warnf("Failed to submit fingerprint for scene %d: %v", sub.SceneID, err) continue } @@ -395,9 +324,8 @@ func (r *mutationResolver) SubmitFingerprintSubmissions(ctx context.Context, end successfulSubmissions = append(successfulSubmissions, sub) } - // Delete successful submissions from the queue if len(successfulSubmissions) > 0 { - if err := r.withTxn(ctx, func(ctx context.Context) error { + if err := r.withTxn(context.Background(), func(ctx context.Context) error { for _, sub := range successfulSubmissions { if err := r.repository.FingerprintSubmission.Delete(ctx, sub.Endpoint, sub.StashID); err != nil { return err @@ -405,9 +333,7 @@ func (r *mutationResolver) SubmitFingerprintSubmissions(ctx context.Context, end } return nil }); err != nil { - return false, err + logger.Warnf("Failed to delete fingerprint submissions: %v", err) } } - - return true, nil } diff --git a/internal/api/resolver_query_stash_box.go b/internal/api/resolver_query_stash_box.go index 13551a15c..c2c783899 100644 --- a/internal/api/resolver_query_stash_box.go +++ b/internal/api/resolver_query_stash_box.go @@ -6,9 +6,9 @@ import ( "github.com/stashapp/stash/pkg/models" ) -func (r *queryResolver) PendingFingerprintSubmissions(ctx context.Context, endpoint string) (ret []*models.FingerprintSubmission, err error) { +func (r *queryResolver) PendingFingerprintSubmissions(ctx context.Context, stashBoxEndpoint string) (ret []*models.FingerprintSubmission, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - ret, err = r.repository.FingerprintSubmission.FindByEndpoint(ctx, endpoint) + ret, err = r.repository.FingerprintSubmission.FindByEndpoint(ctx, stashBoxEndpoint) return err }); err != nil { return nil, err diff --git a/pkg/models/fingerprint_submission.go b/pkg/models/fingerprint_submission.go index 6e446ef3b..18a0ab995 100644 --- a/pkg/models/fingerprint_submission.go +++ b/pkg/models/fingerprint_submission.go @@ -34,7 +34,6 @@ type FingerprintSubmission struct { type FingerprintSubmissionReader interface { FindByEndpoint(ctx context.Context, endpoint string) ([]*FingerprintSubmission, error) - Find(ctx context.Context, endpoint string, stashID string) (*FingerprintSubmission, error) } type FingerprintSubmissionWriter interface { diff --git a/pkg/sqlite/fingerprint_submission.go b/pkg/sqlite/fingerprint_submission.go index da92f8b9f..79434a8e0 100644 --- a/pkg/sqlite/fingerprint_submission.go +++ b/pkg/sqlite/fingerprint_submission.go @@ -2,8 +2,6 @@ package sqlite import ( "context" - "database/sql" - "errors" "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" @@ -18,8 +16,7 @@ const ( var ( fingerprintSubmissionsTableMgr = &table{ - table: goqu.T(fingerprintSubmissionsTable), - idColumn: goqu.T(fingerprintSubmissionsTable).Col("endpoint"), // not a real ID column, but needed for table struct + table: goqu.T(fingerprintSubmissionsTable), } ) @@ -100,19 +97,6 @@ func (qb *FingerprintSubmissionStore) DeleteByEndpoint(ctx context.Context, endp return nil } -func (qb *FingerprintSubmissionStore) Find(ctx context.Context, endpoint string, stashID string) (*models.FingerprintSubmission, error) { - q := qb.selectDataset().Where( - qb.table().Col("endpoint").Eq(endpoint), - qb.table().Col("stash_id").Eq(stashID), - ) - - ret, err := qb.get(ctx, q) - if errors.Is(err, sql.ErrNoRows) { - return nil, nil - } - return ret, err -} - func (qb *FingerprintSubmissionStore) FindByEndpoint(ctx context.Context, endpoint string) ([]*models.FingerprintSubmission, error) { q := qb.selectDataset().Where( qb.table().Col("endpoint").Eq(endpoint), @@ -121,19 +105,6 @@ func (qb *FingerprintSubmissionStore) FindByEndpoint(ctx context.Context, endpoi return qb.getMany(ctx, q) } -func (qb *FingerprintSubmissionStore) get(ctx context.Context, q *goqu.SelectDataset) (*models.FingerprintSubmission, error) { - ret, err := qb.getMany(ctx, q) - if err != nil { - return nil, err - } - - if len(ret) == 0 { - return nil, sql.ErrNoRows - } - - return ret[0], nil -} - func (qb *FingerprintSubmissionStore) getMany(ctx context.Context, q *goqu.SelectDataset) ([]*models.FingerprintSubmission, error) { const single = false var ret []*models.FingerprintSubmission diff --git a/pkg/sqlite/fingerprint_submission_test.go b/pkg/sqlite/fingerprint_submission_test.go index 739aefeb4..40f95e3df 100644 --- a/pkg/sqlite/fingerprint_submission_test.go +++ b/pkg/sqlite/fingerprint_submission_test.go @@ -26,13 +26,12 @@ func TestFingerprintSubmissionCreate(t *testing.T) { assert.NoError(t, err) // Verify it was created - found, err := db.FingerprintSubmission.Find(ctx, submission.Endpoint, submission.StashID) + found, err := db.FingerprintSubmission.FindByEndpoint(ctx, submission.Endpoint) assert.NoError(t, err) - assert.NotNil(t, found) - assert.Equal(t, submission.Endpoint, found.Endpoint) - assert.Equal(t, submission.StashID, found.StashID) - assert.Equal(t, submission.SceneID, found.SceneID) - assert.Equal(t, submission.Vote, found.Vote) + assert.Len(t, found, 1) + assert.Equal(t, submission.StashID, found[0].StashID) + assert.Equal(t, submission.SceneID, found[0].SceneID) + assert.Equal(t, submission.Vote, found[0].Vote) return nil }) @@ -64,20 +63,10 @@ func TestFingerprintSubmissionCreateDuplicate(t *testing.T) { assert.NoError(t, err) // Original should still exist unchanged - found, err := db.FingerprintSubmission.Find(ctx, submission.Endpoint, submission.StashID) + found, err := db.FingerprintSubmission.FindByEndpoint(ctx, submission.Endpoint) assert.NoError(t, err) - assert.Equal(t, models.FingerprintVoteValid, found.Vote) - - return nil - }) -} - -func TestFingerprintSubmissionFind(t *testing.T) { - withTxn(func(ctx context.Context) error { - // Find non-existent - found, err := db.FingerprintSubmission.Find(ctx, "non-existent", "non-existent") - assert.NoError(t, err) - assert.Nil(t, found) + assert.Len(t, found, 1) + assert.Equal(t, models.FingerprintVoteValid, found[0].Vote) return nil }) @@ -138,9 +127,9 @@ func TestFingerprintSubmissionDelete(t *testing.T) { assert.NoError(t, err) // Verify it's gone - found, err := db.FingerprintSubmission.Find(ctx, submission.Endpoint, submission.StashID) + found, err := db.FingerprintSubmission.FindByEndpoint(ctx, submission.Endpoint) assert.NoError(t, err) - assert.Nil(t, found) + assert.Len(t, found, 0) return nil }) diff --git a/pkg/stashbox/scene.go b/pkg/stashbox/scene.go index 4bb0e6826..754404868 100644 --- a/pkg/stashbox/scene.go +++ b/pkg/stashbox/scene.go @@ -460,16 +460,8 @@ func (c Client) submitFingerprints(ctx context.Context, fingerprints []graphql.F return true, nil } -// FingerprintVote represents the vote type for a fingerprint submission -type FingerprintVote string - -const ( - FingerprintVoteValid FingerprintVote = "VALID" - FingerprintVoteInvalid FingerprintVote = "INVALID" -) - // SubmitFingerprintsWithVote submits fingerprints for a scene with an explicit stash-box scene ID and vote -func (c Client) SubmitFingerprintsWithVote(ctx context.Context, scene *models.Scene, stashBoxSceneID string, vote FingerprintVote) error { +func (c Client) SubmitFingerprintsWithVote(ctx context.Context, scene *models.Scene, stashBoxSceneID string, vote models.FingerprintVote) error { var fingerprints []graphql.FingerprintSubmission for _, f := range scene.Files.List() { diff --git a/ui/v2.5/graphql/mutations/stash-box.graphql b/ui/v2.5/graphql/mutations/stash-box.graphql index 1788b6778..8d05ce0b2 100644 --- a/ui/v2.5/graphql/mutations/stash-box.graphql +++ b/ui/v2.5/graphql/mutations/stash-box.graphql @@ -32,6 +32,6 @@ mutation RemoveFingerprintSubmission($input: RemoveFingerprintInput!) { removeFingerprintSubmission(input: $input) } -mutation SubmitFingerprintSubmissions($endpoint: String!) { - submitFingerprintSubmissions(endpoint: $endpoint) +mutation SubmitFingerprintSubmissions($stash_box_endpoint: String!) { + submitFingerprintSubmissions(stash_box_endpoint: $stash_box_endpoint) } diff --git a/ui/v2.5/graphql/queries/misc.graphql b/ui/v2.5/graphql/queries/misc.graphql index 73abaf229..9f13f5511 100644 --- a/ui/v2.5/graphql/queries/misc.graphql +++ b/ui/v2.5/graphql/queries/misc.graphql @@ -47,8 +47,8 @@ query LatestVersion { } } -query PendingFingerprintSubmissions($endpoint: String!) { - pendingFingerprintSubmissions(endpoint: $endpoint) { +query PendingFingerprintSubmissions($stash_box_endpoint: String!) { + pendingFingerprintSubmissions(stash_box_endpoint: $stash_box_endpoint) { endpoint stash_id scene { diff --git a/ui/v2.5/src/components/Tagger/context.tsx b/ui/v2.5/src/components/Tagger/context.tsx index 8d1ff6cb6..0dc25ed86 100644 --- a/ui/v2.5/src/components/Tagger/context.tsx +++ b/ui/v2.5/src/components/Tagger/context.tsx @@ -245,7 +245,7 @@ export const TaggerContext: React.FC = ({ children }) => { // Query pending fingerprint submissions from the backend const endpoint = currentSource?.sourceInput.stash_box_endpoint; const { data: pendingData, refetch: refetchPending } = GQL.usePendingFingerprintSubmissionsQuery({ - variables: { endpoint: endpoint ?? "" }, + variables: { stash_box_endpoint: endpoint ?? "" }, skip: !endpoint, }); @@ -275,7 +275,7 @@ export const TaggerContext: React.FC = ({ children }) => { setLoading(true); await submitFingerprintsMutation({ variables: { - endpoint, + stash_box_endpoint: endpoint, }, });