ag2s20150909 пре 3 година
родитељ
комит
78362e7783

+ 3 - 0
app/build.gradle

@@ -250,6 +250,9 @@ dependencies {
     //颜色选择
     //颜色选择
     implementation('com.jaredrummler:colorpicker:1.1.0')
     implementation('com.jaredrummler:colorpicker:1.1.0')
 
 
+    //压缩解压
+    implementation 'me.zhanghai.android.libarchive:library:1.0.1'
+
     //apache
     //apache
     implementation('org.apache.commons:commons-text:1.10.0')
     implementation('org.apache.commons:commons-text:1.10.0')
     //noinspection GradleDependency,1.23.0对安卓支持不好
     //noinspection GradleDependency,1.23.0对安卓支持不好

+ 1 - 0
app/src/main/java/io/legado/app/constant/PreferKey.kt

@@ -89,6 +89,7 @@ object PreferKey {
     const val syncBookProgress = "syncBookProgress"
     const val syncBookProgress = "syncBookProgress"
     const val cronet = "Cronet"
     const val cronet = "Cronet"
     const val antiAlias = "antiAlias"
     const val antiAlias = "antiAlias"
+    const val useLibArchive = "useLibArchive"
     const val bitmapCacheSize = "bitmapCacheSize"
     const val bitmapCacheSize = "bitmapCacheSize"
     const val preDownloadNum = "preDownloadNum"
     const val preDownloadNum = "preDownloadNum"
     const val autoRefresh = "auto_refresh"
     const val autoRefresh = "auto_refresh"

+ 19 - 2
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.AppConst
 import io.legado.app.constant.PreferKey
 import io.legado.app.constant.PreferKey
 import io.legado.app.data.appDb
 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
 import splitties.init.appCtx
 
 
 @Suppress("MemberVisibilityCanBePrivate")
 @Suppress("MemberVisibilityCanBePrivate")
 object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
 object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
     val isCronet = appCtx.getPrefBoolean(PreferKey.cronet)
     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 userAgent: String = getPrefUserAgent()
     var isEInkMode = appCtx.getPrefString(PreferKey.themeMode) == "3"
     var isEInkMode = appCtx.getPrefString(PreferKey.themeMode) == "3"
     var clickActionTL = appCtx.getPrefInt(PreferKey.clickActionTL, 2)
     var clickActionTL = appCtx.getPrefInt(PreferKey.clickActionTL, 2)
@@ -63,6 +75,11 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
                 appCtx.getPrefBoolean(PreferKey.useZhLayout)
                 appCtx.getPrefBoolean(PreferKey.useZhLayout)
 
 
             PreferKey.userAgent -> userAgent = getPrefUserAgent()
             PreferKey.userAgent -> userAgent = getPrefUserAgent()
+
+            PreferKey.antiAlias -> useAntiAlias = appCtx.getPrefBoolean(PreferKey.antiAlias)
+
+            PreferKey.useLibArchive -> useLibArchive =
+                appCtx.getPrefBoolean(PreferKey.useLibArchive)
         }
         }
     }
     }
 
 

+ 2 - 2
app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt

