This commit is contained in:
Horis 2023-05-11 13:55:19 +08:00
parent a966eea067
commit 85c07b55df
11 changed files with 103 additions and 60 deletions

View File

@ -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"))
}

View File

@ -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 {

View File

@ -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<BookProgress>(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

View File

@ -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 {

View File

@ -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<Int>()
//暂时保存跳转前进度
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)
}
}
}
}

View File

@ -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<BookSource>(it).getOrThrow())
}
}
@ -166,7 +166,7 @@ class ImportBookSourceViewModel(app: Application) : BaseViewModel(app) {
} else {
url(url)
}
}.byteStream().let {
}.byteStream().use {
allSources.addAll(GSON.fromJsonArray<BookSource>(it).getOrThrow())
}
}

View File

@ -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)
}

View File

@ -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<String>()
private val displayTitleMap = ConcurrentHashMap<String, String>()
private val handler = Handler(Looper.getMainLooper())
override val diffItemCallback: DiffUtil.ItemCallback<BookChapter>
get() = object : DiffUtil.ItemCallback<BookChapter>() {
@ -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)
}
}

View File

@ -158,6 +158,7 @@ class BackupConfigFragment : PreferenceFragment(),
showHelp()
return true
}
R.id.menu_log -> showDialogFragment<AppLogDialog>()
}
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)

View File

@ -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']

View File

@ -27,7 +27,7 @@ const store = useSourceStore();
const printDebug = ref("");
const searchKey = ref("");
watchEffect(() => {
watch(() => store.isDebuging, () => {
if (store.isDebuging) startDebug();
});