Files
bookhoard/internal/sync/format.go
T
john-okeefe bb3c32c59f Phase 1 Week 2: Format detection and progress conversion engine
- Add internal/sync package with format detection
- FormatGroup types: reflowable, fixed_layout, comic_archive
- DetectFormatGroup() function based on mimetype and file extension
- MimeType mappings for common ebook formats
- Progress conversion engine with:
  - ConvertProgress() between format groups
  - Extract percentage from various progress formats
  - PageToPercentage / PercentageToPage helpers
  - CharacterToPercentage / PercentageToCharacter helpers
  - MergeProgress() with 'max progress wins' strategy
  - FormatProgressForDisplay() for UI rendering
- Add sqlc queries for format detection and progress updates
- BulkUpdateFormatGroups query for auto-format detection
- GetUniversalProgress query with all location references
- UpdateUniversalProgress query with device sync metadata
- ReadingHistory queries for session tracking
2026-01-30 16:07:00 -05:00

133 lines
3.2 KiB
Go

package sync
import (
"path/filepath"
"strings"
)
// FormatGroup represents the three main format categories
type FormatGroup string
const (
FormatGroupReflowable FormatGroup = "reflowable"
FormatGroupFixedLayout FormatGroup = "fixed_layout"
FormatGroupComicArchive FormatGroup = "comic_archive"
FormatGroupUnknown FormatGroup = "unknown"
)
// MimeType mappings for common ebook formats
var mimeTypes = map[string]string{
".epub": "application/epub+zip",
".mobi": "application/x-mobipocket-ebook",
".azw": "application/x-mobipocket-ebook",
".azw3": "application/vnd.amazon.mobi8-ebook",
".pdf": "application/pdf",
".djvu": "image/vnd.djvu",
".cbz": "application/x-cbz",
".cbr": "application/x-cbr",
".cb7": "application/x-cb7",
".cbt": "application/x-cbt",
".fb2": "application/x-fictionbook+xml",
".txt": "text/plain",
".rtf": "application/rtf",
".doc": "application/msword",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".lit": "application/x-ms-reader",
".pdb": "application/vnd.palm",
".prc": "application/vnd.palm",
}
// ReflowableFormats are formats that support text reflow
var ReflowableFormats = map[string]bool{
".epub": true,
".mobi": true,
".azw": true,
".azw3": true,
".fb2": true,
".txt": true,
".rtf": true,
".doc": true,
".docx": true,
".lit": true,
".pdb": true,
".prc": true,
}
// FixedLayoutFormats are formats with fixed page layouts
var FixedLayoutFormats = map[string]bool{
".pdf": true,
".djvu": true,
}
// ComicArchiveFormats are comic archive formats
var ComicArchiveFormats = map[string]bool{
".cbz": true,
".cbr": true,
".cb7": true,
".cbt": true,
}
// DetectFormatGroup determines the format group based on mimetype and file path
func DetectFormatGroup(mimetype string, filePath string) FormatGroup {
ext := strings.ToLower(filepath.Ext(filePath))
// Check by mimetype first
switch mimetype {
case "application/epub+zip",
"application/x-mobipocket-ebook",
"application/vnd.amazon.mobi8-ebook",
"application/x-fictionbook+xml",
"text/plain":
return FormatGroupReflowable
case "application/pdf",
"image/vnd.djvu":
return FormatGroupFixedLayout
case "application/x-cbr",
"application/x-cbz",
"application/x-cb7",
"application/x-cbt":
return FormatGroupComicArchive
}
// Fall back to file extension
if ReflowableFormats[ext] {
return FormatGroupReflowable
}
if FixedLayoutFormats[ext] {
return FormatGroupFixedLayout
}
if ComicArchiveFormats[ext] {
return FormatGroupComicArchive
}
return FormatGroupUnknown
}
// GetMimeType returns the mimetype for a given file extension
func GetMimeType(filePath string) string {
ext := strings.ToLower(filepath.Ext(filePath))
if mt, ok := mimeTypes[ext]; ok {
return mt
}
return ""
}
// IsReflowable checks if a format is reflowable
func IsReflowable(formatGroup FormatGroup) bool {
return formatGroup == FormatGroupReflowable
}
// HasFixedLayout checks if a format has fixed layout
func HasFixedLayout(formatGroup FormatGroup) bool {
return formatGroup == FormatGroupFixedLayout
}
// IsComicArchive checks if a format is a comic archive
func IsComicArchive(formatGroup FormatGroup) bool {
return formatGroup == FormatGroupComicArchive
}