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

refactor: 清零换源与目录 DAO 直连

5151561 3 месяцев назад
Родитель
Сommit
20cd2e6355

+ 22 - 0
app/src/main/java/io/legado/app/data/repository/BookRepository.kt

@@ -14,6 +14,14 @@ class BookRepository(
     private val bookDao: BookDao,
     private val bookChapterDao: BookChapterDao
 ) {
+    fun flowBook(bookUrl: String): Flow<Book?> {
+        return bookDao.flowGetBook(bookUrl)
+    }
+
+    fun flowChapters(bookUrl: String): Flow<List<BookChapter>> {
+        return bookChapterDao.getChapterListFlow(bookUrl)
+    }
+
     fun getAllBooks(): Flow<List<Book>> {
         return bookDao.flowAll()
     }
@@ -84,6 +92,12 @@ class BookRepository(
         }
     }
 
+    suspend fun getChapters(bookUrl: String): List<BookChapter> {
+        return withContext(Dispatchers.IO) {
+            bookChapterDao.getChapterList(bookUrl)
+        }
+    }
+
     suspend fun update(vararg book: Book) {
         withContext(Dispatchers.IO) {
             bookDao.update(*book)
@@ -126,4 +140,12 @@ class BookRepository(
         }
     }
 
+    suspend fun replaceChaptersAndUpdateBook(book: Book, chapters: List<BookChapter>) {
+        withContext(Dispatchers.IO) {
+            bookChapterDao.delByBook(book.bookUrl)
+            bookChapterDao.insert(*chapters.toTypedArray())
+            bookDao.update(book)
+        }
+    }
+
 }

+ 25 - 0
app/src/main/java/io/legado/app/data/repository/BookSourceRepository.kt

@@ -107,6 +107,31 @@ class BookSourceRepository(private val bookSourceDao: BookSourceDao) {
         SourceHelp.deleteBookSourceParts(sources)
     }
 
+    suspend fun deleteSource(sourceUrl: String) = withContext(Dispatchers.IO) {
+        SourceHelp.deleteBookSource(sourceUrl)
+    }
+
+    suspend fun disableSource(sourceUrl: String) = withContext(Dispatchers.IO) {
+        bookSourceDao.getBookSource(sourceUrl)?.let { source ->
+            source.enabled = false
+            bookSourceDao.update(source)
+        }
+    }
+
+    suspend fun moveSourceToTop(sourceUrl: String): Int? = withContext(Dispatchers.IO) {
+        val source = bookSourceDao.getBookSource(sourceUrl) ?: return@withContext null
+        source.customOrder = bookSourceDao.minOrder - 1
+        bookSourceDao.update(source)
+        source.customOrder
+    }
+
+    suspend fun moveSourceToBottom(sourceUrl: String): Int? = withContext(Dispatchers.IO) {
+        val source = bookSourceDao.getBookSource(sourceUrl) ?: return@withContext null
+        source.customOrder = bookSourceDao.maxOrder + 1
+        bookSourceDao.update(source)
+        source.customOrder
+    }
+
     suspend fun updateSources(vararg sources: BookSource) = withContext(Dispatchers.IO) {
         bookSourceDao.update(*sources)
     }

+ 12 - 0
app/src/main/java/io/legado/app/data/repository/BookmarkRepository.kt

