mirror of
https://github.com/booklore-app/booklore.git
synced 2026-02-18 00:17:53 +01:00
fix(hardcover-sync): Don't send repeated read status to Hardcover.app (#2609)
* fix(hardcover-sync): Don't send repeated read status to Hardcover.app * fix(hardcover-sync): Use progress percentage instead of read status * fix(hardcover-sync): Add test cases * fix(hardcover-sync): Also check read status for changes
This commit is contained in:
@@ -186,6 +186,9 @@ public class KoboReadingStateService {
|
||||
return newProgress;
|
||||
});
|
||||
|
||||
Float prevousKoboProgressPercent = progress.getKoboProgressPercent();
|
||||
ReadStatus previousReadStatus = progress.getReadStatus();
|
||||
|
||||
KoboReadingState.CurrentBookmark bookmark = readingState.getCurrentBookmark();
|
||||
if (bookmark != null) {
|
||||
if (bookmark.getProgressPercent() != null) {
|
||||
@@ -219,7 +222,12 @@ public class KoboReadingStateService {
|
||||
log.debug("Synced Kobo progress: bookId={}, progress={}%", bookId, progress.getKoboProgressPercent());
|
||||
|
||||
// Sync progress to Hardcover asynchronously (if enabled for this user)
|
||||
hardcoverSyncService.syncProgressToHardcover(book.getId(), progress.getKoboProgressPercent(), userId);
|
||||
// But only if the progress percentage has changed from last time, or the read status has changed
|
||||
if (progress.getKoboProgressPercent() != null
|
||||
&& (!progress.getKoboProgressPercent().equals(prevousKoboProgressPercent)
|
||||
|| progress.getReadStatus() != previousReadStatus)) {
|
||||
hardcoverSyncService.syncProgressToHardcover(book.getId(), progress.getKoboProgressPercent(), userId);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Invalid entitlement ID format: {}", readingState.getEntitlementId());
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ public class KoreaderService {
|
||||
BookLoreUserEntity user = findBookLoreUser(authDetails.getBookLoreUserId());
|
||||
|
||||
UserBookProgressEntity userProgress = getOrCreateUserProgress(user, book);
|
||||
Float previousProgressPercent = userProgress.getKoreaderProgressPercent();
|
||||
ReadStatus previousReadStatus = userProgress.getReadStatus();
|
||||
updateProgressData(userProgress, koProgress, authDetails.isSyncWithBookloreReader(), book);
|
||||
|
||||
progressRepository.save(userProgress);
|
||||
@@ -83,8 +85,13 @@ public class KoreaderService {
|
||||
|
||||
log.info("saveProgress: saved progress='{}' percentage={} for userId={} bookHash={}", koProgress.getProgress(), koProgress.getPercentage(), authDetails.getBookLoreUserId(), bookHash);
|
||||
|
||||
Float progressPercent = normalizeProgressPercent(koProgress.getPercentage());
|
||||
hardcoverSyncService.syncProgressToHardcover(book.getId(), progressPercent, authDetails.getBookLoreUserId());
|
||||
// Sync progress to Hardcover asynchronously (if enabled for this user)
|
||||
// But only if the progress percentage has changed from last time, or the read status has changed
|
||||
if (koProgress.getPercentage() != null && (!koProgress.getPercentage().equals(previousProgressPercent)
|
||||
|| userProgress.getReadStatus() != previousReadStatus)) {
|
||||
Float progressPercent = normalizeProgressPercent(koProgress.getPercentage());
|
||||
hardcoverSyncService.syncProgressToHardcover(book.getId(), progressPercent, authDetails.getBookLoreUserId());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveToFileProgress(BookLoreUserEntity user, BookEntity book, UserBookProgressEntity progress) {
|
||||
|
||||
@@ -150,6 +150,51 @@ class KoboReadingStateServiceTest {
|
||||
"Existing finished date should not be overwritten during sync");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should not update Hardcover.app when progress hasn't changed")
|
||||
void testSyncKoboProgressToUserBookProgress_IgnoreHardcoverUpdateWhenNoChange() {
|
||||
String entitlementId = "100";
|
||||
testSettings.setProgressMarkAsFinishedThreshold(99f);
|
||||
|
||||
Instant originalFinishedDate = Instant.parse("2025-01-15T10:30:00Z");
|
||||
UserBookProgressEntity existingProgress = new UserBookProgressEntity();
|
||||
existingProgress.setUser(testUserEntity);
|
||||
existingProgress.setBook(testBook);
|
||||
existingProgress.setKoboProgressPercent(12.0f);
|
||||
existingProgress.setReadStatus(ReadStatus.READING);
|
||||
existingProgress.setDateFinished(originalFinishedDate);
|
||||
|
||||
KoboReadingState.CurrentBookmark bookmark = KoboReadingState.CurrentBookmark.builder()
|
||||
.progressPercent(12)
|
||||
.build();
|
||||
|
||||
KoboReadingState readingState = KoboReadingState.builder()
|
||||
.entitlementId(entitlementId)
|
||||
.currentBookmark(bookmark)
|
||||
.build();
|
||||
|
||||
KoboReadingStateEntity entity = new KoboReadingStateEntity();
|
||||
when(mapper.toEntity(any())).thenReturn(entity);
|
||||
when(mapper.toDto(any(KoboReadingStateEntity.class))).thenReturn(readingState);
|
||||
when(repository.findByEntitlementIdAndUserId(entitlementId, 1L)).thenReturn(Optional.empty());
|
||||
when(repository.save(any())).thenReturn(entity);
|
||||
when(bookRepository.findById(100L)).thenReturn(Optional.of(testBook));
|
||||
when(userRepository.findById(1L)).thenReturn(Optional.of(testUserEntity));
|
||||
when(progressRepository.findByUserIdAndBookId(1L, 100L)).thenReturn(Optional.of(existingProgress));
|
||||
|
||||
ArgumentCaptor<UserBookProgressEntity> progressCaptor = ArgumentCaptor.forClass(UserBookProgressEntity.class);
|
||||
when(progressRepository.save(progressCaptor.capture())).thenReturn(existingProgress);
|
||||
|
||||
service.saveReadingState(List.of(readingState));
|
||||
|
||||
UserBookProgressEntity savedProgress = progressCaptor.getValue();
|
||||
assertEquals(12.0f, savedProgress.getKoboProgressPercent());
|
||||
assertEquals(ReadStatus.READING, savedProgress.getReadStatus());
|
||||
assertEquals(originalFinishedDate, savedProgress.getDateFinished(),
|
||||
"Existing finished date should not be overwritten during sync");
|
||||
verify(hardcoverSyncService, never()).syncProgressToHardcover(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle invalid entitlement ID gracefully")
|
||||
void testSyncKoboProgressToUserBookProgress_InvalidEntitlementId() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.booklore.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.booklore.config.security.userdetails.KoreaderUserDetails;
|
||||
@@ -9,6 +10,7 @@ import org.booklore.model.dto.progress.KoreaderProgress;
|
||||
import org.booklore.model.entity.BookEntity;
|
||||
import org.booklore.model.entity.BookLoreUserEntity;
|
||||
import org.booklore.model.entity.UserBookProgressEntity;
|
||||
import org.booklore.model.enums.ReadStatus;
|
||||
import org.booklore.repository.BookRepository;
|
||||
import org.booklore.repository.UserBookProgressRepository;
|
||||
import org.booklore.repository.UserRepository;
|
||||
@@ -230,6 +232,31 @@ class KoreaderServiceTest {
|
||||
assertEquals(0.4F, existing.getKoreaderProgressPercent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveProgress_updatesExistingNoProgressChange_noHardcoverUpdate() {
|
||||
when(details.isSyncEnabled()).thenReturn(true);
|
||||
var book = new BookEntity();
|
||||
book.setId(8L);
|
||||
when(bookRepo.findByCurrentHash("h")).thenReturn(Optional.of(book));
|
||||
var user = new BookLoreUserEntity();
|
||||
user.setId(42L);
|
||||
when(userRepo.findById(42L)).thenReturn(Optional.of(user));
|
||||
var existing = new UserBookProgressEntity();
|
||||
existing.setKoreaderProgressPercent(0.4F);
|
||||
existing.setReadStatus(ReadStatus.READING);
|
||||
when(progressRepo.findByUserIdAndBookId(42L, 8L))
|
||||
.thenReturn(Optional.of(existing));
|
||||
|
||||
var dto = KoreaderProgress.builder()
|
||||
.document("h").progress("y").percentage(0.4F).device("d").device_id("id").build();
|
||||
service.saveProgress("h", dto);
|
||||
|
||||
verify(progressRepo).save(existing);
|
||||
assertEquals("y", existing.getKoreaderProgress());
|
||||
assertEquals(0.4F, existing.getKoreaderProgressPercent());
|
||||
verify(hardcoverSyncService, never()).syncProgressToHardcover(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveProgress_syncDisabled() {
|
||||
when(details.isSyncEnabled()).thenReturn(false);
|
||||
|
||||
Reference in New Issue
Block a user