fix(scanner): classify .docx as reflowable — was landing format_group unknown

.docx is accepted for ingest but the format-group switch had no case
for it, so every DOCX row stored format_group 'unknown' with
is_reflowable=false. Extract the classification switch into a pure
classifyFormatGroup helper with a table test, and classify .docx
reflowable like the other reflowable text formats (.rtf/.doc stay out
until they are readable client-side). Existing rows: dev alice (DOCX)
reclassified by hand; production rows reclassify on rescan.
This commit is contained in:
John O'Keefe
2026-10-03 14:17:58 -04:00
parent ad1ad11fb5
commit f2629942e4
2 changed files with 57 additions and 22 deletions
+26 -22
View File
@@ -974,30 +974,12 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool,
// Set format group, total characters, and chapter count // Set format group, total characters, and chapter count
mimeType := s.getMimeType(path) mimeType := s.getMimeType(path)
ext := strings.ToLower(filepath.Ext(path)) ext := strings.ToLower(filepath.Ext(path))
var formatGroup string var epubIsFixedLayout bool
var isReflowable, hasFixedLayout bool if ext == ".epub" {
switch ext {
case ".epub":
isFixed, fixedErr := s.DetectFixedLayoutEPUB(path) isFixed, fixedErr := s.DetectFixedLayoutEPUB(path)
if fixedErr == nil && isFixed { epubIsFixedLayout = 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"
} }
formatGroup, isReflowable, hasFixedLayout := classifyFormatGroup(ext, epubIsFixedLayout)
err = s.db.UpdateMediaItemFormatGroup(ctx, database.UpdateMediaItemFormatGroupParams{ err = s.db.UpdateMediaItemFormatGroup(ctx, database.UpdateMediaItemFormatGroupParams{
ID: createdItem.ID, ID: createdItem.ID,
FormatGroup: formatGroup, 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 // extractHashInfo calculates hash and extracts OPF identifiers for a file
func (s *MediaScanner) extractHashInfo(filePath string) (*HashInfo, *FormatInfo, error) { func (s *MediaScanner) extractHashInfo(filePath string) (*HashInfo, *FormatInfo, error) {
// Calculate SHA-256 // Calculate SHA-256
+31
View File
@@ -119,3 +119,34 @@ func TestProcessDirtyDirectories_CollectsReadyDirs(t *testing.T) {
scanner.dirtyDirsMu.RUnlock() scanner.dirtyDirsMu.RUnlock()
assert.Equal(t, 0, count, "Ready directory should be removed from dirty list") 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)
}
})
}
}