From 3d7af47e5f3a7e2f0b4e8fc9b414cfbfbf06f90f Mon Sep 17 00:00:00 2001 From: Max Grakov Date: Sat, 1 Feb 2025 18:51:18 +0100 Subject: [PATCH] Feature/99 fix progress sync (#115) --- app/build.gradle.kts | 4 +-- .../common/AudiobookshelfChannel.kt | 6 ++-- .../RecentListeningResponseConverter.kt | 5 +-- .../lissen/content/LissenMediaProvider.kt | 35 ++++++++++++++++++- .../content/cache/api/CachedBookRepository.kt | 2 +- .../CachedBookEntityRecentConverter.kt | 4 ++- .../org/grakovne/lissen/domain/RecentBook.kt | 1 + 7 files changed, 48 insertions(+), 9 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a504abb6..3221122d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,8 +29,8 @@ android { applicationId = "org.grakovne.lissen" minSdk = 28 targetSdk = 35 - versionCode = 67 - versionName = "1.2.6" + versionCode = 68 + versionName = "1.2.7" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/common/AudiobookshelfChannel.kt b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/common/AudiobookshelfChannel.kt index cb8f7d24..77c96b16 100644 --- a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/common/AudiobookshelfChannel.kt +++ b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/common/AudiobookshelfChannel.kt @@ -56,14 +56,16 @@ abstract class AudiobookshelfChannel( .map { libraryResponseConverter.apply(it) } override suspend fun fetchRecentListenedBooks(libraryId: String): ApiResult> { - val progress: Map = dataRepository + val progress: Map> = dataRepository .fetchUserInfoResponse() .fold( onSuccess = { it .user .mediaProgress - ?.associate { item -> item.libraryItemId to item.progress } + ?.groupBy { item -> item.libraryItemId } + ?.map { (item, value) -> item to value.maxBy { progress -> progress.lastUpdate } } + ?.associate { (item, progress) -> item to (progress.lastUpdate to progress.progress) } ?: emptyMap() }, onFailure = { emptyMap() }, diff --git a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/common/converter/RecentListeningResponseConverter.kt b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/common/converter/RecentListeningResponseConverter.kt index 9c10db61..9b067afc 100644 --- a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/common/converter/RecentListeningResponseConverter.kt +++ b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/common/converter/RecentListeningResponseConverter.kt @@ -10,7 +10,7 @@ class RecentListeningResponseConverter @Inject constructor() { fun apply( response: List, - progress: Map, + progress: Map>, ): List = response .find { it.labelStringKey == LABEL_CONTINUE_LISTENING } ?.entities @@ -20,7 +20,8 @@ class RecentListeningResponseConverter @Inject constructor() { id = it.id, title = it.media.metadata.title, author = it.media.metadata.authorName, - listenedPercentage = progress[it.id]?.let { it * 100 }?.toInt(), + listenedPercentage = progress[it.id]?.second?.let { it * 100 }?.toInt(), + listenedLastUpdate = progress[it.id]?.first , ) } ?: emptyList() diff --git a/app/src/main/java/org/grakovne/lissen/content/LissenMediaProvider.kt b/app/src/main/java/org/grakovne/lissen/content/LissenMediaProvider.kt index ac13f087..b639b24b 100644 --- a/app/src/main/java/org/grakovne/lissen/content/LissenMediaProvider.kt +++ b/app/src/main/java/org/grakovne/lissen/content/LissenMediaProvider.kt @@ -151,7 +151,9 @@ class LissenMediaProvider @Inject constructor( return when (sharedPreferences.isForceCache()) { true -> localCacheRepository.fetchRecentListenedBooks() - false -> providePreferredChannel().fetchRecentListenedBooks(libraryId) + false -> providePreferredChannel() + .fetchRecentListenedBooks(libraryId) + .map { items -> syncFromLocalProgress(items) } } } @@ -182,6 +184,37 @@ class LissenMediaProvider @Inject constructor( return provideAuthService().authorize(host, username, password) } + private suspend fun syncFromLocalProgress( + detailedItems: List, + ): List { + val localRecentlyBooks = localCacheRepository + .fetchRecentListenedBooks() + .fold( + onSuccess = { it }, + onFailure = { return@fold detailedItems }, + ) + + val syncedRecentlyBooks = detailedItems + .mapNotNull { item -> localRecentlyBooks.find { it.id == item.id }?.let { item to it } } + .map { (remote, local) -> + val localTimestamp = local.listenedLastUpdate ?: return@map remote + val remoteTimestamp = remote.listenedLastUpdate ?: return@map remote + + when (remoteTimestamp > localTimestamp) { + true -> remote + false -> local + } + } + + return detailedItems + .map { item -> + syncedRecentlyBooks + .find { item.id == it.id } + ?.let { local -> item.copy(listenedPercentage = local.listenedPercentage) } + ?: item + } + } + private suspend fun syncFromLocalProgress(detailedItem: DetailedItem): DetailedItem { val cachedBook = localCacheRepository.fetchBook(detailedItem.id) ?: return detailedItem diff --git a/app/src/main/java/org/grakovne/lissen/content/cache/api/CachedBookRepository.kt b/app/src/main/java/org/grakovne/lissen/content/cache/api/CachedBookRepository.kt index 0b308386..9c5f0839 100644 --- a/app/src/main/java/org/grakovne/lissen/content/cache/api/CachedBookRepository.kt +++ b/app/src/main/java/org/grakovne/lissen/content/cache/api/CachedBookRepository.kt @@ -78,7 +78,7 @@ class CachedBookRepository @Inject constructor( val progress = recentBooks .map { it.id } .mapNotNull { bookDao.fetchMediaProgress(it) } - .associate { it.bookId to it.currentTime } + .associate { it.bookId to (it.lastUpdate to it.currentTime) } return recentBooks .map { cachedBookEntityRecentConverter.apply(it, progress[it.id]) } diff --git a/app/src/main/java/org/grakovne/lissen/content/cache/converter/CachedBookEntityRecentConverter.kt b/app/src/main/java/org/grakovne/lissen/content/cache/converter/CachedBookEntityRecentConverter.kt index 7a807507..a5981143 100644 --- a/app/src/main/java/org/grakovne/lissen/content/cache/converter/CachedBookEntityRecentConverter.kt +++ b/app/src/main/java/org/grakovne/lissen/content/cache/converter/CachedBookEntityRecentConverter.kt @@ -8,11 +8,13 @@ import javax.inject.Singleton @Singleton class CachedBookEntityRecentConverter @Inject constructor() { - fun apply(entity: BookEntity, currentTime: Double?): RecentBook = RecentBook( + fun apply(entity: BookEntity, currentTime: Pair?): RecentBook = RecentBook( id = entity.id, title = entity.title, author = entity.author, + listenedLastUpdate = currentTime?.first ?: 0, listenedPercentage = currentTime + ?.second ?.let { it / entity.duration } ?.let { it * 100 } ?.toInt(), diff --git a/app/src/main/java/org/grakovne/lissen/domain/RecentBook.kt b/app/src/main/java/org/grakovne/lissen/domain/RecentBook.kt index 85979147..51290e89 100644 --- a/app/src/main/java/org/grakovne/lissen/domain/RecentBook.kt +++ b/app/src/main/java/org/grakovne/lissen/domain/RecentBook.kt @@ -5,4 +5,5 @@ data class RecentBook( val title: String, val author: String?, val listenedPercentage: Int?, + val listenedLastUpdate: Long?, )