package sync import "log" type LocatorSource string const ( LocatorSourceKOReader LocatorSource = "koreader" LocatorSourceKobo LocatorSource = "kobo" LocatorSourceWeb LocatorSource = "web" ) type CanonicalLocator struct { CFI string Precision string Percentage float64 } type DeviceLocator struct { Position string Precision string Percentage float64 } func isConvertible(formatGroup string) bool { return formatGroup == string(FormatGroupReflowable) } func ConvertToCanonical( source LocatorSource, devicePos string, percentage float64, contextText string, formatGroup string, epubPath string, kepubPath string, ) CanonicalLocator { if !isConvertible(formatGroup) || epubPath == "" { return CanonicalLocator{ CFI: devicePos, Precision: "passthrough", Percentage: percentage, } } switch source { case LocatorSourceKOReader: if !IsCREXPointer(devicePos) { return CanonicalLocator{CFI: devicePos, Precision: "already-standard", Percentage: percentage} } converter := NewCFIConverter(epubPath) result, err := converter.ConvertCREToStandard(devicePos, percentage, contextText) if err != nil || result == nil { log.Printf("Bookhoard: locator CRE→CFI conversion failed: %v", err) return CanonicalLocator{CFI: devicePos, Precision: "fallback", Percentage: percentage} } if result.EPUBCFI != "" { return CanonicalLocator{CFI: result.EPUBCFI, Precision: result.Precision, Percentage: result.Percentage} } if result.Href != "" { return CanonicalLocator{CFI: result.Href, Precision: result.Precision, Percentage: result.Percentage} } return CanonicalLocator{CFI: devicePos, Precision: result.Precision, Percentage: percentage} case LocatorSourceKobo: if kepubPath == "" { return CanonicalLocator{CFI: devicePos, Precision: "no-kepub", Percentage: percentage} } converter := NewKEPUBCFIConverter(epubPath, kepubPath) result, err := converter.ConvertKEPUBCFIToStandard(devicePos, percentage, contextText) if err != nil || result == nil { log.Printf("Bookhoard: locator KEPUB→CFI conversion failed: %v", err) return CanonicalLocator{CFI: devicePos, Precision: "fallback", Percentage: percentage} } if result.CFI != "" { return CanonicalLocator{CFI: result.CFI, Precision: result.Precision, Percentage: result.Percentage} } return CanonicalLocator{CFI: devicePos, Precision: result.Precision, Percentage: percentage} default: return CanonicalLocator{CFI: devicePos, Precision: "passthrough", Percentage: percentage} } } func ConvertFromCanonical( source LocatorSource, canonicalCFI string, percentage float64, contextText string, formatGroup string, epubPath string, kepubPath string, ) DeviceLocator { if !isConvertible(formatGroup) || epubPath == "" || canonicalCFI == "" { return DeviceLocator{ Position: canonicalCFI, Precision: "passthrough", Percentage: percentage, } } switch source { case LocatorSourceKOReader: converter := NewCFIConverter(epubPath) result, err := converter.ConvertStandardToCRE(canonicalCFI, percentage, contextText) if err != nil || result == nil { log.Printf("Bookhoard: locator CFI→CRE conversion failed: %v", err) return DeviceLocator{Position: canonicalCFI, Precision: "fallback", Percentage: percentage} } if result.XPointer != "" { return DeviceLocator{Position: result.XPointer, Precision: result.Precision, Percentage: result.Percentage} } return DeviceLocator{Position: canonicalCFI, Precision: result.Precision, Percentage: percentage} case LocatorSourceKobo: if kepubPath == "" { return DeviceLocator{Position: canonicalCFI, Precision: "no-kepub", Percentage: percentage} } converter := NewKEPUBCFIConverter(epubPath, kepubPath) result, err := converter.ConvertStandardCFIToKEPUB(canonicalCFI, percentage, contextText) if err != nil || result == nil { log.Printf("Bookhoard: locator CFI→KEPUB conversion failed: %v", err) return DeviceLocator{Position: canonicalCFI, Precision: "fallback", Percentage: percentage} } if result.CFI != "" { return DeviceLocator{Position: result.CFI, Precision: result.Precision, Percentage: result.Percentage} } return DeviceLocator{Position: canonicalCFI, Precision: result.Precision, Percentage: percentage} default: return DeviceLocator{Position: canonicalCFI, Precision: "passthrough", Percentage: percentage} } }