Просмотр исходного кода

Localbook实现导入压缩文件

Xwite 3 лет назад
Родитель
Сommit
593a0654f0

+ 0 - 2
.github/workflows/test.yml

@@ -2,8 +2,6 @@ name: Test Build
 
 on:
   push:
-    branches:
-      - master
     paths:
       - '**'
       - '!**/assets/**'

+ 19 - 2
app/src/main/java/io/legado/app/model/localBook/LocalBook.kt

@@ -6,6 +6,7 @@ import androidx.documentfile.provider.DocumentFile
 import com.script.SimpleBindings
 import io.legado.app.R
 import io.legado.app.constant.AppConst
+import io.legado.app.constant.AppPattern
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.BookType
 import io.legado.app.data.appDb
@@ -31,7 +32,7 @@ import java.util.regex.Pattern
 
 /**
  * 书籍文件导入 目录正文解析
- * 支持在线文件(txt epub umd 压缩文件需要用户解压) 本地文件
+ * 支持在线文件(txt epub umd 压缩文件 本地文件
  */
 object LocalBook {
 
@@ -143,7 +144,6 @@ object LocalBook {
 
     /**
      * 下载在线的文件并自动导入到阅读(txt umd epub)
-     * 压缩文件请先提示用户解压
      */
     fun importFileOnLine(
         str: String,
@@ -188,6 +188,23 @@ object LocalBook {
         return book
     }
 
+    fun importArchiveFile(
+        uri: Uri,
+        saveFileName: String,
+        filter: ((String) -> Boolean)? = null
+    ): List<Book> {
+        val files = ArchiveUtils.deCompress(uri, filter = filter)
+        if (files.isEmpty()) throw NoStackTraceException(appCtx.getString(R.string.unsupport_archivefile_entry))
+        return files.map {
+            saveBookFile(
+                    FileInputStream(it),
+                    saveFileName
+            ).let {
+                importFile(it)
+            }
+        }
+    }
+
     /**
      * 从文件分析书籍必要信息(书名 作者等)
      */

+ 10 - 10
app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt

@@ -534,13 +534,13 @@ class BookInfoActivity :
             } else if (webFile.isSupportDecompress) {
                 /* 解压筛选后再选择导入项 */
                 viewModel.importOrDownloadWebFile<Uri>(webFile) { uri ->
-                    viewModel.deCompress(uri) { files ->
-                        if (files.size == 1) {
-                            viewModel.importBook(files[0]) {
+                    viewModel.getArchiveEntriesName(uri) { fileNames ->
+                        if (fileNames.size == 1) {
+                            viewModel.importArchiveBook(uri, fileNames[0]) {
                                 onClick?.invoke(it)
                             }
                         } else {
-                            showDecompressFileImportAlert(files, onClick)
+                            showDecompressFileImportAlert(uri, fileNames, onClick)
                         }
                     }
                 }
@@ -562,19 +562,19 @@ class BookInfoActivity :
     }
 
     private fun showDecompressFileImportAlert(
-        files: List<File>,
+        archiveFileUri: Uri,
+        fileNames: List<String>,
         success: ((Book) -> Unit)? = null
     ) {
-        if (files.isEmpty()) {
+        if (fileNames.isEmpty()) {
             toastOnUi(R.string.unsupport_archivefile_entry)
             return
         }
-        val selectorNames = files.map { it.name }
         selector(
             R.string.import_select_book,
-            selectorNames
-        ) { _, _, index ->
-            viewModel.importBook(files[index]) {
+            fileNames
+        ) { _, name, _ ->
+            viewModel.importArchiveBook(archiveFileUri, name) {
                 success?.invoke(it)
             }
         }

+ 19 - 13
app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt

@@ -289,17 +289,17 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
         }
     }
 
-    fun deCompress(archiveFileUri: Uri, onSuccess: (List<File>) -> Unit) {
+    fun getArchiveEntriesName(archiveFileUri: Uri, onSuccess: (List<String>) -> Unit) {
         execute {
-            ArchiveUtils.deCompress(archiveFileUri).filter {
-                AppPattern.bookFileRegex.matches(it.name)
+            ArchiveUtils.getArchiveFilesName(archiveFileUri) {
+                AppPattern.bookFileRegex.matches(it)
             }
         }.onError {
             when (it) {
                 is UnsupportedRarV5Exception -> context.toastOnUi("暂不支持 rar v5 解压")
                 else -> {
-                    AppLog.put("DeCompress Error:\n${it.localizedMessage}", it)
-                    context.toastOnUi("DeCompress Error:\n${it.localizedMessage}")
+                    AppLog.put("getArchiveEntriesName Error:\n${it.localizedMessage}", it)
+                    context.toastOnUi("getArchiveEntriesName Error:\n${it.localizedMessage}")
                 }
             }
         }.onSuccess {
@@ -308,19 +308,25 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
     }
 
     @Suppress("BlockingMethodInNonBlockingContext")
-    fun importBook(file: File, success: ((Book) -> Unit)? = null) {
+    fun importArchiveBook(
+        archiveFileUri: Uri,
+        archiveEntryName: String,
+        success: ((Book) -> Unit)? = null
+    ) {
         execute {
-            val suffix = file.name.substringAfterLast(".")
-            LocalBook.saveBookFile(
-                FileInputStream(file),
+            val suffix = archiveEntryName.substringAfterLast(".")
+            LocalBook.importArchiveFile(
+                archiveFileUri,
                 bookData.value!!.getExportFileName(suffix)
-            )
+            ) {
+                it.contains(archiveEntryName)
+            }.first()
         }.onSuccess {
-            val book = changeToLocalBook(LocalBook.importFile(it))
+            val book = changeToLocalBook(it)
             success?.invoke(book)
         }.onError {
-            AppLog.put("ImportBook Error:\n${it.localizedMessage}", it)
-            context.toastOnUi("ImportBook Error:\n${it.localizedMessage}")
+            AppLog.put("importArchiveBook Error:\n${it.localizedMessage}", it)
+            context.toastOnUi("importArchiveBook Error:\n${it.localizedMessage}")
         }
     }
 

+ 58 - 12
app/src/main/java/io/legado/app/utils/ArchiveUtils.kt

@@ -20,50 +20,96 @@ object ArchiveUtils {
 
     fun deCompress(
         archiveUri: Uri,
-        path: String = TEMP_PATH
+        path: String = TEMP_PATH,
+        filter: ((String) -> Boolean)? = null
     ): List<File> {
-        return deCompress(FileDoc.fromUri(archiveUri, false), path)
+        return deCompress(FileDoc.fromUri(archiveUri, false), path, filter)
     }
 
     fun deCompress(
         archivePath: String,
-        path: String = TEMP_PATH
+        path: String = TEMP_PATH,
+        filter: ((String) -> Boolean)? = null
     ): List<File> {
-        return deCompress(Uri.parse(archivePath), path)
+        return deCompress(Uri.parse(archivePath), path, filter)
     }
 
     fun deCompress(
         archiveFile: File,
-        path: String = TEMP_PATH
+        path: String = TEMP_PATH,
+        filter: ((String) -> Boolean)? = null
     ): List<File> {
-        return deCompress(FileDoc.fromFile(archiveFile), path)
+        return deCompress(FileDoc.fromFile(archiveFile), path, filter)
     }
 
     fun deCompress(
         archiveDoc: DocumentFile,
-        path: String = TEMP_PATH
+        path: String = TEMP_PATH,
+        filter: ((String) -> Boolean)? = null
     ): List<File> {
-        return deCompress(FileDoc.fromDocumentFile(archiveDoc), path)
+        return deCompress(FileDoc.fromDocumentFile(archiveDoc), path, filter)
     }
 
     fun deCompress(
         archiveFileDoc: FileDoc,
-        path: String = TEMP_PATH
+        path: String = TEMP_PATH,
+        filter: ((String) -> Boolean)? = null
     ): List<File> {
         if (archiveFileDoc.isDir) throw IllegalArgumentException("Unexpected Folder input")
         val name = archiveFileDoc.name
+        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)
-                name.endsWith(".rar", ignoreCase = true) -> RarUtils.unRarToPath(it, workPath)
-                name.endsWith(".7z", ignoreCase = true) -> SevenZipUtils.un7zToPath(it, workPath)
+                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> = getArchiveEntriesName(FileDoc.fromUri(fileUri, false), filter)
+ 
+    fun getArchiveFilesName(
+        fileDoc: FileDoc,
+        filter: ((String) -> Boolean)? = null
+    ): List<String> {
+        val name = fileDoc.name
+        checkAchieve(name)
+        return fileDoc.openInputStream().getOrThrow().use {
+            when {
+                name.endsWith(".rar", ignoreCase = true) -> {
+                    RarUtils.getFilesName(it) {
+                        filter?.invoke(it)
+                    }
+                }
+                name.endsWith(".zip", ignoreCase = true) -> {
+                    ZipUtils.getFilesName(it) {
+                        filter?.invoke(it)
+                    }
+                }
+                name.endsWith(".7z", ignoreCase = true) -> {
+                    SevenZipUtils.getFilesName(it) {
+                        filter?.invoke(it)
+                    }
+                }
+                else -> emptyList()
+           }
+        }
+    }
+
+    fun checkAchieve(name: String) {
+        if (
+            name.endsWith(".zip", ignoreCase = true) ||
+            name.endsWith(".rar", ignoreCase = true) ||
+            name.endsWith(".7z", ignoreCase = true)
+        ) return
+        throw IllegalArgumentException("Unexpected file suffix: Only 7z rar zip Accepted")
+    }
+
     private fun getCacheFolderFileDoc(
         archiveName: String,
         workPath: String

+ 52 - 18
app/src/main/java/io/legado/app/utils/compress/RarUtils.kt

@@ -11,60 +11,65 @@ import java.io.InputStream
 object RarUtils {
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun unRarToPath(inputStream: InputStream, path: String): List<File> {
-        return unRarToPath(inputStream, File(path))
+    fun unRarToPath(inputStream: InputStream, path: String, filter: ((String) -> Boolean)? = null): List<File> {
+        return unRarToPath(inputStream, File(path), filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun unRarToPath(byteArray: ByteArray, path: String): List<File> {
-        return unRarToPath(byteArray, File(path))
+    fun unRarToPath(byteArray: ByteArray, path: String, filter: ((String) -> Boolean)? = null): List<File> {
+        return unRarToPath(byteArray, File(path), filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun unRarToPath(zipPath: String, path: String): List<File> {
-        return unRarToPath(zipPath, File(path))
+    fun unRarToPath(zipPath: String, path: String, filter: ((String) -> Boolean)? = null): List<File> {
+        return unRarToPath(zipPath, File(path), filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun unRarToPath(file: File, path: String): List<File> {
-        return unRarToPath(file, File(path))
+    fun unRarToPath(file: File, path: String, filter: ((String) -> Boolean)? = null): List<File> {
+        return unRarToPath(file, File(path), filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun unRarToPath(inputStream: InputStream, destDir: File?): List<File> {
+    fun unRarToPath(inputStream: InputStream, destDir: File?, filter: ((String) -> Boolean)? = null): List<File> {
         return Archive(inputStream).use {
-            unRarToPath(it, destDir)
+            unRarToPath(it, destDir, filter)
         }
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun unRarToPath(byteArray: ByteArray, destDir: File?): List<File> {
+    fun unRarToPath(byteArray: ByteArray, destDir: File?, filter: ((String) -> Boolean)? = null): List<File> {
         return Archive(ByteArrayInputStream(byteArray)).use {
-            unRarToPath(it, destDir)
+            unRarToPath(it, destDir, filter)
         }
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun unRarToPath(filePath: String, destDir: File?): List<File> {
+    fun unRarToPath(filePath: String, destDir: File?, filter: ((String) -> Boolean)? = null): List<File> {
         return Archive(File(filePath)).use {
-            unRarToPath(it, destDir)
+            unRarToPath(it, destDir, filter)
         }
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun unRarToPath(file: File, destDir: File?): List<File> {
+    fun unRarToPath(file: File, destDir: File?, filter: ((String) -> Boolean)? = null): List<File> {
         return Archive(file).use {
-            unRarToPath(it, destDir)
+            unRarToPath(it, destDir, filter)
         }
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    private fun unRarToPath(archive: Archive, destDir: File?): List<File> {
+    private fun unRarToPath(
+        archive: Archive, 
+        destDir: File?,
+        filter: ((String) -> Boolean)? = null
+    ): List<File> {
         destDir ?: throw NullPointerException("解决路径不能为空")
         val files = arrayListOf<File>()
         var entry: FileHeader?
         while (archive.nextFileHeader().also { entry = it } != null) {
-            val entryFile = File(destDir, entry!!.fileName)
+            val entryName = entry!!.fileName
+            val entryFile = File(destDir, entryName)
             if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath)) {
                 throw SecurityException("压缩文件只能解压到指定路径")
             }
@@ -77,6 +82,7 @@ object RarUtils {
             if (entryFile.parentFile?.exists() != true) {
                 entryFile.parentFile?.mkdirs()
             }
+            if (filter != null && !filter.invoke(entryName)) continue
             if (!entryFile.exists()) {
                 entryFile.createNewFile()
                 entryFile.setReadable(true)
@@ -90,4 +96,32 @@ object RarUtils {
         return files
     }
 
+
+    /* 遍历目录获取所有文件名 */
+    @Throws(NullPointerException::class, SecurityException::class)
+    fun getFilesName(
+        inputStream: InputStream,
+        filter: ((String) -> Boolean)? = null
+    ): List<String> {
+        return Archive(inputStream).use {
+            getFilesName(it, filter)
+        }
+    }
+
+    @Throws(NullPointerException::class, SecurityException::class)
+    private fun getFilesName(
+        archive: Archive,
+        filter: ((String) -> Boolean)? = null
+    ): List<String> {
+        val fileNames = mutableList<String>()
+        var entry: FileHeader?
+        while (archive.nextFileHeader().also { entry = it } != null) {
+            if (entry!!.isDirectory) {
+                continue
+            }
+            fileNames.add(entry!!.fileName)
+        }
+        return fileNames
+    }
+
 }

+ 54 - 22
app/src/main/java/io/legado/app/utils/compress/SevenZipUtils.kt

@@ -15,63 +15,64 @@ import java.nio.channels.FileChannel
 object SevenZipUtils {
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun un7zToPath(inputStream: InputStream, path: String): List<File> {
-        return un7zToPath(inputStream, File(path))
+    fun un7zToPath(inputStream: InputStream, path: String, filter: ((String) -> Boolean)? = null): List<File> {
+        return un7zToPath(inputStream, File(path), filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun un7zToPath(byteArray: ByteArray, path: String): List<File> {
-        return un7zToPath(byteArray, File(path))
+    fun un7zToPath(byteArray: ByteArray, path: String, filter: ((String) -> Boolean)? = null): List<File> {
+        return un7zToPath(byteArray, File(path),filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun un7zToPath(pfd: ParcelFileDescriptor, path: String): List<File> {
-        return un7zToPath(pfd, File(path))
+    fun un7zToPath(pfd: ParcelFileDescriptor, path: String, filter: ((String) -> Boolean)? = null): List<File> {
+        return un7zToPath(pfd, File(path), filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun un7zToPath(fileChannel: FileChannel, path: String): List<File> {
-        return un7zToPath(fileChannel, File(path))
+    fun un7zToPath(fileChannel: FileChannel, path: String, filter: ((String) -> Boolean)? = null): List<File> {
+        return un7zToPath(fileChannel, File(path), filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun un7zToPath(inputStream: InputStream, destDir: File?): List<File> {
-        return un7zToPath(SevenZFile(SeekableInMemoryByteChannel(inputStream.readBytes())), destDir)
+    fun un7zToPath(inputStream: InputStream, destDir: File?, filter: ((String) -> Boolean)? = null): List<File> {
+        return un7zToPath(SevenZFile(SeekableInMemoryByteChannel(inputStream.readBytes())), destDir, filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun un7zToPath(byteArray: ByteArray, destDir: File?): List<File> {
-        return un7zToPath(SevenZFile(SeekableInMemoryByteChannel(byteArray)), destDir)
+    fun un7zToPath(byteArray: ByteArray, destDir: File?, filter: ((String) -> Boolean)? = null): List<File> {
+        return un7zToPath(SevenZFile(SeekableInMemoryByteChannel(byteArray)), destDir, filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun un7zToPath(pfd: ParcelFileDescriptor, destDir: File?): List<File> {
-        return un7zToPath(SevenZFile(ParcelFileDescriptorChannel(pfd)), destDir)
+    fun un7zToPath(pfd: ParcelFileDescriptor, destDir: File?, filter: ((String) -> Boolean)? = null): List<File> {
+        return un7zToPath(SevenZFile(ParcelFileDescriptorChannel(pfd)), destDir, filter)
     }
 
     @SuppressLint("NewApi")
     @Throws(NullPointerException::class, SecurityException::class)
-    fun un7zToPath(fileChannel: FileChannel, destDir: File?): List<File> {
-        return un7zToPath(SevenZFile(fileChannel), destDir)
+    fun un7zToPath(fileChannel: FileChannel, destDir: File?, filter: ((String) -> Boolean)? = null): List<File> {
+        return un7zToPath(SevenZFile(fileChannel), destDir, filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun un7zToPath(file: File, destDir: File?): List<File> {
-        return un7zToPath(SevenZFile(file), destDir)
+    fun un7zToPath(file: File, destDir: File?, filter: ((String) -> Boolean)? = null): List<File> {
+        return un7zToPath(SevenZFile(file), destDir, filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    fun un7zToPath(filePath: String, destDir: File?): List<File> {
-        return un7zToPath(SevenZFile(File(filePath)), destDir)
+    fun un7zToPath(filePath: String, destDir: File?, filter: ((String) -> Boolean)? = null): List<File> {
+        return un7zToPath(SevenZFile(File(filePath)), destDir, filter)
     }
 
     @Throws(NullPointerException::class, SecurityException::class)
-    private fun un7zToPath(sevenZFile: SevenZFile, destDir: File?): List<File> {
+    private fun un7zToPath(sevenZFile: SevenZFile, destDir: File?, filter: ((String) -> Boolean)? = null): List<File> {
         destDir ?: throw NullPointerException("解决路径不能为空")
         val files = arrayListOf<File>()
         var entry: SevenZArchiveEntry?
         while (sevenZFile.nextEntry.also { entry = it } != null) {
-            val entryFile = File(destDir, entry!!.name)
+            val entryName = entry!!.name
+            val entryFile = File(destDir, entryName)
             if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath)) {
                 throw SecurityException("压缩文件只能解压到指定路径")
             }
@@ -84,6 +85,7 @@ object SevenZipUtils {
             if (entryFile.parentFile?.exists() != true) {
                 entryFile.parentFile?.mkdirs()
             }
+            if (filter != null && !filter.invoke(entryName)) continue
             if (!entryFile.exists()) {
                 entryFile.createNewFile()
                 entryFile.setReadable(true)
@@ -96,4 +98,34 @@ object SevenZipUtils {
         }
         return files
     }
+
+    /* 遍历目录获取所有文件名 */
+    @Throws(NullPointerException::class, SecurityException::class)
+    fun getFilesName(
+        inputStream: InputStream,
+        filter: ((String) -> Boolean)? = null
+    ): List<String> {
+        return getFilesName(
+           SevenZFile(SeekableInMemoryByteChannel(inputStream.readBytes())),
+           filter
+        )
+    }
+
+    @Throws(NullPointerException::class, SecurityException::class)
+    private fun getFilesName(
+        sevenZFile: SevenZFile,
+        filter: ((String) -> Boolean)? = null
+    ): List<String> {
+        val fileNames = mutableList<String>()
+        var entry: SevenZArchiveEntry?
+        while (sevenZFile.nextEntry.also { entry = it } != null) {
+            if (entry!!.isDirectory) {
+                continue
+            }
+            fileNames.add(entry!!.name)
+        }
+        return fileNames
+    }
+
+
 }

+ 42 - 10
app/src/main/java/io/legado/app/utils/compress/ZipUtils.kt

@@ -179,39 +179,44 @@ object ZipUtils {
     }
 
     @Throws(SecurityException::class)
-    fun unZipToPath(file: File, path: String): List<File> {
+    fun unZipToPath(file: File, path: String, filter: ((String) -> Boolean)? = null): List<File> {
         return FileInputStream(file).use {
-            unZipToPath(it, path)
+            unZipToPath(it, path, filter)
         }
     }
 
     @Throws(SecurityException::class)
-    fun unZipToPath(file: File, dir: File): List<File> {
+    fun unZipToPath(file: File, dir: File, filter: ((String) -> Boolean)? = null): List<File> {
         return FileInputStream(file).use {
-            unZipToPath(it, dir)
+            unZipToPath(it, dir, filter)
         }
     }
 
     @Throws(SecurityException::class)
-    fun unZipToPath(inputStream: InputStream, path: String): List<File> {
+    fun unZipToPath(inputStream: InputStream, path: String, filter: ((String) -> Boolean)? = null): List<File> {
         return ZipArchiveInputStream(inputStream).use {
-            unZipToPath(it, File(path))
+            unZipToPath(it, File(path), filter)
         }
     }
 
     @Throws(SecurityException::class)
-    fun unZipToPath(inputStream: InputStream, dir: File): List<File> {
+    fun unZipToPath(inputStream: InputStream, dir: File, filter: ((String) -> Boolean)? = null): List<File> {
         return ZipArchiveInputStream(inputStream).use {
-            unZipToPath(it, dir)
+            unZipToPath(it, dir, filter)
         }
     }
 
     @Throws(SecurityException::class)
-    private fun unZipToPath(zipInputStream: ZipArchiveInputStream, dir: File): List<File> {
+    private fun unZipToPath(
+        zipInputStream: ZipArchiveInputStream,
+        dir: File,
+        filter: ((String) -> Boolean)? = null
+    ): List<File> {
         val files = arrayListOf<File>()
         var entry: ArchiveEntry?
         while (zipInputStream.nextEntry.also { entry = it } != null) {
-            val entryFile = File(dir, entry!!.name)
+            val entryName = entry!!.name
+            val entryFile = File(dir, entryName)
             if (!entryFile.canonicalPath.startsWith(dir.canonicalPath)) {
                 throw SecurityException("压缩文件只能解压到指定路径")
             }
@@ -224,6 +229,7 @@ object ZipUtils {
             if (entryFile.parentFile?.exists() != true) {
                 entryFile.parentFile?.mkdirs()
             }
+            if (filter != null && !filter.invoke(entryName)) continue
             if (!entryFile.exists()) {
                 entryFile.createNewFile()
                 entryFile.setReadable(true)
@@ -237,6 +243,32 @@ object ZipUtils {
         return files
     }
 
+    /* 遍历目录获取所有文件名 */
+    @Throws(SecurityException::class)
+    fun getFilesName(
+        inputStream: InputStream,
+        filter: ((String) -> Boolean)? = null
+    ): List<String> {
+        return ZipArchiveInputStream(inputStream).use {
+            getFilesName(it, filter)
+        }
+    }
+
+    @Throws(SecurityException::class)
+    private fun getFilesName(
+        zipInputStream: ZipArchiveInputStream,
+        filter: ((String) -> Boolean)? = null
+    ): List<String> {
+        val fileNames = mutableList<String>()
+        var entry: ArchiveEntry?
+        while (zipInputStream.nextEntry.also { entry = it } != null) {
+            if (entry!!.isDirectory) {
+                continue
+            }
+            fileNames.add(entry!!.name)
+        }
+        return fileNames
+    }
 
     /**
      * Return the files' path in ZIP file.