@@ -1104,7 +1104,7 @@ class PhotoView @JvmOverloads constructor(
         }
         }
 
 
         override fun onFling(
         override fun onFling(
-            e1: MotionEvent,
+            e1: MotionEvent?,
             e2: MotionEvent,
             e2: MotionEvent,
             velocityX: Float,
             velocityX: Float,
             velocityY: Float
             velocityY: Float
@@ -1139,7 +1139,7 @@ class PhotoView @JvmOverloads constructor(
         }
         }
 
 
         override fun onScroll(
         override fun onScroll(
-            e1: MotionEvent,
+            e1: MotionEvent?,
             e2: MotionEvent,
             e2: MotionEvent,
             distanceX: Float,
             distanceX: Float,
             distanceY: Float
             distanceY: Float

+ 1 - 1
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?) :
 class MultilineTextView(context: Context, attrs: AttributeSet?) :
     AppCompatTextView(context, attrs) {
     AppCompatTextView(context, attrs) {
 
 
-    override fun onDraw(canvas: Canvas?) {
+    override fun onDraw(canvas: Canvas) {
         calculateLines()
         calculateLines()
         super.onDraw(canvas)
         super.onDraw(canvas)
     }
     }

+ 1 - 1
app/src/main/java/io/legado/app/ui/widget/text/ScrollMultiAutoCompleteTextView.kt

@@ -57,7 +57,7 @@ open class ScrollMultiAutoCompleteTextView @JvmOverloads constructor(
             }
             }
 
 
             override fun onScroll(
             override fun onScroll(
-                e1: MotionEvent,
+                e1: MotionEvent?,
                 e2: MotionEvent,
                 e2: MotionEvent,
                 distanceX: Float,
                 distanceX: Float,
                 distanceY: Float
                 distanceY: Float

+ 1 - 1
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(
             override fun onScroll(
-                e1: MotionEvent,
+                e1: MotionEvent?,
                 e2: MotionEvent,
                 e2: MotionEvent,
                 distanceX: Float,
                 distanceX: Float,
                 distanceY: Float
                 distanceY: Float

+ 64 - 9
app/src/main/java/io/legado/app/utils/ArchiveUtils.kt

@@ -3,6 +3,8 @@ package io.legado.app.utils
 import android.net.Uri
 import android.net.Uri
 import androidx.documentfile.provider.DocumentFile
 import androidx.documentfile.provider.DocumentFile
 import io.legado.app.constant.AppPattern.archiveFileRegex
 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.RarUtils
 import io.legado.app.utils.compress.SevenZipUtils
 import io.legado.app.utils.compress.SevenZipUtils
 import io.legado.app.utils.compress.ZipUtils
 import io.legado.app.utils.compress.ZipUtils
@@ -14,6 +16,7 @@ import java.io.File
 object ArchiveUtils {
 object ArchiveUtils {
 
 
     const val TEMP_FOLDER_NAME = "ArchiveTemp"
     const val TEMP_FOLDER_NAME = "ArchiveTemp"
+
     // 临时目录 下次启动自动删除
     // 临时目录 下次启动自动删除
     val TEMP_PATH: String by lazy {
     val TEMP_PATH: String by lazy {
         appCtx.externalCache.getFile(TEMP_FOLDER_NAME).createFolderReplace().absolutePath
         appCtx.externalCache.getFile(TEMP_FOLDER_NAME).createFolderReplace().absolutePath
@@ -61,19 +64,68 @@ object ArchiveUtils {
         checkAchieve(name)
         checkAchieve(name)
         val workPathFileDoc = getCacheFolderFileDoc(name, path)
         val workPathFileDoc = getCacheFolderFileDoc(name, path)
         val workPath = workPathFileDoc.toString()
         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<String> = getArchiveFilesName(FileDoc.fromUri(fileUri, false), filter)
- 
+    fun getArchiveFilesName(fileUri: Uri, filter: ((String) -> Boolean)? = null): List<String> =
+        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<String> {
+        val name = fileDoc.name
+        checkAchieve(name)
+
+        return fileDoc.openReadPfd().getOrThrow().use {
+            try {
+                LibArchiveUtils.getFilesName(it, filter)
+            } catch (e: Exception) {
+                emptyList()
+            }
+
+        }
+
+
+    }
+
     fun getArchiveFilesName(
     fun getArchiveFilesName(
         fileDoc: FileDoc,
         fileDoc: FileDoc,
         filter: ((String) -> Boolean)? = null
         filter: ((String) -> Boolean)? = null
@@ -85,14 +137,17 @@ object ArchiveUtils {
                 name.endsWith(".rar", ignoreCase = true) -> {
                 name.endsWith(".rar", ignoreCase = true) -> {
                     RarUtils.getFilesName(it, filter)
                     RarUtils.getFilesName(it, filter)
                 }
                 }
+
                 name.endsWith(".zip", ignoreCase = true) -> {
                 name.endsWith(".zip", ignoreCase = true) -> {
                     ZipUtils.getFilesName(it, filter)
                     ZipUtils.getFilesName(it, filter)
                 }
                 }
+
                 name.endsWith(".7z", ignoreCase = true) -> {
                 name.endsWith(".7z", ignoreCase = true) -> {
                     SevenZipUtils.getFilesName(it, filter)
                     SevenZipUtils.getFilesName(it, filter)
                 }
                 }
+
                 else -> emptyList()
                 else -> emptyList()
-           }
+            }
         }
         }
     }
     }
 
 

+ 9 - 0
app/src/main/java/io/legado/app/utils/FileDocExtensions.kt

@@ -6,6 +6,7 @@ import android.app.DownloadManager
 import android.content.Context
 import android.content.Context
 import android.database.Cursor
 import android.database.Cursor
 import android.net.Uri
 import android.net.Uri
+import android.os.ParcelFileDescriptor
 import android.provider.DocumentsContract
 import android.provider.DocumentsContract
 import androidx.documentfile.provider.DocumentFile
 import androidx.documentfile.provider.DocumentFile
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
@@ -236,6 +237,14 @@ fun FileDoc.openOutputStream(): Result<OutputStream> {
     return uri.outputStream(appCtx)
     return uri.outputStream(appCtx)
 }
 }
 
 
+fun FileDoc.openReadPfd(): Result<ParcelFileDescriptor> {
+    return uri.toReadPfd(appCtx)
+}
+
+fun FileDoc.openWritePfd(): Result<ParcelFileDescriptor> {
+    return uri.toWritePfd(appCtx)
+}
+
 fun FileDoc.exists(
 fun FileDoc.exists(
     fileName: String,
     fileName: String,
     vararg subDirs: String
     vararg subDirs: String

+ 3 - 3
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)
             bitmap!!.setPixels(pixels, 0, heightPix, 0, 0, heightPix, heightPix)
             if (logo != null) {
             if (logo != null) {
                 bitmap = addLogo(bitmap, logo, ratio)
                 bitmap = addLogo(bitmap, logo, ratio)
@@ -141,7 +141,7 @@ object QRCodeUtils {
 
 
         //logo大小为二维码整体大小
         //logo大小为二维码整体大小
         val scaleFactor = srcWidth * ratio / logoWidth
         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 {
         try {
             val canvas = Canvas(bitmap!!)
             val canvas = Canvas(bitmap!!)
             canvas.drawBitmap(src, 0f, 0f, null)
             canvas.drawBitmap(src, 0f, 0f, null)
@@ -449,7 +449,7 @@ object QRCodeUtils {
         if (srcWidth <= 0 || srcHeight <= 0) {
         if (srcWidth <= 0 || srcHeight <= 0) {
             return null
             return null
         }
         }
-        var bitmap = Bitmap.createBitmap(
+        var bitmap: Bitmap? = Bitmap.createBitmap(
             srcWidth,
             srcWidth,
             srcHeight + textSize + offset * 2,
             srcHeight + textSize + offset * 2,
             Bitmap.Config.ARGB_8888
             Bitmap.Config.ARGB_8888

+ 63 - 0
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.content.Context
 import android.net.Uri
 import android.net.Uri
+import android.os.ParcelFileDescriptor
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.app.AppCompatActivity
 import androidx.documentfile.provider.DocumentFile
 import androidx.documentfile.provider.DocumentFile
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.Fragment
@@ -232,6 +233,68 @@ fun Uri.outputStream(context: Context): Result<OutputStream> {
     }
     }
 }
 }
 
 
+fun Uri.toReadPfd(context: Context): Result<ParcelFileDescriptor> {
+    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<ParcelFileDescriptor> {
+    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 {
 fun Uri.toRequestBody(contentType: MediaType? = null): RequestBody {
     val uri = this
     val uri = this
     return object : RequestBody() {
     return object : RequestBody() {

+ 348 - 0
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<Any?>(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<Any?>(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<Any?>(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<Any?>(archive) { _, _, request ->
+                try {
+                    channel.position(channel.position() + request)
+                } catch (e: IOException) {
+                    throw ArchiveException(Archive.ERRNO_FATAL, "SeekableByteChannel.position", e)
+                }
+                request
+            }
+            Archive.readSetSeekCallback<Any?>(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<Any>(
+                    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<Any>(
+                    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<Any>(
+                    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<File> {
+        return unArchive(openArchive(pfd), destDir, filter)
+    }
+
+
+    @Throws(NullPointerException::class, SecurityException::class)
+    private fun unArchive(
+        archive: Long,
+        destDir: File?,
+        filter: ((String) -> Boolean)? = null
+    ): List<File> {
+        destDir ?: throw NullPointerException("解决路径不能为空")
+        val files = arrayListOf<File>()
+
+
+        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<String> {
+        return getFilesName(openArchive(pfd), filter)
+    }
+
+
+    @Throws(SecurityException::class)
+    private fun getFilesName(
+        archive: Long,
+        filter: ((String) -> Boolean)? = null
+    ): List<String> {
+        val fileNames = mutableListOf<String>()
+        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))
+
+    }
+
+
+}

+ 3 - 1
app/src/main/res/values-es-rES/strings.xml

@@ -1120,7 +1120,7 @@
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="page_to">页至</string>
     <string name="page_to">页至</string>
     <string name="result_analyzed">Analyzed</string>
     <string name="result_analyzed">Analyzed</string>
-    <string name="bookshelf_px_4" >Comprehensive</string>
+    <string name="bookshelf_px_4">Comprehensive</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@@ -1129,4 +1129,6 @@
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="export_wait">等待导出</string>
     <string name="export_wait">等待导出</string>
     <string name="default_home_page">默认主页</string>
     <string name="default_home_page">默认主页</string>
+    <string name="use_lib_archive">Use LibArchive</string>
+    <string name="pref_use_lib_archive_summary">Using libarchive to extract files, it supports extracting RAR5.</string>
 </resources>
 </resources>

+ 3 - 1
app/src/main/res/values-ja-rJP/strings.xml

@@ -1123,7 +1123,7 @@
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="page_to">页至</string>
     <string name="page_to">页至</string>
     <string name="result_analyzed">Analyzed</string>
     <string name="result_analyzed">Analyzed</string>
-    <string name="bookshelf_px_4" >Comprehensive</string>
+    <string name="bookshelf_px_4">Comprehensive</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@@ -1132,4 +1132,6 @@
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="export_wait">等待导出</string>
     <string name="export_wait">等待导出</string>
     <string name="default_home_page">默认主页</string>
     <string name="default_home_page">默认主页</string>
+    <string name="use_lib_archive">Use LibArchive</string>
+    <string name="pref_use_lib_archive_summary">Using libarchive to extract files, it supports extracting RAR5.</string>
 </resources>
 </resources>

+ 3 - 1
app/src/main/res/values-pt-rBR/strings.xml

@@ -1123,7 +1123,7 @@
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="page_to">页至</string>
     <string name="page_to">页至</string>
     <string name="result_analyzed">Analyzed</string>
     <string name="result_analyzed">Analyzed</string>
-    <string name="bookshelf_px_4" >Comprehensive</string>
+    <string name="bookshelf_px_4">Comprehensive</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@@ -1132,4 +1132,6 @@
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="export_wait">等待导出</string>
     <string name="export_wait">等待导出</string>
     <string name="default_home_page">默认主页</string>
     <string name="default_home_page">默认主页</string>
+    <string name="use_lib_archive">Use LibArchive</string>
+    <string name="pref_use_lib_archive_summary">Using libarchive to extract files, it supports extracting RAR5.</string>
 </resources>
 </resources>

+ 4 - 2
app/src/main/res/values-vi/strings.xml

@@ -1117,9 +1117,9 @@ Còn </string>
      <string name="show_wait_up_count">Hiển thị số bản cập nhật đang đợi</string>
      <string name="show_wait_up_count">Hiển thị số bản cập nhật đang đợi</string>
      <string name="exit_app">Thoát khỏi phần mềm</string>
      <string name="exit_app">Thoát khỏi phần mềm</string>
      <string name="add_all_to_bookshelf">Thêm tất cả vào giá sách</string>
      <string name="add_all_to_bookshelf">Thêm tất cả vào giá sách</string>
-     <string name="page_to">Trang tới</string>
+    <string name="page_to">Trang tới</string>
     <string name="result_analyzed">Analyzed</string>
     <string name="result_analyzed">Analyzed</string>
-    <string name="bookshelf_px_4" >Comprehensive</string>
+    <string name="bookshelf_px_4">Comprehensive</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@@ -1127,4 +1127,6 @@ Còn </string>
     <string name="change_source_delay">换源间隔</string>
     <string name="change_source_delay">换源间隔</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="export_wait">等待导出</string>
     <string name="export_wait">等待导出</string>
+    <string name="use_lib_archive">Use LibArchive</string>
+    <string name="pref_use_lib_archive_summary">Using libarchive to extract files, it supports extracting RAR5.</string>
 </resources>
 </resources>

+ 3 - 1
app/src/main/res/values-zh-rHK/strings.xml

@@ -1120,7 +1120,7 @@
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="page_to">页至</string>
     <string name="page_to">页至</string>
     <string name="result_analyzed">解析示例</string>
     <string name="result_analyzed">解析示例</string>
-    <string name="bookshelf_px_4" >綜合排序</string>
+    <string name="bookshelf_px_4">綜合排序</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@@ -1128,4 +1128,6 @@
     <string name="change_source_delay">换源间隔</string>
     <string name="change_source_delay">换源间隔</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="export_wait">等待导出</string>
     <string name="export_wait">等待导出</string>
+    <string name="use_lib_archive">使用LibArchive</string>
+    <string name="pref_use_lib_archive_summary">使用libarchive解壓檔案,支持解壓RAR5。</string>
 </resources>
 </resources>

+ 3 - 1
app/src/main/res/values-zh-rTW/strings.xml

@@ -1122,7 +1122,7 @@
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="page_to">页至</string>
     <string name="page_to">页至</string>
     <string name="result_analyzed">解析示例</string>
     <string name="result_analyzed">解析示例</string>
-    <string name="bookshelf_px_4" >綜合排序</string>
+    <string name="bookshelf_px_4">綜合排序</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@@ -1130,4 +1130,6 @@
     <string name="change_source_delay">换源间隔</string>
     <string name="change_source_delay">换源间隔</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="export_wait">等待导出</string>
     <string name="export_wait">等待导出</string>
+    <string name="use_lib_archive">使用LibArchive</string>
+    <string name="pref_use_lib_archive_summary">使用libarchive解壓檔案,支持解壓RAR5。</string>
 </resources>
 </resources>

+ 3 - 1
app/src/main/res/values-zh/strings.xml

@@ -1122,7 +1122,7 @@
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="page_to">页至</string>
     <string name="page_to">页至</string>
     <string name="result_analyzed">解析示例</string>
     <string name="result_analyzed">解析示例</string>
-    <string name="bookshelf_px_4" >综合排序</string>
+    <string name="bookshelf_px_4">综合排序</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@@ -1130,4 +1130,6 @@
     <string name="change_source_delay">换源间隔</string>
     <string name="change_source_delay">换源间隔</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="export_wait">等待导出</string>
     <string name="export_wait">等待导出</string>
+    <string name="use_lib_archive">使用LibArchive</string>
+    <string name="pref_use_lib_archive_summary">使用libarchive解压文件,支持解压RAR5。</string>
 </resources>
 </resources>

+ 3 - 1
app/src/main/res/values/strings.xml

@@ -1123,7 +1123,7 @@
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="add_all_to_bookshelf">全部加入书架</string>
     <string name="page_to">页至</string>
     <string name="page_to">页至</string>
     <string name="result_analyzed">Analyzed</string>
     <string name="result_analyzed">Analyzed</string>
-    <string name="bookshelf_px_4" >Comprehensive</string>
+    <string name="bookshelf_px_4">Comprehensive</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="effective_replaces">起效的替换</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book">导出书籍</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
     <string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@@ -1132,4 +1132,6 @@
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="open_book_info_by_click_title">点击书名打开详情</string>
     <string name="export_wait">等待导出</string>
     <string name="export_wait">等待导出</string>
     <string name="default_home_page">默认主页</string>
     <string name="default_home_page">默认主页</string>
+    <string name="use_lib_archive">Use LibArchive</string>
+    <string name="pref_use_lib_archive_summary">Using libarchive to extract files, it supports extracting RAR5.</string>
 </resources>
 </resources>

+ 6 - 0
app/src/main/res/xml/pref_config_other.xml

@@ -98,6 +98,12 @@
             android:summary="@string/pref_cronet_summary"
             android:summary="@string/pref_cronet_summary"
             android:title="Cronet" />
             android:title="Cronet" />
 
 
+        <io.legado.app.lib.prefs.SwitchPreference
+            android:defaultValue="false"
+            android:key="useLibArchive"
+            android:summary="@string/pref_use_lib_archive_summary"
+            android:title="@string/use_lib_archive" />
+
         <io.legado.app.lib.prefs.SwitchPreference
         <io.legado.app.lib.prefs.SwitchPreference
             android:defaultValue="false"
             android:defaultValue="false"
             android:key="antiAlias"
             android:key="antiAlias"

+ 6 - 6
build.gradle

@@ -2,12 +2,12 @@
 
 
 buildscript {
 buildscript {
     ext{
     ext{
-        compile_sdk_version = 33
-        build_tool_version = '33.0.1'
-        kotlin_version = '1.9.0'
-        ksp_version="1.0.12"
-        agp_version = '8.1.0'
-        media3_version = "1.1.0"
+        compile_sdk_version = 34
+        build_tool_version = '34.0.0'
+        kotlin_version = '1.9.10'
+        ksp_version = "1.0.13"
+        agp_version = '8.1.1'
+        media3_version = "1.1.1"
         splitties_version = '3.0.0'
         splitties_version = '3.0.0'
         room_version = '2.5.2'
         room_version = '2.5.2'
     }
     }

+ 2 - 2
gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
-#Sat Jul 22 18:39:27 CST 2023
+#Tue Sep 12 01:36:42 CST 2023
 distributionBase=GRADLE_USER_HOME
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
 zipStorePath=wrapper/dists