From 85c07b55dfa9a8044198b1af936c5962b1435cd8 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Thu, 11 May 2023 13:55:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/lib/cronet/AbsCallBack.kt | 3 +- .../io/legado/app/lib/cronet/NewCallBack.kt | 2 + .../main/java/io/legado/app/help/AppWebDav.kt | 23 ++++++-- .../java/io/legado/app/lib/webdav/WebDav.kt | 26 +++++++-- .../main/java/io/legado/app/model/ReadBook.kt | 31 +++++++---- .../association/ImportBookSourceViewModel.kt | 4 +- .../app/ui/book/read/ReadBookActivity.kt | 55 +++++++++---------- .../app/ui/book/toc/ChapterListAdapter.kt | 8 ++- .../app/ui/config/BackupConfigFragment.kt | 7 +++ modules/web/src/components.d.ts | 2 - modules/web/src/components/SourceDebug.vue | 2 +- 11 files changed, 103 insertions(+), 60 deletions(-) diff --git a/app/src/app/java/io/legado/app/lib/cronet/AbsCallBack.kt b/app/src/app/java/io/legado/app/lib/cronet/AbsCallBack.kt index defa8b202..e2959fb9b 100644 --- a/app/src/app/java/io/legado/app/lib/cronet/AbsCallBack.kt +++ b/app/src/app/java/io/legado/app/lib/cronet/AbsCallBack.kt @@ -124,7 +124,7 @@ abstract class AbsCallBack( onSuccess(response) //打印协议,用于调试 - DebugLog.i(javaClass.simpleName, "start[${info.negotiatedProtocol}]${info.url}") + DebugLog.i(javaClass.simpleName, "onResponseStarted[${info.negotiatedProtocol}][${info.httpStatusCode}]${info.url}") if (eventListener != null) { eventListener.responseHeadersEnd(mCall, response) eventListener.responseBodyStart(mCall) @@ -169,6 +169,7 @@ abstract class AbsCallBack( override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) { canceled.set(true) callbackResults.add(CallbackResult(CallbackStep.ON_CANCELED)) + //DebugLog.i(javaClass.simpleName, "cancel[${info?.negotiatedProtocol}]${info?.url}") eventListener?.callEnd(mCall) //onError(IOException("Cronet Request Canceled")) } diff --git a/app/src/app/java/io/legado/app/lib/cronet/NewCallBack.kt b/app/src/app/java/io/legado/app/lib/cronet/NewCallBack.kt index 4a17593bd..abe02704c 100644 --- a/app/src/app/java/io/legado/app/lib/cronet/NewCallBack.kt +++ b/app/src/app/java/io/legado/app/lib/cronet/NewCallBack.kt @@ -3,6 +3,7 @@ package io.legado.app.lib.cronet import android.os.Build import androidx.annotation.Keep import androidx.annotation.RequiresApi +import io.legado.app.utils.DebugLog import okhttp3.Call import okhttp3.Request import okhttp3.Response @@ -20,6 +21,7 @@ class NewCallBack(originalRequest: Request, mCall: Call) : AbsCallBack(originalR @Throws(IOException::class) override fun waitForDone(urlRequest: UrlRequest): Response { urlRequest.start() + //DebugLog.i(javaClass.simpleName, "start ${originalRequest.method} ${originalRequest.url}") return if (mCall.timeout().timeoutNanos() > 0) { responseFuture.get(mCall.timeout().timeoutNanos(), TimeUnit.NANOSECONDS) } else { diff --git a/app/src/main/java/io/legado/app/help/AppWebDav.kt b/app/src/main/java/io/legado/app/help/AppWebDav.kt index 28e40b49e..920f0a01c 100644 --- a/app/src/main/java/io/legado/app/help/AppWebDav.kt +++ b/app/src/main/java/io/legado/app/help/AppWebDav.kt @@ -69,6 +69,9 @@ object AppWebDav { if (!account.isNullOrBlank() && !password.isNullOrBlank()) { val mAuthorization = Authorization(account, password) checkAuthorization(mAuthorization) + WebDav(rootWebDavUrl, mAuthorization).makeAsDir() + WebDav(bookProgressUrl, mAuthorization).makeAsDir() + WebDav(exportsWebDavUrl, mAuthorization).makeAsDir() val rootBooksUrl = "${rootWebDavUrl}books" defaultBookWebDav = RemoteBookWebDav(rootBooksUrl, mAuthorization) authorization = mAuthorization @@ -78,10 +81,7 @@ object AppWebDav { @Throws(WebDavException::class) private suspend fun checkAuthorization(authorization: Authorization) { - if (!WebDav(rootWebDavUrl, authorization).makeAsDir() || - !WebDav(bookProgressUrl, authorization).makeAsDir() || - !WebDav(exportsWebDavUrl, authorization).makeAsDir() - ) { + if (!WebDav(rootWebDavUrl, authorization).check()) { appCtx.removePref(PreferKey.webDavPassword) appCtx.toastOnUi(R.string.webdav_application_authorization_error) throw WebDavException(appCtx.getString(R.string.webdav_application_authorization_error)) @@ -213,7 +213,11 @@ object AppWebDav { } private fun getProgressUrl(name: String, author: String): String { - return bookProgressUrl + UrlUtil.replaceReservedChar("${name}_${author}") + ".json" + return bookProgressUrl + getProgressFileName(name, author) + } + + private fun getProgressFileName(name: String, author: String): String { + return UrlUtil.replaceReservedChar("${name}_${author}") + ".json" } /** @@ -229,15 +233,22 @@ object AppWebDav { return GSON.fromJsonObject(json).getOrNull() } } + }.onFailure { + AppLog.put("获取书籍进度失败\n${it.localizedMessage}", it) } } return null } suspend fun downloadAllBookProgress() { - authorization ?: return + val authorization = authorization ?: return if (!NetworkUtils.isAvailable()) return + val bookProgressFiles = WebDav(bookProgressUrl, authorization).listFiles().map { + it.displayName + }.toHashSet() appDb.bookDao.all.forEach { book -> + val progressFileName = getProgressFileName(book.name, book.author) + if (!bookProgressFiles.contains(progressFileName)) return@forEach getBookProgress(book)?.let { bookProgress -> if (bookProgress.durChapterIndex > book.durChapterIndex || (bookProgress.durChapterIndex == book.durChapterIndex diff --git a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt index 0c7f63a5b..02aa799f0 100644 --- a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt +++ b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt @@ -229,10 +229,24 @@ open class WebDav( addHeader("Depth", "0") val requestBody = EXISTS.toRequestBody("application/xml".toMediaType()) method("PROPFIND", requestBody) - }.isSuccessful + }.use { it.isSuccessful } }.getOrDefault(false) } + /** + * 检查用户名密码是否有效 + */ + suspend fun check(): Boolean { + return kotlin.runCatching { + webDavClient.newCallResponse { + url(url) + addHeader("Depth", "0") + val requestBody = EXISTS.toRequestBody("application/xml".toMediaType()) + method("PROPFIND", requestBody) + }.use { it.code != 401 } + }.getOrDefault(true) + } + /** * 根据自己的URL,在远程处创建对应的文件夹 * @return 是否创建成功 @@ -245,7 +259,7 @@ open class WebDav( webDavClient.newCallResponse { url(url) method("MKCOL", null) - }.let { + }.use { checkResult(it) } } @@ -300,7 +314,7 @@ open class WebDav( webDavClient.newCallResponse { url(url) put(fileBody) - }.let { + }.use { checkResult(it) } } @@ -320,7 +334,7 @@ open class WebDav( webDavClient.newCallResponse { url(url) put(fileBody) - }.let { + }.use { checkResult(it) } } @@ -340,7 +354,7 @@ open class WebDav( webDavClient.newCallResponse { url(url) put(fileBody) - }.let { + }.use { checkResult(it) } } @@ -371,7 +385,7 @@ open class WebDav( webDavClient.newCallResponse { url(url) method("DELETE", null) - }.let { + }.use { checkResult(it) } }.onFailure { diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index d66457326..3a29aff8e 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import splitties.init.appCtx import kotlin.math.min @@ -51,6 +52,9 @@ object ReadBook : CoroutineScope by MainScope() { /* web端阅读进度记录 */ var webBookProgress: BookProgress? = null + var preDownloadTask: Coroutine<*>? = null + val downloadedChapters = hashSetOf() + //暂时保存跳转前进度 fun saveCurrentBookProcess() { if (lastBookPress != null) return //避免进度条连续跳转不能覆盖最初的进度记录 @@ -319,7 +323,7 @@ object ReadBook : CoroutineScope by MainScope() { /** * 下载正文 */ - private fun downloadIndex(index: Int) { + private suspend fun downloadIndex(index: Int) { if (index < 0) return if (index > chapterSize - 1) { upToc() @@ -331,7 +335,9 @@ object ReadBook : CoroutineScope by MainScope() { appDb.bookChapterDao.getChapter(book.bookUrl, index)?.let { chapter -> if (BookHelp.hasContent(book, chapter)) { removeLoading(chapter.index) + downloadedChapters.add(chapter.index) } else { + delay(1000) download(this, chapter, false) } } ?: removeLoading(index) @@ -482,17 +488,22 @@ object ReadBook : CoroutineScope by MainScope() { if (AppConfig.preDownloadNum < 2) { return } - Coroutine.async { + preDownloadTask?.cancel() + preDownloadTask = Coroutine.async { //预下载 - val maxChapterIndex = durChapterIndex + AppConfig.preDownloadNum - for (i in durChapterIndex.plus(2)..maxChapterIndex) { - delay(1000) - downloadIndex(i) + launch { + val maxChapterIndex = min(durChapterIndex + AppConfig.preDownloadNum, chapterSize) + for (i in durChapterIndex.plus(2)..maxChapterIndex) { + if (downloadedChapters.contains(i)) continue + downloadIndex(i) + } } - val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum) - for (i in durChapterIndex.minus(2) downTo minChapterIndex) { - delay(1000) - downloadIndex(i) + launch { + val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum) + for (i in durChapterIndex.minus(2) downTo minChapterIndex) { + if (downloadedChapters.contains(i)) continue + downloadIndex(i) + } } } } diff --git a/app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt index 49f14bbc1..fcd1cb298 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt @@ -143,7 +143,7 @@ class ImportBookSourceViewModel(app: Application) : BaseViewModel(app) { mText.isUri() -> { val uri = Uri.parse(mText) - uri.inputStream(context).getOrThrow().let { + uri.inputStream(context).getOrThrow().use { allSources.addAll(GSON.fromJsonArray(it).getOrThrow()) } } @@ -166,7 +166,7 @@ class ImportBookSourceViewModel(app: Application) : BaseViewModel(app) { } else { url(url) } - }.byteStream().let { + }.byteStream().use { allSources.addAll(GSON.fromJsonArray(it).getOrThrow()) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 6ae731842..cc665f404 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -1220,30 +1220,27 @@ class ReadBookActivity : BaseReadBookActivity(), val previousResult = binding.searchMenu.previousSearchResult fun jumpToPosition() { - ReadBook.curTextChapter?.let { - binding.searchMenu.updateSearchInfo() - val (pageIndex, lineIndex, charIndex, addLine, charIndex2) = - viewModel.searchResultPositions(it, searchResult) - ReadBook.skipToPage(pageIndex) { - launch { - isSelectingSearchResult = true - binding.readView.curPage.selectStartMoveIndex(0, lineIndex, charIndex) - when (addLine) { - 0 -> binding.readView.curPage.selectEndMoveIndex( - 0, - lineIndex, - charIndex + viewModel.searchContentQuery.length - 1 - ) - 1 -> binding.readView.curPage.selectEndMoveIndex( - 0, lineIndex + 1, charIndex2 - ) - //consider change page, jump to scroll position - -1 -> binding.readView.curPage.selectEndMoveIndex(1, 0, charIndex2) - } - binding.readView.isTextSelected = true - isSelectingSearchResult = false - } + val curTextChapter = ReadBook.curTextChapter ?: return + binding.searchMenu.updateSearchInfo() + val (pageIndex, lineIndex, charIndex, addLine, charIndex2) = + viewModel.searchResultPositions(curTextChapter, searchResult) + ReadBook.skipToPage(pageIndex) { + isSelectingSearchResult = true + binding.readView.curPage.selectStartMoveIndex(0, lineIndex, charIndex) + when (addLine) { + 0 -> binding.readView.curPage.selectEndMoveIndex( + 0, + lineIndex, + charIndex + viewModel.searchContentQuery.length - 1 + ) + 1 -> binding.readView.curPage.selectEndMoveIndex( + 0, lineIndex + 1, charIndex2 + ) + //consider change page, jump to scroll position + -1 -> binding.readView.curPage.selectEndMoveIndex(1, 0, charIndex2) } + binding.readView.isTextSelected = true + isSelectingSearchResult = false } } @@ -1281,13 +1278,11 @@ class ReadBookActivity : BaseReadBookActivity(), private fun startBackupJob() { backupJob?.cancel() - backupJob = launch { + backupJob = launch(IO) { delay(300000) - withContext(IO) { - ReadBook.book?.let { - AppWebDav.uploadBookProgress(it) - Backup.autoBack(this@ReadBookActivity) - } + ReadBook.book?.let { + AppWebDav.uploadBookProgress(it) + Backup.autoBack(this@ReadBookActivity) } } } @@ -1323,6 +1318,8 @@ class ReadBookActivity : BaseReadBookActivity(), if (ReadBook.callBack === this) { ReadBook.callBack = null } + ReadBook.preDownloadTask?.cancel() + ReadBook.downloadedChapters.clear() if (!BuildConfig.DEBUG) { Backup.autoBack(this) } diff --git a/app/src/main/java/io/legado/app/ui/book/toc/ChapterListAdapter.kt b/app/src/main/java/io/legado/app/ui/book/toc/ChapterListAdapter.kt index 261326685..0ef139856 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/ChapterListAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/ChapterListAdapter.kt @@ -1,6 +1,8 @@ package io.legado.app.ui.book.toc import android.content.Context +import android.os.Handler +import android.os.Looper import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import io.legado.app.R @@ -19,7 +21,6 @@ import io.legado.app.utils.gone import io.legado.app.utils.longToastOnUi import io.legado.app.utils.visible import kotlinx.coroutines.* -import kotlinx.coroutines.Dispatchers.Main import java.util.concurrent.ConcurrentHashMap class ChapterListAdapter(context: Context, val callback: Callback) : @@ -27,6 +28,7 @@ class ChapterListAdapter(context: Context, val callback: Callback) : val cacheFileNames = hashSetOf() private val displayTitleMap = ConcurrentHashMap() + private val handler = Handler(Looper.getMainLooper()) override val diffItemCallback: DiffUtil.ItemCallback get() = object : DiffUtil.ItemCallback() { @@ -80,7 +82,7 @@ class ChapterListAdapter(context: Context, val callback: Callback) : val displayTitle = item.getDisplayTitle(replaceRules, useReplace) ensureActive() displayTitleMap[item.title] = displayTitle - withContext(Main) { + handler.post { notifyItemChanged(i, true) } } @@ -94,7 +96,7 @@ class ChapterListAdapter(context: Context, val callback: Callback) : val displayTitle = item.getDisplayTitle(replaceRules, useReplace) ensureActive() displayTitleMap[item.title] = displayTitle - withContext(Main) { + handler.post { notifyItemChanged(i, true) } } diff --git a/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt index 847b24421..b2475c0fd 100644 --- a/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt @@ -158,6 +158,7 @@ class BackupConfigFragment : PreferenceFragment(), showHelp() return true } + R.id.menu_log -> showDialogFragment() } return false @@ -174,6 +175,7 @@ class BackupConfigFragment : PreferenceFragment(), } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + context ?: return when (key) { PreferKey.backupPath -> upPreferenceSummary(key, getPrefString(key)) PreferKey.webDavUrl, @@ -183,6 +185,7 @@ class BackupConfigFragment : PreferenceFragment(), upPreferenceSummary(key, getPrefString(key)) viewModel.upWebDavConfig() } + PreferKey.webDavDeviceName -> upPreferenceSummary(key, getPrefString(key)) } } @@ -196,22 +199,26 @@ class BackupConfigFragment : PreferenceFragment(), } else { preference.summary = value.toString() } + PreferKey.webDavAccount -> if (value.isNullOrBlank()) { preference.summary = getString(R.string.web_dav_account_s) } else { preference.summary = value.toString() } + PreferKey.webDavPassword -> if (value.isNullOrBlank()) { preference.summary = getString(R.string.web_dav_pw_s) } else { preference.summary = "*".repeat(value.toString().length) } + PreferKey.webDavDir -> preference.summary = when (value) { null -> "legado" else -> value } + else -> { if (preference is ListPreference) { val index = preference.findIndexOfValue(value) diff --git a/modules/web/src/components.d.ts b/modules/web/src/components.d.ts index e63bb697a..3753cd434 100644 --- a/modules/web/src/components.d.ts +++ b/modules/web/src/components.d.ts @@ -22,14 +22,12 @@ declare module '@vue/runtime-core' { ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElOption: typeof import('element-plus/es')['ElOption'] - ElPopover: typeof import('element-plus/es')['ElPopover'] ElSelect: typeof import('element-plus/es')['ElSelect'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] ElText: typeof import('element-plus/es')['ElText'] - ElTooltip: typeof import('element-plus/es')['ElTooltip'] PopCatalog: typeof import('./components/PopCatalog.vue')['default'] ReadSettings: typeof import('./components/ReadSettings.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] diff --git a/modules/web/src/components/SourceDebug.vue b/modules/web/src/components/SourceDebug.vue index 2b78ef2fd..633169492 100644 --- a/modules/web/src/components/SourceDebug.vue +++ b/modules/web/src/components/SourceDebug.vue @@ -27,7 +27,7 @@ const store = useSourceStore(); const printDebug = ref(""); const searchKey = ref(""); -watchEffect(() => { +watch(() => store.isDebuging, () => { if (store.isDebuging) startDebug(); });