feat: autotag mixed-bag splitting, search relevance fixes, and multi-library download imports
Autotag: detect "junk drawer" folders with no artist/album consensus and split them into synthetic per-cluster groups instead of forcing one match on an unrelated pile of tracks; repair tagging_items rows left behind by a prior scan orphan-cleanup gap. Explore: fix an exact artist-name search being drowned out by its own catalog entries in intent-prior scoring, and prune stale in_library bookkeeping left behind when a referenced library row is deleted. Download: fix a multi-library regression where every import failed with "no library root configured" — the importer resolved the library root from a legacy single-library config field that nothing populates in the current multi-library model. It now resolves the destination library per-request from the request's own library_id. Also widen the Soulseek search window (12s -> 20s), measured against real request history to be missing available peers on live queries. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y2Agd9af5hE7qzti2ackiS
This commit is contained in:
@@ -2,6 +2,7 @@ package download
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
@@ -11,6 +12,12 @@ import (
|
||||
"yellowjacket/backend/events"
|
||||
)
|
||||
|
||||
// ErrNoLibrary means the request did not name a library to attach the
|
||||
// download to. Letting it through would hit the download_requests /
|
||||
// download_wants foreign key on library_id and surface as a raw SQLite
|
||||
// error, so it is rejected here with a message the UI can show.
|
||||
var ErrNoLibrary = errors.New("no library selected")
|
||||
|
||||
// Service is the frontend-facing surface of the download subsystem.
|
||||
// Its methods are bound into Wails and called from TypeScript, so
|
||||
// signatures use plain types and return errors the UI can render.
|
||||
@@ -71,7 +78,39 @@ func (s *Service) ProviderKinds() []Descriptor {
|
||||
|
||||
// ListProviders returns the user's configured download clients.
|
||||
func (s *Service) ListProviders() ([]Config, error) {
|
||||
return s.store.ListProviders(context.Background())
|
||||
cfgs, err := s.store.ListProviders(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range cfgs {
|
||||
cfgs[i].SetSecrets = s.setSecretFlags(cfgs[i])
|
||||
}
|
||||
|
||||
return cfgs, nil
|
||||
}
|
||||
|
||||
// setSecretFlags reports, for each secret field the provider's kind
|
||||
// declares, whether a value is already stored — so the settings form
|
||||
// can distinguish an unset secret from one it simply isn't shown.
|
||||
func (s *Service) setSecretFlags(cfg Config) map[string]bool {
|
||||
desc, ok := DescriptorFor(cfg.Kind)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
flags := map[string]bool{}
|
||||
|
||||
for _, field := range desc.Fields {
|
||||
if !field.Secret {
|
||||
continue
|
||||
}
|
||||
|
||||
v, err := s.secrets.Get(cfg.ID, field.Key)
|
||||
flags[field.Key] = err == nil && v != ""
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
// AddProvider creates a provider and stores any secret settings
|
||||
@@ -248,6 +287,10 @@ type StartResult struct {
|
||||
// Start searches for a release and either auto-picks a clear winner or
|
||||
// returns ranked candidates for the user to choose from.
|
||||
func (s *Service) Start(req SearchRequest) (StartResult, error) {
|
||||
if req.LibraryID <= 0 {
|
||||
return StartResult{}, ErrNoLibrary
|
||||
}
|
||||
|
||||
r := Request{
|
||||
ID: newID(),
|
||||
LibraryID: req.LibraryID,
|
||||
@@ -397,6 +440,10 @@ type WantRequest struct {
|
||||
// pass, so the user sees something happen rather than waiting six hours
|
||||
// for the next scheduled one.
|
||||
func (s *Service) AddWant(req WantRequest) (int64, error) {
|
||||
if req.LibraryID <= 0 {
|
||||
return 0, ErrNoLibrary
|
||||
}
|
||||
|
||||
entity := Entity(req.Entity)
|
||||
if !entity.Valid() {
|
||||
return 0, fmt.Errorf("%w: entity %q", ErrUnsupported, req.Entity)
|
||||
|
||||
Reference in New Issue
Block a user