Add album view and bulk actions to Review
The Review list can now be grouped by album (disc folders folded in), showing per-album counts of upgrades, identical files, downgrades and approximate matches. Whole albums or individual songs can be selected and linked, promoted, ignored or un-ignored in one action. Every bulk action is previewed server-side first: how many songs will be processed, which are skipped and why, and warnings with examples for downgrades, approximate matches, duration mismatches, folder moves and renames. Downgrades are excluded unless explicitly included, and replacing master files requires an acknowledgement. Link and promote run as a background job that re-plans each song and uses the normal single-item operation, so all preflight checks still apply. IDs from another pair are refused and duplicates are ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0ae4e85bd2
commit
1d31999793
9 files changed
+1209
-7
No files matched your search
@@ -310,6 +310,7 @@ func (s *Store) AllMatches(ctx context.Context, pairID int64) (map[string]Match,
|
||||
type MatchFilter struct {
|
||||
Status string // a status, "review" (different+similar), "ignored" or ""
|
||||
Query string
|
||||
IDs []int64 // restrict to these matches (at most maxIDFilter)
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
@@ -335,9 +336,52 @@ func matchWhere(pairID int64, f MatchFilter) (string, []any) {
|
||||
q := "%" + strings.NewReplacer(`\`, `\\`, `%`, `\%`, `_`, `\_`).Replace(f.Query) + "%"
|
||||
args = append(args, q, q)
|
||||
}
|
||||
if len(f.IDs) > 0 {
|
||||
ids := f.IDs
|
||||
if len(ids) > maxIDFilter {
|
||||
ids = ids[:maxIDFilter]
|
||||
}
|
||||
where = append(where, "id IN ("+strings.TrimSuffix(strings.Repeat("?,", len(ids)), ",")+")")
|
||||
for _, id := range ids {
|
||||
args = append(args, id)
|
||||
}
|
||||
}
|
||||
return strings.Join(where, " AND "), args
|
||||
}
|
||||
|
||||
const maxIDFilter = 500
|
||||
|
||||
// MatchesByIDs loads matches by id (any pair; callers check ownership).
|
||||
func (s *Store) MatchesByIDs(ctx context.Context, ids []int64) ([]Match, error) {
|
||||
out := make([]Match, 0, len(ids))
|
||||
for start := 0; start < len(ids); start += maxIDFilter {
|
||||
chunk := ids[start:min(start+maxIDFilter, len(ids))]
|
||||
args := make([]any, len(chunk))
|
||||
for i, id := range chunk {
|
||||
args[i] = id
|
||||
}
|
||||
rows, err := s.DB.QueryContext(ctx, `SELECT `+matchCols+` FROM matches WHERE id IN (`+
|
||||
strings.TrimSuffix(strings.Repeat("?,", len(chunk)), ",")+`)`, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
m, err := scanMatch(rows)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
err = rows.Err()
|
||||
rows.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Store) Matches(ctx context.Context, pairID int64, f MatchFilter) ([]Match, int, error) {
|
||||
w, args := matchWhere(pairID, f)
|
||||
var total int
|
||||
|
||||
Reference in new issue
Block a user