diff --git a/build.gradle.kts b/build.gradle.kts index c535211..05e6a75 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,57 +1,41 @@ -apply { - from("https://raw.githubusercontent.com/ligi/gradle-common/master/versions_plugin_stable_only.gradle") -} - -buildscript { - repositories { - jcenter() - maven("https://jitpack.io") - } - - dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}") - classpath("com.github.ben-manes:gradle-versions-plugin:${Versions.versions_plugin}") - } -} - - -subprojects { - repositories { - jcenter() - maven("https://jitpack.io") - maven("https://kotlin.bintray.com/kotlinx") - } - - apply(plugin = "jacoco") - apply(plugin = "maven") - apply(plugin = "kotlin") - - tasks.withType { - useJUnitPlatform() - } - - configure { - withSourcesJar() - withJavadocJar() - } - - afterEvaluate { - - dependencies { - "implementation"("org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}") - - "testImplementation"("org.assertj:assertj-core:3.22.0") - "testImplementation"("org.junit.jupiter:junit-jupiter-api:${Versions.jupiter}") - "testImplementation"("org.junit.jupiter:junit-jupiter-params:${Versions.jupiter}") - "testRuntime"("org.junit.jupiter:junit-jupiter-engine:${Versions.jupiter}") - - "testImplementation"("org.jetbrains.kotlin:kotlin-test") - "testImplementation"("io.mockk:mockk:1.12.0") - } - - - } - val compileTestKotlin by tasks.getting(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class) { - kotlinOptions.jvmTarget = "1.8" - } -} +//apply { +// from("https://raw.githubusercontent.com/ligi/gradle-common/master/versions_plugin_stable_only.gradle") +//} + + +//subprojects { +// repositories { +// mavenCentral() +// maven("https://jitpack.io") +// maven("https://kotlin.bintray.com/kotlinx") +// } +// +// tasks.withType { +// useJUnitPlatform() +// } +// +// configure { +// withSourcesJar() +// withJavadocJar() +// } +// +// afterEvaluate { +// +// dependencies { +// "implementation"("org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}") +// +// "testImplementation"("org.assertj:assertj-core:3.22.0") +// "testImplementation"("org.junit.jupiter:junit-jupiter-api:${Versions.jupiter}") +// "testImplementation"("org.junit.jupiter:junit-jupiter-params:${Versions.jupiter}") +// "testImplementation"("org.junit.jupiter:junit-jupiter-engine:${Versions.jupiter}") +// +// "testImplementation"("org.jetbrains.kotlin:kotlin-test") +// "testImplementation"("io.mockk:mockk:1.12.4") +// } +// +// +// } +//// val compileTestKotlin by tasks.getting(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class) { +//// kotlinOptions.jvmTarget = "1.8" +//// } +//} diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 37cb9ba..ca69032 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,5 +1,10 @@ object Versions { - const val kotlin = "1.3.72" + const val kotlin = "1.7.10" + const val serialization= "1.3.3" const val versions_plugin = "0.28.0" const val jupiter = "5.6.2" + const val okhttp = "4.10.0" + const val coroutines = "1.6.4" + const val ktor = "2.1.0" + const val okio = "3.2.0" } \ No newline at end of file diff --git a/example/build.gradle.kts b/example/build.gradle.kts index ae9f178..e0dcf5c 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -1,11 +1,12 @@ plugins { + kotlin("jvm") application } application { - mainClassName = "org.kethereum.example_cli.ExampleCLIKt" + mainClass.set("io.ipfs.example_cli.ExampleCLIKt") } dependencies { - implementation(project(":lib")) + implementation(project(":ipfs-api")) } diff --git a/example/src/main/kotlin/io/ipfs/example_cli/ExampleCLI.kt b/example/src/main/kotlin/io/ipfs/example_cli/ExampleCLI.kt deleted file mode 100644 index 115994c..0000000 --- a/example/src/main/kotlin/io/ipfs/example_cli/ExampleCLI.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.ipfs.example_cli - -import io.ipfs.kotlin.IPFS -import io.ipfs.kotlin.IPFSConfiguration - -fun main() { - println(IPFS(IPFSConfiguration()).info.version()) -} \ No newline at end of file diff --git a/example/src/main/kotlin/io/ipfs/example_cli/ExampleInfuraCLI.kt b/example/src/main/kotlin/io/ipfs/example_cli/ExampleInfuraCLI.kt new file mode 100644 index 0000000..c1c64cc --- /dev/null +++ b/example/src/main/kotlin/io/ipfs/example_cli/ExampleInfuraCLI.kt @@ -0,0 +1,25 @@ +package io.ipfs.example_cli + +import io.ipfs.kotlin.commands.AddProgress +import io.ipfs.kotlin.commands.UploadProgress +import io.ipfs.kotlin.defaults.InfuraIPFS +import kotlinx.coroutines.runBlocking +import okio.Path.Companion.toOkioPath +import java.io.File + +fun main(args: Array) = runBlocking { + require(args.size == 3) + val projectId = args[0] + val projectSecret = args[1] + val filePath = args[2] + val ipfs = InfuraIPFS(projectId, projectSecret) + println(ipfs.info.version()) + println( + ipfs.add.file( + File(filePath).toOkioPath() + ) { uploadProgress: UploadProgress?, addProgress: AddProgress? -> + uploadProgress?.let { println(it) } + addProgress?.let { println(it) } + } + ) +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..b8d590b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +kotlin.code.style=official + +kotlin.native.binary.memoryModel=experimental diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fd0c5a3..ae04661 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/ipfs-api/build.gradle.kts b/ipfs-api/build.gradle.kts new file mode 100644 index 0000000..ab01d45 --- /dev/null +++ b/ipfs-api/build.gradle.kts @@ -0,0 +1,114 @@ +plugins { + kotlin("multiplatform") + kotlin("plugin.serialization") + id("jacoco") + id("com.github.ben-manes.versions") + id("maven-publish") +} + +group = "com.github.ligi" +version = "0.16" + + +repositories { + mavenCentral() +} + +kotlin { + val darwinTargets = arrayOf( + "macosX64", "macosArm64", + "iosArm64", "iosX64", "iosSimulatorArm64", + "tvosArm64", "tvosX64", "tvosSimulatorArm64", + "watchosArm32", "watchosArm64", "watchosX86", "watchosX64", "watchosSimulatorArm64", + ) + val linuxTargets = arrayOf("linuxX64", /*"linuxArm64"*/) + val mingwTargets = arrayOf("mingwX64") + val nativeTargets = linuxTargets + darwinTargets + mingwTargets + + jvm { + compilations.all { + kotlinOptions.jvmTarget = "11" + } + } + js(IR) { + nodejs() + } + for (target in nativeTargets) { + targets.add(presets.getByName(target).createTarget(target)) + } + sourceSets { + val commonMain by getting { + dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.serialization}") + api("io.ktor:ktor-client-core:${Versions.ktor}") + implementation("io.ktor:ktor-client-content-negotiation:${Versions.ktor}") + implementation("io.ktor:ktor-serialization-kotlinx-json:${Versions.ktor}") + api("com.squareup.okio:okio:${Versions.okio}") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutines}") + } + } + + val jvmMain by getting { + dependsOn(commonMain) + dependencies { + implementation("io.ktor:ktor-client-okhttp:${Versions.ktor}") + } + } + + val jsMain by getting { + dependsOn(commonMain) + dependencies { + implementation("io.ktor:ktor-client-js:${Versions.ktor}") + implementation("com.squareup.okio:okio-nodefilesystem:${Versions.okio}") + } + } + + + val jvmTest by getting { + dependencies { + implementation("org.mockito:mockito-core:4.5.1") + implementation("com.squareup.okhttp3:mockwebserver:${Versions.okhttp}") + implementation("org.assertj:assertj-core:3.22.0") + } + } + val nativeMain by creating { + dependsOn(commonMain) + } + + val darwinMain by creating { + dependsOn(nativeMain) + dependencies { + implementation("io.ktor:ktor-client-darwin:${Versions.ktor}") + } + } + darwinTargets.forEach { target -> + getByName("${target}Main") { + dependsOn(darwinMain) + } + } + val linuxAndMingwMain by creating { + dependsOn(nativeMain) + dependencies { + implementation("io.ktor:ktor-client-curl:${Versions.ktor}") + } + } + (mingwTargets + linuxTargets).forEach { target -> + getByName("${target}Main") { + dependsOn(linuxAndMingwMain) + } + } + + } +} + +tasks.withType { + kotlinOptions { + jvmTarget = "11" + } +} \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/IPFS.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/IPFS.kt similarity index 55% rename from lib/src/main/kotlin/io/ipfs/kotlin/IPFS.kt rename to ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/IPFS.kt index 1f35cca..df58655 100644 --- a/lib/src/main/kotlin/io/ipfs/kotlin/IPFS.kt +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/IPFS.kt @@ -1,15 +1,23 @@ package io.ipfs.kotlin -import com.squareup.moshi.Moshi import io.ipfs.kotlin.commands.* -import io.ipfs.kotlin.defaults.createMoshi -import io.ipfs.kotlin.defaults.createOKHTTP +import io.ipfs.kotlin.defaults.createFileSystem +import io.ipfs.kotlin.defaults.createKTOR import io.ipfs.kotlin.model.MessageWithCode -import okhttp3.OkHttpClient +import io.ktor.client.* +import okio.FileSystem -data class IPFSConfiguration(val base_url: String = "http://127.0.0.1:5001/api/v0/", - val okHttpClient: OkHttpClient = createOKHTTP(), - val moshi: Moshi = createMoshi()) +data class IPFSConfiguration( + val base_url: String = "http://127.0.0.1:5001/api/v0/", + val ktorClient: HttpClient = createKTOR(), + val fileSystem: FileSystem = createFileSystem(), + val basicAuthCredentials: BasicAuth? = null +) { + class BasicAuth( + val username: String, + val password: String + ) +} open class IPFS(configuration: IPFSConfiguration) { diff --git a/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/IPFSConnection.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/IPFSConnection.kt new file mode 100644 index 0000000..341660f --- /dev/null +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/IPFSConnection.kt @@ -0,0 +1,40 @@ +package io.ipfs.kotlin + +import io.ipfs.kotlin.model.MessageWithCode +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json + +open class IPFSConnection(val config: IPFSConfiguration) { + + var lastError: MessageWithCode? = null + + private fun buildRequest(cmd: String, block: HttpRequestBuilder.() -> Unit = {}): HttpRequestBuilder { + val request = HttpRequestBuilder().apply { + url(config.base_url + cmd) + contentType(ContentType.Any) + config.basicAuthCredentials?.apply { + basicAuth(username, password) + } + block() + } + + return request + } + + suspend fun callCmd(cmd: String): HttpResponse { + val request = buildRequest(cmd) + return config.ktorClient.post(request) + } + + suspend fun prepareCallCmd(cmd: String, block: HttpRequestBuilder.() -> Unit = {}): HttpStatement { + val request = buildRequest(cmd, block) + return config.ktorClient.preparePost(request) + } + + fun setErrorByJSON(jsonString: String) { + lastError = Json.decodeFromString(jsonString) + } +} \ No newline at end of file diff --git a/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Add.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Add.kt new file mode 100644 index 0000000..0f551c9 --- /dev/null +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Add.kt @@ -0,0 +1,145 @@ +package io.ipfs.kotlin.commands + +import io.ipfs.kotlin.IPFSConnection +import io.ipfs.kotlin.model.NamedResponse +import io.ktor.client.plugins.* +import io.ktor.client.request.* +import io.ktor.client.request.forms.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.utils.io.* +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okio.Path + +data class UploadProgress(val bytesSent: Long, val byteSize: Long) { + val percentage = (bytesSent.toDouble() / byteSize.toDouble()) * 100.0 +} + +data class AddProgress(val bytesProcessed: Long, val byteSize: Long) { + val percentage = (bytesProcessed.toDouble() / byteSize.toDouble()) * 100.0 +} + +typealias UploadAndAddProgressListener = ((UploadProgress?, AddProgress?) -> Unit) + +class Add(val ipfs: IPFSConnection) { + + /*** Accepts a single file or directory and returns the named hash. + * For directories, we return the hash of the enclosing + * directory because that makes the most sense, also for + * consistency with the java-ipfs-api implementation. + **/ + suspend fun file( + file: Path, + name: String = "file", + filename: String = name, + progressListener: UploadAndAddProgressListener? = null + ) = addGeneric(progressListener) { + addFile(file, name, filename) + }.last() + + /*** + * Accepts a single file's ByteArray and returns the named hash. + **/ + suspend fun file( + source: ByteArray, + name: String = "file", + filename: String = name, + progressListener: UploadAndAddProgressListener? = null + ) = addGeneric(progressListener) { + val encodedFileName = filename.encodeURLParameter() + val headersBuilder = HeadersBuilder() + headersBuilder.append(HttpHeaders.ContentDisposition, "filename=\"$encodedFileName\"") + headersBuilder.append("Content-Transfer-Encoding", "binary") + headersBuilder.append(HttpHeaders.ContentType, ContentType.Application.OctetStream) + append(name, source, headersBuilder.build()) + }.last() + + + /*** + * Accepts a single file or directory and returns the named hash. + * Returns a collection of named hashes for the containing directory + * and all sub-directories. + */ + suspend fun directory(path: Path, name: String = "file", filename: String = name) = addGeneric(null) { + addFile(path, name, filename) + } + + + // this has to be outside the lambda because it is reentrant to handle subdirectory structures + private fun FormBuilder.addFile(path: Path, name: String, filename: String) { + val encodedFileName = filename.encodeURLParameter() + val headersBuilder = HeadersBuilder() + headersBuilder.append(HttpHeaders.ContentDisposition, "filename=\"$encodedFileName\"") + headersBuilder.append("Content-Transfer-Encoding", "binary") + + val dirFiles = ipfs.config.fileSystem.listOrNull(path) + val isDir = dirFiles?.isNotEmpty() ?: false + if (isDir) { + // add directory + headersBuilder.append(HttpHeaders.ContentType, ContentType("application", "x-directory")) + append("", "", headersBuilder.build()) + + // add files and subdirectories + for (p: Path in dirFiles!!) { + addFile(p, p.name, filename + "/" + p.name) + } + } else { + headersBuilder.append(HttpHeaders.ContentType, ContentType.Application.OctetStream) + ipfs.config.fileSystem.read(path) { + append(name, this.readByteArray(), headersBuilder.build()) + } + } + + } + + suspend fun string(text: String, name: String = "string", filename: String = name): NamedResponse { + + return addGeneric(null) { + append(name, text, Headers.build { + append(HttpHeaders.ContentType, ContentType.Application.OctetStream) + append(HttpHeaders.ContentDisposition, "filename=\"$filename\"") + }) + }.last() + // there can be only one + + } + + private suspend fun addGeneric( + progressListener: UploadAndAddProgressListener?, + withBuilder: FormBuilder.() -> Unit + ): List { + val request = MultiPartFormDataContent(formData(withBuilder)) + val progress = progressListener != null + val result: List = + ipfs.prepareCallCmd("add?progress=$progress") { + onUpload { bytesSentTotal, contentLength -> + val uploadProgress = UploadProgress(bytesSentTotal, contentLength) + progressListener?.invoke(uploadProgress, null) + } + setBody(request) + }.execute { httpResponse -> + // todo: figure out how to calculate the total size returned by ipfs before add completion. This isn't really correct to set byteSize with content length. Ipfs returns a slightly larger final number + val contentLength = + httpResponse.call.request.content.contentLength + val addResults = mutableListOf() + val channel = httpResponse.bodyAsChannel() + while (!channel.isClosedForRead) { + val progressNamedResponse: NamedResponse? = + channel.readUTF8Line()?.let { Json.decodeFromString(it) } + val ipfsAddProgress = if (progressNamedResponse?.bytes != null) { + contentLength?.let { AddProgress(progressNamedResponse.bytes, it) } + } else if (progressNamedResponse?.hash != null) { + addResults.add(progressNamedResponse) + contentLength?.let { AddProgress(progressNamedResponse.size!!.toLong(), it) } + } else { + null + } + progressListener?.invoke(null, ipfsAddProgress) + } + + return@execute addResults + } + return result + } +} diff --git a/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Get.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Get.kt new file mode 100644 index 0000000..f79c638 --- /dev/null +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Get.kt @@ -0,0 +1,50 @@ +package io.ipfs.kotlin.commands + +import io.ipfs.kotlin.IPFSConnection +import io.ktor.client.call.* +import io.ktor.client.statement.* +import io.ktor.utils.io.* + +class Get(val ipfs: IPFSConnection) { + + /** + * Cat IPFS content and return it as string. + * + * @param hash The hash of the content in base58. + */ + suspend fun cat(hash: String): String = catBytes(hash).decodeToString() + + /** + * Cat IPFS content and return it as ByteArray. + * + * @param hash The hash of the content in base58. + */ + suspend fun catBytes(hash: String): ByteArray { + val r = ipfs.callCmd("cat?arg=$hash") + return r.readBytes() + } + + /** + * Cat IPFS content and process it using ByteReadChannel. + * + * @param hash The hash of the content in base58. + * @param handler Callback which handle processing the input stream. When the callback return the stream and the request body will be closed. + */ + suspend fun catReadChannel(hash: String, handler: CatReadChannelHandler) = + ipfs.prepareCallCmd("cat?arg=$hash").execute { httpResponse -> + httpResponse.apply { + handler( + ByteReadChannelAndContentLength( + httpResponse.body(), + httpResponse.headers["X-Content-Length"]!!.toLong() + ) + ) + } + return@execute + } + +} + +typealias CatReadChannelHandler = suspend HttpResponse.(ByteReadChannelAndContentLength) -> Unit + +data class ByteReadChannelAndContentLength(val byteReadChannel: ByteReadChannel, val contentLength: Long) \ No newline at end of file diff --git a/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Info.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Info.kt new file mode 100644 index 0000000..06d0892 --- /dev/null +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Info.kt @@ -0,0 +1,18 @@ +package io.ipfs.kotlin.commands + +import io.ipfs.kotlin.IPFSConnection +import io.ipfs.kotlin.model.VersionInfo +import io.ktor.client.call.* + +class Info(val ipfs: IPFSConnection) { + + + suspend fun version(): VersionInfo? { + return try { + ipfs.callCmd("version").body() as VersionInfo + } catch (_: Throwable){ + null + } + } + +} \ No newline at end of file diff --git a/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Name.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Name.kt new file mode 100644 index 0000000..ed25ba9 --- /dev/null +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Name.kt @@ -0,0 +1,35 @@ +package io.ipfs.kotlin.commands + +import io.ipfs.kotlin.IPFSConnection +import io.ipfs.kotlin.model.NameValue +import io.ipfs.kotlin.model.Path +import io.ktor.client.statement.* +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json + +class Name(val ipfs: IPFSConnection) { + + suspend fun publish(hash: String): String? { + val resultString = ipfs.callCmd("name/publish/$hash").let { it.bodyAsText() } + val resultBoolean = resultString.contains(hash) + if (!resultBoolean) { + ipfs.setErrorByJSON(resultString) + return null + } + return Json.decodeFromString(resultString).Name + } + + suspend fun resolve(hash: String): String? { + val resultString = ipfs.callCmd("name/resolve/$hash").bodyAsText() + + return when { + resultString.isBlank() -> null + resultString.contains("Path") -> Json.decodeFromString(resultString).Path + else -> { + ipfs.setErrorByJSON(resultString) + null + } + } + } + +} \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Pins.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Pins.kt similarity index 64% rename from lib/src/main/kotlin/io/ipfs/kotlin/commands/Pins.kt rename to ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Pins.kt index 0d7a900..c18e21e 100644 --- a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Pins.kt +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Pins.kt @@ -1,11 +1,12 @@ package io.ipfs.kotlin.commands import io.ipfs.kotlin.IPFSConnection +import io.ktor.client.statement.* class Pins(val ipfs: IPFSConnection) { - fun add(hash: String): Boolean { - val resultString = ipfs.callCmd("pin/add/$hash").use { it.string() } + suspend fun add(hash: String): Boolean { + val resultString = ipfs.callCmd("pin/add/$hash").let { it.bodyAsText() } val resultBoolean = resultString.contains(hash) if (!resultBoolean) { ipfs.setErrorByJSON(resultString) diff --git a/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Repo.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Repo.kt new file mode 100644 index 0000000..891f47c --- /dev/null +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Repo.kt @@ -0,0 +1,27 @@ +package io.ipfs.kotlin.commands + +import io.ipfs.kotlin.IPFSConnection +import io.ipfs.kotlin.model.Key +import io.ipfs.kotlin.model.KeyV2 +import io.ktor.client.statement.* +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json + +class Repo(val ipfs: IPFSConnection) { + + suspend fun gc() = ipfs.callCmd("repo/gc").let { listFromNDJson(it) } + + suspend fun listFromNDJson(source: HttpResponse): List { + val jsonString = source.bodyAsText() + return try { + jsonString.parseNDJSON { it?.let { Json.decodeFromString(it).Key.hash } } + } catch (e: Throwable) { + jsonString.parseNDJSON { Json.decodeFromString(it!!).Key } + } + } + + // http://ndjson.org + private fun String.parseNDJSON(convert: (input: String?) -> String?) = replace("\r", "").split("\n").asSequence().filter { !it.isEmpty() }.map { + convert(it) + }.filterNotNull().toList() +} \ No newline at end of file diff --git a/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Stats.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Stats.kt new file mode 100644 index 0000000..8793404 --- /dev/null +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/commands/Stats.kt @@ -0,0 +1,20 @@ +package io.ipfs.kotlin.commands + +import io.ipfs.kotlin.IPFSConnection +import io.ipfs.kotlin.model.BandWidthInfo +import io.ktor.client.call.* + +class Stats(val ipfs: IPFSConnection) { + + + suspend fun bandWidth(): BandWidthInfo? { + return try { + val result : BandWidthInfo = ipfs.callCmd("stats/bw").body() + result + } catch (t : Throwable){ + println(t) + null + } + } + +} \ No newline at end of file diff --git a/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/defaults/Defaults.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/defaults/Defaults.kt new file mode 100644 index 0000000..5e39fdc --- /dev/null +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/defaults/Defaults.kt @@ -0,0 +1,25 @@ +package io.ipfs.kotlin.defaults + +import io.ktor.client.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.serialization.json.Json +import okio.FileSystem + + +internal fun createKTOR() = HttpClient { + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true + }) + } + install(HttpTimeout) { + // this socketTimeout can occur w/ okhttp engine when posting larger file + socketTimeoutMillis = 600_000 // 10 minutes + } +} + +expect internal fun createFileSystem(): FileSystem + + diff --git a/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/defaults/InfuraIPFS.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/defaults/InfuraIPFS.kt new file mode 100644 index 0000000..3782d6e --- /dev/null +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/defaults/InfuraIPFS.kt @@ -0,0 +1,14 @@ +package io.ipfs.kotlin.defaults + +import io.ipfs.kotlin.IPFS +import io.ipfs.kotlin.IPFSConfiguration + +//https://docs.infura.io/infura/networks/ipfs/how-to/authenticate-requests +fun infuraIPFSConfig(projectId: String, projectSecret: String) = + IPFSConfiguration( + "https://ipfs.infura.io:5001/api/v0/", + basicAuthCredentials = IPFSConfiguration.BasicAuth(projectId, projectSecret) + ) + + +open class InfuraIPFS(projectId: String, projectSecret: String) : IPFS(infuraIPFSConfig(projectId, projectSecret)) \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/defaults/LocalIPFS.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/defaults/LocalIPFS.kt similarity index 67% rename from lib/src/main/kotlin/io/ipfs/kotlin/defaults/LocalIPFS.kt rename to ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/defaults/LocalIPFS.kt index 611e32c..f1ebe5a 100644 --- a/lib/src/main/kotlin/io/ipfs/kotlin/defaults/LocalIPFS.kt +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/defaults/LocalIPFS.kt @@ -4,7 +4,7 @@ import io.ipfs.kotlin.IPFS import io.ipfs.kotlin.IPFSConfiguration val localIPFSConfig by lazy { - IPFSConfiguration("http://127.0.0.1:5001/api/v0/", createOKHTTP(), createMoshi()) + IPFSConfiguration("http://127.0.0.1:5001/api/v0/") } open class LocalIPFS : IPFS(localIPFSConfig) \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/model/BandWidthInfo.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/BandWidthInfo.kt similarity index 85% rename from lib/src/main/kotlin/io/ipfs/kotlin/model/BandWidthInfo.kt rename to ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/BandWidthInfo.kt index bb46d3b..8cdad84 100644 --- a/lib/src/main/kotlin/io/ipfs/kotlin/model/BandWidthInfo.kt +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/BandWidthInfo.kt @@ -1,5 +1,6 @@ package io.ipfs.kotlin.model +@kotlinx.serialization.Serializable data class BandWidthInfo(val TotalIn: Int, val TotalOut: Int, val RateIn: Double, diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/model/Key.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/Key.kt similarity index 62% rename from lib/src/main/kotlin/io/ipfs/kotlin/model/Key.kt rename to ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/Key.kt index bc94369..61cb0a7 100644 --- a/lib/src/main/kotlin/io/ipfs/kotlin/model/Key.kt +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/Key.kt @@ -1,3 +1,4 @@ package io.ipfs.kotlin.model +@kotlinx.serialization.Serializable data class Key(val Key: String) \ No newline at end of file diff --git a/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/KeyV2.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/KeyV2.kt new file mode 100644 index 0000000..7d79394 --- /dev/null +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/KeyV2.kt @@ -0,0 +1,8 @@ +package io.ipfs.kotlin.model + +import kotlinx.serialization.SerialName + +@kotlinx.serialization.Serializable +data class KeyV2Content(@SerialName("/") val hash: String) +@kotlinx.serialization.Serializable +data class KeyV2(val Key: KeyV2Content) \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/model/MessageWithCode.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/MessageWithCode.kt similarity index 76% rename from lib/src/main/kotlin/io/ipfs/kotlin/model/MessageWithCode.kt rename to ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/MessageWithCode.kt index 5a3bf4e..2777738 100644 --- a/lib/src/main/kotlin/io/ipfs/kotlin/model/MessageWithCode.kt +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/MessageWithCode.kt @@ -1,4 +1,5 @@ package io.ipfs.kotlin.model +@kotlinx.serialization.Serializable data class MessageWithCode(val Message: String, val Code: Int) \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/model/NameValue.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/NameValue.kt similarity index 75% rename from lib/src/main/kotlin/io/ipfs/kotlin/model/NameValue.kt rename to ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/NameValue.kt index bb52a5e..62cdfe9 100644 --- a/lib/src/main/kotlin/io/ipfs/kotlin/model/NameValue.kt +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/NameValue.kt @@ -1,4 +1,5 @@ package io.ipfs.kotlin.model +@kotlinx.serialization.Serializable data class NameValue(val Name: String, val Value: String) \ No newline at end of file diff --git a/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/NamedResponse.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/NamedResponse.kt new file mode 100644 index 0000000..df8008c --- /dev/null +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/NamedResponse.kt @@ -0,0 +1,16 @@ +package io.ipfs.kotlin.model + +import kotlinx.serialization.SerialName + + +@kotlinx.serialization.Serializable +data class NamedResponse( + @SerialName("Name") + val name: String, + @SerialName("Bytes") + val bytes: Long? = null, + @SerialName("Hash") + val hash: String? = null, + @SerialName("Size") + val size: String? = null +) diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/model/Path.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/Path.kt similarity index 63% rename from lib/src/main/kotlin/io/ipfs/kotlin/model/Path.kt rename to ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/Path.kt index 22bc4fa..83af1ad 100644 --- a/lib/src/main/kotlin/io/ipfs/kotlin/model/Path.kt +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/Path.kt @@ -1,3 +1,4 @@ package io.ipfs.kotlin.model +@kotlinx.serialization.Serializable data class Path(val Path: String) \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/model/VersionInfo.kt b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/VersionInfo.kt similarity index 81% rename from lib/src/main/kotlin/io/ipfs/kotlin/model/VersionInfo.kt rename to ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/VersionInfo.kt index 28b347a..014771c 100644 --- a/lib/src/main/kotlin/io/ipfs/kotlin/model/VersionInfo.kt +++ b/ipfs-api/src/commonMain/kotlin/io/ipfs/kotlin/model/VersionInfo.kt @@ -1,5 +1,6 @@ package io.ipfs.kotlin.model +@kotlinx.serialization.Serializable data class VersionInfo(val Version: String, val Commit: String, val Repo: String) \ No newline at end of file diff --git a/ipfs-api/src/jsMain/kotlin/io/ipfs/kotlin/defaults/createFileSystem.kt b/ipfs-api/src/jsMain/kotlin/io/ipfs/kotlin/defaults/createFileSystem.kt new file mode 100644 index 0000000..eb0567e --- /dev/null +++ b/ipfs-api/src/jsMain/kotlin/io/ipfs/kotlin/defaults/createFileSystem.kt @@ -0,0 +1,6 @@ +package io.ipfs.kotlin.defaults + +import okio.FileSystem +import okio.NodeJsFileSystem + +internal actual fun createFileSystem(): FileSystem = NodeJsFileSystem diff --git a/ipfs-api/src/jvmMain/kotlin/io/ipfs/kotlin/defaults/createFileSystem.kt b/ipfs-api/src/jvmMain/kotlin/io/ipfs/kotlin/defaults/createFileSystem.kt new file mode 100644 index 0000000..75a1c81 --- /dev/null +++ b/ipfs-api/src/jvmMain/kotlin/io/ipfs/kotlin/defaults/createFileSystem.kt @@ -0,0 +1,5 @@ +package io.ipfs.kotlin.defaults + +import okio.FileSystem + +internal actual fun createFileSystem(): FileSystem = FileSystem.SYSTEM diff --git a/lib/src/test/kotlin/io/ipfs/kotlin/BaseIPFSWebserverTest.kt b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/BaseIPFSWebserverTest.kt similarity index 99% rename from lib/src/test/kotlin/io/ipfs/kotlin/BaseIPFSWebserverTest.kt rename to ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/BaseIPFSWebserverTest.kt index 1a680c7..a52d024 100644 --- a/lib/src/test/kotlin/io/ipfs/kotlin/BaseIPFSWebserverTest.kt +++ b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/BaseIPFSWebserverTest.kt @@ -15,6 +15,7 @@ abstract class BaseIPFSWebserverTest { @Before fun runBeforeEveryTest() { server.start() + } @After diff --git a/lib/src/test/kotlin/io/ipfs/kotlin/TestAdd.kt b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestAdd.kt similarity index 53% rename from lib/src/test/kotlin/io/ipfs/kotlin/TestAdd.kt rename to ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestAdd.kt index ea3e66d..29135c2 100644 --- a/lib/src/test/kotlin/io/ipfs/kotlin/TestAdd.kt +++ b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestAdd.kt @@ -1,25 +1,31 @@ package io.ipfs.kotlin +import io.ktor.http.* +import kotlinx.coroutines.test.runTest import okhttp3.mockwebserver.MockResponse +import okio.Path.Companion.toOkioPath import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.io.File import java.nio.file.Files import java.nio.file.Paths -class TestAdd :BaseIPFSWebserverTest() { +class TestAdd : BaseIPFSWebserverTest() { @Test - fun testAddString() { + fun testAddString() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Hash\":\"hashprobe\",\"Name\":\"nameprobe\"}")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("""{"Hash":"hashprobe","Name":"nameprobe", "Size":"1"}""") + ) // invoke val addString = ipfs.add.string("foo") // assert - assertThat(addString.Hash).isEqualTo("hashprobe") - assertThat(addString.Name).isEqualTo("nameprobe") + assertThat(addString.hash).isEqualTo("hashprobe") + assertThat(addString.name).isEqualTo("nameprobe") val executedRequest = server.takeRequest(); assertThat(executedRequest.path).startsWith("/add") @@ -27,16 +33,19 @@ class TestAdd :BaseIPFSWebserverTest() { } @Test - fun testAddFile() { + fun testAddFile() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Hash\":\"hashprobe\",\"Name\":\"nameprobe\"}")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("""{"Hash":"hashprobe","Name":"nameprobe", "Size":"1"}""") + ) // invoke - val addString = ipfs.add.file(File.createTempFile("temptestfile", null)) + val addString = ipfs.add.file(File.createTempFile("temptestfile", null).toOkioPath()) // assert - assertThat(addString.Hash).isEqualTo("hashprobe") - assertThat(addString.Name).isEqualTo("nameprobe") + assertThat(addString.hash).isEqualTo("hashprobe") + assertThat(addString.name).isEqualTo("nameprobe") val executedRequest = server.takeRequest(); assertThat(executedRequest.path).startsWith("/add"); @@ -44,25 +53,28 @@ class TestAdd :BaseIPFSWebserverTest() { } @Test - fun testAddDirectory() { + fun testAddDirectory() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Hash\":\"hashprobe\",\"Name\":\"nameprobe\"}")); + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("""{"Hash":"hashprobe","Name":"nameprobe", "Size":"1"}""") + ); // create nested subdirectories val path = Files.createTempDirectory("temptestdir") val dttf = File.createTempFile("dirtemptestfile", null, path.toFile()) dttf.writeText("Contents of temptestdir/dirtemptestfile") - val subdirpath = Files.createDirectory(Paths.get(path.toString()+File.separator+"subdir")) - val sdttf = File.createTempFile("subdirtemptestfile", null,subdirpath.toFile()) + val subdirpath = Files.createDirectory(Paths.get(path.toString() + File.separator + "subdir")) + val sdttf = File.createTempFile("subdirtemptestfile", null, subdirpath.toFile()) sdttf.writeText("Contents of temptestdir/subdir/subdirtemptestfile") val dttf2 = File.createTempFile("dirtemptestfile2", null, path.toFile()) dttf2.writeText("Contents of temptestdir/dirtemptestfile2") - val result = ipfs.add.directory(path.toFile(),path.fileName.toString()) + val result = ipfs.add.directory(path.toOkioPath(), path.fileName.toString()) // assert - assertThat(result.first().Hash).isEqualTo("hashprobe") - assertThat(result.first().Name).isEqualTo("nameprobe") + assertThat(result.first().hash).isEqualTo("hashprobe") + assertThat(result.first().name).isEqualTo("nameprobe") val executedRequest = server.takeRequest() val body = executedRequest.body.readUtf8() diff --git a/lib/src/test/kotlin/io/ipfs/kotlin/TestGet.kt b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestGet.kt similarity index 87% rename from lib/src/test/kotlin/io/ipfs/kotlin/TestGet.kt rename to ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestGet.kt index 70ee146..7fd055a 100644 --- a/lib/src/test/kotlin/io/ipfs/kotlin/TestGet.kt +++ b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestGet.kt @@ -1,5 +1,6 @@ package io.ipfs.kotlin +import kotlinx.coroutines.test.runTest import okhttp3.mockwebserver.MockResponse import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -7,7 +8,7 @@ import org.junit.Test class TestGet : BaseIPFSWebserverTest() { @Test - fun testAddString() { + fun testAddString() = runTest { // setup server.enqueue(MockResponse().setBody("result")) diff --git a/lib/src/test/kotlin/io/ipfs/kotlin/TestInfo.kt b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestInfo.kt similarity index 67% rename from lib/src/test/kotlin/io/ipfs/kotlin/TestInfo.kt rename to ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestInfo.kt index 92aff54..105fe0a 100644 --- a/lib/src/test/kotlin/io/ipfs/kotlin/TestInfo.kt +++ b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestInfo.kt @@ -1,5 +1,7 @@ package io.ipfs.kotlin +import io.ktor.http.* +import kotlinx.coroutines.test.runTest import okhttp3.mockwebserver.MockResponse import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -7,9 +9,12 @@ import org.junit.Test class TestInfo : BaseIPFSWebserverTest() { @Test - fun testInfo() { + fun testInfo() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Version\":\"0.4.2\",\"Commit\":\"1654bbf\",\"Repo\":\"3\"}\n")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("{\"Version\":\"0.4.2\",\"Commit\":\"1654bbf\",\"Repo\":\"3\"}\n") + ) // invoke val addString = ipfs.info.version() diff --git a/lib/src/test/kotlin/io/ipfs/kotlin/TestName.kt b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestName.kt similarity index 61% rename from lib/src/test/kotlin/io/ipfs/kotlin/TestName.kt rename to ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestName.kt index 4fd63b0..6058660 100644 --- a/lib/src/test/kotlin/io/ipfs/kotlin/TestName.kt +++ b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestName.kt @@ -1,5 +1,7 @@ package io.ipfs.kotlin +import io.ktor.http.* +import kotlinx.coroutines.test.runTest import okhttp3.mockwebserver.MockResponse import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -7,9 +9,12 @@ import org.junit.Test class TestName : BaseIPFSWebserverTest() { @Test - fun testPublishSuccess() { + fun testPublishSuccess() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Name\":\"hashname\",\"Value\":\"hashvalue\"}\n")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("{\"Name\":\"hashname\",\"Value\":\"hashvalue\"}\n") + ) // invoke val result = ipfs.name.publish("hashvalue") @@ -23,9 +28,12 @@ class TestName : BaseIPFSWebserverTest() { } @Test - fun testPublishFail() { + fun testPublishFail() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Message\":\"invalid ipfs ref path\",\"Code\":0}")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("{\"Message\":\"invalid ipfs ref path\",\"Code\":0}") + ) // invoke val result = ipfs.name.publish("hashname") @@ -41,9 +49,12 @@ class TestName : BaseIPFSWebserverTest() { } @Test - fun testResolveSuccess() { + fun testResolveSuccess() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Path\":\"/ipfs/somehash\"}\n")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("{\"Path\":\"/ipfs/somehash\"}\n") + ) // invoke val result = ipfs.name.resolve("somehash") @@ -57,9 +68,12 @@ class TestName : BaseIPFSWebserverTest() { } @Test - fun testResolveFail() { + fun testResolveFail() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Message\":\"Could not resolve name.\",\"Code\":0}")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("{\"Message\":\"Could not resolve name.\",\"Code\":0}") + ) // invoke val result = ipfs.name.resolve("somehash") diff --git a/lib/src/test/kotlin/io/ipfs/kotlin/TestPins.kt b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestPins.kt similarity index 62% rename from lib/src/test/kotlin/io/ipfs/kotlin/TestPins.kt rename to ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestPins.kt index 2da9891..1005447 100644 --- a/lib/src/test/kotlin/io/ipfs/kotlin/TestPins.kt +++ b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestPins.kt @@ -1,5 +1,7 @@ package io.ipfs.kotlin +import io.ktor.http.* +import kotlinx.coroutines.test.runTest import okhttp3.mockwebserver.MockResponse import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -7,9 +9,12 @@ import org.junit.Test class TestPins : BaseIPFSWebserverTest() { @Test - fun testAddPin() { + fun testAddPin() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Pins\":[\"QmPFDyWdL6yjz92jdc6bzWXHKVvydAhgTzhefSmmkDXzSZ\"]}")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("{\"Pins\":[\"QmPFDyWdL6yjz92jdc6bzWXHKVvydAhgTzhefSmmkDXzSZ\"]}") + ) // invoke val result = ipfs.pins.add("QmPFDyWdL6yjz92jdc6bzWXHKVvydAhgTzhefSmmkDXzSZ") @@ -23,9 +28,12 @@ class TestPins : BaseIPFSWebserverTest() { } @Test - fun testAddPinFail() { + fun testAddPinFail() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Message\":\"pin: invalid ipfs ref path\",\"Code\":0}")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("{\"Message\":\"pin: invalid ipfs ref path\",\"Code\":0}") + ) // invoke val result = ipfs.pins.add("foo") diff --git a/lib/src/test/kotlin/io/ipfs/kotlin/TestRepo.kt b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestRepo.kt similarity index 51% rename from lib/src/test/kotlin/io/ipfs/kotlin/TestRepo.kt rename to ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestRepo.kt index a573f14..b801fa9 100644 --- a/lib/src/test/kotlin/io/ipfs/kotlin/TestRepo.kt +++ b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestRepo.kt @@ -1,13 +1,15 @@ package io.ipfs.kotlin +import io.ktor.http.* +import kotlinx.coroutines.test.runTest import okhttp3.mockwebserver.MockResponse import org.assertj.core.api.Assertions.assertThat import org.junit.Test -class TestRepo :BaseIPFSWebserverTest() { +class TestRepo : BaseIPFSWebserverTest() { @Test - fun testEmptyGC() { + fun testEmptyGC() = runTest { // setup server.enqueue(MockResponse().setBody("")) @@ -23,45 +25,54 @@ class TestRepo :BaseIPFSWebserverTest() { } @Test - fun testGC() { + fun testGC() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Key\":\"k1\"}\n{\"Key\":\"k2\"}\n{\"Key\":\"k3\"}\n")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("{\"Key\":\"k1\"}\n{\"Key\":\"k2\"}\n{\"Key\":\"k3\"}\n") + ) // invoke val addString = ipfs.repo.gc() // assert - assertThat(addString).containsExactly("k1","k2","k3") + assertThat(addString).containsExactly("k1", "k2", "k3") val executedRequest = server.takeRequest(); assertThat(executedRequest.path).startsWith("/repo/gc") } @Test - fun testNewGCResponseStyle() { + fun testNewGCResponseStyle() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Key\":{\"/\":\"k1\"}}\n{\"Key\":{\"/\":\"k2\"}}\n{\"Key\":{\"/\":\"k3\"}}\n")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("{\"Key\":{\"/\":\"k1\"}}\n{\"Key\":{\"/\":\"k2\"}}\n{\"Key\":{\"/\":\"k3\"}}\n") + ) // invoke val addString = ipfs.repo.gc() // assert - assertThat(addString).containsExactly("k1","k2","k3") + assertThat(addString).containsExactly("k1", "k2", "k3") val executedRequest = server.takeRequest(); assertThat(executedRequest.path).startsWith("/repo/gc") } @Test - fun testDifferentSeparator() { + fun testDifferentSeparator() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"Key\":\"k1\"}\r\n{\"Key\":\"k2\"}\r\n{\"Key\":\"k3\"}")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("{\"Key\":\"k1\"}\r\n{\"Key\":\"k2\"}\r\n{\"Key\":\"k3\"}") + ) // invoke val addString = ipfs.repo.gc() // assert - assertThat(addString).containsExactly("k1","k2","k3") + assertThat(addString).containsExactly("k1", "k2", "k3") val executedRequest = server.takeRequest(); assertThat(executedRequest.path).startsWith("/repo/gc") diff --git a/lib/src/test/kotlin/io/ipfs/kotlin/TestStats.kt b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestStats.kt similarity index 67% rename from lib/src/test/kotlin/io/ipfs/kotlin/TestStats.kt rename to ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestStats.kt index 127f23c..33e4272 100644 --- a/lib/src/test/kotlin/io/ipfs/kotlin/TestStats.kt +++ b/ipfs-api/src/jvmTest/kotlin/io/ipfs/kotlin/TestStats.kt @@ -1,5 +1,7 @@ package io.ipfs.kotlin +import io.ktor.http.* +import kotlinx.coroutines.test.runTest import okhttp3.mockwebserver.MockResponse import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -7,9 +9,12 @@ import org.junit.Test class TestStats : BaseIPFSWebserverTest() { @Test - fun testBandWidthStats() { + fun testBandWidthStats() = runTest { // setup - server.enqueue(MockResponse().setBody("{\"TotalIn\":80461165,\"TotalOut\":70998948,\"RateIn\":1103.8830769540511,\"RateOut\":1814.6417381019044}\n")) + server.enqueue( + MockResponse().setHeader("Content-Type", ContentType.Application.Json) + .setBody("{\"TotalIn\":80461165,\"TotalOut\":70998948,\"RateIn\":1103.8830769540511,\"RateOut\":1814.6417381019044}\n") + ) // invoke val statsBandWidth = ipfs.stats.bandWidth() diff --git a/ipfs-api/src/nativeMain/kotlin/io/ipfs/kotlin/defaults/createFileSystem.kt b/ipfs-api/src/nativeMain/kotlin/io/ipfs/kotlin/defaults/createFileSystem.kt new file mode 100644 index 0000000..75a1c81 --- /dev/null +++ b/ipfs-api/src/nativeMain/kotlin/io/ipfs/kotlin/defaults/createFileSystem.kt @@ -0,0 +1,5 @@ +package io.ipfs.kotlin.defaults + +import okio.FileSystem + +internal actual fun createFileSystem(): FileSystem = FileSystem.SYSTEM diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock new file mode 100644 index 0000000..44494f1 --- /dev/null +++ b/kotlin-js-store/yarn.lock @@ -0,0 +1,615 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ungap/promise-all-settled@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" + integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== + +abort-controller@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +camelcase@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +debug@4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +dukat@0.5.8-rc.4: + version "0.5.8-rc.4" + resolved "https://registry.yarnpkg.com/dukat/-/dukat-0.5.8-rc.4.tgz#90384dcb50b14c26f0e99dae92b2dea44f5fce21" + integrity sha512-ZnMt6DGBjlVgK2uQamXfd7uP/AxH7RqI0BL9GLrrJb2gKdDxvJChWy+M9AQEaL+7/6TmxzJxFOsRiInY9oGWTA== + dependencies: + google-protobuf "3.12.2" + typescript "3.9.5" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +format-util@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" + integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +google-protobuf@3.12.2: + version "3.12.2" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.12.2.tgz#50ce9f9b6281235724eb243d6a83e969a2176e53" + integrity sha512-4CZhpuRr1d6HjlyrxoXoocoGFnRYgKULgMtikMddA9ztRyYR59Aondv2FioyxWVamRo0rF2XpYawkTCBEQOSkA== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +js-yaml@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +log-symbols@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +mocha@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" + integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.2.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + nanoid "3.3.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== + +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +source-map-support@0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + +typescript@3.9.5: + version "3.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" + integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/lib/build.gradle b/lib/build.gradle deleted file mode 100644 index 83af898..0000000 --- a/lib/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -buildscript { - ext.kotlin_version = '1.3.72' - ext.okhttp_version = '3.11.0' - - repositories { - jcenter() - } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0' - } -} - -plugins { - id 'jacoco' -} - - -apply plugin: "kotlin" -apply plugin: 'maven' -apply plugin: 'com.github.ben-manes.versions' - -group = 'com.github.ligi' - -repositories { - jcenter() -} - -dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" - // we can't yet use moshi 1.5.0 as we need @Json https://github.com/square/moshi/issues/315 - compile 'com.squareup.moshi:moshi:1.4.0' - compile "com.squareup.okhttp3:okhttp:${okhttp_version}" - - testCompile 'junit:junit:4.13' - testCompile 'org.mockito:mockito-core:3.3.3' - testCompile "com.squareup.okhttp3:mockwebserver:${okhttp_version}" - testCompile 'org.assertj:assertj-core:3.10.0' -} diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/IPFSConnection.kt b/lib/src/main/kotlin/io/ipfs/kotlin/IPFSConnection.kt deleted file mode 100644 index a787aa8..0000000 --- a/lib/src/main/kotlin/io/ipfs/kotlin/IPFSConnection.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.ipfs.kotlin - -import com.squareup.moshi.JsonAdapter -import io.ipfs.kotlin.model.MessageWithCode -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.ResponseBody - -open class IPFSConnection(val config: IPFSConfiguration) { - - var lastError: MessageWithCode? = null - - private val errorAdapter: JsonAdapter by lazy { - config.moshi.adapter(MessageWithCode::class.java) - } - - fun callCmd(cmd: String): ResponseBody { - val request = Request.Builder() - .post(RequestBody.create(null, "")) - .url(config.base_url + cmd) - .build() - - return config.okHttpClient.newCall(request).execute().body()!! - } - - fun setErrorByJSON(jsonString: String) { - lastError = errorAdapter.fromJson(jsonString) - } -} \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Add.kt b/lib/src/main/kotlin/io/ipfs/kotlin/commands/Add.kt deleted file mode 100644 index 63da417..0000000 --- a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Add.kt +++ /dev/null @@ -1,80 +0,0 @@ -package io.ipfs.kotlin.commands - -import com.squareup.moshi.JsonAdapter -import io.ipfs.kotlin.IPFSConnection -import io.ipfs.kotlin.model.NamedHash -import okhttp3.* -import java.io.File -import java.net.URLEncoder - -class Add(val ipfs: IPFSConnection) { - - private val adapter: JsonAdapter by lazy { - ipfs.config.moshi.adapter(NamedHash::class.java) - } - - // Accepts a single file or directory and returns the named hash. - // For directories we return the hash of the enclosing - // directory because that makes the most sense, also for - // consistency with the java-ipfs-api implementation. - fun file(file: File, name: String = "file", filename: String = name) = addGeneric { - addFile(it, file, name, filename) - }.last() - - - // Accepts a single file or directory and returns the named hash. - // Returns a collection of named hashes for the containing directory - // and all sub-directories. - fun directory(file: File, name: String = "file", filename: String = name) = addGeneric { - addFile(it, file, name, filename) - } - - - // this has to be outside the lambda because it is reentrant to handle subdirectory structures - private fun addFile(builder: MultipartBody.Builder, file: File, name: String, filename: String) { - - val encodedFileName = URLEncoder.encode(filename, "UTF-8") - val headers = Headers.of("Content-Disposition", "file; filename=\"$encodedFileName\"", "Content-Transfer-Encoding", "binary") - if (file.isDirectory) { - // add directory - builder.addPart(headers, RequestBody.create(MediaType.parse("application/x-directory"), "")) - // add files and subdirectories - for (f: File in file.listFiles()) { - addFile(builder, f, f.name, filename + "/" + f.name) - } - } else { - builder.addPart(headers, RequestBody.create(MediaType.parse("application/octet-stream"), file)) - } - - } - - fun string(text: String, name: String = "string", filename: String = name): NamedHash { - - return addGeneric { - val body = RequestBody.create(MediaType.parse("application/octet-stream"), text) - it.addFormDataPart(name, filename, body) - }.last() - // there can be only one - - } - - private fun addGeneric(withBuilder: (MultipartBody.Builder) -> Any): List { - - val builder = MultipartBody.Builder().setType(MultipartBody.FORM) - withBuilder(builder) - val requestBody = builder.build() - - val request = Request.Builder() - .url("${ipfs.config.base_url}add?stream-channels=true&progress=false") - .post(requestBody) - .build() - - val response = ipfs.config.okHttpClient.newCall(request).execute().body() - - response.use { responseBody -> - return responseBody!!.charStream().readLines().asSequence().map { - adapter.fromJson(it) - }.filterNotNull().toList() - } - } -} diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Get.kt b/lib/src/main/kotlin/io/ipfs/kotlin/commands/Get.kt deleted file mode 100644 index 9056d6f..0000000 --- a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Get.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.ipfs.kotlin.commands - -import io.ipfs.kotlin.IPFSConnection -import okhttp3.ResponseBody -import java.io.InputStream - -class Get(val ipfs: IPFSConnection) { - - /** - * Cat IPFS content and return it as string. - * - * @param hash The hash of the content in base58. - */ - fun cat(hash: String): String = ipfs.callCmd("cat/$hash").use(ResponseBody::string) - - /** - * Cat IPFS content and return it as ByteArray. - * - * @param hash The hash of the content in base58. - */ - fun catBytes(hash: String): ByteArray = ipfs.callCmd("cat/$hash").use(ResponseBody::bytes) - - /** - * Cat IPFS content and process it using InputStream. - * - * @param hash The hash of the content in base58. - * @param handler Callback which handle processing the input stream. When the callback return the stream and the request body will be closed. - */ - fun catStream(hash: String, handler: (stream: InputStream) -> Unit): Unit = - ipfs.callCmd("cat/$hash").use { body -> - val inputStream = body.byteStream() - inputStream.use(handler) - } - -} \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Info.kt b/lib/src/main/kotlin/io/ipfs/kotlin/commands/Info.kt deleted file mode 100644 index e6b2e98..0000000 --- a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Info.kt +++ /dev/null @@ -1,15 +0,0 @@ -package io.ipfs.kotlin.commands - -import io.ipfs.kotlin.IPFSConnection -import io.ipfs.kotlin.model.VersionInfo - -class Info(val ipfs: IPFSConnection) { - - private val versionAdapter= ipfs.config.moshi.adapter(VersionInfo::class.java) - - fun version(): VersionInfo? { - val response = ipfs.callCmd("version") - return response.use { versionAdapter.fromJson(it.source()) } - } - -} \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Name.kt b/lib/src/main/kotlin/io/ipfs/kotlin/commands/Name.kt deleted file mode 100644 index 32ded05..0000000 --- a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Name.kt +++ /dev/null @@ -1,41 +0,0 @@ -package io.ipfs.kotlin.commands - -import com.squareup.moshi.JsonAdapter -import io.ipfs.kotlin.IPFSConnection -import io.ipfs.kotlin.model.NameValue -import io.ipfs.kotlin.model.Path - -class Name(val ipfs: IPFSConnection) { - - private val adapter: JsonAdapter by lazy { - ipfs.config.moshi.adapter(NameValue::class.java) - } - - private val pathAdapter: JsonAdapter by lazy { - ipfs.config.moshi.adapter(Path::class.java) - } - - fun publish(hash: String): String? { - val resultString = ipfs.callCmd("name/publish/$hash").use { it.string() } - val resultBoolean = resultString.contains(hash) - if (!resultBoolean) { - ipfs.setErrorByJSON(resultString) - return null - } - return adapter.fromJson(resultString)?.Name - } - - fun resolve(hash: String): String? { - val resultString = ipfs.callCmd("name/resolve/$hash").use { it.string() } - - return when { - resultString == null -> null - resultString.contains("Path") -> pathAdapter.fromJson(resultString)?.Path - else -> { - ipfs.setErrorByJSON(resultString) - null - } - } - } - -} \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Repo.kt b/lib/src/main/kotlin/io/ipfs/kotlin/commands/Repo.kt deleted file mode 100644 index 89fcbf0..0000000 --- a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Repo.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.ipfs.kotlin.commands - -import com.squareup.moshi.JsonDataException -import io.ipfs.kotlin.IPFSConnection -import io.ipfs.kotlin.model.Key -import io.ipfs.kotlin.model.KeyV2 -import okio.BufferedSource - -class Repo(val ipfs: IPFSConnection) { - - fun gc() = ipfs.callCmd("repo/gc").use { listFromNDJson(it.source()) } - - fun listFromNDJson(source: BufferedSource): List { - val jsonString = source.readUtf8() - return try { - val keyV2Adapter = ipfs.config.moshi.adapter(KeyV2::class.java) - jsonString.parseNDJSON { keyV2Adapter.fromJson(it)?.Key?.hash } - } catch (e: JsonDataException) { - val keyAdapter = ipfs.config.moshi.adapter(Key::class.java) - jsonString.parseNDJSON { keyAdapter.fromJson(it)?.Key } - } - } - - // http://ndjson.org - private fun String.parseNDJSON(convert: (input: String?) -> String?) = replace("\r", "").split("\n").asSequence().filter { !it.isEmpty() }.map { - convert(it) - }.filterNotNull().toList() -} \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Stats.kt b/lib/src/main/kotlin/io/ipfs/kotlin/commands/Stats.kt deleted file mode 100644 index 9a87348..0000000 --- a/lib/src/main/kotlin/io/ipfs/kotlin/commands/Stats.kt +++ /dev/null @@ -1,15 +0,0 @@ -package io.ipfs.kotlin.commands - -import io.ipfs.kotlin.IPFSConnection -import io.ipfs.kotlin.model.BandWidthInfo - -class Stats(val ipfs: IPFSConnection) { - - private val bandWidthAdapter = ipfs.config.moshi.adapter(BandWidthInfo::class.java) - - fun bandWidth(): BandWidthInfo? { - val response = ipfs.callCmd("stats/bw") - return response.use { bandWidthAdapter.fromJson(it.source()) } - } - -} \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/defaults/Defaults.kt b/lib/src/main/kotlin/io/ipfs/kotlin/defaults/Defaults.kt deleted file mode 100644 index 04ab679..0000000 --- a/lib/src/main/kotlin/io/ipfs/kotlin/defaults/Defaults.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.ipfs.kotlin.defaults - -import com.squareup.moshi.Moshi -import io.ipfs.kotlin.commands.* -import io.ipfs.kotlin.model.MessageWithCode -import okhttp3.OkHttpClient - -internal fun createOKHTTP() = OkHttpClient.Builder().build() - -internal fun createMoshi() = Moshi.Builder().build() \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/defaults/InfuraIPFS.kt b/lib/src/main/kotlin/io/ipfs/kotlin/defaults/InfuraIPFS.kt deleted file mode 100644 index 6434431..0000000 --- a/lib/src/main/kotlin/io/ipfs/kotlin/defaults/InfuraIPFS.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.ipfs.kotlin.defaults - -import io.ipfs.kotlin.IPFS -import io.ipfs.kotlin.IPFSConfiguration - -val infuraIPFSConfig by lazy { - IPFSConfiguration("https://ipfs.infura.io:5001/api/v0/", createOKHTTP(), createMoshi()) -} - -open class InfuraIPFS : IPFS(infuraIPFSConfig) \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/model/KeyV2.kt b/lib/src/main/kotlin/io/ipfs/kotlin/model/KeyV2.kt deleted file mode 100644 index e808d94..0000000 --- a/lib/src/main/kotlin/io/ipfs/kotlin/model/KeyV2.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.ipfs.kotlin.model - -import com.squareup.moshi.Json - -data class KeyV2Content(@Json(name = "/") val hash: String) -data class KeyV2(val Key: KeyV2Content) \ No newline at end of file diff --git a/lib/src/main/kotlin/io/ipfs/kotlin/model/NamedHash.kt b/lib/src/main/kotlin/io/ipfs/kotlin/model/NamedHash.kt deleted file mode 100644 index 54b99b2..0000000 --- a/lib/src/main/kotlin/io/ipfs/kotlin/model/NamedHash.kt +++ /dev/null @@ -1,4 +0,0 @@ -package io.ipfs.kotlin.model - -data class NamedHash(val Name: String, - val Hash: String) \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index d1ca3ba..0000000 --- a/settings.gradle +++ /dev/null @@ -1,4 +0,0 @@ -include 'example' -include 'lib' - -rootProject.name = 'ipfs-api-kotlin' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..a78a187 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,24 @@ +rootProject.name = "ipfs-api-kotlin" + +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } + + plugins { + kotlin("multiplatform") version "1.7.10" + kotlin("jvm") version "1.7.10" + kotlin("plugin.serialization") version "1.7.10" + id("com.github.ben-manes.versions") version "0.42.0" + } +} + +dependencyResolutionManagement { + repositories { + mavenCentral() + } +} + +include("example") +include("ipfs-api")