[修复] bookUrl 变更导致本地书裂变/误合并问题

This commit is contained in:
Kudomaga authored and GitHub committed 2026-02-21 01:22:20 +08:00
commit 3ca7c9f7e3
2 files changed
+90 -14

No files matched your search

@@ -121,6 +121,29 @@ fun Book.contains(word: String?): Boolean {
|| intro?.contains(word) == true || intro?.contains(word) == true
} }
/**
* 仅在目标bookUrl未被其他书占用,或判定为同一本书时,允许迁移主键。
*/
fun Book.canSafelyRebindTo(newBookUrl: String): Boolean {
if (newBookUrl == bookUrl) return true
val targetBook = appDb.bookDao.getBook(newBookUrl) ?: return true
val sameOriginName = originName.isNotBlank() && originName == targetBook.originName
val sameNameAuthor = name.isNotBlank() && author.isNotBlank()
&& name == targetBook.name && author == targetBook.author
val sameOrigin = origin.isNotBlank() && origin == targetBook.origin
val canMerge = sameOriginName && (sameNameAuthor || sameOrigin)
if (!canMerge) {
AppLog.put(
"书籍重定位冲突,已跳过迁移\n" +
"old=$bookUrl\nnew=$newBookUrl\n" +
"oldName=$name oldAuthor=$author oldOriginName=$originName\n" +
"targetName=${targetBook.name} targetAuthor=${targetBook.author} targetOriginName=${targetBook.originName}"
)
}
return canMerge
}
private val localUriCache by lazy { private val localUriCache by lazy {
ConcurrentHashMap<String, Uri>() ConcurrentHashMap<String, Uri>()
} }
@@ -150,17 +173,31 @@ fun Book.getLocalUri(): Uri {
// 查找书籍保存目录 // 查找书籍保存目录
if (!defaultBookDir.isNullOrBlank()) { if (!defaultBookDir.isNullOrBlank()) {
val treeUri = Uri.parse(defaultBookDir) val treeUri = defaultBookDir.toUri()
val treeFileDoc = FileDoc.fromUri(treeUri, true) val treeFileDoc = FileDoc.fromUri(treeUri, true)
if (!treeFileDoc.exists()) { if (!treeFileDoc.exists()) {
appCtx.toastOnUi("书籍保存目录失效,请重新设置!") appCtx.toastOnUi("书籍保存目录失效,请重新设置!")
} else { } else {
val fileDoc = treeFileDoc.find(originName, 5, 100) val fileDoc = treeFileDoc.find(originName, 5, 100)
if (fileDoc != null) { if (fileDoc != null) {
localUriCache[bookUrl] = fileDoc.uri val newBookUrl = fileDoc.toString()
//更新bookUrl 重启不用再找一遍 val oldBook = copy()
bookUrl = fileDoc.toString() if (!oldBook.canSafelyRebindTo(newBookUrl)) {
save() return fileDoc.uri
}
appDb.runInTransaction {
if (oldBook.bookUrl == newBookUrl) {
save()
} else {
val newBook = oldBook.copy(bookUrl = newBookUrl)
appDb.bookDao.replace(oldBook, newBook)
BookHelp.updateCacheFolder(oldBook, newBook)
this.bookUrl = newBookUrl
}
}
localUriCache[newBookUrl] = fileDoc.uri
return fileDoc.uri return fileDoc.uri
} }
} }
@@ -176,9 +213,23 @@ fun Book.getLocalUri(): Uri {
val treeFileDoc = FileDoc.fromUri(treeUri, true) val treeFileDoc = FileDoc.fromUri(treeUri, true)
val fileDoc = treeFileDoc.find(originName, 5, 100) val fileDoc = treeFileDoc.find(originName, 5, 100)
if (fileDoc != null) { if (fileDoc != null) {
localUriCache[bookUrl] = fileDoc.uri val newBookUrl = fileDoc.toString()
bookUrl = fileDoc.toString() val oldBook = copy()
save() if (!oldBook.canSafelyRebindTo(newBookUrl)) {
return fileDoc.uri
}
appDb.runInTransaction {
if (oldBook.bookUrl == newBookUrl) {
save()
} else {
val newBook = oldBook.copy(bookUrl = newBookUrl)
appDb.bookDao.replace(oldBook, newBook)
BookHelp.updateCacheFolder(oldBook, newBook)
this.bookUrl = newBookUrl
}
}
localUriCache[newBookUrl] = fileDoc.uri
return fileDoc.uri return fileDoc.uri
} }
} }
@@ -438,4 +489,4 @@ fun tryParesExportFileName(jsStr: String): Boolean {
RhinoScriptEngine.eval(jsStr, bindings) RhinoScriptEngine.eval(jsStr, bindings)
true true
}.getOrDefault(false) }.getOrDefault(false)
} }
@@ -23,6 +23,8 @@ import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.ContentProcessor import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.book.addType import io.legado.app.help.book.addType
import io.legado.app.help.book.archiveName import io.legado.app.help.book.archiveName
import io.legado.app.help.book.cacheLocalUri
import io.legado.app.help.book.canSafelyRebindTo
import io.legado.app.help.book.getArchiveUri import io.legado.app.help.book.getArchiveUri
import io.legado.app.help.book.getLocalUri import io.legado.app.help.book.getLocalUri
import io.legado.app.help.book.getRemoteUrl import io.legado.app.help.book.getRemoteUrl
@@ -37,6 +39,7 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.WebDav
import io.legado.app.lib.webdav.WebDavException import io.legado.app.lib.webdav.WebDavException
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.analyzeRule.CustomUrl
import io.legado.app.utils.ArchiveUtils import io.legado.app.utils.ArchiveUtils
import io.legado.app.utils.FileDoc import io.legado.app.utils.FileDoc
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
@@ -50,6 +53,7 @@ import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.isContentScheme import io.legado.app.utils.isContentScheme
import io.legado.app.utils.isDataUrl import io.legado.app.utils.isDataUrl
import io.legado.app.utils.printOnDebug import io.legado.app.utils.printOnDebug
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.apache.commons.text.StringEscapeUtils import org.apache.commons.text.StringEscapeUtils
import splitties.init.appCtx import splitties.init.appCtx
@@ -60,7 +64,6 @@ import java.io.FileNotFoundException
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.InputStream import java.io.InputStream
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.coroutines.coroutineContext
/** /**
* 书籍文件导入 目录正文解析 * 书籍文件导入 目录正文解析
@@ -403,7 +406,7 @@ object LocalBook {
val inputStream = when { val inputStream = when {
str.isAbsUrl() -> AnalyzeUrl( str.isAbsUrl() -> AnalyzeUrl(
str, source = source, callTimeout = 0, str, source = source, callTimeout = 0,
coroutineContext = coroutineContext coroutineContext = currentCoroutineContext()
).getInputStreamAwait() ).getInputStreamAwait()
str.isDataUrl() -> ByteArrayInputStream( str.isDataUrl() -> ByteArrayInputStream(
@@ -458,7 +461,7 @@ object LocalBook {
fun isOnBookShelf( fun isOnBookShelf(
fileName: String fileName: String
): Boolean { ): Boolean {
return appDb.bookDao.hasFile(fileName) == true return appDb.bookDao.hasFile(fileName)
} }
//文件类书源 合并在线书籍信息 在线 > 本地 //文件类书源 合并在线书籍信息 在线 > 本地
@@ -501,9 +504,31 @@ object LocalBook {
localBook.bookUrl = newBook.bookUrl localBook.bookUrl = newBook.bookUrl
} else { } else {
// txt epub pdf umd // txt epub pdf umd
val oldBook = localBook.copy()
val fileUri = saveBookFile(it, localBook.originName) val fileUri = saveBookFile(it, localBook.originName)
localBook.bookUrl = FileDoc.fromUri(fileUri, false).toString() val newBookUrl = FileDoc.fromUri(fileUri, false).toString()
localBook.save() if (!oldBook.canSafelyRebindTo(newBookUrl)) {
localBook.cacheLocalUri(fileUri)
return true
}
appDb.runInTransaction {
if (oldBook.bookUrl == newBookUrl) {
localBook.origin =
BookType.webDavTag + CustomUrl(webDavUrl).toString()
localBook.save()
} else {
val newBook = oldBook.copy(
bookUrl = newBookUrl,
origin = BookType.webDavTag + CustomUrl(webDavUrl).toString()
)
appDb.bookDao.replace(oldBook, newBook)
BookHelp.updateCacheFolder(oldBook, newBook)
localBook.bookUrl = newBookUrl
localBook.origin = newBook.origin
}
}
} }
} }
return true return true