Skip to content
This repository has been archived by the owner on Dec 1, 2024. It is now read-only.

Commit

Permalink
Use reflection to find Loader class to load resources from (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
05nelsonm authored Mar 24, 2023
1 parent cafc521 commit fce30a1
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 <init> (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 <init> (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;
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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 {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -45,28 +46,49 @@ 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<String>,
): TorResource.Binaries() {

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"

Expand All @@ -79,6 +101,34 @@ public class TorBinaryResource private constructor(
arch: String,
sha256sum: String,
resourceManifest: List<String>
): 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<String>
): TorBinaryResource {
require(arch.isNotBlank()) { "arch cannot be blank" }
require(!arch.contains('/')) { "arch cannot contain '/'" }
Expand Down Expand Up @@ -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()
)
}
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
}

Expand All @@ -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)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {

Expand All @@ -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",
Expand All @@ -54,5 +60,6 @@ actual class ExtractorUnitTest: BaseExtractorJvmJsUnitTest() {
)

assertBinaryResourceExtractionIsSuccessful(resource)
assertEquals("$loaderPrefix.${os.lowercaseName}.$arch.Loader", resource.loadPath)
}
}
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit fce30a1

Please sign in to comment.