diff --git a/app/src/main/java/io/legado/app/data/repository/RemoteBookRepository.kt b/app/src/main/java/io/legado/app/data/repository/RemoteBookRepository.kt index 2881216bf..f81885eca 100644 --- a/app/src/main/java/io/legado/app/data/repository/RemoteBookRepository.kt +++ b/app/src/main/java/io/legado/app/data/repository/RemoteBookRepository.kt @@ -1,19 +1,90 @@ package io.legado.app.data.repository +import io.legado.app.constant.AppConst import io.legado.app.constant.BookType import io.legado.app.data.AppDatabase import io.legado.app.data.entities.Book +import io.legado.app.exception.NoStackTraceException +import io.legado.app.help.AppWebDav +import io.legado.app.help.book.getRemoteUrl +import io.legado.app.help.book.isLocal +import io.legado.app.help.config.AppConfig import io.legado.app.lib.webdav.Authorization +import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.CustomUrl import io.legado.app.model.localBook.LocalBook import io.legado.app.model.remote.RemoteBook import io.legado.app.model.remote.RemoteBookWebDav +import io.legado.app.utils.isContentScheme import kotlinx.coroutines.flow.Flow class RemoteBookRepository( private val appDb: AppDatabase ) { + suspend fun getWebDav(book: Book): RemoteBookWebDav? { + val remoteUrl = book.getRemoteUrl() ?: return null + val analyzeUrl = AnalyzeUrl(remoteUrl) + + // 优先从书籍 origin 中解析 serverID + val serverId = analyzeUrl.serverID + if (serverId != null && serverId != AppConst.DEFAULT_WEBDAV_ID) { + appDb.serverDao.get(serverId)?.getWebDavConfig()?.let { + return RemoteBookWebDav(it.url, Authorization(it), serverId) + } + } + + // 如果没有指定 serverID,尝试使用目前选择的 WebDAV 服务器 + val currentServerId = AppConfig.remoteServerId + if (currentServerId != AppConst.DEFAULT_WEBDAV_ID) { + appDb.serverDao.get(currentServerId)?.getWebDavConfig()?.let { + return RemoteBookWebDav(it.url, Authorization(it), currentServerId) + } + } + + // 最后回退到默认的 WebDAV 配置 + return AppWebDav.defaultBookWebDav + } + + suspend fun refreshLocalBook(book: Book): Boolean { + if (!book.isLocal) return false + val remoteUrl = book.getRemoteUrl() ?: return false + val webDav = getWebDav(book) ?: throw NoStackTraceException("webDav没有配置") + val remoteBook = webDav.getRemoteBook(remoteUrl) + if (remoteBook == null) { + book.origin = BookType.localTag + return true + } + if (remoteBook.lastModify > book.lastCheckTime) { + val uri = webDav.downloadRemoteBook(remoteBook) + book.bookUrl = if (uri.isContentScheme()) uri.toString() else uri.path!! + book.lastCheckTime = remoteBook.lastModify + return true + } + return false + } + + suspend fun syncBookFromRemote(book: Book): Book { + val remoteUrl = book.getRemoteUrl() ?: throw NoStackTraceException("不是远程书籍") + val webDav = getWebDav(book) ?: throw NoStackTraceException("webDav没有配置") + val remoteBook = + webDav.getRemoteBook(remoteUrl) ?: throw NoStackTraceException("远程文件不存在") + val downloadBookUri = webDav.downloadRemoteBook(remoteBook) + val importedBooks = LocalBook.importFiles(downloadBookUri) + val newBook = importedBooks.firstOrNull() ?: throw NoStackTraceException("导入失败") + newBook.durChapterIndex = book.durChapterIndex + newBook.durChapterPos = book.durChapterPos + newBook.order = book.order + newBook.group = book.group + return newBook + } + + suspend fun uploadBook(book: Book) { + val webDav = getWebDav(book) ?: throw NoStackTraceException("未配置webDav") + webDav.upload(book) + book.lastCheckTime = System.currentTimeMillis() + } + suspend fun createWebDav(serverId: Long): RemoteBookWebDav? { return appDb.serverDao.get(serverId) ?.getWebDavConfig() diff --git a/app/src/main/java/io/legado/app/di/appModule.kt b/app/src/main/java/io/legado/app/di/appModule.kt index 6e26dd2f5..77bf1499c 100644 --- a/app/src/main/java/io/legado/app/di/appModule.kt +++ b/app/src/main/java/io/legado/app/di/appModule.kt @@ -12,6 +12,7 @@ import io.legado.app.data.repository.UploadRepository import io.legado.app.ui.book.bookmark.AllBookmarkViewModel import io.legado.app.ui.book.explore.ExploreShowViewModel import io.legado.app.ui.book.import.remote.RemoteBookViewModel +import io.legado.app.ui.book.info.BookInfoViewModel import io.legado.app.ui.book.readRecord.ReadRecordViewModel import io.legado.app.ui.book.searchContent.SearchContentViewModel import io.legado.app.ui.book.toc.TocViewModel @@ -51,6 +52,7 @@ val appModule = module { viewModelOf(::OtherConfigViewModel) viewModelOf(::TocViewModel) viewModelOf(::RemoteBookViewModel) + viewModelOf(::BookInfoViewModel) viewModel { (route: ReplaceEditRoute) -> ReplaceEditViewModel( diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt index ea4c1b81b..f6edd72bb 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt @@ -22,7 +22,6 @@ import android.view.View import android.widget.CheckBox import android.widget.LinearLayout import androidx.activity.addCallback -import androidx.activity.viewModels import androidx.core.app.ActivityCompat import androidx.core.app.ActivityOptionsCompat import androidx.core.content.ContextCompat @@ -44,10 +43,7 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookSource import io.legado.app.databinding.ActivityBookInfoBinding import io.legado.app.databinding.DialogEditTextBinding -import io.legado.app.exception.NoStackTraceException -import io.legado.app.help.AppWebDav import io.legado.app.help.book.addType -import io.legado.app.help.book.getRemoteUrl import io.legado.app.help.book.isAudio import io.legado.app.help.book.isImage import io.legado.app.help.book.isLocal @@ -60,7 +56,6 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.selector import io.legado.app.model.BookCover import io.legado.app.model.SourceCallBack -import io.legado.app.model.remote.RemoteBookWebDav import io.legado.app.ui.about.AppLogDialog import io.legado.app.ui.book.audio.AudioPlayActivity import io.legado.app.ui.book.changecover.ChangeCoverDialog @@ -101,6 +96,7 @@ import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.koin.androidx.viewmodel.ext.android.viewModel class BookInfoActivity : VMBaseActivity(), @@ -170,7 +166,7 @@ class BookInfoActivity : private val book get() = viewModel.getBook(false) override val binding by viewBinding(ActivityBookInfoBinding::inflate) - override val viewModel by viewModels() + override val viewModel: BookInfoViewModel by viewModel() @SuppressLint("PrivateResource") override fun onCreate(savedInstanceState: Bundle?) { @@ -333,14 +329,9 @@ class BookInfoActivity : R.id.menu_delete_alert -> LocalConfig.bookInfoDeleteAlert = !item.isChecked R.id.menu_upload -> { viewModel.getBook()?.let { book -> - book.getRemoteUrl()?.let { - alert(R.string.draw, R.string.sure_upload) { - okButton { - upLoadBook(book) - } - cancelButton() - } - } ?: upLoadBook(book) + viewModel.uploadBook(book) { + toastOnUi("上传成功") + } } } } @@ -374,17 +365,6 @@ class BookInfoActivity : } } -// override fun dispatchTouchEvent(ev: MotionEvent): Boolean { -// if (ev.action == MotionEvent.ACTION_DOWN) { -// currentFocus?.let { -// if (it === binding.tvIntro && binding.tvIntro.hasSelection()) { -// it.clearFocus() -// } -// } -// } -// return super.dispatchTouchEvent(ev) -// } - private fun setupBackCallback() { onBackPressedDispatcher.addCallback(this) { if (!viewModel.inBookshelf && AppConfig.showAddToShelfAlert) { @@ -413,28 +393,6 @@ class BookInfoActivity : } } - private fun upLoadBook( - book: Book, - bookWebDav: RemoteBookWebDav? = AppWebDav.defaultBookWebDav, - ) { - lifecycleScope.launch { - waitDialog.setText("上传中.....") - waitDialog.show() - try { - bookWebDav - ?.upload(book) - ?: throw NoStackTraceException("未配置webDav") - //更新书籍最后更新时间,使之比远程书籍的时间新 - book.lastCheckTime = System.currentTimeMillis() - viewModel.saveBook(book) - } catch (e: Exception) { - toastOnUi(e.localizedMessage) - } finally { - waitDialog.dismiss() - } - } - } - private fun showBook(book: Book) = binding.run { showCover(book) addColorScheme(binding.ivCover.drawable) @@ -1172,4 +1130,4 @@ class BookInfoActivity : toastOnUi("已添加到书架") } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt index 863daba7b..958cc52f6 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt @@ -15,12 +15,11 @@ 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.repository.RemoteBookRepository import io.legado.app.exception.NoBooksDirException import io.legado.app.exception.NoStackTraceException -import io.legado.app.help.AppWebDav import io.legado.app.help.book.BookHelp import io.legado.app.help.book.getExportFileName -import io.legado.app.help.book.getRemoteUrl import io.legado.app.help.book.isLocal import io.legado.app.help.book.isNotShelf import io.legado.app.help.book.isSameNameAuthor @@ -38,13 +37,15 @@ import io.legado.app.model.localBook.LocalBook import io.legado.app.model.webBook.WebBook import io.legado.app.utils.ArchiveUtils import io.legado.app.utils.UrlUtil -import io.legado.app.utils.isContentScheme import io.legado.app.utils.postEvent import io.legado.app.utils.toastOnUi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO -class BookInfoViewModel(application: Application) : BaseViewModel(application) { +class BookInfoViewModel( + application: Application, + private val remoteBookRepository: RemoteBookRepository +) : BaseViewModel(application) { val bookData = MutableLiveData() val chapterListData = MutableLiveData>() val webFiles = mutableListOf() @@ -122,18 +123,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { executeLazy(executeContext = IO) { if (book.isLocal) { book.tocUrl = "" - book.getRemoteUrl()?.let { - val bookWebDav = AppWebDav.defaultBookWebDav - ?: throw NoStackTraceException("webDav没有配置") - val remoteBook = bookWebDav.getRemoteBook(it) - if (remoteBook == null) { - book.origin = BookType.localTag - } else if (remoteBook.lastModify > book.lastCheckTime) { - val uri = bookWebDav.downloadRemoteBook(remoteBook) - book.bookUrl = if (uri.isContentScheme()) uri.toString() else uri.path!! - book.lastCheckTime = remoteBook.lastModify - } - } + remoteBookRepository.refreshLocalBook(book) } else { val bs = bookSource ?: return@executeLazy if (book.originName != bs.bookSourceName) { @@ -159,25 +149,9 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { val book = bookData.value ?: return if (!book.isLocal) return - val remoteUrl = book.getRemoteUrl() ?: return - execute { waitDialogData.postValue(true) - val bookWebDav = AppWebDav.defaultBookWebDav - ?: throw NoStackTraceException("webDav没有配置") - - val remoteBook = bookWebDav.getRemoteBook(remoteUrl) - ?: throw NoStackTraceException("远程文件不存在") - - val downloadBookUri = bookWebDav.downloadRemoteBook(remoteBook) - - val importedBooks = LocalBook.importFiles(downloadBookUri) - val newBook = importedBooks.firstOrNull() ?: throw NoStackTraceException("导入失败") - - newBook.durChapterIndex = book.durChapterIndex - newBook.durChapterPos = book.durChapterPos - newBook.order = book.order - newBook.group = book.group + val newBook = remoteBookRepository.syncBookFromRemote(book) appDb.bookDao.delete(book) appDb.bookDao.insert(newBook) @@ -188,6 +162,21 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { context.toastOnUi("同步完成") }.onFinally { waitDialogData.postValue(false) + }.onError { + context.toastOnUi(it.localizedMessage) + } + } + + fun uploadBook(book: Book, success: () -> Unit) { + execute { + waitDialogData.postValue(true) + remoteBookRepository.uploadBook(book) + saveBook(book) + success.invoke() + }.onFinally { + waitDialogData.postValue(false) + }.onError { + context.toastOnUi(it.localizedMessage) } }