diff --git a/internal/services/media_scanner.go b/internal/services/media_scanner.go index 11101a3..8a20567 100644 --- a/internal/services/media_scanner.go +++ b/internal/services/media_scanner.go @@ -974,30 +974,12 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool, // Set format group, total characters, and chapter count mimeType := s.getMimeType(path) ext := strings.ToLower(filepath.Ext(path)) - var formatGroup string - var isReflowable, hasFixedLayout bool - switch ext { - case ".epub": + var epubIsFixedLayout bool + if ext == ".epub" { isFixed, fixedErr := s.DetectFixedLayoutEPUB(path) - if fixedErr == nil && isFixed { - formatGroup = "fixed_layout" - hasFixedLayout = true - } else { - formatGroup = "reflowable" - isReflowable = true - } - case ".mobi", ".azw", ".azw3", ".fb2", ".txt": - formatGroup = "reflowable" - isReflowable = true - case ".pdf", ".djvu": - formatGroup = "fixed_layout" - hasFixedLayout = true - case ".cbz", ".cbr", ".cb7", ".cbt": - formatGroup = "comic_archive" - hasFixedLayout = true - default: - formatGroup = "unknown" + epubIsFixedLayout = fixedErr == nil && isFixed } + formatGroup, isReflowable, hasFixedLayout := classifyFormatGroup(ext, epubIsFixedLayout) err = s.db.UpdateMediaItemFormatGroup(ctx, database.UpdateMediaItemFormatGroupParams{ ID: createdItem.ID, FormatGroup: formatGroup, @@ -3880,6 +3862,28 @@ func (s *MediaScanner) detectFormatType(filePath string) string { } } +// classifyFormatGroup maps a lowercased file extension to the format +// group and layout flags stored on the media item. `.epub` is passed in +// with its detected fixed-layout flag because that check needs the file +// itself. +func classifyFormatGroup(ext string, epubIsFixedLayout bool) (formatGroup string, isReflowable, hasFixedLayout bool) { + switch ext { + case ".epub": + if epubIsFixedLayout { + return "fixed_layout", false, true + } + return "reflowable", true, false + case ".mobi", ".azw", ".azw3", ".fb2", ".txt", ".docx": + return "reflowable", true, false + case ".pdf", ".djvu": + return "fixed_layout", false, true + case ".cbz", ".cbr", ".cb7", ".cbt": + return "comic_archive", false, true + default: + return "unknown", false, false + } +} + // extractHashInfo calculates hash and extracts OPF identifiers for a file func (s *MediaScanner) extractHashInfo(filePath string) (*HashInfo, *FormatInfo, error) { // Calculate SHA-256 diff --git a/internal/services/media_scanner_test.go b/internal/services/media_scanner_test.go index 9a87f3e..1ea9e47 100644 --- a/internal/services/media_scanner_test.go +++ b/internal/services/media_scanner_test.go @@ -119,3 +119,34 @@ func TestProcessDirtyDirectories_CollectsReadyDirs(t *testing.T) { scanner.dirtyDirsMu.RUnlock() assert.Equal(t, 0, count, "Ready directory should be removed from dirty list") } + +func TestClassifyFormatGroup(t *testing.T) { + tests := []struct { + name string + ext string + epubFixed bool + group string + reflow bool + fixed bool + }{ + {"EPUB reflowable", ".epub", false, "reflowable", true, false}, + {"EPUB fixed-layout", ".epub", true, "fixed_layout", false, true}, + {"MOBI", ".mobi", false, "reflowable", true, false}, + {"FB2", ".fb2", false, "reflowable", true, false}, + {"TXT", ".txt", false, "reflowable", true, false}, + {"DOCX (was unknown before the fix)", ".docx", false, "reflowable", true, false}, + {"PDF", ".pdf", false, "fixed_layout", false, true}, + {"DJVU", ".djvu", false, "fixed_layout", false, true}, + {"CBZ", ".cbz", false, "comic_archive", false, true}, + {"unrecognized extension", ".xyz", false, "unknown", false, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + group, reflow, fixed := classifyFormatGroup(tt.ext, tt.epubFixed) + if group != tt.group || reflow != tt.reflow || fixed != tt.fixed { + t.Errorf("classifyFormatGroup(%q, %v) = (%q, %v, %v), want (%q, %v, %v)", + tt.ext, tt.epubFixed, group, reflow, fixed, tt.group, tt.reflow, tt.fixed) + } + }) + } +}