[优化] 远程书籍同步逻辑
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<ActivityBookInfoBinding, BookInfoViewModel>(),
|
||||
@@ -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<BookInfoViewModel>()
|
||||
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("已添加到书架")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Book>()
|
||||
val chapterListData = MutableLiveData<List<BookChapter>>()
|
||||
val webFiles = mutableListOf<WebFile>()
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user