diff --git a/app/build.gradle b/app/build.gradle index a80f86849..c416bea90 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ dependencies { //media implementation("androidx.media:media:1.6.0") - def exoplayer_version = '2.17.1' + def exoplayer_version = '2.18.0' implementation("com.google.android.exoplayer:exoplayer-core:$exoplayer_version") implementation("com.google.android.exoplayer:extension-okhttp:$exoplayer_version") diff --git a/app/cronetlib/cronet_api.jar b/app/cronetlib/cronet_api.jar index a3378143e..f1592db02 100644 Binary files a/app/cronetlib/cronet_api.jar and b/app/cronetlib/cronet_api.jar differ diff --git a/app/cronetlib/cronet_impl_common_java.jar b/app/cronetlib/cronet_impl_common_java.jar index 98fdab106..33754c6fa 100644 Binary files a/app/cronetlib/cronet_impl_common_java.jar and b/app/cronetlib/cronet_impl_common_java.jar differ diff --git a/app/cronetlib/cronet_impl_native_java.jar b/app/cronetlib/cronet_impl_native_java.jar index 77d65dcb6..63321b2e6 100644 Binary files a/app/cronetlib/cronet_impl_native_java.jar and b/app/cronetlib/cronet_impl_native_java.jar differ diff --git a/app/src/main/assets/cronet.json b/app/src/main/assets/cronet.json index d175808e5..596b8eba8 100644 --- a/app/src/main/assets/cronet.json +++ b/app/src/main/assets/cronet.json @@ -1 +1 @@ -{"x86_64":"136f62aca55e9479aa3994213be6ca1d","x86":"2a7c8d7107832839d2459334c1d8f966","armeabi-v7a":"d890a3023e76d3e942485b80c74f8e9b","arm64-v8a":"d4bb1dce5666d38fda7c46a53f2c5aaa","version":"102.0.5005.125"} \ No newline at end of file +{"x86_64":"cbb7148b4ac5771e6ebd9392bfb65010","armeabi-v7a":"f4006d0fef882d5c09f75e59e5256933","arm64-v8a":"ee4a9a6c4b5faf0588a050f3fbd0f0b5","x86":"a323610b6994b79bbf17223da1192945","version":"103.0.5060.53"} \ No newline at end of file diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index a39af77be..6fb1f815b 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -13,6 +13,7 @@ **2022/06/16** +* 更新cronet: 103.0.5060.53 * 导出使用单线程,导出占用大量内存,并行容易OOM * 导出到WebDav文件夹改为books,方便导入 * WebDav导入基本能用了 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 fcd5ac6e4..9489a350a 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 @@ -6,6 +6,7 @@ import io.legado.app.exception.NoStackTraceException import io.legado.app.help.http.newCallResponse import io.legado.app.help.http.okHttpClient import io.legado.app.help.http.text +import io.legado.app.utils.NetworkUtils import io.legado.app.utils.printOnDebug import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.asRequestBody @@ -61,24 +62,28 @@ open class WebDav(urlStr: String, val authorization: Authorization) { /** - * 填充文件信息。实例化WebDAVFile对象时,并没有将远程文件的信息填充到实例中。需要手动填充! - * @return 远程文件是否存在 + * 获取当前url文件信息 */ - suspend fun indexFileInfo(): Boolean { - return !propFindResponse(ArrayList()).isNullOrEmpty() + suspend fun getWebDavFile(): WebDavFile? { + return kotlin.runCatching { + propFindResponse(depth = 0)?.let { + return parseBody(it).firstOrNull() + } + }.getOrNull() } /** * 列出当前路径下的文件 - * * @return 文件列表 */ @Throws(WebDavException::class) suspend fun listFiles(): List { propFindResponse()?.let { body -> - return parseDir(body) + return parseBody(body).filter { + it.path != path + } } - return ArrayList() + return emptyList() } /** @@ -112,12 +117,15 @@ open class WebDav(urlStr: String, val authorization: Authorization) { }.body?.text() } - private fun parseDir(s: String): List { + /** + * 解析webDav返回的xml + */ + private fun parseBody(s: String): List { val list = ArrayList() val document = Jsoup.parse(s) val elements = document.getElementsByTag("d:response") httpUrl?.let { urlStr -> - val baseUrl = if (urlStr.endsWith("/")) urlStr else "$urlStr/" + val baseUrl = NetworkUtils.getBaseUrl(urlStr) for (element in elements) { val href = element.getElementsByTag("d:href")[0].text() if (!href.endsWith("/")) { @@ -137,11 +145,12 @@ open class WebDav(urlStr: String, val authorization: Authorization) { val lastModify: Long = kotlin.runCatching { element.getElementsByTag("d:getlastmodified") .firstOrNull()?.text()?.let { - LocalDateTime.parse(it, dateTimeFormatter).toInstant(ZoneOffset.of("+8")).toEpochMilli() + LocalDateTime.parse(it, dateTimeFormatter) + .toInstant(ZoneOffset.of("+8")).toEpochMilli() } }.getOrNull() ?: 0 webDavFile = WebDavFile( - baseUrl + fileName, + "$baseUrl$href", authorization, displayName = fileName, urlName = urlName, @@ -163,12 +172,7 @@ open class WebDav(urlStr: String, val authorization: Authorization) { * 文件是否存在 */ suspend fun exists(): Boolean { - return kotlin.runCatching { - val response = propFindResponse(depth = 0) ?: return false - val document = Jsoup.parse(response) - val elements = document.getElementsByTag("d:response") - return elements.isNotEmpty() - }.getOrDefault(false) + return getWebDavFile() != null } /** @@ -297,6 +301,9 @@ open class WebDav(urlStr: String, val authorization: Authorization) { }.isSuccess } + /** + * 检测返回结果是否正确 + */ private fun checkResult(response: Response) { if (!response.isSuccessful) { throw WebDavException("${url}\n${response.code}:${response.message}") 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 614d71fc5..db820a572 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.Dispatchers.IO import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay import splitties.init.appCtx +import kotlin.math.min @Suppress("MemberVisibilityCanBePrivate") @@ -437,7 +438,7 @@ object ReadBook : CoroutineScope by MainScope() { delay(1000) download(i) } - val minChapterIndex = durChapterIndex - 5 + val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum) for (i in durChapterIndex.minus(2) downTo minChapterIndex) { delay(1000) download(i) diff --git a/app/src/main/java/io/legado/app/ui/book/remote/RemoteBook.kt b/app/src/main/java/io/legado/app/ui/book/remote/RemoteBook.kt index b0a45c332..1b3a2d003 100644 --- a/app/src/main/java/io/legado/app/ui/book/remote/RemoteBook.kt +++ b/app/src/main/java/io/legado/app/ui/book/remote/RemoteBook.kt @@ -2,9 +2,15 @@ package io.legado.app.ui.book.remote data class RemoteBook( val filename: String, - val urlName: String, + val path: String, val size: Long, val contentType: String, val lastModify: Long, val isOnBookShelf: Boolean -) \ No newline at end of file +) { + + val isDir by lazy { + contentType == "folder" + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookActivity.kt index c6f109962..8e4e171ca 100644 --- a/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookActivity.kt @@ -12,6 +12,7 @@ import io.legado.app.base.VMBaseActivity import io.legado.app.databinding.ActivityRemoteBookBinding +import io.legado.app.ui.book.remote.manager.RemoteBookWebDav import io.legado.app.ui.widget.dialog.WaitDialog import io.legado.app.utils.toastOnUi @@ -50,7 +51,7 @@ class RemoteBookActivity : VMBaseActivity { - viewModel.loadRemoteBookList() + viewModel.loadRemoteBookList( + viewModel.dirList.lastOrNull()?.path ?: RemoteBookWebDav.rootBookUrl + ) } } return super.onCompatOptionsItemSelected(item) } + override fun finish() { + if (viewModel.dirList.isEmpty()) { + super.finish() + } else { + viewModel.dirList.removeLastOrNull() + val remoteBook = viewModel.dirList.lastOrNull() + viewModel.dataCallback?.clear() + if (remoteBook != null) { + binding.titleBar.title = remoteBook.filename + viewModel.loadRemoteBookList(remoteBook.path) + } else { + binding.titleBar.setTitle(R.string.remote_book) + viewModel.loadRemoteBookList(RemoteBookWebDav.rootBookUrl) + } + } + } + + override fun openDir(remoteBook: RemoteBook) { + viewModel.dirList.add(remoteBook) + binding.titleBar.title = remoteBook.filename + viewModel.dataCallback?.clear() + viewModel.loadRemoteBookList(remoteBook.path) + } + @SuppressLint("NotifyDataSetChanged") override fun addToBookshelf(remoteBook: RemoteBook) { waitDialog.show() diff --git a/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookAdapter.kt b/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookAdapter.kt index c02094d0e..97602562e 100644 --- a/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookAdapter.kt @@ -2,6 +2,7 @@ package io.legado.app.ui.book.remote import android.content.Context import android.view.ViewGroup +import androidx.core.view.isGone import cn.hutool.core.date.LocalDateTimeUtil import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.RecyclerAdapter @@ -13,8 +14,8 @@ import io.legado.app.utils.ConvertUtils * 适配器 * @author qianfanguojin */ -class RemoteBookAdapter (context: Context, val callBack: CallBack) : - RecyclerAdapter(context){ +class RemoteBookAdapter(context: Context, val callBack: CallBack) : + RecyclerAdapter(context) { override fun getViewBinding(parent: ViewGroup): ItemRemoteBookBinding { return ItemRemoteBookBinding.inflate(inflater, parent, false) @@ -37,21 +38,31 @@ class RemoteBookAdapter (context: Context, val callBack: CallBack) : tvName.text = item.filename.substringBeforeLast(".") tvContentType.text = item.contentType tvSize.text = ConvertUtils.formatFileSize(item.size) - tvDate.text = LocalDateTimeUtil.format(LocalDateTimeUtil.of(item.lastModify), "yyyy-MM-dd") + tvDate.text = + LocalDateTimeUtil.format(LocalDateTimeUtil.of(item.lastModify), "yyyy-MM-dd") + llInfo.isGone = item.isDir + tvContentType.isGone = item.isDir + btnDownload.isGone = item.isDir } } override fun registerListener(holder: ItemViewHolder, binding: ItemRemoteBookBinding) { - - binding.btnDownload.setOnClickListener { - getItem(holder.layoutPosition)?.let { - callBack.addToBookshelf(it) + binding.root.setOnClickListener { + getItem(holder.layoutPosition)?.let { + if (it.isDir) { + callBack.openDir(it) } + } + } + binding.btnDownload.setOnClickListener { + getItem(holder.layoutPosition)?.let { + callBack.addToBookshelf(it) + } } - } interface CallBack { + fun openDir(remoteBook: RemoteBook) fun addToBookshelf(remoteBook: RemoteBook) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookManager.kt b/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookManager.kt index 132c6f354..72f975c05 100644 --- a/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookManager.kt +++ b/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookManager.kt @@ -5,7 +5,7 @@ import android.net.Uri abstract class RemoteBookManager { protected val remoteBookFolder : String = "books" abstract suspend fun initRemoteContext() - abstract suspend fun getRemoteBookList(): MutableList + abstract suspend fun getRemoteBookList(path: String): MutableList abstract suspend fun upload(localBookUri: Uri): Boolean abstract suspend fun delete(remoteBookUrl: String): Boolean diff --git a/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookViewModel.kt index 6de7c9a94..3699d79dc 100644 --- a/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/remote/RemoteBookViewModel.kt @@ -13,7 +13,10 @@ import kotlinx.coroutines.flow.flowOn import java.util.* class RemoteBookViewModel(application: Application): BaseViewModel(application){ - private var dataCallback : DataCallback? = null + + val dirList = arrayListOf() + + var dataCallback: DataCallback? = null val dataFlow = callbackFlow> { @@ -49,10 +52,10 @@ class RemoteBookViewModel(application: Application): BaseViewModel(application){ } } - fun loadRemoteBookList() { + fun loadRemoteBookList(path: String) { execute { dataCallback?.clear() - val bookList = RemoteBookWebDav.getRemoteBookList() + val bookList = RemoteBookWebDav.getRemoteBookList(path) dataCallback?.setItems(bookList) }.onError { context.toastOnUi("获取webDav书籍出错\n${it.localizedMessage}") diff --git a/app/src/main/java/io/legado/app/ui/book/remote/manager/RemoteBookWebDav.kt b/app/src/main/java/io/legado/app/ui/book/remote/manager/RemoteBookWebDav.kt index 70378138e..9c67171c5 100644 --- a/app/src/main/java/io/legado/app/ui/book/remote/manager/RemoteBookWebDav.kt +++ b/app/src/main/java/io/legado/app/ui/book/remote/manager/RemoteBookWebDav.kt @@ -19,7 +19,7 @@ import java.io.File import java.net.URLDecoder object RemoteBookWebDav : RemoteBookManager() { - private val remoteBookUrl get() = "${AppWebDav.rootWebDavUrl}${remoteBookFolder}" + val rootBookUrl get() = "${AppWebDav.rootWebDavUrl}${remoteBookFolder}" init { runBlocking { @@ -29,7 +29,7 @@ object RemoteBookWebDav : RemoteBookManager() { override suspend fun initRemoteContext() { AppWebDav.authorization?.let { - WebDav(remoteBookUrl, it).makeAsDir() + WebDav(rootBookUrl, it).makeAsDir() } } @@ -37,32 +37,37 @@ object RemoteBookWebDav : RemoteBookManager() { * 获取远程书籍列表 */ @Throws(Exception::class) - override suspend fun getRemoteBookList(): MutableList { + override suspend fun getRemoteBookList(path: String): MutableList { val remoteBooks = mutableListOf() AppWebDav.authorization?.let { //读取文件列表 - val remoteWebDavFileList: List = WebDav(remoteBookUrl, it).listFiles() + val remoteWebDavFileList: List = WebDav(path, it).listFiles() //转化远程文件信息到本地对象 remoteWebDavFileList.forEach { webDavFile -> var webDavFileName = webDavFile.displayName - var webDavUrlName = "${remoteBookUrl}${File.separator}${webDavFile.displayName}" webDavFileName = URLDecoder.decode(webDavFileName, "utf-8") - webDavUrlName = URLDecoder.decode(webDavUrlName, "utf-8") - webDavFile.isDir - - //分割后缀 - val fileExtension = webDavFileName.substringAfterLast(".") - - //扩展名符合阅读的格式则认为是书籍 - if (bookFileRegex.matches(webDavFileName)) { - val isOnBookShelf = LocalBook.isOnBookShelf(webDavFileName) + if (webDavFile.isDir) { remoteBooks.add( RemoteBook( - webDavFileName, webDavUrlName, webDavFile.size, - fileExtension, webDavFile.lastModify, isOnBookShelf + webDavFileName, webDavFile.path, webDavFile.size, + "folder", webDavFile.lastModify, false ) ) + } else { + //分割后缀 + val fileExtension = webDavFileName.substringAfterLast(".") + + //扩展名符合阅读的格式则认为是书籍 + if (bookFileRegex.matches(webDavFileName)) { + val isOnBookShelf = LocalBook.isOnBookShelf(webDavFileName) + remoteBooks.add( + RemoteBook( + webDavFileName, webDavFile.path, webDavFile.size, + fileExtension, webDavFile.lastModify, isOnBookShelf + ) + ) + } } } } ?: throw NoStackTraceException("webDav没有配置") @@ -74,7 +79,7 @@ object RemoteBookWebDav : RemoteBookManager() { */ override suspend fun getRemoteBook(remoteBook: RemoteBook): Uri? { return AppWebDav.authorization?.let { - val webdav = WebDav(remoteBook.urlName, it) + val webdav = WebDav(remoteBook.path, it) webdav.download().let { bytes -> LocalBook.saveBookFile(bytes, remoteBook.filename) } @@ -88,7 +93,7 @@ object RemoteBookWebDav : RemoteBookManager() { if (!NetworkUtils.isAvailable()) return false val localBookName = localBookUri.path?.substringAfterLast(File.separator) - val putUrl = "${remoteBookUrl}${File.separator}${localBookName}" + val putUrl = "${rootBookUrl}${File.separator}${localBookName}" AppWebDav.authorization?.let { if (localBookUri.isContentScheme()) { WebDav(putUrl, it).upload( diff --git a/app/src/main/res/layout/activity_remote_book.xml b/app/src/main/res/layout/activity_remote_book.xml index 35b5353d8..90280ac77 100644 --- a/app/src/main/res/layout/activity_remote_book.xml +++ b/app/src/main/res/layout/activity_remote_book.xml @@ -10,7 +10,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" - app:title="远程书籍" /> + app:title="@string/remote_book" /> Fail to decode bitmap Image url is empty, check replacement rules 变量说明(variableComment) + 远程书籍 diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 324995eb8..a1a09f6f7 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -999,4 +999,5 @@ Fail to decode bitmap Image url is empty, check replacement rules 变量说明(variableComment) + 远程书籍 diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 6fae8a053..a3d9f12c5 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -999,4 +999,5 @@ Fail to decode bitmap Image url is empty, check replacement rules 变量说明(variableComment) + 远程书籍 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 8afa0fb70..8bd464fcb 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -996,4 +996,5 @@ 图片解码失败 图片链接为空,检查替换净化规则 变量说明(variableComment) + 远程书籍 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 2958a2c5a..9ac815bf4 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -998,4 +998,5 @@ 图片解码失败 图片链接为空,检查替换净化规则 变量说明(variableComment) + 远程书籍 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index c9bb20512..6fd055716 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -998,4 +998,5 @@ 图片解码失败 图片链接为空,检查替换净化规则 变量说明(variableComment) + 远程书籍 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c81e399db..d32d7d058 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -999,4 +999,5 @@ Fail to decode bitmap Image url is empty, check replacement rules 变量说明(variableComment) + 远程书籍 diff --git a/gradle.properties b/gradle.properties index feea44a5d..e98ef0796 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,4 +26,4 @@ android.experimental.enableNewResourceShrinker.preciseShrinking=true # and none from the library's dependencies, thereby reducing the size of the R class for that library. android.nonTransitiveRClass=true #https://chromiumdash.appspot.com/releases?platform=Android -CronetVersion=102.0.5005.125 +CronetVersion=103.0.5060.53