diff --git a/app/src/main/java/io/aatricks/novelscraper/data/repository/LibraryRepository.kt b/app/src/main/java/io/aatricks/novelscraper/data/repository/LibraryRepository.kt index 80f6638..451e172 100644 --- a/app/src/main/java/io/aatricks/novelscraper/data/repository/LibraryRepository.kt +++ b/app/src/main/java/io/aatricks/novelscraper/data/repository/LibraryRepository.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.flow.* import kotlinx.coroutines.withContext import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withPermit @@ -331,48 +332,49 @@ class LibraryRepository @Inject constructor( } } + val channel = Channel>>(Channel.UNLIMITED) + activeGroups.forEach { channel.trySend(it.key to it.value) } + channel.close() + val allUpdates = coroutineScope { - activeGroups.map { (baseTitle, items) -> + val workers = (1..5).map { async { - semaphore.withPermit { + val localUpdates = mutableListOf() + for ((baseTitle, items) in channel) { if (items.isNotEmpty()) { val latestInLibrary = items.last() if (latestInLibrary.baseNovelUrl.isNotBlank() && latestInLibrary.sourceName.isNotBlank()) { - runCatching("Failed to refresh updates for $baseTitle", emptyList()) { - val details = - exploreRepository.getNovelDetails( - latestInLibrary.baseNovelUrl, - latestInLibrary.sourceName - ) + val newUpdates = runCatching("Failed to refresh updates for $baseTitle", emptyList()) { + val details = exploreRepository.getNovelDetails( + latestInLibrary.baseNovelUrl, + latestInLibrary.sourceName + ) if (details != null && details.chapters.isNotEmpty()) { val sourceChapterCount = details.chapters.size if (sourceChapterCount > latestInLibrary.totalChapters) { - val itemToMark = - items.find { it.isCurrentlyReading } ?: latestInLibrary - val updatedItems = items.map { item -> + val itemToMark = items.find { it.isCurrentlyReading } ?: latestInLibrary + items.map { item -> var newItem = item.copy(totalChapters = sourceChapterCount) if (item.id == itemToMark.id && !item.hasUpdates) { newItem = newItem.copy(hasUpdates = true) } newItem } - updatedItems } else { - emptyList() + emptyList() } } else { - emptyList() + emptyList() } } ?: emptyList() - } else { - emptyList() + localUpdates.addAll(newUpdates) } - } else { - emptyList() } } + localUpdates } - }.awaitAll().flatten() + } + workers.awaitAll().flatten() } if (allUpdates.isNotEmpty()) { diff --git a/app/src/test/java/io/aatricks/novelscraper/data/repository/LibraryRepositoryBenchmarkTest.kt b/app/src/test/java/io/aatricks/novelscraper/data/repository/LibraryRepositoryBenchmarkTest.kt index 2355af1..5e9843f 100644 --- a/app/src/test/java/io/aatricks/novelscraper/data/repository/LibraryRepositoryBenchmarkTest.kt +++ b/app/src/test/java/io/aatricks/novelscraper/data/repository/LibraryRepositoryBenchmarkTest.kt @@ -38,14 +38,11 @@ class LibraryRepositoryBenchmarkTest { @Test fun benchmarkRefreshLibraryUpdates() = runBlocking { - // Setup 100 novels (groups). - // 50 "recent" (lastRead within 2 days) - // 50 "old" (lastRead older than 10 days) + // Setup 10000 novels (groups). val recentCutoff = System.currentTimeMillis() - 2 * 24 * 60 * 60 * 1000L - val oldCutoff = System.currentTimeMillis() - 10 * 24 * 60 * 60 * 1000L - val recentItems = (1..50).map { i -> + val recentItems = (1..10000).map { i -> LibraryItem( id = "recent_$i", title = "Recent Novel $i", @@ -59,27 +56,10 @@ class LibraryRepositoryBenchmarkTest { ) } - val oldItems = (1..50).map { i -> - LibraryItem( - id = "old_$i", - title = "Old Novel $i", - url = "url_old_$i", - baseTitle = "Old Novel $i", - baseNovelUrl = "novel_old_$i", - sourceName = "Source1", - totalChapters = 10, - lastRead = oldCutoff - 10000L, // Definitely old - dateAdded = oldCutoff - 10000L // Added long ago - ) - } - - val allItems = recentItems + oldItems - - whenever(libraryDao.getAllItems()).thenReturn(flowOf(allItems)) + whenever(libraryDao.getAllItems()).thenReturn(flowOf(recentItems)) - // Mock getNovelDetails with a delay to simulate network latency + // Mock getNovelDetails with NO delay to simulate pure overhead whenever(exploreRepository.getNovelDetails(any(), any())).thenAnswer { - Thread.sleep(10) // Simulate 10ms network delay per request ExploreItem("Dummy", "url", source = "Source1", chapters = emptyList()) } @@ -90,7 +70,7 @@ class LibraryRepositoryBenchmarkTest { repository.refreshLibraryUpdates(exploreRepository) } - println("BENCHMARK_RESULT: ${time}ms for 100 novels (50 recent, 50 old)") + println("BENCHMARK_RESULT: ${time}ms for 10000 novels") assertTrue(time > 0) }