From 99c2a2e654dc161b8b58be623c1a8347e92018d9 Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Tue, 12 Sep 2023 09:17:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0LibArchive=E8=A7=A3=E5=8E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 + .../java/io/legado/app/constant/PreferKey.kt | 1 + .../io/legado/app/help/config/AppConfig.kt | 21 +- .../legado/app/ui/widget/image/PhotoView.kt | 4 +- .../app/ui/widget/text/MultilineTextView.kt | 2 +- .../text/ScrollMultiAutoCompleteTextView.kt | 2 +- .../app/ui/widget/text/ScrollTextView.kt | 2 +- .../java/io/legado/app/utils/ArchiveUtils.kt | 73 +++- .../io/legado/app/utils/FileDocExtensions.kt | 9 + .../java/io/legado/app/utils/QRCodeUtils.kt | 6 +- .../java/io/legado/app/utils/UriExtensions.kt | 63 ++++ .../app/utils/compress/LibArchiveUtils.kt | 348 ++++++++++++++++++ app/src/main/res/values-es-rES/strings.xml | 4 +- app/src/main/res/values-ja-rJP/strings.xml | 4 +- app/src/main/res/values-pt-rBR/strings.xml | 4 +- app/src/main/res/values-vi/strings.xml | 6 +- app/src/main/res/values-zh-rHK/strings.xml | 4 +- app/src/main/res/values-zh-rTW/strings.xml | 4 +- app/src/main/res/values-zh/strings.xml | 4 +- app/src/main/res/values/strings.xml | 4 +- app/src/main/res/xml/pref_config_other.xml | 6 + build.gradle | 12 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 23 files changed, 553 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/io/legado/app/utils/compress/LibArchiveUtils.kt diff --git a/app/build.gradle b/app/build.gradle index 66959fbd80eb..6842b2294820 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -250,6 +250,8 @@ dependencies { //颜色选择 implementation('com.jaredrummler:colorpicker:1.1.0') + implementation 'me.zhanghai.android.libarchive:library:1.0.0' + //apache implementation('org.apache.commons:commons-text:1.10.0') //noinspection GradleDependency,1.23.0对安卓支持不好 diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index 3b2efff86bbb..a94d433e9701 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -89,6 +89,7 @@ object PreferKey { const val syncBookProgress = "syncBookProgress" const val cronet = "Cronet" const val antiAlias = "antiAlias" + const val useLibArchive = "useLibArchive" const val bitmapCacheSize = "bitmapCacheSize" const val preDownloadNum = "preDownloadNum" const val autoRefresh = "auto_refresh" diff --git a/app/src/main/java/io/legado/app/help/config/AppConfig.kt b/app/src/main/java/io/legado/app/help/config/AppConfig.kt index 7dd34330eece..804d53794d0f 100644 --- a/app/src/main/java/io/legado/app/help/config/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/AppConfig.kt @@ -7,13 +7,25 @@ import io.legado.app.BuildConfig import io.legado.app.constant.AppConst import io.legado.app.constant.PreferKey import io.legado.app.data.appDb -import io.legado.app.utils.* +import io.legado.app.utils.getPrefBoolean +import io.legado.app.utils.getPrefInt +import io.legado.app.utils.getPrefLong +import io.legado.app.utils.getPrefString +import io.legado.app.utils.isNightMode +import io.legado.app.utils.putPrefBoolean +import io.legado.app.utils.putPrefInt +import io.legado.app.utils.putPrefLong +import io.legado.app.utils.putPrefString +import io.legado.app.utils.removePref +import io.legado.app.utils.sysConfiguration +import io.legado.app.utils.toastOnUi import splitties.init.appCtx @Suppress("MemberVisibilityCanBePrivate") object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { val isCronet = appCtx.getPrefBoolean(PreferKey.cronet) - val useAntiAlias = appCtx.getPrefBoolean(PreferKey.antiAlias) + var useAntiAlias = appCtx.getPrefBoolean(PreferKey.antiAlias) + var useLibArchive = appCtx.getPrefBoolean(PreferKey.useLibArchive) var userAgent: String = getPrefUserAgent() var isEInkMode = appCtx.getPrefString(PreferKey.themeMode) == "3" var clickActionTL = appCtx.getPrefInt(PreferKey.clickActionTL, 2) @@ -63,6 +75,11 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { appCtx.getPrefBoolean(PreferKey.useZhLayout) PreferKey.userAgent -> userAgent = getPrefUserAgent() + + PreferKey.antiAlias -> useAntiAlias = appCtx.getPrefBoolean(PreferKey.antiAlias) + + PreferKey.useLibArchive -> useLibArchive = + appCtx.getPrefBoolean(PreferKey.useLibArchive) } } diff --git a/app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt b/app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt index 2ce1704b9917..4dc85da4faf1 100644 --- a/app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt @@ -1104,7 +1104,7 @@ class PhotoView @JvmOverloads constructor( } override fun onFling( - e1: MotionEvent, + e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float @@ -1139,7 +1139,7 @@ class PhotoView @JvmOverloads constructor( } override fun onScroll( - e1: MotionEvent, + e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float diff --git a/app/src/main/java/io/legado/app/ui/widget/text/MultilineTextView.kt b/app/src/main/java/io/legado/app/ui/widget/text/MultilineTextView.kt index 56e36f07d422..f38aeef37361 100644 --- a/app/src/main/java/io/legado/app/ui/widget/text/MultilineTextView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/text/MultilineTextView.kt @@ -8,7 +8,7 @@ import androidx.appcompat.widget.AppCompatTextView class MultilineTextView(context: Context, attrs: AttributeSet?) : AppCompatTextView(context, attrs) { - override fun onDraw(canvas: Canvas?) { + override fun onDraw(canvas: Canvas) { calculateLines() super.onDraw(canvas) } diff --git a/app/src/main/java/io/legado/app/ui/widget/text/ScrollMultiAutoCompleteTextView.kt b/app/src/main/java/io/legado/app/ui/widget/text/ScrollMultiAutoCompleteTextView.kt index 0015c85a18ab..59f020edfec2 100644 --- a/app/src/main/java/io/legado/app/ui/widget/text/ScrollMultiAutoCompleteTextView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/text/ScrollMultiAutoCompleteTextView.kt @@ -57,7 +57,7 @@ open class ScrollMultiAutoCompleteTextView @JvmOverloads constructor( } override fun onScroll( - e1: MotionEvent, + e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float diff --git a/app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt b/app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt index 8d0bb8362e70..cecd86e00552 100644 --- a/app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt @@ -57,7 +57,7 @@ class ScrollTextView(context: Context, attrs: AttributeSet?) : } override fun onScroll( - e1: MotionEvent, + e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float diff --git a/app/src/main/java/io/legado/app/utils/ArchiveUtils.kt b/app/src/main/java/io/legado/app/utils/ArchiveUtils.kt index 2851122d0af4..af686aa78378 100644 --- a/app/src/main/java/io/legado/app/utils/ArchiveUtils.kt +++ b/app/src/main/java/io/legado/app/utils/ArchiveUtils.kt @@ -3,6 +3,8 @@ package io.legado.app.utils import android.net.Uri import androidx.documentfile.provider.DocumentFile import io.legado.app.constant.AppPattern.archiveFileRegex +import io.legado.app.help.config.AppConfig +import io.legado.app.utils.compress.LibArchiveUtils import io.legado.app.utils.compress.RarUtils import io.legado.app.utils.compress.SevenZipUtils import io.legado.app.utils.compress.ZipUtils @@ -14,6 +16,7 @@ import java.io.File object ArchiveUtils { const val TEMP_FOLDER_NAME = "ArchiveTemp" + // 临时目录 下次启动自动删除 val TEMP_PATH: String by lazy { appCtx.externalCache.getFile(TEMP_FOLDER_NAME).createFolderReplace().absolutePath @@ -61,19 +64,68 @@ object ArchiveUtils { checkAchieve(name) val workPathFileDoc = getCacheFolderFileDoc(name, path) val workPath = workPathFileDoc.toString() - return archiveFileDoc.openInputStream().getOrThrow().use { - when { - name.endsWith(".zip", ignoreCase = true) -> ZipUtils.unZipToPath(it, workPath, filter) - name.endsWith(".rar", ignoreCase = true) -> RarUtils.unRarToPath(it, workPath, filter) - name.endsWith(".7z", ignoreCase = true) -> SevenZipUtils.un7zToPath(it, workPath, filter) - else -> throw IllegalArgumentException("Unexpected archive format") + + return if (AppConfig.useLibArchive) { + archiveFileDoc.openReadPfd().getOrThrow().use { + LibArchiveUtils.unArchive(it, File(workPath), filter) + } + } else { + archiveFileDoc.openInputStream().getOrThrow().use { + when { + name.endsWith(".zip", ignoreCase = true) -> ZipUtils.unZipToPath( + it, + workPath, + filter + ) + + name.endsWith(".rar", ignoreCase = true) -> RarUtils.unRarToPath( + it, + workPath, + filter + ) + + name.endsWith(".7z", ignoreCase = true) -> SevenZipUtils.un7zToPath( + it, + workPath, + filter + ) + + else -> throw IllegalArgumentException("Unexpected archive format") + } } } + + } /* 遍历目录获取文件名 */ - fun getArchiveFilesName(fileUri: Uri, filter: ((String) -> Boolean)? = null): List = getArchiveFilesName(FileDoc.fromUri(fileUri, false), filter) - + fun getArchiveFilesName(fileUri: Uri, filter: ((String) -> Boolean)? = null): List = + if (AppConfig.useLibArchive) { + getLibArchiveFilesName(FileDoc.fromUri(fileUri, false), filter) + } else { + getArchiveFilesName(FileDoc.fromUri(fileUri, false), filter) + } + + + fun getLibArchiveFilesName( + fileDoc: FileDoc, + filter: ((String) -> Boolean)? = null + ): List { + val name = fileDoc.name + checkAchieve(name) + + return fileDoc.openReadPfd().getOrThrow().use { + try { + LibArchiveUtils.getFilesName(it, filter) + } catch (e: Exception) { + emptyList() + } + + } + + + } + fun getArchiveFilesName( fileDoc: FileDoc, filter: ((String) -> Boolean)? = null @@ -85,14 +137,17 @@ object ArchiveUtils { name.endsWith(".rar", ignoreCase = true) -> { RarUtils.getFilesName(it, filter) } + name.endsWith(".zip", ignoreCase = true) -> { ZipUtils.getFilesName(it, filter) } + name.endsWith(".7z", ignoreCase = true) -> { SevenZipUtils.getFilesName(it, filter) } + else -> emptyList() - } + } } } diff --git a/app/src/main/java/io/legado/app/utils/FileDocExtensions.kt b/app/src/main/java/io/legado/app/utils/FileDocExtensions.kt index a62e5fb108e6..0ebe60858414 100644 --- a/app/src/main/java/io/legado/app/utils/FileDocExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/FileDocExtensions.kt @@ -6,6 +6,7 @@ import android.app.DownloadManager import android.content.Context import android.database.Cursor import android.net.Uri +import android.os.ParcelFileDescriptor import android.provider.DocumentsContract import androidx.documentfile.provider.DocumentFile import io.legado.app.exception.NoStackTraceException @@ -236,6 +237,14 @@ fun FileDoc.openOutputStream(): Result { return uri.outputStream(appCtx) } +fun FileDoc.openReadPfd(): Result { + return uri.toReadPfd(appCtx) +} + +fun FileDoc.openWritePfd(): Result { + return uri.toWritePfd(appCtx) +} + fun FileDoc.exists( fileName: String, vararg subDirs: String diff --git a/app/src/main/java/io/legado/app/utils/QRCodeUtils.kt b/app/src/main/java/io/legado/app/utils/QRCodeUtils.kt index 7bae9ac10707..5113ba7b763a 100644 --- a/app/src/main/java/io/legado/app/utils/QRCodeUtils.kt +++ b/app/src/main/java/io/legado/app/utils/QRCodeUtils.kt @@ -96,7 +96,7 @@ object QRCodeUtils { } // 生成二维码图片的格式 - var bitmap = Bitmap.createBitmap(heightPix, heightPix, Bitmap.Config.ARGB_8888) + var bitmap: Bitmap? = Bitmap.createBitmap(heightPix, heightPix, Bitmap.Config.ARGB_8888) bitmap!!.setPixels(pixels, 0, heightPix, 0, 0, heightPix, heightPix) if (logo != null) { bitmap = addLogo(bitmap, logo, ratio) @@ -141,7 +141,7 @@ object QRCodeUtils { //logo大小为二维码整体大小 val scaleFactor = srcWidth * ratio / logoWidth - var bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888) + var bitmap: Bitmap? = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888) try { val canvas = Canvas(bitmap!!) canvas.drawBitmap(src, 0f, 0f, null) @@ -449,7 +449,7 @@ object QRCodeUtils { if (srcWidth <= 0 || srcHeight <= 0) { return null } - var bitmap = Bitmap.createBitmap( + var bitmap: Bitmap? = Bitmap.createBitmap( srcWidth, srcHeight + textSize + offset * 2, Bitmap.Config.ARGB_8888 diff --git a/app/src/main/java/io/legado/app/utils/UriExtensions.kt b/app/src/main/java/io/legado/app/utils/UriExtensions.kt index ba50ffd2a033..4f59f7f61ff2 100644 --- a/app/src/main/java/io/legado/app/utils/UriExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/UriExtensions.kt @@ -2,6 +2,7 @@ package io.legado.app.utils import android.content.Context import android.net.Uri +import android.os.ParcelFileDescriptor import androidx.appcompat.app.AppCompatActivity import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment @@ -232,6 +233,68 @@ fun Uri.outputStream(context: Context): Result { } } +fun Uri.toReadPfd(context: Context): Result { + val uri = this + return kotlin.runCatching { + try { + if (isContentScheme()) { + DocumentFile.fromSingleUri(context, uri) + ?: throw NoStackTraceException("未获取到文件") + return@runCatching context.contentResolver.openFileDescriptor(uri, "r")!! + } else { + val path = RealPathUtil.getPath(context, uri) + ?: throw NoStackTraceException("未获取到文件") + val file = File(path) + if (file.exists()) { + return@runCatching ParcelFileDescriptor.open( + file, + ParcelFileDescriptor.MODE_READ_ONLY + ) + } else { + throw NoStackTraceException("文件不存在") + } + } + + + } catch (e: Exception) { + e.printOnDebug() + AppLog.put("读取inputStream失败:${e.localizedMessage}", e) + throw e + } + } +} + +fun Uri.toWritePfd(context: Context): Result { + val uri = this + return kotlin.runCatching { + try { + if (isContentScheme()) { + DocumentFile.fromSingleUri(context, uri) + ?: throw NoStackTraceException("未获取到文件") + return@runCatching context.contentResolver.openFileDescriptor(uri, "w")!! + } else { + val path = RealPathUtil.getPath(context, uri) + ?: throw NoStackTraceException("未获取到文件") + val file = File(path) + if (file.exists()) { + return@runCatching ParcelFileDescriptor.open( + file, + ParcelFileDescriptor.MODE_WRITE_ONLY + ) + } else { + throw NoStackTraceException("文件不存在") + } + } + + + } catch (e: Exception) { + e.printOnDebug() + AppLog.put("读取inputStream失败:${e.localizedMessage}", e) + throw e + } + } +} + fun Uri.toRequestBody(contentType: MediaType? = null): RequestBody { val uri = this return object : RequestBody() { diff --git a/app/src/main/java/io/legado/app/utils/compress/LibArchiveUtils.kt b/app/src/main/java/io/legado/app/utils/compress/LibArchiveUtils.kt new file mode 100644 index 000000000000..1f06961ddfa7 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/compress/LibArchiveUtils.kt @@ -0,0 +1,348 @@ +package io.legado.app.utils.compress + +import android.os.ParcelFileDescriptor +import android.system.ErrnoException +import android.system.Os +import android.system.OsConstants +import android.system.OsConstants.S_IFDIR +import android.system.OsConstants.S_ISDIR +import io.legado.app.lib.icu4j.CharsetDetector +import me.zhanghai.android.libarchive.Archive +import me.zhanghai.android.libarchive.ArchiveEntry +import me.zhanghai.android.libarchive.ArchiveException +import java.io.File +import java.io.FileDescriptor +import java.io.IOException +import java.io.InterruptedIOException +import java.nio.ByteBuffer +import java.nio.channels.SeekableByteChannel +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets + + +object LibArchiveUtils { + + +// @Throws(ArchiveException::class) +// fun openArchive( +// inputStream: InputStream, +// ): Long { +// val archive: Long = Archive.readNew() +// var successful = false +// try { +// Archive.setCharset(archive, StandardCharsets.UTF_8.name().toByteArray()) +// Archive.readSupportFilterAll(archive) +// Archive.readSupportFormatAll(archive) +// Archive.readSetCallbackData(archive, null) +// val buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE) +// Archive.readSetReadCallback(archive) { _, _ -> +// buffer.clear() +// val bytesRead = try { +// inputStream.read(buffer.array()) +// } catch (e: IOException) { +// throw ArchiveException(Archive.ERRNO_FATAL, "InputStream.read", e) +// } +// if (bytesRead != -1) { +// buffer.limit(bytesRead) +// buffer +// } else { +// null +// } +// } +// Archive.readSetSkipCallback(archive) { _, _, request -> +// try { +// inputStream.skip(request) +// } catch (e: IOException) { +// throw ArchiveException(Archive.ERRNO_FATAL, "InputStream.skip", e) +// } +// } +// Archive.readOpen1(archive) +// successful = true +// return archive +// +// +// } finally { +// if (!successful) { +// Archive.free(archive) +// } +// } +// +// +// } + + + @Throws(ArchiveException::class) + private fun openArchive( + channel: SeekableByteChannel, + ): Long { + val archive: Long = Archive.readNew() + var successful = false + try { + Archive.setCharset(archive, StandardCharsets.UTF_8.name().toByteArray()) + Archive.readSupportFilterAll(archive) + Archive.readSupportFormatAll(archive) + Archive.readSetCallbackData(archive, null) + val buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE) + Archive.readSetReadCallback(archive) { _, _ -> + buffer.clear() + val bytesRead = try { + channel.read(buffer) + } catch (e: IOException) { + throw ArchiveException(Archive.ERRNO_FATAL, "SeekableByteChannel.read", e) + } + if (bytesRead != -1) { + buffer.flip() + buffer + } else { + null + } + } + Archive.readSetSkipCallback(archive) { _, _, request -> + try { + channel.position(channel.position() + request) + } catch (e: IOException) { + throw ArchiveException(Archive.ERRNO_FATAL, "SeekableByteChannel.position", e) + } + request + } + Archive.readSetSeekCallback(archive) { _, _, offset, whence -> + val newPosition: Long + try { + newPosition = when (whence) { + OsConstants.SEEK_SET -> offset + OsConstants.SEEK_CUR -> channel.position() + offset + OsConstants.SEEK_END -> channel.size() + offset + else -> throw ArchiveException( + Archive.ERRNO_FATAL, + "Unknown whence $whence" + ) + } + channel.position(newPosition) + } catch (e: IOException) { + throw ArchiveException(Archive.ERRNO_FATAL, "SeekableByteChannel.position", e) + } + newPosition + } + Archive.readOpen1(archive) + successful = true + return archive + + + } finally { + if (!successful) { + Archive.free(archive) + } + } + + + } + + + @Throws(ArchiveException::class) + private fun openArchive( + pfd: ParcelFileDescriptor, + useCb: Boolean = true + ): Long { + val archive: Long = Archive.readNew() + var successful = false + try { + Archive.setCharset(archive, StandardCharsets.UTF_8.name().toByteArray()) + Archive.readSupportFilterAll(archive) + Archive.readSupportFormatAll(archive) + if (useCb) { + Archive.readSetCallbackData(archive, pfd.fileDescriptor) + val buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE) + Archive.readSetReadCallback( + archive + ) { _1: Long, fd: Any? -> + buffer.clear() + try { + Os.read(fd as FileDescriptor?, buffer) + } catch (e: ErrnoException) { + throw ArchiveException(Archive.ERRNO_FATAL, "Os.read", e) + } catch (e: InterruptedIOException) { + throw ArchiveException(Archive.ERRNO_FATAL, "Os.read", e) + } + buffer.flip() + buffer + } + Archive.readSetSkipCallback( + archive + ) { _1: Long, fd: Any?, request: Long -> + try { + Os.lseek( + fd as FileDescriptor?, request, OsConstants.SEEK_CUR + ) + } catch (e: ErrnoException) { + throw ArchiveException(Archive.ERRNO_FATAL, "Os.lseek", e) + } + request + } + Archive.readSetSeekCallback( + archive + ) { _1: Long, fd: Any?, offset: Long, whence: Int -> + try { + return@readSetSeekCallback Os.lseek( + fd as FileDescriptor?, offset, whence + ) + } catch (e: ErrnoException) { + throw ArchiveException(Archive.ERRNO_FATAL, "Os.lseek", e) + } + } + Archive.readOpen1(archive) + + } else { + Archive.readOpenFd(archive, pfd.fd, DEFAULT_BUFFER_SIZE.toLong()) + } + + successful = true + return archive + + + } finally { + if (!successful) { + Archive.free(archive) + } + } + + + } + + + @Throws(NullPointerException::class, SecurityException::class) + fun unArchive( + pfd: ParcelFileDescriptor, + destDir: File, + filter: ((String) -> Boolean)? + ): List { + return unArchive(openArchive(pfd), destDir, filter) + } + + + @Throws(NullPointerException::class, SecurityException::class) + private fun unArchive( + archive: Long, + destDir: File?, + filter: ((String) -> Boolean)? = null + ): List { + destDir ?: throw NullPointerException("解决路径不能为空") + val files = arrayListOf() + + + try { + var entry = Archive.readNextHeader(archive) + while (entry != 0L) { + val entryName = + getEntryString(ArchiveEntry.pathnameUtf8(entry), ArchiveEntry.pathname(entry)) + ?: continue + val entryFile = File(destDir, entryName) + if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath)) { + throw SecurityException("压缩文件只能解压到指定路径") + } + val entryStat = ArchiveEntry.stat(entry) + + //判断是否是文件夹 + if (S_ISDIR(entryStat.stMode)) { + if (!entryFile.exists()) { + entryFile.mkdirs() + } + entry = Archive.readNextHeader(archive) + continue + } + + + + if (entryFile.parentFile?.exists() != true) { + entryFile.parentFile?.mkdirs() + } + if (filter != null && !filter.invoke(entryName)) continue + if (!entryFile.exists()) { + entryFile.createNewFile() + entryFile.setReadable(true) + entryFile.setExecutable(true) + } + + ParcelFileDescriptor.open(entryFile, ParcelFileDescriptor.MODE_WRITE_ONLY).use { + Archive.readDataIntoFd(archive, it.fd) + files.add(entryFile) + } + + entry = Archive.readNextHeader(archive) + + + } + } finally { + Archive.free(archive) + } + + + + + + return files + + + } + + fun getFilesName(pfd: ParcelFileDescriptor, filter: ((String) -> Boolean)?): List { + return getFilesName(openArchive(pfd), filter) + } + + + @Throws(SecurityException::class) + private fun getFilesName( + archive: Long, + filter: ((String) -> Boolean)? = null + ): List { + val fileNames = mutableListOf() + try { + //Archive.readOpenFd(archive, pfd.fd, 8192) + + var entry = Archive.readNextHeader(archive) + //val formatName: String = newStringFromBytes(Archive.formatName(archive)) + while (entry != 0L) { + val fileName = + getEntryString(ArchiveEntry.pathnameUtf8(entry), ArchiveEntry.pathname(entry)) + ?: continue + + + val entryStat = ArchiveEntry.stat(entry) + val fileType = entryStat.stMode and S_IFDIR + + + if (S_ISDIR(entryStat.stMode)) { + entry = Archive.readNextHeader(archive) + continue + } + + if (filter != null && filter.invoke(fileName)) + fileNames.add(fileName) + + entry = Archive.readNextHeader(archive) + + + } + } finally { + Archive.free(archive) + } + + + return fileNames + } + + + private fun getEntryString(utf8: String?, bytes: ByteArray?): String? { + return utf8 ?: newStringFromBytes(bytes) + } + + private fun newStringFromBytes(bytes: ByteArray?): String? { + bytes ?: return null + val cd = CharsetDetector() + cd.setText(bytes) + val c = cd.detectAll().first().name + return String(bytes, Charset.forName(c)) + + } + + +} \ No newline at end of file diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index de8aa6522b78..d525779a1b7e 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -1120,7 +1120,7 @@ 全部加入书架 页至 Analyzed - Comprehensive + Comprehensive 起效的替换 导出书籍 正在导出(%1$s),还有%2$d本待导出 @@ -1129,4 +1129,6 @@ 点击书名打开详情 等待导出 默认主页 + Use LibArchive + Using libarchive to extract files, it supports extracting RAR5. diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 098a2d0155b6..081095035c3c 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -1123,7 +1123,7 @@ 全部加入书架 页至 Analyzed - Comprehensive + Comprehensive 起效的替换 导出书籍 正在导出(%1$s),还有%2$d本待导出 @@ -1132,4 +1132,6 @@ 点击书名打开详情 等待导出 默认主页 + Use LibArchive + Using libarchive to extract files, it supports extracting RAR5. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index f6c98883a5d3..1d70d5cd9a90 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1123,7 +1123,7 @@ 全部加入书架 页至 Analyzed - Comprehensive + Comprehensive 起效的替换 导出书籍 正在导出(%1$s),还有%2$d本待导出 @@ -1132,4 +1132,6 @@ 点击书名打开详情 等待导出 默认主页 + Use LibArchive + Using libarchive to extract files, it supports extracting RAR5. diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 8992615d525a..2454a780ddbf 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1117,9 +1117,9 @@ Còn Hiển thị số bản cập nhật đang đợi Thoát khỏi phần mềm Thêm tất cả vào giá sách - Trang tới + Trang tới Analyzed - Comprehensive + Comprehensive 起效的替换 导出书籍 正在导出(%1$s),还有%2$d本待导出 @@ -1127,4 +1127,6 @@ Còn 换源间隔 点击书名打开详情 等待导出 + Use LibArchive + Using libarchive to extract files, it supports extracting RAR5. \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 5e7be45d5dcc..2fab1ce4b6d6 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -1120,7 +1120,7 @@ 全部加入书架 页至 解析示例 - 綜合排序 + 綜合排序 起效的替换 导出书籍 正在导出(%1$s),还有%2$d本待导出 @@ -1128,4 +1128,6 @@ 换源间隔 点击书名打开详情 等待导出 + 使用LibArchive + 使用libarchive解壓檔案,支持解壓RAR5。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 804cef6ddcf8..63816229194d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1122,7 +1122,7 @@ 全部加入书架 页至 解析示例 - 綜合排序 + 綜合排序 起效的替换 导出书籍 正在导出(%1$s),还有%2$d本待导出 @@ -1130,4 +1130,6 @@ 换源间隔 点击书名打开详情 等待导出 + 使用LibArchive + 使用libarchive解壓檔案,支持解壓RAR5。 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index c89a7f0b04b6..879bf5f66af7 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1122,7 +1122,7 @@ 全部加入书架 页至 解析示例 - 综合排序 + 综合排序 起效的替换 导出书籍 正在导出(%1$s),还有%2$d本待导出 @@ -1130,4 +1130,6 @@ 换源间隔 点击书名打开详情 等待导出 + 使用LibArchive + 使用libarchive解压文件,支持解压RAR5。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f129c9b9182c..d4b2a000b90b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1123,7 +1123,7 @@ 全部加入书架 页至 Analyzed - Comprehensive + Comprehensive 起效的替换 导出书籍 正在导出(%1$s),还有%2$d本待导出 @@ -1132,4 +1132,6 @@ 点击书名打开详情 等待导出 默认主页 + Use LibArchive + Using libarchive to extract files, it supports extracting RAR5. diff --git a/app/src/main/res/xml/pref_config_other.xml b/app/src/main/res/xml/pref_config_other.xml index 06299637b0e3..31de420b9d20 100644 --- a/app/src/main/res/xml/pref_config_other.xml +++ b/app/src/main/res/xml/pref_config_other.xml @@ -98,6 +98,12 @@ android:summary="@string/pref_cronet_summary" android:title="Cronet" /> + +