Просмотр исходного кода

fix(toc): reuse replaced chapter titles

HapeLee 3 месяцев назад
Родитель
Сommit
cf8330ddcf

+ 46 - 3
app/src/main/java/io/legado/app/ui/book/toc/TocViewModel.kt

@@ -112,14 +112,39 @@ private data class TocPreferences(
     val showWordCount: Boolean
 )
 
-private data class TitleCacheKey(
+internal data class TitleCacheKey(
     val bookUrl: String,
     val useReplace: Boolean,
     val rulesFingerprint: Int,
     val chineseConverterType: Int,
-    val chapterCount: Int
+    val chapterCount: Int,
+    val chaptersFingerprint: Long
 )
 
+internal object TocTitleCache {
+    private const val MAX_ENTRIES = 8
+    private val entries = object : LinkedHashMap<TitleCacheKey, Map<Int, String>>(
+        MAX_ENTRIES,
+        0.75f,
+        true
+    ) {
+        override fun removeEldestEntry(
+            eldest: MutableMap.MutableEntry<TitleCacheKey, Map<Int, String>>?
+        ): Boolean = size > MAX_ENTRIES
+    }
+
+    @Synchronized
+    fun get(key: TitleCacheKey): Map<Int, String>? = entries[key]
+
+    @Synchronized
+    fun put(key: TitleCacheKey, titles: Map<Int, String>) {
+        entries[key] = titles.toMap()
+    }
+
+    @Synchronized
+    fun clear() = entries.clear()
+}
+
 private data class TitleReplaceState(
     val cacheKey: TitleCacheKey? = null,
     val titles: Map<Int, String> = emptyMap(),
@@ -658,7 +683,10 @@ class TocViewModel(
             useReplace = true,
             rulesFingerprint = rulesFingerprint,
             chineseConverterType = AppConfig.chineseConverterType,
-            chapterCount = chapters.size
+            chapterCount = chapters.size,
+            chaptersFingerprint = chapters.fold(0L) { fingerprint, chapter ->
+                fingerprint + 31L * chapter.index + chapter.title.hashCode()
+            }
         )
 
         val currentTitleState = titleReplaceState.value
@@ -672,6 +700,20 @@ class TocViewModel(
             return
         }
 
+        TocTitleCache.get(key)?.let { cachedTitles ->
+            lastTitleCacheKey = key
+            titleCacheJob?.cancel()
+            titleCacheJob = null
+            titleReplaceState.value = TitleReplaceState(
+                cacheKey = key,
+                titles = cachedTitles,
+                completed = chapters.size,
+                total = chapters.size,
+                isRunning = false
+            )
+            return
+        }
+
         lastTitleCacheKey = key
         titleCacheJob?.cancel()
         titleReplaceState.value = TitleReplaceState(
@@ -738,6 +780,7 @@ class TocViewModel(
                     )
                 }
             }
+            TocTitleCache.put(key, newCache)
         }
     }
 

+ 46 - 0
app/src/test/java/io/legado/app/ui/book/toc/TocTitleCacheTest.kt

@@ -0,0 +1,46 @@
+package io.legado.app.ui.book.toc
+
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotSame
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+
+class TocTitleCacheTest {
+
+    @Before
+    @After
+    fun clearCache() {
+        TocTitleCache.clear()
+    }
+
+    @Test
+    fun `completed titles are reused by a new directory instance`() {
+        val key = cacheKey()
+        val titles = mutableMapOf(0 to "处理后的标题")
+
+        TocTitleCache.put(key, titles)
+        titles[0] = "外部修改"
+
+        val cached = TocTitleCache.get(key)
+        assertEquals("处理后的标题", cached?.get(0))
+        assertNotSame(titles, cached)
+    }
+
+    @Test
+    fun `changed chapter fingerprint does not reuse stale titles`() {
+        TocTitleCache.put(cacheKey(chaptersFingerprint = 1L), mapOf(0 to "旧标题"))
+
+        assertNull(TocTitleCache.get(cacheKey(chaptersFingerprint = 2L)))
+    }
+
+    private fun cacheKey(chaptersFingerprint: Long = 1L) = TitleCacheKey(
+        bookUrl = "book-url",
+        useReplace = true,
+        rulesFingerprint = 1,
+        chineseConverterType = 0,
+        chapterCount = 1,
+        chaptersFingerprint = chaptersFingerprint
+    )
+}