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:
+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