From 7b00faceb3fa489c8bb69e383b2aedb9dcf7941e Mon Sep 17 00:00:00 2001 From: SuperMonster003 Date: Fri, 6 Jun 2025 14:30:16 +0800 Subject: [PATCH] =?UTF-8?q?6.7.0=20-=20Alpha=20-=20=E6=96=B0=E5=A2=9E=20zi?= =?UTF-8?q?p=20=E6=A8=A1=E5=9D=97=20(Ref=20to=20Auto.js=20Pro)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changelog/lang_zh-Hans.json | 3 + .../autojs/extension/StringExtensions.kt | 7 + .../autojs/autojs/runtime/ScriptRuntime.kt | 2 + .../autojs/runtime/api/augment/zip/Zip.kt | 139 +++++ .../api/augment/zip/ZipNativeObject.kt | 488 ++++++++++++++++++ 5 files changed, 639 insertions(+) create mode 100644 app/src/main/java/org/autojs/autojs/runtime/api/augment/zip/Zip.kt create mode 100644 app/src/main/java/org/autojs/autojs/runtime/api/augment/zip/ZipNativeObject.kt diff --git a/.changelog/lang_zh-Hans.json b/.changelog/lang_zh-Hans.json index c56f4141..f7f6832e 100644 --- a/.changelog/lang_zh-Hans.json +++ b/.changelog/lang_zh-Hans.json @@ -2,6 +2,9 @@ "$data": { "v6.7.0": { "released_date": "2025/06/06", + "feature": [ + "zip 模块, 用于文件压缩与解压缩操作 (Ref to [Auto.js Pro](https://g.pro.autojs.org/)) (参阅 项目文档 > [Zip](https://docs.autojs6.com/#/zip))" + ], "fix": [ "版本历史页面部分系统因字体差别导致统计数据显示不完整的问题" ], diff --git a/app/src/main/java/org/autojs/autojs/extension/StringExtensions.kt b/app/src/main/java/org/autojs/autojs/extension/StringExtensions.kt index bb5a08de..887fe9f5 100644 --- a/app/src/main/java/org/autojs/autojs/extension/StringExtensions.kt +++ b/app/src/main/java/org/autojs/autojs/extension/StringExtensions.kt @@ -1,6 +1,7 @@ package org.autojs.autojs.extension import androidx.core.net.toUri +import java.io.File import java.text.Normalizer object StringExtensions { @@ -68,4 +69,10 @@ object StringExtensions { return true } + @JvmStatic + fun String.toFile(): File { + val path = this + return File(path) + } + } diff --git a/app/src/main/java/org/autojs/autojs/runtime/ScriptRuntime.kt b/app/src/main/java/org/autojs/autojs/runtime/ScriptRuntime.kt index a13cc310..051b4828 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/ScriptRuntime.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/ScriptRuntime.kt @@ -93,6 +93,7 @@ import org.autojs.autojs.runtime.api.augment.util.VersionCodesInfo import org.autojs.autojs.runtime.api.augment.web.Web import org.autojs.autojs.runtime.api.augment.web.WebSocket import org.autojs.autojs.runtime.api.augment.web.WebSocketFields +import org.autojs.autojs.runtime.api.augment.zip.Zip import org.autojs.autojs.runtime.exception.ScriptEnvironmentException import org.autojs.autojs.runtime.exception.ScriptException import org.autojs.autojs.runtime.exception.ScriptInterruptedException @@ -764,6 +765,7 @@ class ScriptRuntime private constructor(builder: Builder) { Mime(this).augment(target, mime, true) SysProps(this).augment(target, true) SQLite(this).augment(target, true) + Zip(this).augment(target, true) NanoID.augment(target, true) Pinyin.augment(target, true) Pinyin4j.augment(target, true) diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/augment/zip/Zip.kt b/app/src/main/java/org/autojs/autojs/runtime/api/augment/zip/Zip.kt new file mode 100644 index 00000000..49edb02c --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/runtime/api/augment/zip/Zip.kt @@ -0,0 +1,139 @@ +package org.autojs.autojs.runtime.api.augment.zip + +import okhttp3.internal.closeQuietly +import org.autojs.autojs.annotation.RhinoRuntimeFunctionInterface +import org.autojs.autojs.extension.AnyExtensions.isJsNullish +import org.autojs.autojs.extension.AnyExtensions.jsBrief +import org.autojs.autojs.extension.FlexibleArray.Companion.component1 +import org.autojs.autojs.extension.FlexibleArray.Companion.component2 +import org.autojs.autojs.extension.FlexibleArray.Companion.component3 +import org.autojs.autojs.extension.StringExtensions.toFile +import org.autojs.autojs.runtime.ScriptRuntime +import org.autojs.autojs.runtime.api.augment.Augmentable +import org.autojs.autojs.runtime.api.augment.Invokable +import org.autojs.autojs.util.RhinoUtils.coerceString +import org.mozilla.javascript.NativeObject +import java.io.File +import java.time.LocalDateTime.now +import java.time.format.DateTimeFormatter.ofPattern +import java.util.UUID.randomUUID + +class Zip(private val scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime), Invokable { + + override val key = super.key.lowercase() + + override val selfAssignmentFunctions = listOf( + ::open.name, + ::zipFile.name, + ::zipDir.name, + ::zipFiles.name, + ::unzip.name, + ) + + override fun invoke(vararg args: Any?): ZipNativeObject = ensureArgumentsOnlyOne(args) { zipPath -> + open(scriptRuntime, arrayOf(zipPath)) + } + + companion object { + + @JvmStatic + @RhinoRuntimeFunctionInterface + fun open(scriptRuntime: ScriptRuntime, args: Array): ZipNativeObject = ensureArgumentsLengthInRange(args, 1..2) { argList -> + val (zipPath, options) = argList + ZipNativeObject(scriptRuntime, ::open.name, zipPath, options) + } + + @JvmStatic + @RhinoRuntimeFunctionInterface + fun zipFile(scriptRuntime: ScriptRuntime, args: Array): ZipNativeObject = ensureArgumentsLengthInRange(args, 2..3) { argList -> + var (filePath, destZipPath, options) = argList + val fileToZip = scriptRuntime.files.nonNullPath(coerceString(filePath)).toFile() + if (argList.size == 2 && destZipPath is NativeObject) { + options = destZipPath + destZipPath = null + } + val pathString = when { + !destZipPath.isJsNullish() -> coerceString(destZipPath) + else -> "${fileToZip.nameWithoutExtension}.zip" + } + ZipNativeObject(scriptRuntime, ::zipFile.name, pathString, options).tryWithSelf { + zipFile.addFile(fileToZip, zipParameters) + } + } + + @JvmStatic + @RhinoRuntimeFunctionInterface + fun zipDir(scriptRuntime: ScriptRuntime, args: Array): ZipNativeObject = ensureArgumentsLengthInRange(args, 2..3) { argList -> + var (filePath, destZipPath, options) = argList + val fileToZip = scriptRuntime.files.nonNullPath(coerceString(filePath)).toFile() + if (argList.size == 2 && destZipPath is NativeObject) { + options = destZipPath + destZipPath = null + } + val pathString = when { + !destZipPath.isJsNullish() -> coerceString(destZipPath) + else -> "${fileToZip.nameWithoutExtension}.zip" + } + ZipNativeObject(scriptRuntime, ::zipDir.name, pathString, options).tryWithSelf { + zipFile.createSplitZipFileFromFolder(fileToZip, zipParameters, false, -1) + } + } + + @JvmStatic + @RhinoRuntimeFunctionInterface + fun zipFiles(scriptRuntime: ScriptRuntime, args: Array): ZipNativeObject = ensureArgumentsLengthInRange(args, 2..3) { argList -> + var (filePathList, destZipPath, options) = argList + require(filePathList is Iterable<*>) { + "Argument filePathList ${filePathList.jsBrief()} for zip.${Companion::zipFiles.name} must be an Iterable" + } + val fileList = mutableListOf() + filePathList.forEach { filePath -> + val file = scriptRuntime.files.nonNullPath(coerceString(filePath)).toFile() + require(file.exists()) { + "File ${file.absolutePath} does not exist" + } + fileList.add(file) + } + if (argList.size == 2 && destZipPath is NativeObject) { + options = destZipPath + destZipPath = null + } + val zipDestinationPath = when { + !destZipPath.isJsNullish() -> coerceString(destZipPath) + fileList.size == 1 -> "${fileList.first().nameWithoutExtension}.zip" + else -> fileList.map { + if (it.isFile) it.parent else it.absolutePath + }.toSet().singleOrNull()?.toFile()?.nameWithoutExtension?.let { "$it.zip" } ?: randomZipName() + } + ZipNativeObject(scriptRuntime, ::zipFiles.name, zipDestinationPath, options).tryWithSelf { + zipFile.addFiles(fileList, zipParameters) + } + } + + @JvmStatic + @RhinoRuntimeFunctionInterface + fun unzip(scriptRuntime: ScriptRuntime, args: Array): ZipNativeObject = ensureArgumentsLengthInRange(args, 2..3) { argList -> + var (zipPath, destPath, options) = argList + if (argList.size == 2 && destPath is NativeObject) { + options = destPath + destPath = null + } + ZipNativeObject(scriptRuntime, ::unzip.name, zipPath, options).tryWithSelf { + zipFile.extractAll(scriptRuntime.files.nonNullPath(coerceString(destPath, "")), unzipParameters) + } + } + + private fun randomZipName(): String { + val ts = now().format(ofPattern("yyyyMMdd-HHmmss")) + val rand = "${randomUUID()}".substring(0, 4).uppercase() + return "${ts}-${rand}.zip" + } + + private fun ZipNativeObject.tryWithSelf(func: ZipNativeObject.() -> Unit): ZipNativeObject { + runCatching { func() }.getOrElse { t -> zipFile.closeQuietly(); throw t } + return this + } + + } + +} diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/augment/zip/ZipNativeObject.kt b/app/src/main/java/org/autojs/autojs/runtime/api/augment/zip/ZipNativeObject.kt new file mode 100644 index 00000000..17564520 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/runtime/api/augment/zip/ZipNativeObject.kt @@ -0,0 +1,488 @@ +package org.autojs.autojs.runtime.api.augment.zip + +import net.lingala.zip4j.ZipFile +import net.lingala.zip4j.model.ExcludeFileFilter +import net.lingala.zip4j.model.FileHeader +import net.lingala.zip4j.model.UnzipParameters +import net.lingala.zip4j.model.ZipParameters +import net.lingala.zip4j.model.ZipParameters.SymbolicLinkAction +import net.lingala.zip4j.model.enums.AesKeyStrength +import net.lingala.zip4j.model.enums.AesVersion +import net.lingala.zip4j.model.enums.CompressionLevel +import net.lingala.zip4j.model.enums.CompressionMethod +import net.lingala.zip4j.model.enums.EncryptionMethod +import org.autojs.autojs.annotation.RhinoFunctionBody +import org.autojs.autojs.annotation.RhinoStandardFunctionInterface +import org.autojs.autojs.extension.AnyExtensions.isJsNullish +import org.autojs.autojs.extension.AnyExtensions.isJsNumber +import org.autojs.autojs.extension.AnyExtensions.jsBrief +import org.autojs.autojs.extension.ArrayExtensions.toNativeArray +import org.autojs.autojs.extension.FlexibleArray.Companion.ensureArgumentsIsEmpty +import org.autojs.autojs.extension.FlexibleArray.Companion.ensureArgumentsLengthInRange +import org.autojs.autojs.extension.FlexibleArray.Companion.ensureArgumentsOnlyOne +import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire +import org.autojs.autojs.extension.StringExtensions.toFile +import org.autojs.autojs.runtime.ScriptRuntime +import org.autojs.autojs.runtime.api.StringReadable +import org.autojs.autojs.runtime.exception.WrappedIllegalArgumentException +import org.autojs.autojs.util.RhinoUtils +import org.autojs.autojs.util.RhinoUtils.NOT_CONSTRUCTABLE +import org.autojs.autojs.util.RhinoUtils.callFunction +import org.autojs.autojs.util.RhinoUtils.coerceBoolean +import org.autojs.autojs.util.RhinoUtils.coerceIntNumber +import org.autojs.autojs.util.RhinoUtils.coerceLongNumber +import org.autojs.autojs.util.RhinoUtils.coerceString +import org.autojs.autojs.util.RhinoUtils.coerceStringUppercase +import org.autojs.autojs.util.RhinoUtils.newBaseFunction +import org.autojs.autojs.util.RhinoUtils.newNativeObject +import org.autojs.autojs.util.RhinoUtils.undefined +import org.mozilla.javascript.BaseFunction +import org.mozilla.javascript.Context +import org.mozilla.javascript.Function +import org.mozilla.javascript.NativeArray +import org.mozilla.javascript.NativeObject +import org.mozilla.javascript.Scriptable +import org.mozilla.javascript.ScriptableObject +import org.mozilla.javascript.Undefined +import java.io.File + +@Suppress("unused", "UNUSED_PARAMETER") +class ZipNativeObject( + private val scriptRuntime: ScriptRuntime, + private val operationName: String, + rawPath: Any?, + rawOptions: Any? = null, +) : NativeObject(), StringReadable { + + @JvmField + val path: String = when { + rawPath.isJsNullish() -> throw WrappedIllegalArgumentException("Argument path ${rawPath.jsBrief()} is invalid for zip.$operationName") + else -> { + val pathString = coerceString(rawPath) + require(pathString.isNotBlank()) { + "Argument path ${pathString.jsBrief()} for zip.$operationName cannot be empty" + } + scriptRuntime.files.nonNullPath(pathString) + } + } + + @JvmField + val options: ScriptableObject = when { + rawOptions.isJsNullish() -> newNativeObject() + else -> { + val niceOptions = rawOptions + require(niceOptions is ScriptableObject) { + "Argument options ${niceOptions.jsBrief()} for zip.$operationName must be a JavaScript Object" + } + niceOptions + } + } + + private val optionsJavaMap = mutableMapOf() + + @JvmField + val zipFile = ZipFile(path) + + @JvmField + val zipParameters: ZipParameters = buildZipParameters(operationName, options) + + @JvmField + val unzipParameters: UnzipParameters = buildUnzipParameters(operationName, options) + + private val mPropertyNames = arrayOf( + "name" to operationName, + "path" to path, + "zipFile" to zipFile, + "options" to options, + "zipParameters" to zipParameters, + "unzipParameters" to unzipParameters, + ) + + private val mFunctionNames = arrayOf( + "toString", + ::addFile.name, + ::addFiles.name, + ::addFolder.name, + ::extractAll.name, + ::extractFile.name, + ::setPassword.name, + ::getFileHeader.name, + ::getFileHeaders.name, + ::isEncrypted.name, + ::removeFile.name, + ::isValidZipFile.name, + ::getPath.name, + ::getZipFile.name, + ) + + init { + RhinoUtils.initNativeObjectPrototype(this) + defineFunctionProperties(mFunctionNames, javaClass, PERMANENT) + defineProperty(StringReadable.KEY, newBaseFunction(StringReadable.KEY, { toStringReadable() }, NOT_CONSTRUCTABLE), READONLY or DONTENUM or PERMANENT) + mPropertyNames.forEach { pair -> + val (propertyName, value) = pair + defineProperty(propertyName, value, READONLY or PERMANENT) + } + } + + private fun buildZipParameters(funcName: String, opts: Any?): ZipParameters { + val params = ZipParameters() + if (opts.isJsNullish()) { + return params + } + require(opts is ScriptableObject) { + "Argument options ${opts.jsBrief()} for zip.$funcName must be a JavaScript Object" + } + params.aesKeyStrength = opts.inquire("aesKeyStrength", { o, def -> + o.toAesKeyStrength() ?: def + }, AesKeyStrength.KEY_STRENGTH_256).also { optionsJavaMap["aesKeyStrength"] = it } + params.aesVersion = opts.inquire("aesVersion", { o, def -> + o.toAesVersion() ?: def + }, AesVersion.TWO).also { optionsJavaMap["aesVersion"] = it } + params.compressionLevel = opts.inquire("compressionLevel", { o, def -> + o.toCompressionLevel() ?: def + }, CompressionLevel.NORMAL).also { optionsJavaMap["compressionLevel"] = it } + params.compressionMethod = opts.inquire("compressionMethod", { o, def -> + o.toCompressionMethod() ?: def + }, CompressionMethod.DEFLATE).also { optionsJavaMap["compressionMethod"] = it } + params.encryptionMethod = opts.inquire("encryptionMethod", { o, def -> + o.toEncryptionMethod() ?: def + }, EncryptionMethod.AES).also { optionsJavaMap["encryptionMethod"] = it } + opts.inquire("defaultFolderPath", ::coerceString, null)?.let { + params.defaultFolderPath = it.also { optionsJavaMap["defaultFolderPath"] = it } + } + opts.inquire("entryCRC", ::coerceLongNumber, null)?.let { + params.entryCRC = it.also { optionsJavaMap["entryCRC"] = it } + } + opts.inquire("entrySize", ::coerceLongNumber, null)?.let { + params.entrySize = it.also { optionsJavaMap["entrySize"] = it } + } + opts.inquire("excludeFileFilter") { o -> + when { + o.isJsNullish() -> null + o is BaseFunction -> ExcludeFileFilter { file -> + coerceBoolean(callFunction(o, opts, opts, arrayOf(file)), false) + } + o is ExcludeFileFilter -> o + else -> listOf( + "Options property excludeFileFilter ${o.jsBrief()}", + "for zip.$funcName must be a ExcludeFileFilter", + "or a JavaScript Function that can be used as an ExcludeFileFilter", + ).joinToString(" ").let { throw WrappedIllegalArgumentException(it) } + } + }?.let { + params.excludeFileFilter = it.also { optionsJavaMap["excludeFileFilter"] = it } + } + opts.inquire(listOf("fileComment", "comment"), ::coerceString, null)?.let { + params.fileComment = it.also { + optionsJavaMap["fileComment"] = it + optionsJavaMap["comment"] = it + } + } + opts.inquire("fileNameInZip", ::coerceString, null)?.let { + params.fileNameInZip = it.also { optionsJavaMap["fileNameInZip"] = it } + } + opts.inquire(listOf("isIncludeRootFolder", "includeRootFolder"), ::coerceBoolean, null)?.let { + params.isIncludeRootFolder = it.also { + optionsJavaMap["isIncludeRootFolder"] = it + optionsJavaMap["includeRootFolder"] = it + } + } + opts.inquire(listOf("isOverrideExistingFilesInZip", "overrideExistingFilesInZip"), ::coerceBoolean, null)?.let { + params.isOverrideExistingFilesInZip = it.also { + optionsJavaMap["isOverrideExistingFilesInZip"] = it + optionsJavaMap["overrideExistingFilesInZip"] = it + } + } + opts.inquire(listOf("isReadHiddenFiles", "readHiddenFiles"), ::coerceBoolean, null)?.let { + params.isReadHiddenFiles = it.also { + optionsJavaMap["isReadHiddenFiles"] = it + optionsJavaMap["readHiddenFiles"] = it + } + } + opts.inquire(listOf("isReadHiddenFolders", "readHiddenFolders"), ::coerceBoolean, null)?.let { + params.isReadHiddenFolders = it.also { + optionsJavaMap["isReadHiddenFolders"] = it + optionsJavaMap["readHiddenFolders"] = it + } + } + opts.inquire(listOf("isUnixMode", "unixMode"), ::coerceBoolean, null)?.let { + params.isUnixMode = it.also { + optionsJavaMap["isUnixMode"] = it + optionsJavaMap["unixMode"] = it + } + } + opts.inquire(listOf("isWriteExtendedLocalFileHeader", "writeExtendedLocalFileHeader"), ::coerceBoolean, null)?.let { + params.isWriteExtendedLocalFileHeader = it.also { + optionsJavaMap["isWriteExtendedLocalFileHeader"] = it + optionsJavaMap["writeExtendedLocalFileHeader"] = it + } + } + opts.inquire("lastModifiedFileTime", ::coerceLongNumber, null)?.let { + params.lastModifiedFileTime = it.also { optionsJavaMap["lastModifiedFileTime"] = it } + } + opts.inquire("rootFolderNameInZip", ::coerceString, null)?.let { + params.rootFolderNameInZip = it.also { optionsJavaMap["rootFolderNameInZip"] = it } + } + params.symbolicLinkAction = opts.inquire("symbolicLinkAction", { o, def -> + o.toSymbolicLinkAction() ?: def + }, SymbolicLinkAction.INCLUDE_LINKED_FILE_ONLY).also { optionsJavaMap["symbolicLinkAction"] = it } + opts.inquire(listOf("isEncryptFiles", "encryptFiles"), ::coerceBoolean, null)?.let { + params.isEncryptFiles = it.also { + optionsJavaMap["isEncryptFiles"] = it + optionsJavaMap["encryptFiles"] = it + } + } + opts.inquire("password") { coerceString(it) }?.let { password -> + zipFile.setPassword(password.toCharArray()) + params.isEncryptFiles = true + optionsJavaMap["password"] = password + } + return params + } + + private fun buildUnzipParameters(funcName: String, opts: Any?): UnzipParameters { + val params = UnzipParameters() + if (opts.isJsNullish()) { + return params + } + require(opts is ScriptableObject) { + "Argument options ${opts.jsBrief()} for zip.$funcName must be a JavaScript Object" + } + opts.inquire("isExtractSymbolicLinks", ::coerceBoolean, true) + opts.inquire("password") { coerceString(it) }?.let { password -> + zipFile.setPassword(password.toCharArray()) + } + opts.inquire(listOf("isIgnoreDateTimeAttributes", "ignoreDateTimeAttributes"), ::coerceBoolean, null)?.let { + throw WrappedIllegalArgumentException("Options property ignoreDateTimeAttributes for zip.$funcName is no longer supported since Zip4j library version 2.x") + } + return params + } + + override fun toStringReadable() = toStringRhino(this) + + private fun Any.toAesKeyStrength(): AesKeyStrength? = when { + this is AesKeyStrength -> this + this.isJsNullish() -> null + this.isJsNumber() -> AesKeyStrength.entries.find { it.rawCode == coerceIntNumber(this) } ?: when (coerceIntNumber(this)) { + 128 -> AesKeyStrength.KEY_STRENGTH_128 + 192 -> AesKeyStrength.KEY_STRENGTH_192 + 256 -> AesKeyStrength.KEY_STRENGTH_256 + else -> throw WrappedIllegalArgumentException("Options property aesKeyStrength ${this.jsBrief()} is invalid for zip.$operationName") + } + else -> when (val s = coerceStringUppercase(this).removePrefix("AES_STRENGTH_")) { + "128" -> AesKeyStrength.KEY_STRENGTH_128 + "192" -> AesKeyStrength.KEY_STRENGTH_192 + "256" -> AesKeyStrength.KEY_STRENGTH_256 + else -> AesKeyStrength.valueOf(s) + } + } + + private fun Any.toAesVersion(): AesVersion? = when { + this is AesVersion -> this + this.isJsNullish() -> null + this.isJsNumber() -> AesVersion.entries.find { it.versionNumber == coerceIntNumber(this) } + else -> when (val s = coerceStringUppercase(this)) { + "1" -> AesVersion.ONE + "2" -> AesVersion.TWO + else -> AesVersion.valueOf(s) + } + } + + private fun Any.toCompressionLevel(): CompressionLevel? = when { + this is CompressionLevel -> this + this.isJsNullish() -> null + this.isJsNumber() -> CompressionLevel.entries.find { it.level == coerceIntNumber(this) } + else -> CompressionLevel.valueOf(coerceStringUppercase(this).removePrefix("DEFLATE_LEVEL_")) + } + + private fun Any.toCompressionMethod(): CompressionMethod? = when { + this is CompressionMethod -> this + this.isJsNullish() -> null + this.isJsNumber() -> CompressionMethod.entries.find { it.code == coerceIntNumber(this) } + else -> when (val methodName = coerceStringUppercase(this).removePrefix("COMP_")) { + "AES_ENC" -> CompressionMethod.AES_INTERNAL_ONLY + else -> CompressionMethod.valueOf(methodName) + } + } + + private fun Any.toEncryptionMethod(): EncryptionMethod? = when { + this is EncryptionMethod -> this + this.isJsNullish() -> null + this.isJsNumber() -> when (coerceIntNumber(this)) { + -1 -> EncryptionMethod.NONE + 0 -> EncryptionMethod.ZIP_STANDARD + 1 -> EncryptionMethod.ZIP_STANDARD_VARIANT_STRONG + 99 -> EncryptionMethod.AES + else -> throw WrappedIllegalArgumentException("Options property encryptionMethod ${this.jsBrief()} is invalid for zip.$operationName") + } + else -> when (val s = coerceStringUppercase(this).removePrefix("ENC_METHOD_").removePrefix("ENC_")) { + "NO_ENCRYPTION", "NO" -> EncryptionMethod.NONE + "STANDARD" -> EncryptionMethod.ZIP_STANDARD + "STRONG" -> EncryptionMethod.ZIP_STANDARD_VARIANT_STRONG + else -> EncryptionMethod.valueOf(s) + } + } + + private fun Any.toSymbolicLinkAction(): SymbolicLinkAction? = when { + this is SymbolicLinkAction -> this + this.isJsNullish() -> null + else -> SymbolicLinkAction.valueOf(coerceStringUppercase(this)) + } + + companion object { + + @JvmStatic + @RhinoStandardFunctionInterface + fun toString(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): String = ensureArgumentsIsEmpty(args) { + toStringRhino(thisObj as ZipNativeObject) + } + + @JvmStatic + @RhinoFunctionBody + fun toStringRhino(thisObj: ZipNativeObject): String { + val opts = thisObj.options + val optsString: String = when (opts) { + !is NativeObject -> opts.toString() + else -> { + val entries = opts.entries + when { + entries.isEmpty() -> "{}" + else -> "{\n${ + entries.joinToString("\n") { + var s = " ${it.key}: ${Context.toString(it.value)}" + if (it.key in thisObj.optionsJavaMap) { + s += " [ ${thisObj.optionsJavaMap[it.key]} ]" + } + "$s," + } + }\n }" + } + } + } + return listOf( + "${Zip::class.java.simpleName} {", + " operation: ${thisObj.operationName},", + " path: ${thisObj.path},", + " options: $optsString,", + "}", + ).joinToString("\n") + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun addFile(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Undefined = ensureArgumentsLengthInRange(args, 1..2) { argList -> + val zip = thisObj as ZipNativeObject + val (filePath, options) = argList + val file = zip.scriptRuntime.files.nonNullPath(coerceString(filePath)).toFile() + val zipParameters = zip.buildZipParameters(Companion::addFile.name, options) + undefined { zip.zipFile.addFile(file, zipParameters) } + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun addFiles(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Undefined = ensureArgumentsLengthInRange(args, 1..2) { argList -> + val zip = thisObj as ZipNativeObject + val (filePathList, options) = argList + require(filePathList is Iterable<*>) { + "Argument filePathList ${filePathList.jsBrief()} for ${ZipNativeObject::class.java.simpleName}#${Companion::addFiles.name} must be an Iterable" + } + val fileList = mutableListOf() + filePathList.forEach { rawFilePath -> + fileList.add(zip.scriptRuntime.files.nonNullPath(coerceString(rawFilePath)).toFile()) + } + val zipParameters = zip.buildZipParameters(Companion::addFiles.name, options) + undefined { zip.zipFile.addFiles(fileList, zipParameters) } + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun addFolder(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Undefined = ensureArgumentsLengthInRange(args, 1..2) { argList -> + val zip = thisObj as ZipNativeObject + val (filePath, options) = argList + val file = zip.scriptRuntime.files.nonNullPath(coerceString(filePath)).toFile() + val zipParameters = zip.buildZipParameters(Companion::addFolder.name, options) + undefined { zip.zipFile.addFolder(file, zipParameters) } + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun extractAll(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Undefined = ensureArgumentsLengthInRange(args, 1..2) { argList -> + val zip = thisObj as ZipNativeObject + val (rawDestPath, options) = argList + val destPath = zip.scriptRuntime.files.nonNullPath(coerceString(rawDestPath)) + val unzipParameters = zip.buildUnzipParameters(Companion::extractAll.name, options) + undefined { zip.zipFile.extractAll(destPath, unzipParameters) } + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun extractFile(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Undefined = ensureArgumentsLengthInRange(args, 2..4) { argList -> + val zip = thisObj as ZipNativeObject + val (rawZipFilePath, rawDestPath, options, rawNewFileName) = argList + val zipFilePath = zip.scriptRuntime.files.nonNullPath(coerceString(rawZipFilePath)) + val destPath = zip.scriptRuntime.files.nonNullPath(coerceString(rawDestPath)) + val newFileName = rawNewFileName.takeUnless { it.isJsNullish() }?.let { coerceString(it) } + val unzipParameters = zip.buildUnzipParameters(Companion::extractFile.name, options) + undefined { zip.zipFile.extractFile(zipFilePath, destPath, newFileName, unzipParameters) } + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun setPassword(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Undefined = ensureArgumentsOnlyOne(args) { password -> + val zip = thisObj as ZipNativeObject + undefined { zip.zipFile.setPassword(coerceString(password).toCharArray()) } + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun getFileHeader(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): FileHeader = ensureArgumentsOnlyOne(args) { fileName -> + val zip = thisObj as ZipNativeObject + zip.zipFile.getFileHeader(coerceString(fileName)) + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun getFileHeaders(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): NativeArray = ensureArgumentsIsEmpty(args) { + val zip = thisObj as ZipNativeObject + zip.zipFile.getFileHeaders().toNativeArray() + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun isEncrypted(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Boolean = ensureArgumentsIsEmpty(args) { + val zip = thisObj as ZipNativeObject + zip.zipFile.isEncrypted + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun removeFile(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Undefined = ensureArgumentsOnlyOne(args) { fileName -> + val zip = thisObj as ZipNativeObject + undefined { zip.zipFile.removeFile(coerceString(fileName)) } + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun isValidZipFile(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Boolean = ensureArgumentsIsEmpty(args) { + val zip = thisObj as ZipNativeObject + zip.zipFile.isValidZipFile + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun getPath(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): String = ensureArgumentsIsEmpty(args) { + val zip = thisObj as ZipNativeObject + zip.path + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun getZipFile(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): ZipFile = ensureArgumentsIsEmpty(args) { + val zip = thisObj as ZipNativeObject + zip.zipFile + } + + } + +}