fix(scanner): track podcast show audio paths during folder reconcile
Extract listPodcastShowAudioFiles and record each show's audio paths in seenPaths during ScanPodcastFolder so reconciliation does not treat still-present episodes as removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+23
-15
@@ -32,23 +32,10 @@ type parsedPodcastEpisode struct {
|
||||
// Returns an os.ErrNotExist-wrapped error if the folder contains zero
|
||||
// audio files.
|
||||
func parsePodcastShow(ctx context.Context, ffprobePath string, folderPath string) (*parsedPodcastShow, error) {
|
||||
entries, err := os.ReadDir(folderPath)
|
||||
audioFiles, err := listPodcastShowAudioFiles(folderPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read podcast folder %s: %w", folderPath, err)
|
||||
return nil, err
|
||||
}
|
||||
var audioFiles []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if SupportsAudioFile(entry.Name()) {
|
||||
audioFiles = append(audioFiles, filepath.Join(folderPath, entry.Name()))
|
||||
}
|
||||
}
|
||||
if len(audioFiles) == 0 {
|
||||
return nil, fmt.Errorf("podcast show %s: %w", folderPath, os.ErrNotExist)
|
||||
}
|
||||
sort.Strings(audioFiles)
|
||||
|
||||
show := &parsedPodcastShow{}
|
||||
for idx, path := range audioFiles {
|
||||
@@ -83,6 +70,27 @@ func parsePodcastShow(ctx context.Context, ffprobePath string, folderPath string
|
||||
return show, nil
|
||||
}
|
||||
|
||||
func listPodcastShowAudioFiles(folderPath string) ([]string, error) {
|
||||
entries, err := os.ReadDir(folderPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read podcast folder %s: %w", folderPath, err)
|
||||
}
|
||||
var audioFiles []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if SupportsAudioFile(entry.Name()) {
|
||||
audioFiles = append(audioFiles, filepath.Join(folderPath, entry.Name()))
|
||||
}
|
||||
}
|
||||
if len(audioFiles) == 0 {
|
||||
return nil, fmt.Errorf("podcast show %s: %w", folderPath, os.ErrNotExist)
|
||||
}
|
||||
sort.Strings(audioFiles)
|
||||
return audioFiles, nil
|
||||
}
|
||||
|
||||
// parseTrackNumber accepts ID3-style track values which may be "5" or
|
||||
// "5/12". Returns 0 if no leading integer is present.
|
||||
func parseTrackNumber(s string) int {
|
||||
|
||||
@@ -56,6 +56,11 @@ func (s *Scanner) ScanPodcastFolder(ctx context.Context, folder *models.MediaFol
|
||||
return err
|
||||
}
|
||||
attempted++
|
||||
if paths, err := listPodcastShowAudioFiles(subPath); err == nil {
|
||||
for _, path := range paths {
|
||||
seenPaths[path] = true
|
||||
}
|
||||
}
|
||||
episodePaths, err := s.reconcilePodcastShow(ctx, folder, subPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
|
||||
@@ -98,6 +98,41 @@ func TestScanPodcastFolderReturnsCanceledContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPodcastShowAudioFilesReturnsSortedAudioPaths(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(root, "02.mp3"), []byte("audio"), 0o644); err != nil {
|
||||
t.Fatalf("write audio: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "notes.txt"), []byte("notes"), 0o644); err != nil {
|
||||
t.Fatalf("write notes: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "01.flac"), []byte("audio"), 0o644); err != nil {
|
||||
t.Fatalf("write audio: %v", err)
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(root, "nested"), 0o755); err != nil {
|
||||
t.Fatalf("mkdir nested: %v", err)
|
||||
}
|
||||
|
||||
got, err := listPodcastShowAudioFiles(root)
|
||||
if err != nil {
|
||||
t.Fatalf("listPodcastShowAudioFiles: %v", err)
|
||||
}
|
||||
want := []string{
|
||||
filepath.Join(root, "01.flac"),
|
||||
filepath.Join(root, "02.mp3"),
|
||||
}
|
||||
if strings.Join(got, "\n") != strings.Join(want, "\n") {
|
||||
t.Fatalf("audio files = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPodcastShowAudioFilesReturnsNotExistForEmptyShow(t *testing.T) {
|
||||
_, err := listPodcastShowAudioFiles(t.TempDir())
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("empty podcast show error = %v, want os.ErrNotExist", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePodcastMediaItemReusesRootScopedContentID(t *testing.T) {
|
||||
finder := &fakeRootContentFinder{contentID: "podcast-root-id"}
|
||||
writer := &fakeFilesystemItemWriter{}
|
||||
|
||||
Reference in New Issue
Block a user