feat(sync): use page index as canonical locator for fixed-layout & comic formats
Fixed-layout EPUBs, PDFs, DjVu, and comic archives (cbz/cbr/cb7/cbt)
are page-based: each page is a fixed image, so a page index is an exact,
universal locator regardless of screen size or device. Sync previously
treated these like reflowable content (CFI-first restore, percentage
fallback, character-offset math), which was both wrong and lossy. This
makes the page index the canonical position for fixed-layout and comic
formats while leaving the reflowable path byte-for-byte unchanged.
Backend:
- progress.go SaveProgress: branch on mediaItem.FormatGroup. For
fixed_layout/comic_archive derive percentage from current_page/
total_pages and skip the CFI/character-offset back-fills (meaningless
for image content). The reflowable derivation block is preserved
verbatim under an else.
- kobo.go: Kobo only sends a percentage, so for fixed-layout/comic
formats derive CurrentPage via PercentageToPage(percentage, pageCount)
using the media item's known page count, so Kobo->web lands on the
exact page.
Reader:
- reader.templ readerInitExpr: pass savedPage/savedTotalPages to the
reader config for fixed_layout/comic_archive formats.
- reader.ts: when isFixedLayout and savedPage is present, restore via
view.init({ lastLocation: savedPage - 1 }) (a bare number navigates
foliate directly to the section index). Reflowable falls through to
the existing CFI->percentage path, unchanged.
This commit is contained in:
@@ -408,6 +408,20 @@ func (h *KoboHandler) Markup(c *echo.Context) error {
|
||||
pgMediaUUID := pgtype.UUID{Bytes: bookhoardUUID, Valid: true}
|
||||
percentage := readingSync.PercentRead / 100.0
|
||||
|
||||
// Kobo only sends a percentage. For fixed-layout & comic formats the page
|
||||
// index is the canonical locator, so derive it from the known page count.
|
||||
var currentPage, totalPages *int
|
||||
if mediaItem, mErr := h.db.GetMediaItem(c.Request().Context(), pgMediaUUID); mErr == nil {
|
||||
if mediaItem.FormatGroup == string(wsync.FormatGroupFixedLayout) || mediaItem.FormatGroup == string(wsync.FormatGroupComicArchive) {
|
||||
if mediaItem.PageCount.Valid && mediaItem.PageCount.Int32 > 0 {
|
||||
total := int(mediaItem.PageCount.Int32)
|
||||
page := wsync.PercentageToPage(percentage, total)
|
||||
currentPage = &page
|
||||
totalPages = &total
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if h.progressSvc != nil {
|
||||
_, err = h.progressSvc.SaveProgress(c.Request().Context(), wsync.SaveProgressRequest{
|
||||
MediaItemID: pgMediaUUID,
|
||||
@@ -415,6 +429,8 @@ func (h *KoboHandler) Markup(c *echo.Context) error {
|
||||
Source: "kobo",
|
||||
DeviceID: pgtype.UUID{Bytes: deviceID, Valid: true},
|
||||
Percentage: &percentage,
|
||||
CurrentPage: currentPage,
|
||||
TotalPages: totalPages,
|
||||
DeviceType: "kobo",
|
||||
DeviceName: device.DeviceName,
|
||||
Broadcast: true,
|
||||
|
||||
+30
-15
@@ -400,21 +400,36 @@ func (s *ProgressService) SaveProgress(ctx context.Context, req SaveProgressRequ
|
||||
params.LastSyncDevice = pgtype.Text{String: req.Source, Valid: true}
|
||||
params.LastSyncSource = pgtype.Text{String: req.Source, Valid: true}
|
||||
|
||||
if params.Percentage.Valid && !params.CharacterOffset.Valid && mediaItem.TotalCharacters.Valid && mediaItem.TotalCharacters.Int64 > 0 {
|
||||
charOff := PercentageToCharacter(params.Percentage.Float64, mediaItem.TotalCharacters.Int64)
|
||||
params.CharacterOffset = pgtype.Int8{Int64: charOff, Valid: true}
|
||||
}
|
||||
if params.Percentage.Valid && !params.CurrentPage.Valid && params.TotalPages.Valid && params.TotalPages.Int32 > 0 {
|
||||
page := PercentageToPage(params.Percentage.Float64, int(params.TotalPages.Int32))
|
||||
params.CurrentPage = pgtype.Int4{Int32: int32(page), Valid: true}
|
||||
}
|
||||
if params.CurrentPage.Valid && params.TotalPages.Valid && params.TotalPages.Int32 > 0 && !params.Percentage.Valid {
|
||||
pct := PageToPercentage(int(params.CurrentPage.Int32), int(params.TotalPages.Int32))
|
||||
params.Percentage = pgtype.Float8{Float64: pct, Valid: true}
|
||||
}
|
||||
if params.CharacterOffset.Valid && mediaItem.TotalCharacters.Valid && mediaItem.TotalCharacters.Int64 > 0 && !params.Percentage.Valid {
|
||||
pct := CharacterToPercentage(params.CharacterOffset.Int64, mediaItem.TotalCharacters.Int64)
|
||||
params.Percentage = pgtype.Float8{Float64: pct, Valid: true}
|
||||
formatGroup := FormatGroup(mediaItem.FormatGroup)
|
||||
isFixed := formatGroup == FormatGroupFixedLayout || formatGroup == FormatGroupComicArchive
|
||||
if isFixed {
|
||||
// Fixed-layout & comic formats: the page index is the canonical locator.
|
||||
// Derive percentage from page; CFI/character-offset are meaningless for
|
||||
// image-based content, so they are intentionally left untouched here.
|
||||
if params.CurrentPage.Valid && params.TotalPages.Valid && params.TotalPages.Int32 > 0 {
|
||||
pct := PageToPercentage(int(params.CurrentPage.Int32), int(params.TotalPages.Int32))
|
||||
params.Percentage = pgtype.Float8{Float64: pct, Valid: true}
|
||||
} else if params.Percentage.Valid && params.TotalPages.Valid && params.TotalPages.Int32 > 0 && !params.CurrentPage.Valid {
|
||||
page := PercentageToPage(params.Percentage.Float64, int(params.TotalPages.Int32))
|
||||
params.CurrentPage = pgtype.Int4{Int32: int32(page), Valid: true}
|
||||
}
|
||||
} else {
|
||||
if params.Percentage.Valid && !params.CharacterOffset.Valid && mediaItem.TotalCharacters.Valid && mediaItem.TotalCharacters.Int64 > 0 {
|
||||
charOff := PercentageToCharacter(params.Percentage.Float64, mediaItem.TotalCharacters.Int64)
|
||||
params.CharacterOffset = pgtype.Int8{Int64: charOff, Valid: true}
|
||||
}
|
||||
if params.Percentage.Valid && !params.CurrentPage.Valid && params.TotalPages.Valid && params.TotalPages.Int32 > 0 {
|
||||
page := PercentageToPage(params.Percentage.Float64, int(params.TotalPages.Int32))
|
||||
params.CurrentPage = pgtype.Int4{Int32: int32(page), Valid: true}
|
||||
}
|
||||
if params.CurrentPage.Valid && params.TotalPages.Valid && params.TotalPages.Int32 > 0 && !params.Percentage.Valid {
|
||||
pct := PageToPercentage(int(params.CurrentPage.Int32), int(params.TotalPages.Int32))
|
||||
params.Percentage = pgtype.Float8{Float64: pct, Valid: true}
|
||||
}
|
||||
if params.CharacterOffset.Valid && mediaItem.TotalCharacters.Valid && mediaItem.TotalCharacters.Int64 > 0 && !params.Percentage.Valid {
|
||||
pct := CharacterToPercentage(params.CharacterOffset.Int64, mediaItem.TotalCharacters.Int64)
|
||||
params.Percentage = pgtype.Float8{Float64: pct, Valid: true}
|
||||
}
|
||||
}
|
||||
|
||||
conflictDetected := false
|
||||
|
||||
Reference in New Issue
Block a user