diff --git a/library/kmp-tor-binary-extract/api/jvm/kmp-tor-binary-extract.api b/library/kmp-tor-binary-extract/api/jvm/kmp-tor-binary-extract.api index d4dfa365..1c2f8586 100644 --- a/library/kmp-tor-binary-extract/api/jvm/kmp-tor-binary-extract.api +++ b/library/kmp-tor-binary-extract/api/jvm/kmp-tor-binary-extract.api @@ -13,7 +13,9 @@ public final class io/matthewnelson/kmp/tor/binary/extract/Extractor { public final class io/matthewnelson/kmp/tor/binary/extract/TorBinaryResource : io/matthewnelson/kmp/tor/binary/extract/TorResource$Binaries { public static final field Companion Lio/matthewnelson/kmp/tor/binary/extract/TorBinaryResource$Companion; public final field arch Ljava/lang/String; - public synthetic fun (Lio/matthewnelson/kmp/tor/binary/extract/TorBinaryResource$OS;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final field loadPath Ljava/lang/String; + public synthetic fun (Lio/matthewnelson/kmp/tor/binary/extract/TorBinaryResource$OS;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public static final fun from (Lio/matthewnelson/kmp/tor/binary/extract/TorBinaryResource$OS;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lio/matthewnelson/kmp/tor/binary/extract/TorBinaryResource; public static final fun from (Lio/matthewnelson/kmp/tor/binary/extract/TorBinaryResource$OS;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lio/matthewnelson/kmp/tor/binary/extract/TorBinaryResource; public fun getResourceDirPath ()Ljava/lang/String; public fun getResourceManifest ()Ljava/util/List; @@ -22,6 +24,7 @@ public final class io/matthewnelson/kmp/tor/binary/extract/TorBinaryResource : i } public final class io/matthewnelson/kmp/tor/binary/extract/TorBinaryResource$Companion { + public final fun from (Lio/matthewnelson/kmp/tor/binary/extract/TorBinaryResource$OS;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lio/matthewnelson/kmp/tor/binary/extract/TorBinaryResource; public final fun from (Lio/matthewnelson/kmp/tor/binary/extract/TorBinaryResource$OS;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lio/matthewnelson/kmp/tor/binary/extract/TorBinaryResource; } diff --git a/library/kmp-tor-binary-extract/src/jsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/-JsPlatform.kt b/library/kmp-tor-binary-extract/src/jsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/-JsPlatform.kt new file mode 100644 index 00000000..810bd5f7 --- /dev/null +++ b/library/kmp-tor-binary-extract/src/jsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/-JsPlatform.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Matthew Nelson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +@file:Suppress("KotlinRedundantDiagnosticSuppress") + +package io.matthewnelson.kmp.tor.binary.extract + +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun String?.toLoadPath(os: TorBinaryResource.OS, arch: String): String { + if (this == null) { + return "kmp-tor-resource-${os.lowercaseName}$arch" + } + + return if (endsWith('-')) { + "$this${os.lowercaseName}$arch" + } else { + "$this-${os.lowercaseName}$arch" + } +} diff --git a/library/kmp-tor-binary-extract/src/jsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/Extractor.kt b/library/kmp-tor-binary-extract/src/jsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/Extractor.kt index fb759d22..9f2cc99e 100644 --- a/library/kmp-tor-binary-extract/src/jsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/Extractor.kt +++ b/library/kmp-tor-binary-extract/src/jsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/Extractor.kt @@ -16,7 +16,6 @@ package io.matthewnelson.kmp.tor.binary.extract import io.matthewnelson.kmp.tor.binary.extract.internal.ExtractorDelegateJs -import io.matthewnelson.kmp.tor.binary.extract.internal.jsModuleName import io.matthewnelson.kmp.tor.binary.extract.internal.readFileSync @@ -77,7 +76,7 @@ public actual class Extractor { is TorResourceMacosX64 -> "kmp-tor-binary-macosx64" is TorResourceMingwX64 -> "kmp-tor-binary-mingwx64" is TorResourceMingwX86 -> "kmp-tor-binary-mingwx86" - is TorBinaryResource -> resource.jsModuleName + is TorBinaryResource -> resource.loadPath } + "/$resourcePath" val resolvedPath = try { diff --git a/library/kmp-tor-binary-extract/src/jsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/internal/-TorBinaryResource.kt b/library/kmp-tor-binary-extract/src/jsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/internal/-TorBinaryResource.kt deleted file mode 100644 index 20889d50..00000000 --- a/library/kmp-tor-binary-extract/src/jsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/internal/-TorBinaryResource.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.matthewnelson.kmp.tor.binary.extract.internal - -import io.matthewnelson.kmp.tor.binary.extract.TorBinaryResource - -internal val TorBinaryResource.jsModuleName: String get() = "kmp-tor-resource-${osName}$arch" diff --git a/library/kmp-tor-binary-extract/src/jvmJsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/-JvmJsPlatform.kt b/library/kmp-tor-binary-extract/src/jvmJsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/-JvmJsPlatform.kt new file mode 100644 index 00000000..1645b941 --- /dev/null +++ b/library/kmp-tor-binary-extract/src/jvmJsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/-JvmJsPlatform.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 Matthew Nelson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +@file:Suppress("KotlinRedundantDiagnosticSuppress") + +package io.matthewnelson.kmp.tor.binary.extract + +@Suppress("NOTHING_TO_INLINE") +internal expect inline fun String?.toLoadPath(os: TorBinaryResource.OS, arch: String): String diff --git a/library/kmp-tor-binary-extract/src/jvmJsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/TorBinaryResource.kt b/library/kmp-tor-binary-extract/src/jvmJsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/TorBinaryResource.kt index 45ace85b..f41db2f7 100644 --- a/library/kmp-tor-binary-extract/src/jvmJsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/TorBinaryResource.kt +++ b/library/kmp-tor-binary-extract/src/jvmJsMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/TorBinaryResource.kt @@ -3,6 +3,7 @@ package io.matthewnelson.kmp.tor.binary.extract import kotlin.jvm.JvmField import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic +import kotlin.jvm.JvmSynthetic /** * Allows for library consumers to provide their own packaged @@ -45,16 +46,34 @@ import kotlin.jvm.JvmStatic * "tor.gz" * ) * - * For NodeJS, binary files must be in a module named kmp-tor-resource-(lowercase os name)(arch) - * - e.g. kmp-tor-resource-linuxx64 + * [loadPath] is the path to find the resources. + * - For JVM: + * - This is the path to a class named `Loader` in the module containing your + * custom binary resources. Simply add `private class Loader` at the classpath + * described below. + * - Defaults to `io.matthewnelson.kmp.tor.resource.(lowercase os name).(arch).Loader` + * - This is only utilized if `javaClass.getResourceAsStream` fails. You can add + * a `private class Loader` to the module containing those binary resources at the + * specified classpath, and reflection will be used to find it in order to load resources + * from that module. + * - e.g. "com.example.kmp.tor.resource.linux.x64.Loader" + * - For Nodejs: + * - This is the module name where the assets are located + * - Defaults to module named `kmp-tor-resource-(lowercase os name)(arch)` + * - e.g. "kmp-tor-resource-linuxx64" + * - [from] has more details on customization of this loadPath via passing + * of a prefix. * * @see [from] * @sample [io.matthewnelson.kmp.tor.binary.extract.TorResourceLinuxX64.resourceManifest] + * @sample [io.matthewnelson.kmp.tor.binary.linux.x64.Loader] * */ public class TorBinaryResource private constructor( private val os: OS, @JvmField public val arch: String, + @JvmField + public val loadPath: String, public override val sha256sum: String, public override val resourceManifest: List, ): TorResource.Binaries() { @@ -62,11 +81,14 @@ public class TorBinaryResource private constructor( public enum class OS { Linux, Macos, - Mingw, + Mingw; + + @get:JvmSynthetic + internal val lowercaseName: String get() = name.lowercase() } @get:JvmName("osName") - public val osName: String get() = os.name.lowercase() + public val osName: String get() = os.lowercaseName public override val resourceDirPath: String get() = "kmptor/$osName/$arch" @@ -79,6 +101,34 @@ public class TorBinaryResource private constructor( arch: String, sha256sum: String, resourceManifest: List + ): TorBinaryResource { + return from(os, arch, null, sha256sum, resourceManifest) + } + + /** + * Validates parameters and returns a [TorBinaryResource]. + * + * [loadPathPrefix] is an optional argument for creating your [loadPath] if + * it differs from the default. + * + * - JVM: classpath prefix to your module's `Loader` class + * - e.g. loadPathPrefix = "com.example" with binaries for os = OS.Linux and arch = "aarch64" + * will result in a loadPath of "com.example.linux.aarch64.Loader" + * - Defaults to: `io.matthewnelson.kmp.tor.resrouce.(lowercase os name).(arch).Loader` + * + * - Nodejs: the module prefix. + * - e.g. loadPathPrefix = "com-example" with bianries for os = OS.Linux and arch = "aarch64" + * will result in a loadPath of "com-example-linuxaarch64" + * - Defaults to: `kmp-tor-resource-(lowercase os name)(arch)` + * */ + @JvmStatic + @Throws(IllegalArgumentException::class) + public fun from( + os: OS, + arch: String, + loadPathPrefix: String?, + sha256sum: String, + resourceManifest: List ): TorBinaryResource { require(arch.isNotBlank()) { "arch cannot be blank" } require(!arch.contains('/')) { "arch cannot contain '/'" } @@ -119,7 +169,13 @@ public class TorBinaryResource private constructor( "a total of '$torFileCount' listed in the resourceManifest." } - return TorBinaryResource(os, arch, sha256sum, manifest.toList()) + return TorBinaryResource( + os, + arch, + loadPathPrefix.toLoadPath(os, arch), + sha256sum, + manifest.toList() + ) } } } diff --git a/library/kmp-tor-binary-extract/src/jvmMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/-JvmPlatform.kt b/library/kmp-tor-binary-extract/src/jvmMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/-JvmPlatform.kt new file mode 100644 index 00000000..51fc9ded --- /dev/null +++ b/library/kmp-tor-binary-extract/src/jvmMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/-JvmPlatform.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Matthew Nelson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +@file:Suppress("KotlinRedundantDiagnosticSuppress") + +package io.matthewnelson.kmp.tor.binary.extract + +@JvmSynthetic +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun String?.toLoadPath(os: TorBinaryResource.OS, arch: String): String { + if (this == null) { + return "io.matthewnelson.kmp.tor.resource.${os.lowercaseName}.$arch.Loader" + } + + return if (endsWith('.')) { + "$this${os.lowercaseName}.$arch.Loader" + } else { + "$this.${os.lowercaseName}.$arch.Loader" + } +} diff --git a/library/kmp-tor-binary-extract/src/jvmMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/Extractor.kt b/library/kmp-tor-binary-extract/src/jvmMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/Extractor.kt index e1473f1c..4763570e 100644 --- a/library/kmp-tor-binary-extract/src/jvmMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/Extractor.kt +++ b/library/kmp-tor-binary-extract/src/jvmMain/kotlin/io/matthewnelson/kmp/tor/binary/extract/Extractor.kt @@ -16,6 +16,7 @@ package io.matthewnelson.kmp.tor.binary.extract import io.matthewnelson.kmp.tor.binary.extract.internal.ExtractorDelegateJvmAndroid +import java.io.InputStream /** * Extracts [TorResource]es to their desired @@ -40,11 +41,7 @@ public actual class Extractor { cleanExtraction: Boolean ) { delegate.extract(resource, destination, cleanExtraction) { resourcePath -> - try { - javaClass.getResourceAsStream("/$resourcePath")!! - } catch (t: Throwable) { - throw delegate.resourceNotFound(resourcePath, t) - } + resource.stream(resourcePath) } } @@ -65,10 +62,47 @@ public actual class Extractor { cleanExtraction: Boolean, ): TorFilePath { return delegate.extract(resource, destinationDir, cleanExtraction) { resourcePath -> - try { - javaClass.getResourceAsStream("/$resourcePath")!! - } catch (t: Throwable) { - throw delegate.resourceNotFound(resourcePath, t) + resource.stream(resourcePath) + } + } + + private fun TorResource.stream(resourcePath: String): InputStream { + try { + return this.javaClass.getResourceAsStream("/$resourcePath")!! + } catch (_: Throwable) {} + + val prefix = "io.matthewnelson.kmp.tor.binary" + val loader = try { + when (this) { + is TorBinaryResource -> findLoaderClass(loadPath) + is TorResourceLinuxX64 -> findLoaderClass("$prefix.linux.x64.Loader") + is TorResourceLinuxX86 -> findLoaderClass("$prefix.linux.x86.Loader") + is TorResourceMacosArm64 -> findLoaderClass("$prefix.macos.arm64.Loader") + is TorResourceMacosX64 -> findLoaderClass("$prefix.macos.x64.Loader") + is TorResourceMingwX64 -> findLoaderClass("$prefix.mingw.x64.Loader") + is TorResourceMingwX86 -> findLoaderClass("$prefix.mingw.x86.Loader") + is TorResource.Geoips -> findLoaderClass("$prefix.geoip.Loader") + } + } catch (e: ClassNotFoundException) { + throw delegate.resourceNotFound(resourcePath, e) + } + + try { + return loader.getResourceAsStream("/$resourcePath")!! + } catch (t: Throwable) { + throw delegate.resourceNotFound(resourcePath, t) + } + } + + @Throws(ClassNotFoundException::class) + private fun findLoaderClass(loaderClasspath: String): Class<*> { + try { + return Class.forName(loaderClasspath) ?: throw ClassNotFoundException("Failed to find $loaderClasspath") + } catch (t: Throwable) { + if (t is ClassNotFoundException) { + throw t + } else { + throw ClassNotFoundException("Failed to find $loaderClasspath", t) } } } diff --git a/library/kmp-tor-binary-extract/src/jvmTest/kotlin/io/matthewnelson/kmp/tor/binary/extract/ExtractorUnitTest.kt b/library/kmp-tor-binary-extract/src/jvmTest/kotlin/io/matthewnelson/kmp/tor/binary/extract/ExtractorUnitTest.kt index 9fa5dfc5..18b97c3d 100644 --- a/library/kmp-tor-binary-extract/src/jvmTest/kotlin/io/matthewnelson/kmp/tor/binary/extract/ExtractorUnitTest.kt +++ b/library/kmp-tor-binary-extract/src/jvmTest/kotlin/io/matthewnelson/kmp/tor/binary/extract/ExtractorUnitTest.kt @@ -17,6 +17,7 @@ package io.matthewnelson.kmp.tor.binary.extract import org.junit.Test import java.io.File +import kotlin.test.assertEquals actual class ExtractorUnitTest: BaseExtractorJvmJsUnitTest() { @@ -40,9 +41,14 @@ actual class ExtractorUnitTest: BaseExtractorJvmJsUnitTest() { @Test fun givenTestResources_whenExtracted_thenIsSuccessful() { + val loaderPrefix = "io.matthewnelson" + val os = TorBinaryResource.OS.Linux + val arch = "test" + val resource = TorBinaryResource.from( - os = TorBinaryResource.OS.Linux, - arch = "test", + os = os, + arch = arch, + loadPathPrefix = loaderPrefix, sha256sum = "a766e07310b1ede3a06ef889cb46023fed5dc8044b326c20adf342242be92ec6", resourceManifest = listOf( "subdir/libcrypto.so.1.1.gz", @@ -54,5 +60,6 @@ actual class ExtractorUnitTest: BaseExtractorJvmJsUnitTest() { ) assertBinaryResourceExtractionIsSuccessful(resource) + assertEquals("$loaderPrefix.${os.lowercaseName}.$arch.Loader", resource.loadPath) } } diff --git a/library/kmp-tor-binary-extract/src/jvmTest/kotlin/io/matthewnelson/linux/test/Loader.kt b/library/kmp-tor-binary-extract/src/jvmTest/kotlin/io/matthewnelson/linux/test/Loader.kt new file mode 100644 index 00000000..25fb60c4 --- /dev/null +++ b/library/kmp-tor-binary-extract/src/jvmTest/kotlin/io/matthewnelson/linux/test/Loader.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023 Matthew Nelson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +package io.matthewnelson.linux.test + +private class Loader