mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
Merge ef742c5f60762ad5e7e746497ef6dbe11493d2d3 into 3867fab4da6ea7142bfc6374c94b34a708e03b4e
This commit is contained in:
commit
71d69ea25b
130
cmd/missing.go
Normal file
130
cmd/missing.go
Normal file
@ -0,0 +1,130 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var missingListFormat string
|
||||
|
||||
func init() {
|
||||
missingListCmd.Flags().StringVarP(&missingListFormat, "format", "f", "csv", "output format [supported values: csv, json]")
|
||||
missingCmd.AddCommand(missingListCmd)
|
||||
missingCmd.AddCommand(missingFixCmd)
|
||||
rootCmd.AddCommand(missingCmd)
|
||||
}
|
||||
|
||||
var (
|
||||
missingCmd = &cobra.Command{
|
||||
Use: "missing",
|
||||
Short: "Manage missing files",
|
||||
Long: "List files marked as missing and remap them onto existing files",
|
||||
}
|
||||
|
||||
missingListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List missing files",
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
runMissingList(cmd.Context())
|
||||
},
|
||||
}
|
||||
|
||||
missingFixCmd = &cobra.Command{
|
||||
Use: "fix <missing path|id> <target path|id>",
|
||||
Short: "Remap a missing file onto an existing file",
|
||||
Long: "Remap a file marked as missing onto an existing (non-missing) file, the same way\n" +
|
||||
"the scanner reconciles moved or renamed files. Each argument may be a media file ID,\n" +
|
||||
"a library-relative path, or a libraryID:path pair.",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runMissingFix(cmd.Context(), args[0], args[1])
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type displayMissingFile struct {
|
||||
ID string `json:"id"`
|
||||
LibraryID int `json:"libraryId"`
|
||||
Path string `json:"path"`
|
||||
Title string `json:"title"`
|
||||
Album string `json:"album"`
|
||||
Artist string `json:"artist"`
|
||||
}
|
||||
|
||||
func runMissingList(ctx context.Context) {
|
||||
if missingListFormat != "csv" && missingListFormat != "json" {
|
||||
log.Fatal("Invalid output format. Must be one of csv, json", "format", missingListFormat)
|
||||
}
|
||||
|
||||
ds, ctx := getAdminContext(ctx)
|
||||
mfs, err := ds.MediaFile(ctx).GetAll(model.QueryOptions{
|
||||
Filters: squirrel.Eq{"missing": true},
|
||||
Sort: "path",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(ctx, "Failed to retrieve missing files", err)
|
||||
}
|
||||
|
||||
if missingListFormat == "json" {
|
||||
display := make([]displayMissingFile, len(mfs))
|
||||
for i, mf := range mfs {
|
||||
display[i] = displayMissingFile{ID: mf.ID, LibraryID: mf.LibraryID, Path: mf.Path, Title: mf.Title, Album: mf.Album, Artist: mf.Artist}
|
||||
}
|
||||
j, _ := json.Marshal(display)
|
||||
fmt.Printf("%s\n", j)
|
||||
} else {
|
||||
w := csv.NewWriter(os.Stdout)
|
||||
_ = w.Write([]string{"id", "library id", "path", "title", "album", "artist"})
|
||||
for _, mf := range mfs {
|
||||
_ = w.Write([]string{mf.ID, strconv.Itoa(mf.LibraryID), mf.Path, mf.Title, mf.Album, mf.Artist})
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func runMissingFix(ctx context.Context, missingRef, targetRef string) {
|
||||
ds, ctx := getAdminContext(ctx)
|
||||
|
||||
missing := resolveMediaFile(ctx, ds, missingRef)
|
||||
target := resolveMediaFile(ctx, ds, targetRef)
|
||||
|
||||
if err := core.NewMaintenance(ds).RemapMissingFile(ctx, missing.ID, target.ID); err != nil {
|
||||
log.Fatal(ctx, "Failed to remap missing file", "missing", missing.Path, "target", target.Path, err)
|
||||
}
|
||||
fmt.Printf("Remapped %q onto %q\n", missing.Path, target.Path)
|
||||
}
|
||||
|
||||
// resolveMediaFile looks up a media file by ID first, then by path (optionally libraryID:path),
|
||||
// following the same "try one, then the other" pattern used by findPlaylist.
|
||||
func resolveMediaFile(ctx context.Context, ds model.DataStore, ref string) *model.MediaFile {
|
||||
mf, err := ds.MediaFile(ctx).Get(ref)
|
||||
if err == nil {
|
||||
return mf
|
||||
}
|
||||
if !errors.Is(err, model.ErrNotFound) {
|
||||
log.Fatal(ctx, "Error looking up media file", "ref", ref, err)
|
||||
}
|
||||
|
||||
mfs, err := ds.MediaFile(ctx).FindByPaths([]string{ref})
|
||||
if err != nil {
|
||||
log.Fatal(ctx, "Error looking up media file by path", "ref", ref, err)
|
||||
}
|
||||
if len(mfs) == 0 {
|
||||
log.Fatal(ctx, "No media file found", "ref", ref)
|
||||
}
|
||||
if len(mfs) > 1 {
|
||||
log.Fatal(ctx, "Path matches multiple files; disambiguate with an ID or libraryID:path", "ref", ref, "matches", len(mfs))
|
||||
}
|
||||
return &mfs[0]
|
||||
}
|
||||
@ -2,6 +2,7 @@ package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
@ -14,11 +15,24 @@ import (
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotMissing is returned when a remap is attempted from a file not marked as missing.
|
||||
ErrNotMissing = errors.New("file is not marked as missing")
|
||||
// ErrTargetMissing is returned when the remap target is itself a missing file.
|
||||
ErrTargetMissing = errors.New("target file is missing")
|
||||
// ErrSameFile is returned when the remap source and target are the same file.
|
||||
ErrSameFile = errors.New("missing and target are the same file")
|
||||
)
|
||||
|
||||
type Maintenance interface {
|
||||
// DeleteMissingFiles deletes specific missing files by their IDs
|
||||
DeleteMissingFiles(ctx context.Context, ids []string) error
|
||||
// DeleteAllMissingFiles deletes all files marked as missing
|
||||
DeleteAllMissingFiles(ctx context.Context) error
|
||||
// RemapMissingFile relocates a missing file's identity onto an existing, non-missing
|
||||
// media file. It is the manual counterpart to the scanner's automatic move detection
|
||||
// (see scanner.phaseMissingTracks.moveMatched).
|
||||
RemapMissingFile(ctx context.Context, missingID, targetID string) error
|
||||
}
|
||||
|
||||
type maintenanceService struct {
|
||||
@ -40,6 +54,94 @@ func (s *maintenanceService) DeleteAllMissingFiles(ctx context.Context) error {
|
||||
return s.deleteMissing(ctx, nil)
|
||||
}
|
||||
|
||||
func (s *maintenanceService) RemapMissingFile(ctx context.Context, missingID, targetID string) error {
|
||||
if missingID == targetID {
|
||||
return fmt.Errorf("%w: %q", ErrSameFile, missingID)
|
||||
}
|
||||
|
||||
missing, err := s.ds.MediaFile(ctx).Get(missingID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading missing file %q: %w", missingID, err)
|
||||
}
|
||||
if !missing.Missing {
|
||||
return fmt.Errorf("%w: %q", ErrNotMissing, missingID)
|
||||
}
|
||||
|
||||
target, err := s.ds.MediaFile(ctx).GetWithParticipants(targetID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading target file %q: %w", targetID, err)
|
||||
}
|
||||
if target.Missing {
|
||||
return fmt.Errorf("%w: %q", ErrTargetMissing, targetID)
|
||||
}
|
||||
|
||||
oldAlbumID, newAlbumID := missing.AlbumID, target.AlbumID
|
||||
|
||||
// Mirrors scanner.phaseMissingTracks.moveMatched: move the missing track's identity
|
||||
// (ID, annotations and references) onto the file found at the new location.
|
||||
err = s.ds.WithTx(func(tx model.DataStore) error {
|
||||
discardedID := target.ID
|
||||
|
||||
// Preserve the original created_at so the remapped track doesn't resurface in "Recently Added"
|
||||
target.CreatedAt = missing.CreatedAt
|
||||
target.ID = missing.ID
|
||||
if err := tx.MediaFile(ctx).Put(target); err != nil {
|
||||
return fmt.Errorf("update matched track: %w", err)
|
||||
}
|
||||
// Discard the target's original row
|
||||
if err := tx.MediaFile(ctx).Delete(discardedID); err != nil {
|
||||
return fmt.Errorf("delete discarded track: %w", err)
|
||||
}
|
||||
|
||||
if oldAlbumID != newAlbumID {
|
||||
// Reassign album annotations (starred, rating) if the old album is now empty
|
||||
oldAlbumTracks, err := tx.MediaFile(ctx).CountAll(model.QueryOptions{Filters: squirrel.Eq{"album_id": oldAlbumID}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get old album tracks: %w", err)
|
||||
}
|
||||
if oldAlbumTracks == 0 {
|
||||
if err := tx.Album(ctx).ReassignAnnotation(oldAlbumID, newAlbumID); err != nil {
|
||||
return fmt.Errorf("reassign album annotations: %w", err)
|
||||
}
|
||||
// Copy across the create_at timestamp from the old album
|
||||
if err := tx.Album(ctx).CopyAttributes(oldAlbumID, newAlbumID, "created_at"); err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||
return fmt.Errorf("copy album attributes: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(ctx, "Error remapping missing file", "missing", missing.Path, "target", target.Path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Clean up now-orphaned records and refresh affected statistics, mirroring deleteMissing
|
||||
if err := s.ds.GC(ctx); err != nil {
|
||||
log.Error(ctx, "Error running GC after remapping missing file", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Refresh artist stats
|
||||
if _, err := s.ds.Artist(ctx).RefreshStats(true); err != nil {
|
||||
log.Error(ctx, "Error refreshing artist stats after deleting missing files", err)
|
||||
} else {
|
||||
log.Debug(ctx, "Successfully refreshed artist stats after deleting missing files")
|
||||
}
|
||||
|
||||
// Refresh album stats if we have affected albums
|
||||
affectedAlbumIDs := slice.Unique(slice.Filter([]string{oldAlbumID, newAlbumID}, func(id string) bool { return id != "" }))
|
||||
if len(affectedAlbumIDs) > 0 {
|
||||
if err := s.refreshAlbums(ctx, affectedAlbumIDs); err != nil {
|
||||
log.Error(ctx, "Error refreshing album stats after deleting missing files", err)
|
||||
} else {
|
||||
log.Debug(ctx, "Successfully refreshed album stats after deleting missing files", "count", len(affectedAlbumIDs))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteMissing handles the deletion of missing files and triggers necessary cleanup operations
|
||||
func (s *maintenanceService) deleteMissing(ctx context.Context, ids []string) error {
|
||||
// Track affected album IDs before deletion for refresh
|
||||
@ -68,7 +170,8 @@ func (s *maintenanceService) deleteMissing(ctx context.Context, ids []string) er
|
||||
return err
|
||||
}
|
||||
|
||||
// Refresh statistics in background
|
||||
// Refresh statistics in background. album/artist play count aggregates are not recalculated
|
||||
// here; they are refreshed by the next scan.
|
||||
s.refreshStatsAsync(ctx, affectedAlbumIDs)
|
||||
|
||||
return nil
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
@ -250,6 +251,156 @@ var _ = Describe("Maintenance", func() {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("RemapMissingFile", func() {
|
||||
It("relocates the missing file's identity onto the target and runs GC", func() {
|
||||
created := time.Now().Add(-30 * 24 * time.Hour)
|
||||
mfRepo.SetData(model.MediaFiles{
|
||||
{ID: "m1", Path: "old/song.mp3", AlbumID: "album1", CreatedAt: created, Missing: true},
|
||||
{ID: "t1", Path: "new/song.mp3", AlbumID: "album1", Missing: false},
|
||||
})
|
||||
|
||||
Expect(service.RemapMissingFile(ctx, "m1", "t1")).To(Succeed())
|
||||
|
||||
got, err := mfRepo.Get("m1")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(got.Path).To(Equal("new/song.mp3")) // moved to target's location
|
||||
Expect(got.Missing).To(BeFalse())
|
||||
Expect(got.CreatedAt).To(BeTemporally("==", created)) // created_at preserved
|
||||
exists, _ := mfRepo.Exists("t1")
|
||||
Expect(exists).To(BeFalse()) // discarded row removed
|
||||
Expect(ds.GCCalled).To(BeTrue())
|
||||
})
|
||||
|
||||
It("reassigns album annotations when the old album is emptied", func() {
|
||||
albumRepo := ds.MockedAlbum.(*extendedAlbumRepo)
|
||||
mfRepo.SetData(model.MediaFiles{
|
||||
{ID: "m1", AlbumID: "album1", Missing: true},
|
||||
{ID: "t1", AlbumID: "album2", Missing: false},
|
||||
})
|
||||
|
||||
mfRepo.SetCountAll(0)
|
||||
Expect(service.RemapMissingFile(ctx, "m1", "t1")).To(Succeed())
|
||||
|
||||
Expect(albumRepo.ReassignAnnotationCalls).To(HaveKeyWithValue("album1", "album2"))
|
||||
})
|
||||
|
||||
It("does not reassign album annotations when the old album is not emptied", func() {
|
||||
albumRepo := ds.MockedAlbum.(*extendedAlbumRepo)
|
||||
mfRepo.SetData(model.MediaFiles{
|
||||
{ID: "m1", AlbumID: "album1", Missing: true},
|
||||
{ID: "m2", AlbumID: "album1", Missing: false},
|
||||
{ID: "t1", AlbumID: "album2", Missing: false},
|
||||
})
|
||||
|
||||
Expect(service.RemapMissingFile(ctx, "m1", "t1")).To(Succeed())
|
||||
|
||||
Expect(albumRepo.ReassignAnnotationCalls).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("does not reassign annotations when the album is unchanged", func() {
|
||||
albumRepo := ds.MockedAlbum.(*extendedAlbumRepo)
|
||||
mfRepo.SetData(model.MediaFiles{
|
||||
{ID: "m1", AlbumID: "album1", Missing: true},
|
||||
{ID: "t1", AlbumID: "album1", Missing: false},
|
||||
})
|
||||
|
||||
Expect(service.RemapMissingFile(ctx, "m1", "t1")).To(Succeed())
|
||||
|
||||
Expect(albumRepo.ReassignAnnotationCalls).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("returns ErrNotFound when the missing file does not exist", func() {
|
||||
mfRepo.SetData(model.MediaFiles{{ID: "t1", Missing: false}})
|
||||
|
||||
Expect(service.RemapMissingFile(ctx, "nope", "t1")).To(MatchError(model.ErrNotFound))
|
||||
})
|
||||
|
||||
It("refuses to remap from a file that is not missing", func() {
|
||||
mfRepo.SetData(model.MediaFiles{
|
||||
{ID: "m1", Missing: false},
|
||||
{ID: "t1", Missing: false},
|
||||
})
|
||||
|
||||
Expect(service.RemapMissingFile(ctx, "m1", "t1")).To(MatchError(ErrNotMissing))
|
||||
})
|
||||
|
||||
It("refuses to remap a file onto itself", func() {
|
||||
mfRepo.SetData(model.MediaFiles{{ID: "m1", Missing: true}})
|
||||
|
||||
Expect(service.RemapMissingFile(ctx, "m1", "m1")).To(MatchError(ErrSameFile))
|
||||
})
|
||||
|
||||
It("refuses to remap onto a target that is itself missing", func() {
|
||||
mfRepo.SetData(model.MediaFiles{
|
||||
{ID: "m1", Missing: true},
|
||||
{ID: "t1", Missing: true},
|
||||
})
|
||||
|
||||
Expect(service.RemapMissingFile(ctx, "m1", "t1")).To(MatchError(ErrTargetMissing))
|
||||
})
|
||||
|
||||
It("refreshes artist and album stats right after the remap", func() {
|
||||
artistRepo := ds.MockedArtist.(*extendedArtistRepo)
|
||||
albumRepo := ds.MockedAlbum.(*extendedAlbumRepo)
|
||||
albumRepo.SetData(model.Albums{
|
||||
{ID: "album1", Name: "Old Album", SongCount: 2, Size: 1100, Duration: 110},
|
||||
{ID: "album2", Name: "New Album", SongCount: 1, Size: 2000, Duration: 200},
|
||||
})
|
||||
mfRepo.SetData(model.MediaFiles{
|
||||
{ID: "m1", Path: "old/1.mp3", Album: "Old Album", AlbumID: "album1", Missing: true, Size: 100, Duration: 10},
|
||||
{ID: "k1", Path: "old/2.mp3", Album: "Old Album", AlbumID: "album1", Missing: false, Size: 1000, Duration: 100},
|
||||
{ID: "t1", Path: "new/1.mp3", Album: "New Album", AlbumID: "album2", Missing: false, Size: 2000, Duration: 200},
|
||||
})
|
||||
|
||||
Expect(service.RemapMissingFile(ctx, "m1", "t1")).To(Succeed())
|
||||
|
||||
Expect(artistRepo.IsRefreshStatsCalled()).To(BeTrue(), "Artist stats should be refreshed")
|
||||
|
||||
// The old album lost the remapped track, so its stats are recalculated from the remaining one
|
||||
oldAlbum, err := albumRepo.Get("album1")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(oldAlbum.SongCount).To(Equal(1))
|
||||
Expect(oldAlbum.Size).To(Equal(int64(1000)))
|
||||
Expect(oldAlbum.Duration).To(BeNumerically("==", 100))
|
||||
|
||||
// The target album keeps the track, now under the missing file's ID
|
||||
newAlbum, err := albumRepo.Get("album2")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(newAlbum.SongCount).To(Equal(1))
|
||||
Expect(newAlbum.Size).To(Equal(int64(2000)))
|
||||
Expect(newAlbum.Duration).To(BeNumerically("==", 200))
|
||||
})
|
||||
|
||||
It("returns an error if GC fails", func() {
|
||||
mfRepo.SetData(model.MediaFiles{
|
||||
{ID: "m1", AlbumID: "album1", Missing: true},
|
||||
{ID: "t1", AlbumID: "album1", Missing: false},
|
||||
})
|
||||
ds.GCError = errors.New("gc failed")
|
||||
|
||||
Expect(service.RemapMissingFile(ctx, "m1", "t1")).To(MatchError(ContainSubstring("gc failed")))
|
||||
})
|
||||
|
||||
It("preserves the target's participants on the remapped track", func() {
|
||||
participant := model.Participant{
|
||||
Artist: model.Artist{ID: "a1", Name: "Artist", OrderArtistName: "artist", MbzArtistID: "mbz-artist"},
|
||||
}
|
||||
mfRepo.SetData(model.MediaFiles{
|
||||
{ID: "m1", AlbumID: "album1", Missing: true},
|
||||
{ID: "t1", AlbumID: "album2", Missing: false, Participants: model.Participants{
|
||||
model.RoleArtist: model.ParticipantList{participant},
|
||||
}},
|
||||
})
|
||||
|
||||
Expect(service.RemapMissingFile(ctx, "m1", "t1")).To(Succeed())
|
||||
|
||||
// The surviving row is the missing file's ID, holding the target's data
|
||||
got, err := mfRepo.GetWithParticipants("m1")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(got.Participants).To(HaveKeyWithValue(model.RoleArtist, model.ParticipantList{participant}))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Test helper to create a mock DataStore with controllable behavior
|
||||
|
||||
@ -311,7 +311,7 @@ var _ = Describe("phaseMissingTracks", func() {
|
||||
When("PurgeMissing is 'always'", func() {
|
||||
BeforeEach(func() {
|
||||
conf.Server.Scanner.PurgeMissing = consts.PurgeMissingAlways
|
||||
mr.CountAllValue = 3
|
||||
mr.SetCountAll(3)
|
||||
mr.DeleteAllMissingValue = 3
|
||||
})
|
||||
It("should purge missing files", func() {
|
||||
@ -325,7 +325,7 @@ var _ = Describe("phaseMissingTracks", func() {
|
||||
When("PurgeMissing is 'full'", func() {
|
||||
BeforeEach(func() {
|
||||
conf.Server.Scanner.PurgeMissing = consts.PurgeMissingFull
|
||||
mr.CountAllValue = 2
|
||||
mr.SetCountAll(2)
|
||||
mr.DeleteAllMissingValue = 2
|
||||
})
|
||||
It("should not purge missing files if not a full scan", func() {
|
||||
@ -346,7 +346,7 @@ var _ = Describe("phaseMissingTracks", func() {
|
||||
When("PurgeMissing is 'never'", func() {
|
||||
BeforeEach(func() {
|
||||
conf.Server.Scanner.PurgeMissing = consts.PurgeMissingNever
|
||||
mr.CountAllValue = 1
|
||||
mr.SetCountAll(1)
|
||||
mr.DeleteAllMissingValue = 1
|
||||
})
|
||||
It("should not purge missing files", func() {
|
||||
|
||||
@ -24,8 +24,9 @@ type MockMediaFileRepo struct {
|
||||
model.MediaFileRepository
|
||||
Data map[string]*model.MediaFile
|
||||
Err bool
|
||||
// Add fields and methods for controlling CountAll and DeleteAllMissing in tests
|
||||
CountAllValue int64
|
||||
// Add fields and methods for controlling CountAll and DeleteAllMissing in tests.
|
||||
// A nil CountAllValue is unset, and CountAll falls back to counting rows in Data.
|
||||
CountAllValue *int64
|
||||
CountAllOptions model.QueryOptions
|
||||
DeleteAllMissingValue int64
|
||||
Options model.QueryOptions
|
||||
@ -40,6 +41,10 @@ func (m *MockMediaFileRepo) SetError(err bool) {
|
||||
m.Err = err
|
||||
}
|
||||
|
||||
func (m *MockMediaFileRepo) SetCountAll(count int64) {
|
||||
m.CountAllValue = &count
|
||||
}
|
||||
|
||||
func (m *MockMediaFileRepo) SetData(mfs model.MediaFiles) {
|
||||
m.Data = make(map[string]*model.MediaFile)
|
||||
for i, mf := range mfs {
|
||||
@ -259,11 +264,11 @@ func (m *MockMediaFileRepo) CountAll(opts ...model.QueryOptions) (int64, error)
|
||||
if m.Err {
|
||||
return 0, errors.New("error")
|
||||
}
|
||||
if m.CountAllValue != 0 {
|
||||
if len(opts) > 0 {
|
||||
m.CountAllOptions = opts[0]
|
||||
}
|
||||
return m.CountAllValue, nil
|
||||
if len(opts) > 0 {
|
||||
m.CountAllOptions = opts[0]
|
||||
}
|
||||
if m.CountAllValue != nil {
|
||||
return *m.CountAllValue, nil
|
||||
}
|
||||
return int64(len(m.Data)), nil
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user