mirror of
https://github.com/gedoor/legado.git
synced 2024-07-04 23:36:56 +08:00
添加LibArchive解压
This commit is contained in:
parent
4db0b50230
commit
d7a79dde22
|
@ -250,6 +250,9 @@ dependencies {
|
|||
//颜色选择
|
||||
implementation('com.jaredrummler:colorpicker:1.1.0')
|
||||
|
||||
//压缩解压
|
||||
implementation 'me.zhanghai.android.libarchive:library:1.0.1'
|
||||
|
||||
//apache
|
||||
implementation('org.apache.commons:commons-text:1.10.0')
|
||||
//noinspection GradleDependency,1.23.0对安卓支持不好
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ open class ScrollMultiAutoCompleteTextView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
override fun onScroll(
|
||||
e1: MotionEvent,
|
||||
e1: MotionEvent?,
|
||||
e2: MotionEvent,
|
||||
distanceX: Float,
|
||||
distanceY: Float
|
||||
|
|
|
@ -57,7 +57,7 @@ class ScrollTextView(context: Context, attrs: AttributeSet?) :
|
|||
}
|
||||
|
||||
override fun onScroll(
|
||||
e1: MotionEvent,
|
||||
e1: MotionEvent?,
|
||||
e2: MotionEvent,
|
||||
distanceX: Float,
|
||||
distanceY: Float
|
||||
|
|
|
@ -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<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(
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<OutputStream> {
|
|||
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(
|
||||
fileName: String,
|
||||
vararg subDirs: String
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<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 {
|
||||
val uri = this
|
||||
return object : RequestBody() {
|
||||
|
|
|
@ -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))
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1120,7 +1120,7 @@
|
|||
<string name="add_all_to_bookshelf">全部加入书架</string>
|
||||
<string name="page_to">页至</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="export_book">导出书籍</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="export_wait">等待导出</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>
|
||||
|
|
|
@ -1123,7 +1123,7 @@
|
|||
<string name="add_all_to_bookshelf">全部加入书架</string>
|
||||
<string name="page_to">页至</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="export_book">导出书籍</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="export_wait">等待导出</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>
|
||||
|
|
|
@ -1123,7 +1123,7 @@
|
|||
<string name="add_all_to_bookshelf">全部加入书架</string>
|
||||
<string name="page_to">页至</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="export_book">导出书籍</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="export_wait">等待导出</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>
|
||||
|
|
|
@ -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="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="page_to">Trang tới</string>
|
||||
<string name="page_to">Trang tới</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="export_book">导出书籍</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="open_book_info_by_click_title">点击书名打开详情</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>
|
|
@ -1120,7 +1120,7 @@
|
|||
<string name="add_all_to_bookshelf">全部加入书架</string>
|
||||
<string name="page_to">页至</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="export_book">导出书籍</string>
|
||||
<string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
|
||||
|
@ -1128,4 +1128,6 @@
|
|||
<string name="change_source_delay">换源间隔</string>
|
||||
<string name="open_book_info_by_click_title">点击书名打开详情</string>
|
||||
<string name="export_wait">等待导出</string>
|
||||
<string name="use_lib_archive">使用LibArchive</string>
|
||||
<string name="pref_use_lib_archive_summary">使用libarchive解壓檔案,支持解壓RAR5。</string>
|
||||
</resources>
|
||||
|
|
|
@ -1122,7 +1122,7 @@
|
|||
<string name="add_all_to_bookshelf">全部加入书架</string>
|
||||
<string name="page_to">页至</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="export_book">导出书籍</string>
|
||||
<string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
|
||||
|
@ -1130,4 +1130,6 @@
|
|||
<string name="change_source_delay">换源间隔</string>
|
||||
<string name="open_book_info_by_click_title">点击书名打开详情</string>
|
||||
<string name="export_wait">等待导出</string>
|
||||
<string name="use_lib_archive">使用LibArchive</string>
|
||||
<string name="pref_use_lib_archive_summary">使用libarchive解壓檔案,支持解壓RAR5。</string>
|
||||
</resources>
|
||||
|
|
|
@ -1122,7 +1122,7 @@
|
|||
<string name="add_all_to_bookshelf">全部加入书架</string>
|
||||
<string name="page_to">页至</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="export_book">导出书籍</string>
|
||||
<string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
|
||||
|
@ -1130,4 +1130,6 @@
|
|||
<string name="change_source_delay">换源间隔</string>
|
||||
<string name="open_book_info_by_click_title">点击书名打开详情</string>
|
||||
<string name="export_wait">等待导出</string>
|
||||
<string name="use_lib_archive">使用LibArchive</string>
|
||||
<string name="pref_use_lib_archive_summary">使用libarchive解压文件,支持解压RAR5。</string>
|
||||
</resources>
|
||||
|
|
|
@ -1123,7 +1123,7 @@
|
|||
<string name="add_all_to_bookshelf">全部加入书架</string>
|
||||
<string name="page_to">页至</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="export_book">导出书籍</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="export_wait">等待导出</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>
|
||||
|
|
|
@ -98,6 +98,12 @@
|
|||
android:summary="@string/pref_cronet_summary"
|
||||
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
|
||||
android:defaultValue="false"
|
||||
android:key="antiAlias"
|
||||
|
|
12
build.gradle
12
build.gradle
|
@ -2,12 +2,12 @@
|
|||
|
||||
buildscript {
|
||||
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'
|
||||
room_version = '2.5.2'
|
||||
}
|
||||
|
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
#Sat Jul 22 18:39:27 CST 2023
|
||||
#Tue Sep 12 01:36:42 CST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
Loading…
Reference in New Issue
Block a user