@@ -13,14 +13,26 @@ class BookmarkRepository(
 
     fun flowAll(): Flow<List<Bookmark>> = dao.flowAll().flowOn(Dispatchers.IO)
 
+    fun flowByBook(bookName: String, bookAuthor: String): Flow<List<Bookmark>> =
+        dao.flowByBook(bookName, bookAuthor).flowOn(Dispatchers.IO)
+
     suspend fun getAll(): List<Bookmark> = withContext(Dispatchers.IO) {
         dao.all
     }
 
+    suspend fun getByBook(bookName: String, bookAuthor: String): List<Bookmark> =
+        withContext(Dispatchers.IO) {
+            dao.getByBook(bookName, bookAuthor)
+        }
+
     suspend fun save(bookmark: Bookmark) = withContext(Dispatchers.IO) {
         dao.insert(bookmark)
     }
 
+    suspend fun saveAll(bookmarks: List<Bookmark>) = withContext(Dispatchers.IO) {
+        dao.insert(*bookmarks.toTypedArray())
+    }
+
     suspend fun delete(bookmark: Bookmark) = withContext(Dispatchers.IO) {
         dao.delete(bookmark)
     }

+ 18 - 2
app/src/main/java/io/legado/app/data/repository/SearchRepository.kt

@@ -28,7 +28,13 @@ interface SearchRepository {
     suspend fun clearSearchKeywords()
     suspend fun saveSearchBooks(books: List<SearchBook>)
     suspend fun saveSearchBook(book: SearchBook)
-    suspend fun findChangeSourceBooks(name: String, author: String): List<SearchBook>
+    suspend fun updateSearchBook(book: SearchBook)
+    suspend fun findChangeSourceBooks(
+        name: String,
+        author: String,
+        screenKey: String = "",
+        group: String = "",
+    ): List<SearchBook>
     suspend fun deleteSearchBooks(books: List<SearchBook>)
     suspend fun getBookSourcePart(sourceUrl: String): BookSourcePart?
     suspend fun getBookSource(sourceUrl: String): BookSource?
@@ -121,11 +127,21 @@ class SearchRepositoryImpl(
         appDb.searchBookDao.insert(book)
     }
 
+    override suspend fun updateSearchBook(book: SearchBook): Unit = withContext(Dispatchers.IO) {
+        appDb.searchBookDao.update(book)
+    }
+
     override suspend fun findChangeSourceBooks(
         name: String,
         author: String,
+        screenKey: String,
+        group: String,
     ): List<SearchBook> = withContext(Dispatchers.IO) {
-        appDb.searchBookDao.changeSourceByGroup(name, author, "")
+        if (screenKey.isEmpty()) {
+            appDb.searchBookDao.changeSourceByGroup(name, author, group)
+        } else {
+            appDb.searchBookDao.changeSourceSearch(name, author, screenKey, group)
+        }
     }
 
     override suspend fun deleteSearchBooks(books: List<SearchBook>): Unit =

+ 7 - 1
app/src/main/java/io/legado/app/di/appModule.kt

@@ -501,7 +501,13 @@ val appModule = module {
     viewModelOf(::ReplaceRuleViewModel)
     viewModelOf(::AllBookmarkViewModel)
     viewModelOf(::TxtTocRuleViewModel)
-    viewModel { TxtTocRulePreviewViewModel(app = get(), repository = get()) }
+    viewModel {
+        TxtTocRulePreviewViewModel(
+            app = get(),
+            bookRepository = get(),
+            repository = get(),
+        )
+    }
     viewModel {
         OtherConfigViewModel(
             appLocaleGateway = get(),

+ 3 - 3
app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt

@@ -13,7 +13,6 @@ import androidx.appcompat.widget.SearchView
 import androidx.appcompat.widget.Toolbar
 import androidx.core.os.bundleOf
 import androidx.core.view.isInvisible
-import androidx.fragment.app.viewModels
 import androidx.lifecycle.Lifecycle.State.STARTED
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
@@ -57,6 +56,7 @@ import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 import org.koin.android.ext.android.inject
+import org.koin.androidx.viewmodel.ext.android.viewModel
 
 /**
  * 换源界面
@@ -75,7 +75,7 @@ class ChangeBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_b
     private val binding by viewBinding(DialogBookChangeSourceBinding::bind)
     private val groups = linkedSetOf<String>()
     private val callBack: CallBack? get() = activity as? CallBack
-    private val viewModel: ChangeBookSourceViewModel by viewModels()
+    private val viewModel: ChangeBookSourceViewModel by viewModel()
     private val changeSourceSettingsGateway by inject<ChangeSourceSettingsGateway>()
     private val waitDialog by lazy { WaitDialog(requireContext()) }
     private val adapter by lazy { ChangeBookSourceAdapter(requireContext(), viewModel, this) }
@@ -324,7 +324,7 @@ class ChangeBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_b
                 val enabled = !item.isChecked
                 updateSetting({ it.copy(checkAuthor = enabled) })
                 item.isChecked = enabled
-                viewModel.refresh()
+                lifecycleScope.launch { viewModel.refresh() }
             }
 
             R.id.menu_load_info -> {

+ 28 - 44
app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt

@@ -8,12 +8,13 @@ import androidx.lifecycle.viewModelScope
 import io.legado.app.base.BaseViewModel
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppPattern
-import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.data.entities.SearchBook
+import io.legado.app.data.repository.BookSourceRepository
+import io.legado.app.data.repository.SearchRepository
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.book.BookHelp
 import io.legado.app.help.book.ContentProcessor
@@ -21,7 +22,6 @@ import io.legado.app.help.book.primaryStr
 import io.legado.app.help.book.releaseHtmlData
 import io.legado.app.help.config.SourceConfig
 import io.legado.app.help.coroutine.Coroutine
-import io.legado.app.help.source.SourceHelp
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.ui.config.otherConfig.OtherConfig
 import io.legado.app.utils.internString
@@ -56,7 +56,11 @@ import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.Executors
 
 @Suppress("MemberVisibilityCanBePrivate")
-open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(application) {
+open class ChangeBookSourceViewModel(
+    application: Application,
+    private val searchRepository: SearchRepository,
+    private val bookSourceRepository: BookSourceRepository,
+) : BaseViewModel(application) {
     private val threadCount = OtherConfig.threadCount
     private var searchPool: ExecutorCoroutineDispatcher? = null
     val searchStateData = MutableLiveData<Boolean>()
@@ -103,9 +107,9 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
 
         searchCallback = object : SourceCallback {
 
-            override fun searchSuccess(searchBook: SearchBook) {
+            override suspend fun searchSuccess(searchBook: SearchBook) {
                 searchBook.releaseHtmlData()
-                appDb.searchBookDao.insert(searchBook)
+                searchRepository.saveSearchBook(searchBook)
                 when {
                     screenKey.isEmpty() -> searchBooks.add(searchBook)
                     searchBook.name.contains(screenKey) -> searchBooks.add(searchBook)
@@ -184,7 +188,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
             .newFixedThreadPool(threadCount).asCoroutineDispatcher()
     }
 
-    fun refresh(): Boolean {
+    suspend fun refresh(): Boolean {
         getDbSearchBooks().let {
             searchBooks.clear()
             searchBooks.addAll(it)
@@ -200,7 +204,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
         execute {
             stopSearch()
             if (searchBooks.isNotEmpty()) {
-                appDb.searchBookDao.delete(*searchBooks.toTypedArray())
+                searchRepository.deleteSearchBooks(searchBooks.toList())
                 searchBooks.clear()
             }
             searchCallback?.upAdapter()
@@ -222,7 +226,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
             tocMap.clear()
             bookMap.clear()
             tocMapChapterCount = 0
-            bookSourceParts.add(appDb.bookSourceDao.getBookSourcePart(origin)!!)
+            bookSourceParts.add(bookSourceRepository.getBookSourcePart(origin)!!)
             searchBooks.removeIf { it.origin == origin }
             initSearchPool()
             search()
@@ -395,7 +399,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
                 searchStateData.postValue(true)
                 _isSearching.value = true
             }.mapParallelSafe(threadCount) {
-                val source = appDb.bookSourceDao.getBookSource(it.origin)!!
+                val source = bookSourceRepository.getBookSource(it.origin)!!
                 withTimeout(60000L) {
                     loadBookInfo(source, it.toBook())
                 }
@@ -408,7 +412,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
         }
     }
 
-    private fun getDbSearchBooks(): List<SearchBook> {
+    private suspend fun getDbSearchBooks(): List<SearchBook> {
         val searchScope = io.legado.app.ui.book.search.SearchScope(
             ChangeSourceConfig.searchScope
         )
@@ -416,19 +420,8 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
             searchScope.isAll() || searchScope.isSource() -> ""
             else -> searchScope.displayNames.firstOrNull() ?: ""
         }
-        return if (screenKey.isEmpty()) {
-            if (ChangeSourceConfig.checkAuthor) {
-                appDb.searchBookDao.changeSourceByGroup(name, author, group)
-            } else {
-                appDb.searchBookDao.changeSourceByGroup(name, "", group)
-            }
-        } else {
-            if (ChangeSourceConfig.checkAuthor) {
-                appDb.searchBookDao.changeSourceSearch(name, author, screenKey, group)
-            } else {
-                appDb.searchBookDao.changeSourceSearch(name, "", screenKey, group)
-            }
-        }
+        val bookAuthor = if (ChangeSourceConfig.checkAuthor) author else ""
+        return searchRepository.findChangeSourceBooks(name, bookAuthor, screenKey, group)
     }
 
     /**
@@ -479,7 +472,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
         return execute {
             val toc = tocMap[book.primaryStr()]
             if (toc != null) {
-                val source = appDb.bookSourceDao.getBookSource(book.origin)
+                val source = bookSourceRepository.getBookSource(book.origin)
                 return@execute Pair(toc, source!!)
             }
             val result = getToc(book).getOrThrow()
@@ -494,7 +487,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
 
     suspend fun getToc(book: Book): Result<Pair<List<BookChapter>, BookSource>> {
         return kotlin.runCatching {
-            val source = appDb.bookSourceDao.getBookSource(book.origin)
+            val source = bookSourceRepository.getBookSource(book.origin)
                 ?: throw NoStackTraceException("书源不存在")
             if (book.tocUrl.isEmpty()) {
                 WebBook.getBookInfoAwait(source, book)
@@ -506,10 +499,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
 
     fun disableSource(searchBook: SearchBook) {
         execute {
-            appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->
-                source.enabled = false
-                appDb.bookSourceDao.update(source)
-            }
+            bookSourceRepository.disableSource(searchBook.origin)
             searchBooks.remove(searchBook)
             searchCallback?.upAdapter()
         }
@@ -517,11 +507,8 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
 
     fun topSource(searchBook: SearchBook) {
         execute {
-            appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->
-                val minOrder = appDb.bookSourceDao.minOrder - 1
-                source.customOrder = minOrder
-                searchBook.originOrder = source.customOrder
-                appDb.bookSourceDao.update(source)
+            bookSourceRepository.moveSourceToTop(searchBook.origin)?.let { customOrder ->
+                searchBook.originOrder = customOrder
                 updateSource(searchBook)
             }
             searchCallback?.upAdapter()
@@ -530,25 +517,22 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
 
     fun bottomSource(searchBook: SearchBook) {
         execute {
-            appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->
-                val maxOrder = appDb.bookSourceDao.maxOrder + 1
-                source.customOrder = maxOrder
-                searchBook.originOrder = source.customOrder
-                appDb.bookSourceDao.update(source)
+            bookSourceRepository.moveSourceToBottom(searchBook.origin)?.let { customOrder ->
+                searchBook.originOrder = customOrder
                 updateSource(searchBook)
             }
             searchCallback?.upAdapter()
         }
     }
 
-    fun updateSource(searchBook: SearchBook) {
-        appDb.searchBookDao.update(searchBook)
+    private suspend fun updateSource(searchBook: SearchBook) {
+        searchRepository.updateSearchBook(searchBook)
     }
 
     fun del(searchBook: SearchBook) {
         execute {
-            SourceHelp.deleteBookSource(searchBook.origin)
-            appDb.searchBookDao.delete(searchBook)
+            bookSourceRepository.deleteSource(searchBook.origin)
+            searchRepository.deleteSearchBooks(listOf(searchBook))
         }
         searchBooks.remove(searchBook)
         searchCallback?.upAdapter()
@@ -592,7 +576,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
 
     interface SourceCallback {
 
-        fun searchSuccess(searchBook: SearchBook)
+        suspend fun searchSuccess(searchBook: SearchBook)
 
         fun upAdapter()
 

+ 10 - 18
app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceViewModel.kt

@@ -125,7 +125,7 @@ class ChangeChapterSourceViewModel(
         }
     }
 
-    private fun getDbSearchBooks(): List<SearchBook> {
+    private suspend fun getDbSearchBooks(): List<SearchBook> {
         val name = oldBook?.name ?: return emptyList()
         val author = oldBook?.author ?: return emptyList()
         val settings = changeSourceSettingsGateway.currentSettings
@@ -138,15 +138,7 @@ class ChangeChapterSourceViewModel(
             }
         }
         val bookAuthor = if (settings.checkAuthor) author else ""
-        return if (screenKey.isEmpty()) {
-            io.legado.app.data.appDb.searchBookDao.changeSourceByGroup(
-                name, bookAuthor, group
-            )
-        } else {
-            io.legado.app.data.appDb.searchBookDao.changeSourceSearch(
-                name, bookAuthor, screenKey, group
-            )
-        }
+        return searchRepository.findChangeSourceBooks(name, bookAuthor, screenKey, group)
     }
 
     fun onIntent(intent: ChangeChapterSourceIntent) {
@@ -277,10 +269,9 @@ class ChangeChapterSourceViewModel(
                 if (selectedUrls.isEmpty()) {
                     searchScope.update("")
                 } else {
-                    val selectedSources =
-                        io.legado.app.data.appDb.bookSourceDao.allEnabledPart.filter {
-                            selectedUrls.contains(it.bookSourceUrl)
-                        }
+                    val selectedSources = _uiState.value.enabledSources.filter {
+                        selectedUrls.contains(it.bookSourceUrl)
+                    }
                     searchScope.updateSources(selectedSources)
                 }
                 updateScopeState()
@@ -297,9 +288,7 @@ class ChangeChapterSourceViewModel(
     private fun startSearch() {
         val book = oldBook ?: return
         stopSearch()
-        if (searchResults.isNotEmpty()) {
-            io.legado.app.data.appDb.searchBookDao.delete(*searchResults.toTypedArray())
-        }
+        val removedResults = searchResults.toList()
         searchResults.clear()
         bookMap.clear()
         val scope = SearchScope(changeSourceSettingsGateway.currentSettings.searchScope)
@@ -312,6 +301,9 @@ class ChangeChapterSourceViewModel(
         filterResults()
 
         searchJob = viewModelScope.launch {
+            if (removedResults.isNotEmpty()) {
+                searchRepository.deleteSearchBooks(removedResults)
+            }
             changeSourceSearchUseCase.search(
                 name = book.name,
                 author = book.author,
@@ -336,7 +328,7 @@ class ChangeChapterSourceViewModel(
                     is ChangeSourceSearchEvent.Result -> {
                         searchResults.add(event.searchBook)
                         bookMap[event.searchBook.primaryStr()] = event.searchBook
-                        io.legado.app.data.appDb.searchBookDao.insert(event.searchBook)
+                        searchRepository.saveSearchBook(event.searchBook)
                         filterResults()
                     }
 

+ 23 - 22
app/src/main/java/io/legado/app/ui/book/toc/TocViewModel.kt

@@ -9,11 +9,13 @@ import androidx.lifecycle.viewModelScope
 import io.legado.app.R
 import io.legado.app.base.BaseRuleViewModel
 import io.legado.app.constant.AppLog
-import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.Bookmark
 import io.legado.app.data.entities.ReplaceRule
+import io.legado.app.data.repository.BookRepository
+import io.legado.app.data.repository.BookSourceRepository
+import io.legado.app.data.repository.BookmarkRepository
 import io.legado.app.data.repository.ReadSettingsRepository
 import io.legado.app.domain.gateway.OtherSettingsGateway
 import io.legado.app.domain.usecase.CacheBookChaptersUseCase
@@ -216,6 +218,9 @@ class TocViewModel(
     application: Application,
     savedStateHandle: SavedStateHandle,
     private val cacheBookChaptersUseCase: CacheBookChaptersUseCase,
+    private val bookRepository: BookRepository,
+    private val bookSourceRepository: BookSourceRepository,
+    private val bookmarkRepository: BookmarkRepository,
     private val readSettingsRepository: ReadSettingsRepository,
     private val otherSettingsGateway: OtherSettingsGateway,
 ) : BaseRuleViewModel<TocItemUi, TocDomainItem, Int, TocActionState>(
@@ -227,7 +232,7 @@ class TocViewModel(
     val bookState = bookUrlFlow
         .filterNotNull()
         .flatMapLatest { url ->
-            appDb.bookDao.flowGetBook(url)
+            bookRepository.flowBook(url)
         }
         .stateIn(viewModelScope, SharingStarted.Eagerly, null)
 
@@ -276,7 +281,7 @@ class TocViewModel(
             book to query
         }
             .flatMapLatest { (book, query) ->
-                appDb.bookmarkDao
+                bookmarkRepository
                     .flowByBook(book.name, book.author)
                     .map { list ->
                         list
@@ -375,7 +380,7 @@ class TocViewModel(
 
     override val rawDataFlow: Flow<List<TocDomainItem>> = combine(
         bookState.filterNotNull().map { it.bookUrl }.distinctUntilChanged()
-            .flatMapLatest { appDb.bookChapterDao.getChapterListFlow(it) },
+            .flatMapLatest { bookRepository.flowChapters(it) },
         downloadContextFlow,
         uiConfigFlow,
         titleReplaceState
@@ -559,7 +564,7 @@ class TocViewModel(
         val currentConfig = currentBook.readConfig ?: Book.ReadConfig()
         val newConfig = currentConfig.copy(reverseToc = !currentConfig.reverseToc)
         val newBook = currentBook.copy(readConfig = newConfig)
-        appDb.bookDao.update(newBook)
+        bookRepository.update(newBook)
         //bookState.value = newBook
     }
 
@@ -575,28 +580,26 @@ class TocViewModel(
             }
             kotlin.runCatching {
                 LocalBook.getChapterList(book).let {
-                    appDb.bookChapterDao.delByBook(book.bookUrl)
-                    appDb.bookChapterDao.insert(*it.toTypedArray())
-                    appDb.bookDao.update(book)
+                    bookRepository.replaceChaptersAndUpdateBook(book, it)
                 }
             }.onFailure {
                 AppLog.put("LoadTocError:${it.localizedMessage}", it)
                 _effects.tryEmit(TocEffect.ShowMessage(it.localizedMessage ?: "Error"))
             }
         } else {
-            val source = appDb.bookSourceDao.getBookSource(book.origin)
+            val source = bookSourceRepository.getBookSource(book.origin)
             source?.let {
                 val oldBook = book.copy()
                 WebBook.getChapterListAwait(it, book, true)
                     .onSuccess { cList ->
                         if (oldBook.bookUrl == book.bookUrl) {
-                            appDb.bookDao.update(book)
+                            bookRepository.update(book)
                         } else {
-                            appDb.bookDao.replace(oldBook, book)
+                            bookRepository.replace(oldBook, book)
                             BookHelp.updateCacheFolder(oldBook, book)
                         }
-                        appDb.bookChapterDao.delByBook(oldBook.bookUrl)
-                        appDb.bookChapterDao.insert(*cList.toTypedArray())
+                        bookRepository.deleteChaptersByBook(oldBook.bookUrl)
+                        bookRepository.insertChapters(*cList.toTypedArray())
                     }.onFailure {
                         AppLog.put("LoadTocError:${it.localizedMessage}", it)
                         _effects.tryEmit(TocEffect.ShowMessage(it.localizedMessage ?: "Error"))
@@ -630,7 +633,7 @@ class TocViewModel(
     fun collapseAllVolumes() = execute {
         val bookUrl = bookState.value?.bookUrl ?: return@execute
         val volumes =
-            appDb.bookChapterDao.getChapterList(bookUrl).filter { it.isVolume }.map { it.index }
+            bookRepository.getChapters(bookUrl).filter { it.isVolume }.map { it.index }
                 .toSet()
         _collapsedVolumes.value = volumes
     }
@@ -689,11 +692,9 @@ class TocViewModel(
     private fun upBookTocRule(book: Book, complete: (Throwable?) -> Unit) {
         _isUploading.value = true
         execute {
-            appDb.bookDao.update(book)
+            bookRepository.update(book)
             LocalBook.getChapterList(book).let { chapters ->
-                appDb.bookChapterDao.delByBook(book.bookUrl)
-                appDb.bookChapterDao.insert(*chapters.toTypedArray())
-                appDb.bookDao.update(book)
+                bookRepository.replaceChaptersAndUpdateBook(book, chapters)
                 ReadBook.onChapterListUpdated(book)
                 //bookState.value = book
             }
@@ -709,7 +710,7 @@ class TocViewModel(
     fun exportCurrentBookBookmarks(fileUri: Uri, isMd: Boolean) = viewModelScope.launch {
         try {
             val book = bookState.value ?: return@launch
-            val bookmarks = appDb.bookmarkDao.getByBook(book.name, book.author)
+            val bookmarks = bookmarkRepository.getByBook(book.name, book.author)
             if (bookmarks.isEmpty()) {
                 showMessage(R.string.no_bookmarks_to_export)
                 return@launch
@@ -725,10 +726,10 @@ class TocViewModel(
     }
 
     fun updateBookmark(bookmark: Bookmark) =
-        viewModelScope.launch(Dispatchers.IO) { appDb.bookmarkDao.insert(bookmark) }
+        viewModelScope.launch(Dispatchers.IO) { bookmarkRepository.save(bookmark) }
 
     fun deleteBookmark(bookmark: Bookmark) =
-        viewModelScope.launch(Dispatchers.IO) { appDb.bookmarkDao.delete(bookmark) }
+        viewModelScope.launch(Dispatchers.IO) { bookmarkRepository.delete(bookmark) }
 
     fun addBookmarksForSelected() = viewModelScope.launch(Dispatchers.IO) {
         val book = bookState.value ?: return@launch
@@ -755,7 +756,7 @@ class TocViewModel(
             )
         }
 
-        appDb.bookmarkDao.insert(*bookmarks.toTypedArray())
+        bookmarkRepository.saveAll(bookmarks)
         showMessage(context.getString(R.string.bookmarks_added_count, bookmarks.size))
         withContext(Dispatchers.Main) {
             clearSelection()

+ 4 - 3
app/src/main/java/io/legado/app/ui/book/toc/rule/preview/TxtTocRulePreviewViewModel.kt

@@ -3,9 +3,9 @@ package io.legado.app.ui.book.toc.rule.preview
 import android.app.Application
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
-import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.TxtTocRule
+import io.legado.app.data.repository.BookRepository
 import io.legado.app.data.repository.TxtTocRuleRepository
 import io.legado.app.help.DefaultData
 import io.legado.app.model.localBook.LocalBook
@@ -27,6 +27,7 @@ import java.util.regex.PatternSyntaxException
 
 class TxtTocRulePreviewViewModel(
     private val app: Application,
+    private val bookRepository: BookRepository,
     private val repository: TxtTocRuleRepository,
 ) : ViewModel() {
 
@@ -98,7 +99,7 @@ class TxtTocRulePreviewViewModel(
     private suspend fun loadRules(bookUrl: String, currentTocRegex: String?) {
         _uiState.update { it.copy(loading = true) }
 
-        val book = runCatching { appDb.bookDao.getBook(bookUrl) }.getOrNull()
+        val book = runCatching { bookRepository.getBook(bookUrl) }.getOrNull()
         this.book = book
         val currentRule = currentTocRegex ?: book?.tocUrl ?: ""
 
@@ -176,7 +177,7 @@ class TxtTocRulePreviewViewModel(
         }
 
         // Save to DB
-        val existing = runCatching { appDb.txtTocRuleDao.getByIds(setOf(updatedRule.id)) }.getOrNull()?.firstOrNull()
+        val existing = runCatching { repository.findById(updatedRule.id) }.getOrNull()
         if (existing != null) {
             repository.update(updatedRule)
         } else {

+ 0 - 4
build.gradle.kts

@@ -221,14 +221,10 @@ val verifyConfigArchitecture = tasks.register<VerifyConfigArchitectureTask>(
         mapOf(
             "io/legado/app/ui/book/audio/AudioPlayViewModel.kt" to 11,
             "io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt" to 3,
-            "io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt" to 20,
-            "io/legado/app/ui/book/changesource/ChangeChapterSourceViewModel.kt" to 5,
             "io/legado/app/ui/book/info/BookInfoViewModel.kt" to 34,
             "io/legado/app/ui/book/info/edit/BookInfoEditViewModel.kt" to 2,
             "io/legado/app/ui/book/manga/ReadMangaViewModel.kt" to 17,
             "io/legado/app/ui/book/read/ReadBookViewModel.kt" to 52,
-            "io/legado/app/ui/book/toc/TocViewModel.kt" to 13,
-            "io/legado/app/ui/book/toc/rule/preview/TxtTocRulePreviewViewModel.kt" to 2,
             "io/legado/app/ui/browser/WebViewModel.kt" to 1,
             "io/legado/app/ui/login/SourceLoginViewModel.kt" to 6,
             "io/legado/app/ui/tagGroupRule/TagGroupRuleViewModel.kt" to 4,