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
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user