feat(sync): add shared BookResolver with format-aware SHA-256 matching
The platform had three duplicated, divergent book resolvers (koreader, kobo, BookMatchingService) and none of them consulted media_item_formats.file_sha256 - per-format hashes for converted files (KEPUB, PDF) are computed and stored at import/conversion time but were never used for lookup. GetMediaItemFormatBySHA256 existed with zero callers. Any client holding a converted file could never match by hash. Add internal/services/book_resolver.go: a single shared resolution path from client-supplied identifier to media_item. ResolveBySHA256 checks media_items.file_sha256 first (indexed GetMediaItemBySHA256), then falls back to media_item_formats. file_sha256 (indexed GetMediaItemFormatBySHA256, first caller) so a converted format matches with equal confidence. The import-time SHA-256 is the canonical identifier shared by every interface. Wire two of the existing resolvers through it: - BookMatchingService.matchBySHA256: replaces the in-memory ListMediaItems scan of up to 1000 rows with the resolver's indexed lookups, and gains format awareness for the link/auto-link UI. MatchMethod now reports sha256_sha256 or sha256_sha256_format - KoboHandler.mapContentIdToBookhoardUUID: the SHA-256 heuristic branch (ContentId that looks like a 64-char hash) now resolves format-aware too. Kobo's entitlement_id wire identity is untouched; only the opportunistic hash branch changed
This commit is contained in:
@@ -46,13 +46,15 @@ type LinkBookRequest struct {
|
||||
|
||||
// BookMatchingService handles universal book matching
|
||||
type BookMatchingService struct {
|
||||
db *database.Queries
|
||||
db *database.Queries
|
||||
resolver *BookResolver
|
||||
}
|
||||
|
||||
// NewBookMatchingService creates a new book matching service
|
||||
func NewBookMatchingService(db *database.Queries) *BookMatchingService {
|
||||
return &BookMatchingService{
|
||||
db: db,
|
||||
db: db,
|
||||
resolver: NewBookResolver(db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,27 +169,21 @@ func (s *BookMatchingService) matchByOPFUUID(ctx context.Context, identifiers []
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchBySHA256 attempts to match by file SHA-256 hash
|
||||
// matchBySHA256 attempts to match by file SHA-256 hash.
|
||||
// Uses the shared BookResolver so it is both indexed (no full-table scan) and
|
||||
// format-aware: a converted/alternate format hash (media_item_formats) matches
|
||||
// in addition to the primary media_items.file_sha256.
|
||||
func (s *BookMatchingService) matchBySHA256(ctx context.Context, sha256 string) *BookMatch {
|
||||
items, err := s.db.ListMediaItems(ctx, database.ListMediaItemsParams{
|
||||
Limit: 1000,
|
||||
Offset: 0,
|
||||
})
|
||||
if err != nil {
|
||||
item, method, err := s.resolver.ResolveBySHA256(ctx, sha256)
|
||||
if err != nil || !item.ID.Valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
if item.FileSha256.Valid && item.FileSha256.String == sha256 {
|
||||
return &BookMatch{
|
||||
MediaItemID: item.ID.Bytes,
|
||||
BookhoardUUID: item.ID.Bytes,
|
||||
Confidence: 0.9,
|
||||
MatchMethod: "sha256_match",
|
||||
}
|
||||
}
|
||||
return &BookMatch{
|
||||
MediaItemID: item.ID.Bytes,
|
||||
BookhoardUUID: item.ID.Bytes,
|
||||
Confidence: 0.9,
|
||||
MatchMethod: "sha256_" + string(method),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchByOPFIdentifier attempts to match by OPF identifier
|
||||
|
||||
Reference in New Issue
Block a user