From cb236b623cc58f94245e6318fca1e58c8b62522c Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 19 Feb 2026 20:59:20 +0800 Subject: [PATCH 1/2] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20bookUri=20=E5=8F=91?= =?UTF-8?q?=E7=94=9F=E6=9B=B4=E6=94=B9=E5=90=8E,=20=E5=B0=8F=E8=AF=B4?= =?UTF-8?q?=E8=A3=82=E5=8F=98=E6=88=90=E4=B8=A4=E4=B8=AA=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/help/book/BookExtensions.kt | 56 +++++++++++++++++-- .../legado/app/model/localBook/LocalBook.kt | 19 ++++++- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt index 4523d1340..2e8ecea70 100644 --- a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt +++ b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt @@ -121,6 +121,29 @@ fun Book.contains(word: String?): Boolean { || intro?.contains(word) == true } +/** + * 仅在目标bookUrl未被其他书占用,或判定为同一本书时,允许迁移主键。 + */ +fun Book.canSafelyRebindTo(newBookUrl: String): Boolean { + if (newBookUrl == bookUrl) return true + val targetBook = appDb.bookDao.getBook(newBookUrl) ?: return true + if (targetBook.bookUrl == bookUrl) 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 { ConcurrentHashMap() } @@ -158,9 +181,19 @@ fun Book.getLocalUri(): Uri { val fileDoc = treeFileDoc.find(originName, 5, 100) if (fileDoc != null) { localUriCache[bookUrl] = fileDoc.uri - //更新bookUrl 重启不用再找一遍 - bookUrl = fileDoc.toString() - save() + //更新bookUrl时要迁移主键,避免save()按新bookUrl插入重复书籍 + val oldBook = copy() + val newBookUrl = fileDoc.toString() + if (!oldBook.canSafelyRebindTo(newBookUrl)) { + return fileDoc.uri + } + bookUrl = newBookUrl + if (oldBook.bookUrl == bookUrl) { + save() + } else { + appDb.bookDao.replace(oldBook, this) + BookHelp.updateCacheFolder(oldBook, this) + } return fileDoc.uri } } @@ -177,8 +210,19 @@ fun Book.getLocalUri(): Uri { val fileDoc = treeFileDoc.find(originName, 5, 100) if (fileDoc != null) { localUriCache[bookUrl] = fileDoc.uri - bookUrl = fileDoc.toString() - save() + //更新bookUrl时要迁移主键,避免save()按新bookUrl插入重复书籍 + val oldBook = copy() + val newBookUrl = fileDoc.toString() + if (!oldBook.canSafelyRebindTo(newBookUrl)) { + return fileDoc.uri + } + bookUrl = newBookUrl + if (oldBook.bookUrl == bookUrl) { + save() + } else { + appDb.bookDao.replace(oldBook, this) + BookHelp.updateCacheFolder(oldBook, this) + } return fileDoc.uri } } @@ -438,4 +482,4 @@ fun tryParesExportFileName(jsStr: String): Boolean { RhinoScriptEngine.eval(jsStr, bindings) true }.getOrDefault(false) -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index e95ef0de4..f14ae46ca 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -23,6 +23,8 @@ import io.legado.app.help.book.BookHelp import io.legado.app.help.book.ContentProcessor import io.legado.app.help.book.addType 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.getLocalUri 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.WebDavException 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.FileDoc import io.legado.app.utils.FileUtils @@ -501,9 +504,21 @@ object LocalBook { localBook.bookUrl = newBook.bookUrl } else { // txt epub pdf umd + val oldBook = localBook.copy() val fileUri = saveBookFile(it, localBook.originName) - localBook.bookUrl = FileDoc.fromUri(fileUri, false).toString() - localBook.save() + val newBookUrl = FileDoc.fromUri(fileUri, false).toString() + if (!oldBook.canSafelyRebindTo(newBookUrl)) { + localBook.cacheLocalUri(fileUri) + return true + } + localBook.bookUrl = newBookUrl + localBook.origin = BookType.webDavTag + CustomUrl(webDavUrl).toString() + if (oldBook.bookUrl == localBook.bookUrl) { + localBook.save() + } else { + appDb.bookDao.replace(oldBook, localBook) + BookHelp.updateCacheFolder(oldBook, localBook) + } } } return true From 754846237947b6150ca196944d77c4692adc5e41 Mon Sep 17 00:00:00 2001 From: HapeLee <1321903405@qq.com> Date: Sat, 21 Feb 2026 01:19:50 +0800 Subject: [PATCH 2/2] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E9=A3=8E=E9=99=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/help/book/BookExtensions.kt | 47 +++++++++++-------- .../legado/app/model/localBook/LocalBook.kt | 30 ++++++++---- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt index 2e8ecea70..53c57add3 100644 --- a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt +++ b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt @@ -127,7 +127,7 @@ fun Book.contains(word: String?): Boolean { fun Book.canSafelyRebindTo(newBookUrl: String): Boolean { if (newBookUrl == bookUrl) return true val targetBook = appDb.bookDao.getBook(newBookUrl) ?: return true - if (targetBook.bookUrl == bookUrl) return true + val sameOriginName = originName.isNotBlank() && originName == targetBook.originName val sameNameAuthor = name.isNotBlank() && author.isNotBlank() && name == targetBook.name && author == targetBook.author @@ -173,27 +173,31 @@ fun Book.getLocalUri(): Uri { // 查找书籍保存目录 if (!defaultBookDir.isNullOrBlank()) { - val treeUri = Uri.parse(defaultBookDir) + val treeUri = defaultBookDir.toUri() val treeFileDoc = FileDoc.fromUri(treeUri, true) + if (!treeFileDoc.exists()) { appCtx.toastOnUi("书籍保存目录失效,请重新设置!") } else { val fileDoc = treeFileDoc.find(originName, 5, 100) if (fileDoc != null) { - localUriCache[bookUrl] = fileDoc.uri - //更新bookUrl时要迁移主键,避免save()按新bookUrl插入重复书籍 - val oldBook = copy() val newBookUrl = fileDoc.toString() + val oldBook = copy() if (!oldBook.canSafelyRebindTo(newBookUrl)) { return fileDoc.uri } - bookUrl = newBookUrl - if (oldBook.bookUrl == bookUrl) { - save() - } else { - appDb.bookDao.replace(oldBook, this) - BookHelp.updateCacheFolder(oldBook, this) + 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 } } @@ -209,20 +213,23 @@ fun Book.getLocalUri(): Uri { val treeFileDoc = FileDoc.fromUri(treeUri, true) val fileDoc = treeFileDoc.find(originName, 5, 100) if (fileDoc != null) { - localUriCache[bookUrl] = fileDoc.uri - //更新bookUrl时要迁移主键,避免save()按新bookUrl插入重复书籍 - val oldBook = copy() val newBookUrl = fileDoc.toString() + val oldBook = copy() if (!oldBook.canSafelyRebindTo(newBookUrl)) { return fileDoc.uri } - bookUrl = newBookUrl - if (oldBook.bookUrl == bookUrl) { - save() - } else { - appDb.bookDao.replace(oldBook, this) - BookHelp.updateCacheFolder(oldBook, this) + + 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 } } diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index f14ae46ca..12149714a 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -53,6 +53,7 @@ import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isContentScheme import io.legado.app.utils.isDataUrl import io.legado.app.utils.printOnDebug +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.runBlocking import org.apache.commons.text.StringEscapeUtils import splitties.init.appCtx @@ -63,7 +64,6 @@ import java.io.FileNotFoundException import java.io.FileOutputStream import java.io.InputStream import java.util.regex.Pattern -import kotlin.coroutines.coroutineContext /** * 书籍文件导入 目录正文解析 @@ -406,7 +406,7 @@ object LocalBook { val inputStream = when { str.isAbsUrl() -> AnalyzeUrl( str, source = source, callTimeout = 0, - coroutineContext = coroutineContext + coroutineContext = currentCoroutineContext() ).getInputStreamAwait() str.isDataUrl() -> ByteArrayInputStream( @@ -461,7 +461,7 @@ object LocalBook { fun isOnBookShelf( fileName: String ): Boolean { - return appDb.bookDao.hasFile(fileName) == true + return appDb.bookDao.hasFile(fileName) } //文件类书源 合并在线书籍信息 在线 > 本地 @@ -511,13 +511,23 @@ object LocalBook { localBook.cacheLocalUri(fileUri) return true } - localBook.bookUrl = newBookUrl - localBook.origin = BookType.webDavTag + CustomUrl(webDavUrl).toString() - if (oldBook.bookUrl == localBook.bookUrl) { - localBook.save() - } else { - appDb.bookDao.replace(oldBook, localBook) - BookHelp.updateCacheFolder(oldBook, localBook) + + 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 + } } } }