fix(uploads): refresh destination previews and improve queue layout
ci / api / api (push) Successful in 50s
ci / db / db (push) Successful in 56s
ci / module boundaries / boundaries (push) Successful in 8s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m2s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m15s
ci / ts / ts (packages + web) (push) Failing after 4m28s
ci / api / api (push) Successful in 50s
ci / db / db (push) Successful in 56s
ci / module boundaries / boundaries (push) Successful in 8s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m2s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m15s
ci / ts / ts (packages + web) (push) Failing after 4m28s
This commit is contained in:
1 parent
eb75437708
commit
30c3c85f89
18 files changed
+603
-402
No files matched your search
@@ -35,8 +35,9 @@ func (h *Handler) fileDTO(ctx context.Context, f File) gen.UploadFile {
|
||||
MetadataEdited: f.MetadataEdited, MatchedArtistId: f.MatchedArtistID, MatchedAlbumId: f.MatchedAlbumID,
|
||||
DuplicateOfTrackId: f.DuplicateOfTrackID, PossibleDuplicate: f.PossibleDuplicate, QualityUpgrade: f.QualityUpgrade,
|
||||
TargetSourceId: f.TargetSourceID, TargetRelPath: f.TargetRelPath, StatusReason: f.StatusReason,
|
||||
TargetSourceName: f.TargetSourceName,
|
||||
ReviewedAt: f.ReviewedAt, CreatedAt: f.CreatedAt, UpdatedAt: f.UpdatedAt,
|
||||
TargetSourceName: f.TargetSourceName,
|
||||
TargetStoragePath: f.TargetStoragePath,
|
||||
ReviewedAt: f.ReviewedAt, CreatedAt: f.CreatedAt, UpdatedAt: f.UpdatedAt,
|
||||
}
|
||||
if out.Tags == nil {
|
||||
out.Tags = map[string]string{}
|
||||
|
||||
@@ -141,6 +141,7 @@ type File struct {
|
||||
QualityUpgrade bool
|
||||
TargetSourceID *uuid.UUID
|
||||
TargetSourceName *string
|
||||
TargetStoragePath *string
|
||||
TargetRelPath *string
|
||||
StatusReason *string
|
||||
ReviewedBy *uuid.UUID
|
||||
|
||||
@@ -3,6 +3,8 @@ package uploads
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
@@ -47,6 +49,41 @@ type placement struct {
|
||||
root string
|
||||
}
|
||||
|
||||
// previewPlacement uses the same current configuration as importFile. Finalized
|
||||
// files keep their recorded destination; reading the queue never changes a decision.
|
||||
func (s *PlacementPlanner) previewPlacement(ctx context.Context, f *File) {
|
||||
f.TargetStoragePath = nil
|
||||
if f.Meta != nil && (f.Status == StatusPendingReview || f.Status == StatusImportFailed || f.Status == StatusApproved) {
|
||||
pl := s.plan(ctx, *f, *f.Meta, f.TargetSourceID)
|
||||
f.TargetSourceID, f.TargetRelPath = pl.SourceID, pl.RelPath
|
||||
f.TargetSourceName = nil
|
||||
if pl.SourceID != nil {
|
||||
f.TargetSourceName = &pl.src.Name
|
||||
}
|
||||
if f.Status == StatusPendingReview || pl.Reason != "" {
|
||||
f.StatusReason = nullable(pl.Reason)
|
||||
}
|
||||
f.MatchedArtistID, f.MatchedAlbumID = pl.ArtistID, pl.AlbumID
|
||||
}
|
||||
if f.TargetSourceID == nil || f.TargetRelPath == nil {
|
||||
return
|
||||
}
|
||||
src, err := s.lib.GetSource(ctx, *f.TargetSourceID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
f.TargetSourceName = &src.Name
|
||||
if src.Type == library.SourceNFS {
|
||||
if src.NFSServer != nil && src.NFSExport != nil {
|
||||
full := *src.NFSServer + ":" + strings.TrimRight(*src.NFSExport, "/") + "/" + *f.TargetRelPath
|
||||
f.TargetStoragePath = &full
|
||||
}
|
||||
} else if root, err := s.roots.Root(src); err == nil {
|
||||
full := filepath.Join(root, filepath.FromSlash(*f.TargetRelPath))
|
||||
f.TargetStoragePath = &full
|
||||
}
|
||||
}
|
||||
|
||||
// pickSource — хранилище: выбранное админом → по умолчанию → единственное принимающее загрузки.
|
||||
func (s *PlacementPlanner) pickSource(ctx context.Context, chosen *uuid.UUID) (library.Source, string) {
|
||||
id := chosen
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package uploads
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"pipu/api/internal/library"
|
||||
)
|
||||
|
||||
type previewCatalog struct{ source library.Source }
|
||||
|
||||
func (c *previewCatalog) GetSource(context.Context, uuid.UUID) (library.Source, error) {
|
||||
return c.source, nil
|
||||
}
|
||||
func (c *previewCatalog) ListSources(context.Context) ([]library.Source, error) {
|
||||
return []library.Source{c.source}, nil
|
||||
}
|
||||
func (c *previewCatalog) Patterns(context.Context, uuid.UUID) ([]library.PatternRow, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type previewStore struct{}
|
||||
|
||||
func (previewStore) Settings(context.Context) (Settings, error) { return Settings{}, nil }
|
||||
func (previewStore) MatchCatalog(context.Context, string, string) (*uuid.UUID, *uuid.UUID, int, error) {
|
||||
return nil, nil, 0, nil
|
||||
}
|
||||
func (previewStore) BatchMaxDisc(context.Context, uuid.UUID, string) (int, error) { return 1, nil }
|
||||
|
||||
type previewRoot string
|
||||
|
||||
func (r previewRoot) Root(library.Source) (string, error) { return string(r), nil }
|
||||
|
||||
func TestPreviewUsesCurrentImportTemplate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
root := t.TempDir()
|
||||
catalog := &previewCatalog{source: library.Source{ID: uuid.New(), Name: "Base", Type: library.SourceLocal,
|
||||
AcceptsUploads: true, WriteTemplate: nullable("{albumartist}/{album}/{title}.{ext}")}}
|
||||
planner := &PlacementPlanner{store: previewStore{}, lib: catalog, roots: previewRoot(root)}
|
||||
track := 3
|
||||
f := File{Status: StatusPendingReview, OriginalName: "song.flac", Meta: &Meta{
|
||||
Artist: "Artist", AlbumArtist: "Artist", Album: "Album", Title: "Song", Track: &track},
|
||||
TargetSourceID: &catalog.source.ID, TargetRelPath: nullable("stale/path.flac"), StatusReason: nullable(CodeNoTarget)}
|
||||
planner.previewPlacement(ctx, &f)
|
||||
require.Equal(t, "Artist/Album/Song.flac", *f.TargetRelPath)
|
||||
require.Equal(t, filepath.Join(root, "Artist", "Album", "Song.flac"), *f.TargetStoragePath)
|
||||
require.Nil(t, f.StatusReason)
|
||||
|
||||
catalog.source.WriteTemplate = nullable("{albumartist}/{album}/{track:02} - {title}.{ext}")
|
||||
planner.previewPlacement(ctx, &f)
|
||||
planned := planner.plan(ctx, f, *f.Meta, f.TargetSourceID)
|
||||
require.Equal(t, "Artist/Album/03 - Song.flac", *f.TargetRelPath)
|
||||
require.Equal(t, planned.RelPath, f.TargetRelPath, "preview and import must use the same path")
|
||||
|
||||
catalog.source.Type = library.SourceNFS
|
||||
catalog.source.NFSServer, catalog.source.NFSExport = nullable("nas"), nullable("/music/")
|
||||
planner.previewPlacement(ctx, &f)
|
||||
require.Equal(t, "nas:/music/Artist/Album/03 - Song.flac", *f.TargetStoragePath)
|
||||
|
||||
f.Status = StatusImported
|
||||
catalog.source.WriteTemplate = nullable("changed/{title}.{ext}")
|
||||
planner.previewPlacement(ctx, &f)
|
||||
require.Equal(t, "Artist/Album/03 - Song.flac", *f.TargetRelPath, "imported files retain their actual destination")
|
||||
|
||||
f.Status = StatusPendingReview
|
||||
catalog.source.WriteTemplate = nil
|
||||
planner.previewPlacement(ctx, &f)
|
||||
require.Nil(t, f.TargetRelPath, "invalid configuration must not show the stale path")
|
||||
require.Nil(t, f.TargetStoragePath)
|
||||
require.Equal(t, CodeTemplate, *f.StatusReason)
|
||||
// Reading a preview does not write to the store or change the review status.
|
||||
require.Equal(t, StatusPendingReview, f.Status)
|
||||
}
|
||||
@@ -268,7 +268,14 @@ func (s *Service) ListAll(ctx context.Context, fl ListFilter) ([]File, string, e
|
||||
if _, err := principal(ctx, auth.PermReviewUploads); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return s.list(ctx, fl)
|
||||
files, next, err := s.list(ctx, fl)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
for i := range files {
|
||||
s.previewPlacement(ctx, &files[i])
|
||||
}
|
||||
return files, next, nil
|
||||
}
|
||||
|
||||
func (s *Service) list(ctx context.Context, fl ListFilter) ([]File, string, error) {
|
||||
@@ -339,7 +346,11 @@ func (s *Service) Update(ctx context.Context, id uuid.UUID, in UpdateInput) (Fil
|
||||
if err := s.store.UpdateMetadata(ctx, id, m, edited, pl); err != nil {
|
||||
return File{}, notFound(err)
|
||||
}
|
||||
return s.store.File(ctx, id)
|
||||
updated, err := s.store.File(ctx, id)
|
||||
if err == nil {
|
||||
s.previewPlacement(ctx, &updated)
|
||||
}
|
||||
return updated, err
|
||||
}
|
||||
|
||||
func normalizeMeta(m Meta) Meta {
|
||||
|
||||
Reference in new issue
Block a user