diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ebe8c539..f3627a55 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,6 +20,12 @@ jobs: mkdir -p ./jdk/binaries/ curl https://cdn.azul.com/zulu/bin/zulu11.56.19-ca-fx-jdk11.0.15-linux_x64.tar.gz --output ./jdk/zulu11.56.19-ca-fx-jdk11.0.15-linux_x64.tar.gz + - task: Bash@3 + inputs: + targetType: 'inline' + script: | + sudo apt-get install libqdbm-dev + - task: JavaToolInstaller@0 inputs: jdkFile: ./jdk/zulu11.56.19-ca-fx-jdk11.0.15-linux_x64.tar.gz diff --git a/multiplatform-settings/build.gradle.kts b/multiplatform-settings/build.gradle.kts index 80c12264..42d4ed18 100644 --- a/multiplatform-settings/build.gradle.kts +++ b/multiplatform-settings/build.gradle.kts @@ -14,6 +14,8 @@ * limitations under the License. */ +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget + plugins { id("com.android.library") kotlin("multiplatform") @@ -25,6 +27,11 @@ plugins { standardConfiguration() kotlin { + targets.getByName("linuxX64") { + compilations["main"].cinterops.create("qdbm-depot") + compilations["main"].cinterops.create("qdbm-relic") + compilations["main"].cinterops.create("qdbm-villa") + } sourceSets { commonMain { dependencies { diff --git a/multiplatform-settings/src/linuxX64Main/kotlin/QdbmDepotSettings.kt b/multiplatform-settings/src/linuxX64Main/kotlin/QdbmDepotSettings.kt new file mode 100644 index 00000000..9cf6a0c0 --- /dev/null +++ b/multiplatform-settings/src/linuxX64Main/kotlin/QdbmDepotSettings.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2022 Russell Wolf + * + * 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 + * + * http://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 com.russhwolf.settings + +import com.russhwolf.settings.cinterop.qdbm.depot.DEPOT +import com.russhwolf.settings.cinterop.qdbm.depot.DP_DOVER +import com.russhwolf.settings.cinterop.qdbm.depot.DP_OCREAT +import com.russhwolf.settings.cinterop.qdbm.depot.DP_OREADER +import com.russhwolf.settings.cinterop.qdbm.depot.DP_OWRITER +import com.russhwolf.settings.cinterop.qdbm.depot.dpclose +import com.russhwolf.settings.cinterop.qdbm.depot.dpecode +import com.russhwolf.settings.cinterop.qdbm.depot.dperrmsg +import com.russhwolf.settings.cinterop.qdbm.depot.dpget +import com.russhwolf.settings.cinterop.qdbm.depot.dpiterinit +import com.russhwolf.settings.cinterop.qdbm.depot.dpiternext +import com.russhwolf.settings.cinterop.qdbm.depot.dpopen +import com.russhwolf.settings.cinterop.qdbm.depot.dpout +import com.russhwolf.settings.cinterop.qdbm.depot.dpput +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.MemScope +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.toKString + +// TODO clean up error checking? +// TODO allow specifying directory +public class QdbmDepotSettings(private val path: String) : Settings { + + override val keys: Set + get() = depotOperation { depot -> + depot.foldKeys(mutableListOf()) { list, key -> list.apply { add(key) } }.toSet() + } + + override val size: Int get() = depotOperation { depot -> depot.foldKeys(0) { size, _ -> size + 1 } } + + public override fun clear(): Unit = depotOperation { depot -> depot.forEachKey { dpout(depot, it, -1) } } + public override fun remove(key: String): Unit = depotOperation { depot -> dpout(depot, key, -1) } + public override fun hasKey(key: String): Boolean = depotOperation { depot -> + depot.forEachKey { if (key == it) return true } + return false + } + + public override fun putInt(key: String, value: Int): Unit = saveString(key, value.toString()) + public override fun getInt(key: String, defaultValue: Int): Int = getIntOrNull(key) ?: defaultValue + public override fun getIntOrNull(key: String): Int? = loadString(key)?.toInt() + + public override fun putLong(key: String, value: Long): Unit = saveString(key, value.toString()) + public override fun getLong(key: String, defaultValue: Long): Long = getLongOrNull(key) ?: defaultValue + public override fun getLongOrNull(key: String): Long? = loadString(key)?.toLong() + + public override fun putString(key: String, value: String): Unit = saveString(key, value) + public override fun getString(key: String, defaultValue: String): String = getStringOrNull(key) ?: defaultValue + public override fun getStringOrNull(key: String): String? = loadString(key) + + public override fun putFloat(key: String, value: Float): Unit = saveString(key, value.toString()) + public override fun getFloat(key: String, defaultValue: Float): Float = getFloatOrNull(key) ?: defaultValue + public override fun getFloatOrNull(key: String): Float? = loadString(key)?.toFloat() + + public override fun putDouble(key: String, value: Double): Unit = saveString(key, value.toString()) + public override fun getDouble(key: String, defaultValue: Double): Double = getDoubleOrNull(key) ?: defaultValue + public override fun getDoubleOrNull(key: String): Double? = loadString(key)?.toDouble() + + public override fun putBoolean(key: String, value: Boolean): Unit = saveString(key, value.toString()) + public override fun getBoolean(key: String, defaultValue: Boolean): Boolean = getBooleanOrNull(key) ?: defaultValue + public override fun getBooleanOrNull(key: String): Boolean? = loadString(key)?.toBoolean() + + private inline fun saveString(key: String, value: String): Unit = depotOperation { depot -> + println("saving $key=$value") + dpput(depot, key, -1, value, -1, DP_DOVER.toInt()) + } + + private inline fun loadString(key: String): String? = depotOperation { depot -> + println("loading $key") + val output = dpget(depot, key, -1, 0, -1, null) + output?.toKString() + } + + private inline fun CPointer.forEachKey(block: (key: String) -> Unit) { + val depot = this + if (dpiterinit(depot) != 0) { + while (true) { + val key = dpiternext(depot, null)?.toKString() + if (key != null) { + println("interating through key $key") + block(key) + } else { + break + } + } + } + } + + private inline fun CPointer.foldKeys(initial: A, block: (accumulator: A, key: String) -> A): A { + var accumulator = initial + forEachKey { accumulator = block(accumulator, it) } + return accumulator + } + + private inline fun depotOperation(action: MemScope.(depot: CPointer) -> T): T = memScoped { + val depot = dpopen(path, (DP_OWRITER or DP_OREADER or DP_OCREAT).toInt(), 0) + if (depot == null) { + val message = dperrmsg(dpecode)?.toKString() + error("error on depot open: $message") + } + val out = action(depot) + dpclose(depot) + out + } +} \ No newline at end of file diff --git a/multiplatform-settings/src/linuxX64Main/kotlin/QdbmRelicSettings.kt b/multiplatform-settings/src/linuxX64Main/kotlin/QdbmRelicSettings.kt new file mode 100644 index 00000000..2ef3fc84 --- /dev/null +++ b/multiplatform-settings/src/linuxX64Main/kotlin/QdbmRelicSettings.kt @@ -0,0 +1,167 @@ +/* + * Copyright 2022 Russell Wolf + * + * 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 + * + * http://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 com.russhwolf.settings + +import com.russhwolf.settings.cinterop.qdbm.relic.DBM +import com.russhwolf.settings.cinterop.qdbm.relic.DBM_REPLACE +import com.russhwolf.settings.cinterop.qdbm.relic.datum +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_clearerr +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_close +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_delete +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_error +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_fetch +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_firstkey +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_nextkey +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_open +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_store +import kotlinx.cinterop.ByteVar +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.CValue +import kotlinx.cinterop.MemScope +import kotlinx.cinterop.cValue +import kotlinx.cinterop.cstr +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.plus +import kotlinx.cinterop.pointed +import kotlinx.cinterop.reinterpret +import kotlinx.cinterop.toCValues +import kotlinx.cinterop.useContents +import kotlinx.cinterop.value +import platform.posix.O_CREAT +import platform.posix.O_RDWR +import platform.posix.S_IRGRP +import platform.posix.S_IROTH +import platform.posix.S_IRUSR +import platform.posix.S_IWUSR +import platform.posix.errno + +// TODO clean up error checking? +// TODO allow specifying directory +public class QdbmRelicSettings(private val path: String) : Settings { + + override val keys: Set + get() = dbmOperation { dbm -> + dbm.foldKeys(mutableListOf()) { list, key -> list.apply { add(key.toKString()!!) } }.toSet() + } + + override val size: Int get() = dbmOperation { dbm -> dbm.foldKeys(0) { size, _ -> size + 1 } } + + public override fun clear(): Unit = dbmOperation { dbm -> dbm.forEachKey { dbm_delete(dbm, it) } } + public override fun remove(key: String): Unit = dbmOperation { dbm -> dbm_delete(dbm, datumOf(key)) } + public override fun hasKey(key: String): Boolean = dbmOperation { dbm -> + dbm.forEachKey { if (key == it.toKString()) return true } + return false + } + + public override fun putInt(key: String, value: Int): Unit = saveBytes(key, value.toByteArray()) + public override fun getInt(key: String, defaultValue: Int): Int = getIntOrNull(key) ?: defaultValue + public override fun getIntOrNull(key: String): Int? = loadBytes(key)?.toInt() + + public override fun putLong(key: String, value: Long): Unit = saveBytes(key, value.toByteArray()) + public override fun getLong(key: String, defaultValue: Long): Long = getLongOrNull(key) ?: defaultValue + public override fun getLongOrNull(key: String): Long? = loadBytes(key)?.toLong() + + public override fun putString(key: String, value: String): Unit = saveBytes(key, value.encodeToByteArray()) + public override fun getString(key: String, defaultValue: String): String = getStringOrNull(key) ?: defaultValue + public override fun getStringOrNull(key: String): String? = loadBytes(key)?.decodeToString() + + public override fun putFloat(key: String, value: Float): Unit = saveBytes(key, value.toRawBits().toByteArray()) + public override fun getFloat(key: String, defaultValue: Float): Float = getFloatOrNull(key) ?: defaultValue + public override fun getFloatOrNull(key: String): Float? = loadBytes(key)?.toInt()?.let { Float.fromBits(it) } + + public override fun putDouble(key: String, value: Double): Unit = saveBytes(key, value.toRawBits().toByteArray()) + public override fun getDouble(key: String, defaultValue: Double): Double = getDoubleOrNull(key) ?: defaultValue + public override fun getDoubleOrNull(key: String): Double? = loadBytes(key)?.toLong()?.let { Double.fromBits(it) } + + public override fun putBoolean(key: String, value: Boolean): Unit = saveBytes(key, byteArrayOf(if (value) 1 else 0)) + public override fun getBoolean(key: String, defaultValue: Boolean): Boolean = getBooleanOrNull(key) ?: defaultValue + public override fun getBooleanOrNull(key: String): Boolean? = loadBytes(key)?.get(0)?.equals(0)?.not() + + private inline fun saveBytes(key: String, bytes: ByteArray): Unit = dbmOperation { dbm -> + dbm_store(dbm, datumOf(key), datumOf(bytes), DBM_REPLACE.toInt()) + } + + private inline fun loadBytes(key: String): ByteArray? = dbmOperation { dbm -> + val datum = dbm_fetch(dbm, datumOf(key)) + datum.toByteArray() + } + + private inline fun CPointer.forEachKey(block: (key: CValue) -> Unit) { + val dbm = this + var key = dbm_firstkey(dbm) + while (key.useContents { dptr != null }) { + block(key) + key = dbm_nextkey(dbm) + } + } + + private inline fun CPointer.foldKeys(initial: A, block: (accumulator: A, key: CValue) -> A): A { + var accumulator = initial + forEachKey { accumulator = block(accumulator, it) } + return accumulator + } + + private inline fun dbmOperation(action: MemScope.(dbm: CPointer) -> T): T = memScoped { + val dbm = dbm_open(path.cstr, O_RDWR or O_CREAT, S_IRUSR or S_IWUSR or S_IRGRP or S_IROTH) + ?: error("Error on dbm_open: $errno") + val out = action(dbm) + val error = dbm_error(dbm) + if (error != 0) { + try { + error("error: $error") + } finally { + dbm_clearerr(dbm) + } + } + dbm_close(dbm) + out + } + + private inline fun ByteArray.toLong(): Long = foldIndexed(0) { index, total: Long, byte: Byte -> + ((0xff.toLong() and byte.toLong()) shl index * Byte.SIZE_BITS) or total + } + + private inline fun ByteArray.toInt(): Int = foldIndexed(0) { index, total: Int, byte: Byte -> + ((0xff and byte.toInt()) shl index * Byte.SIZE_BITS) or total + } + + private inline fun Long.toByteArray(): ByteArray = ByteArray(Long.SIZE_BYTES) { index -> + ((this shr (Byte.SIZE_BITS * index)) and 0xff).toByte() + } + + private inline fun Int.toByteArray(): ByteArray = ByteArray(Int.SIZE_BYTES) { index -> + ((this shr (Byte.SIZE_BITS * index)) and 0xff).toByte() + } + + private inline fun CValue.toKString(): String? = toByteArray()?.decodeToString() + private inline fun CValue.toByteArray(): ByteArray? = useContents { + val size = dsize.toInt() + val firstPtr: CPointer = dptr?.reinterpret() + ?: return null + return ByteArray(size) { + val pointedValue = firstPtr.plus(it)?.pointed?.value + pointedValue ?: 0 + } + } + + private inline fun MemScope.datumOf(string: String): CValue = datumOf(string.encodeToByteArray()) + private inline fun MemScope.datumOf(bytes: ByteArray): CValue = cValue { + val cValues = bytes.toCValues() + dptr = cValues.ptr + dsize = cValues.size.toULong() + } +} \ No newline at end of file diff --git a/multiplatform-settings/src/linuxX64Main/kotlin/QdbmVillaSettings.kt b/multiplatform-settings/src/linuxX64Main/kotlin/QdbmVillaSettings.kt new file mode 100644 index 00000000..03594998 --- /dev/null +++ b/multiplatform-settings/src/linuxX64Main/kotlin/QdbmVillaSettings.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2022 Russell Wolf + * + * 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 + * + * http://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 com.russhwolf.settings + +import com.russhwolf.settings.cinterop.qdbm.villa.VILLA +import com.russhwolf.settings.cinterop.qdbm.villa.VL_CMPLEX +import com.russhwolf.settings.cinterop.qdbm.villa.VL_DOVER +import com.russhwolf.settings.cinterop.qdbm.villa.VL_OCREAT +import com.russhwolf.settings.cinterop.qdbm.villa.VL_OREADER +import com.russhwolf.settings.cinterop.qdbm.villa.VL_OWRITER +import com.russhwolf.settings.cinterop.qdbm.villa.dpecode +import com.russhwolf.settings.cinterop.qdbm.villa.dperrmsg +import com.russhwolf.settings.cinterop.qdbm.villa.vlclose +import com.russhwolf.settings.cinterop.qdbm.villa.vlcurfirst +import com.russhwolf.settings.cinterop.qdbm.villa.vlcurkey +import com.russhwolf.settings.cinterop.qdbm.villa.vlcurnext +import com.russhwolf.settings.cinterop.qdbm.villa.vlget +import com.russhwolf.settings.cinterop.qdbm.villa.vlopen +import com.russhwolf.settings.cinterop.qdbm.villa.vlout +import com.russhwolf.settings.cinterop.qdbm.villa.vlput +import com.russhwolf.settings.cinterop.qdbm.villa.vlrnum +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.MemScope +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.toKString + +// TODO clean up error checking? +// TODO allow specifying directory +public class QdbmVillaSettings(private val path: String) : Settings { + + override val keys: Set + get() = villaOperation { villa -> + villa.foldKeys(mutableListOf()) { list, key -> list.apply { add(key) } }.toSet() + } + + override val size: Int get() = villaOperation { villa -> vlrnum(villa) } + + public override fun clear(): Unit = keys.forEach { remove(it) } + + //villaOperation { villa -> villa.forEachKey { vlout(villa, it, -1) } } + public override fun remove(key: String): Unit = villaOperation { villa -> vlout(villa, key, -1) } + public override fun hasKey(key: String): Boolean = villaOperation { villa -> + villa.forEachKey { if (key == it) return true } + return false + } + + public override fun putInt(key: String, value: Int): Unit = saveString(key, value.toString()) + public override fun getInt(key: String, defaultValue: Int): Int = getIntOrNull(key) ?: defaultValue + public override fun getIntOrNull(key: String): Int? = loadString(key)?.toInt() + + public override fun putLong(key: String, value: Long): Unit = saveString(key, value.toString()) + public override fun getLong(key: String, defaultValue: Long): Long = getLongOrNull(key) ?: defaultValue + public override fun getLongOrNull(key: String): Long? = loadString(key)?.toLong() + + public override fun putString(key: String, value: String): Unit = saveString(key, value) + public override fun getString(key: String, defaultValue: String): String = getStringOrNull(key) ?: defaultValue + public override fun getStringOrNull(key: String): String? = loadString(key) + + public override fun putFloat(key: String, value: Float): Unit = saveString(key, value.toString()) + public override fun getFloat(key: String, defaultValue: Float): Float = getFloatOrNull(key) ?: defaultValue + public override fun getFloatOrNull(key: String): Float? = loadString(key)?.toFloat() + + public override fun putDouble(key: String, value: Double): Unit = saveString(key, value.toString()) + public override fun getDouble(key: String, defaultValue: Double): Double = getDoubleOrNull(key) ?: defaultValue + public override fun getDoubleOrNull(key: String): Double? = loadString(key)?.toDouble() + + public override fun putBoolean(key: String, value: Boolean): Unit = saveString(key, value.toString()) + public override fun getBoolean(key: String, defaultValue: Boolean): Boolean = getBooleanOrNull(key) ?: defaultValue + public override fun getBooleanOrNull(key: String): Boolean? = loadString(key)?.toBoolean() + + private inline fun saveString(key: String, value: String): Unit = villaOperation { villa -> + vlput(villa, key, -1, value, -1, VL_DOVER.toInt()) + } + + private inline fun loadString(key: String): String? = villaOperation { villa -> + val output = vlget(villa, key, -1, null) + output?.toKString() + } + + private inline fun CPointer.forEachKey(block: (key: String) -> Unit) { + val villa = this + if (vlcurfirst(villa) != 0) { + while (true) { + val key = vlcurkey(villa, null) ?: error("error iterating through Villa keys!") + block(key.toKString()) + if (vlcurnext(villa) == 0) { + break + } + } + } + } + + private inline fun CPointer.foldKeys(initial: A, block: (accumulator: A, key: String) -> A): A { + var accumulator = initial + forEachKey { accumulator = block(accumulator, it) } + return accumulator + } + + private inline fun villaOperation(action: MemScope.(villa: CPointer) -> T): T = memScoped { + val villa = vlopen(path, (VL_OWRITER or VL_OREADER or VL_OCREAT).toInt(), VL_CMPLEX) + if (villa == null) { + val message = dperrmsg(dpecode)?.toKString() + error("error on villa open: $message") + } + val out = action(villa) + vlclose(villa) + out + } +} \ No newline at end of file diff --git a/multiplatform-settings/src/linuxX64Test/kotlin/QdbmDepotSettingsTest.kt b/multiplatform-settings/src/linuxX64Test/kotlin/QdbmDepotSettingsTest.kt new file mode 100644 index 00000000..1b5d2799 --- /dev/null +++ b/multiplatform-settings/src/linuxX64Test/kotlin/QdbmDepotSettingsTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2022 Russell Wolf + * + * 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 + * + * http://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 com.russhwolf.settings + +class QdbmDepotSettingsTest : BaseSettingsTest( + platformFactory = object : Settings.Factory { + override fun create(name: String?): Settings = QdbmDepotSettings((name ?: "") + ".depot.db") + }, + hasListeners = false +) { + +// private lateinit var initialDbFiles: List +// +// @BeforeTest +// fun getInitialDbFiles() { +// initialDbFiles = getDbFiles() +// } +// +// @AfterTest +// fun cleanup() { +// val newDbFiles = getDbFiles() - initialDbFiles +// newDbFiles.forEach { +// remove(it) +// } +// } +// +// private fun getDbFiles(): List { +// val directory = opendir("./") ?: return emptyList() +// +// val out = mutableListOf() +// while (true) { +// val entry = readdir(directory) ?: break +// +// val filename = entry.pointed.d_name.toKString() +// if (filename.run { endsWith(".db") || endsWith(".dir") || endsWith(".pag") }) { +// out.add(filename) +// } +// } +// return out +// } + +// @Test +// fun constructor_filename() { +// val filename = "test_dbm" +// val settings = DbmRelicSettings(filename) +// +// memScoped { +// val dbm = dbm_open( +// filename.cstr, +// O_RDWR or O_CREAT, +// S_IRUSR or S_IWUSR or S_IRGRP or S_IROTH +// ) +// ?: error("error: $errno") +// dbm.checkError() +// +// val key = cValue { +// val cValues = "key".encodeToByteArray().toCValues() +// dptr = cValues.ptr +// dsize = cValues.size.toULong() +// } +// val value = cValue { +// val cValues = "value".encodeToByteArray().toCValues() +// dptr = cValues.ptr +// dsize = cValues.size.toULong() +// } +// +// if ( +// dbm_store( +// dbm, +// key, +// value, +// DBM_REPLACE.toInt() +// ) == -1 +// ) { +// dbm.checkError() +// } +// +// dbm_close(dbm) +// } +// +// assertEquals("value", settings["key", ""]) +// } +} + +//private fun CValuesRef.checkError() { +// assertEquals(0, dbm_error(this)) +//} \ No newline at end of file diff --git a/multiplatform-settings/src/linuxX64Test/kotlin/QdbmRelicSettingsTest.kt b/multiplatform-settings/src/linuxX64Test/kotlin/QdbmRelicSettingsTest.kt new file mode 100644 index 00000000..edbe0c29 --- /dev/null +++ b/multiplatform-settings/src/linuxX64Test/kotlin/QdbmRelicSettingsTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2022 Russell Wolf + * + * 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 + * + * http://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 com.russhwolf.settings + +import com.russhwolf.settings.cinterop.qdbm.relic.DBM +import com.russhwolf.settings.cinterop.qdbm.relic.DBM_REPLACE +import com.russhwolf.settings.cinterop.qdbm.relic.datum +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_close +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_error +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_open +import com.russhwolf.settings.cinterop.qdbm.relic.dbm_store +import kotlinx.cinterop.CValuesRef +import kotlinx.cinterop.cValue +import kotlinx.cinterop.cstr +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toCValues +import kotlinx.cinterop.toKString +import platform.posix.O_CREAT +import platform.posix.O_RDWR +import platform.posix.S_IRGRP +import platform.posix.S_IROTH +import platform.posix.S_IRUSR +import platform.posix.S_IWUSR +import platform.posix.errno +import platform.posix.opendir +import platform.posix.readdir +import platform.posix.remove +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class QdbmRelicSettingsTest : BaseSettingsTest( + platformFactory = object : Settings.Factory { + override fun create(name: String?): Settings = QdbmRelicSettings(name ?: "dbm") + }, + hasListeners = false +) { + + private lateinit var initialDbFiles: List + + @BeforeTest + fun getInitialDbFiles() { + initialDbFiles = getDbFiles() + } + + @AfterTest + fun cleanup() { + val newDbFiles = getDbFiles() - initialDbFiles + newDbFiles.forEach { + remove(it) + } + } + + private fun getDbFiles(): List { + val directory = opendir("./") ?: return emptyList() + + val out = mutableListOf() + while (true) { + val entry = readdir(directory) ?: break + + val filename = entry.pointed.d_name.toKString() + if (filename.run { endsWith(".db") || endsWith(".dir") || endsWith(".pag") }) { + out.add(filename) + } + } + return out + } + + @Test + fun constructor_filename() { + val filename = "test_dbm" + val settings = QdbmRelicSettings(filename) + + memScoped { + val dbm = dbm_open( + filename.cstr, + O_RDWR or O_CREAT, + S_IRUSR or S_IWUSR or S_IRGRP or S_IROTH + ) + ?: error("error: $errno") + dbm.checkError() + + val key = cValue { + val cValues = "key".encodeToByteArray().toCValues() + dptr = cValues.ptr + dsize = cValues.size.toULong() + } + val value = cValue { + val cValues = "value".encodeToByteArray().toCValues() + dptr = cValues.ptr + dsize = cValues.size.toULong() + } + + if ( + dbm_store( + dbm, + key, + value, + DBM_REPLACE.toInt() + ) == -1 + ) { + dbm.checkError() + } + + dbm_close(dbm) + } + + assertEquals("value", settings["key", ""]) + } +} + +private fun CValuesRef.checkError() { + assertEquals(0, dbm_error(this)) +} \ No newline at end of file diff --git a/multiplatform-settings/src/linuxX64Test/kotlin/QdbmVillaSettingsTest.kt b/multiplatform-settings/src/linuxX64Test/kotlin/QdbmVillaSettingsTest.kt new file mode 100644 index 00000000..29b6e6b6 --- /dev/null +++ b/multiplatform-settings/src/linuxX64Test/kotlin/QdbmVillaSettingsTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2022 Russell Wolf + * + * 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 + * + * http://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 com.russhwolf.settings + +class QdbmVillaSettingsTest : BaseSettingsTest( + platformFactory = object : Settings.Factory { + override fun create(name: String?): Settings = QdbmVillaSettings((name ?: "") + ".villa.db") + }, + hasListeners = false +) { + +// private lateinit var initialDbFiles: List +// +// @BeforeTest +// fun getInitialDbFiles() { +// initialDbFiles = getDbFiles() +// } +// +// @AfterTest +// fun cleanup() { +// val newDbFiles = getDbFiles() - initialDbFiles +// newDbFiles.forEach { +// remove(it) +// } +// } +// +// private fun getDbFiles(): List { +// val directory = opendir("./") ?: return emptyList() +// +// val out = mutableListOf() +// while (true) { +// val entry = readdir(directory) ?: break +// +// val filename = entry.pointed.d_name.toKString() +// if (filename.run { endsWith(".db") || endsWith(".dir") || endsWith(".pag") }) { +// out.add(filename) +// } +// } +// return out +// } + +// @Test +// fun constructor_filename() { +// val filename = "test_dbm" +// val settings = DbmRelicSettings(filename) +// +// memScoped { +// val dbm = dbm_open( +// filename.cstr, +// O_RDWR or O_CREAT, +// S_IRUSR or S_IWUSR or S_IRGRP or S_IROTH +// ) +// ?: error("error: $errno") +// dbm.checkError() +// +// val key = cValue { +// val cValues = "key".encodeToByteArray().toCValues() +// dptr = cValues.ptr +// dsize = cValues.size.toULong() +// } +// val value = cValue { +// val cValues = "value".encodeToByteArray().toCValues() +// dptr = cValues.ptr +// dsize = cValues.size.toULong() +// } +// +// if ( +// dbm_store( +// dbm, +// key, +// value, +// DBM_REPLACE.toInt() +// ) == -1 +// ) { +// dbm.checkError() +// } +// +// dbm_close(dbm) +// } +// +// assertEquals("value", settings["key", ""]) +// } +} + +//private fun CValuesRef.checkError() { +// assertEquals(0, dbm_error(this)) +//} \ No newline at end of file diff --git a/multiplatform-settings/src/nativeInterop/cinterop/qdbm-depot.def b/multiplatform-settings/src/nativeInterop/cinterop/qdbm-depot.def new file mode 100644 index 00000000..d9f1e1c1 --- /dev/null +++ b/multiplatform-settings/src/nativeInterop/cinterop/qdbm-depot.def @@ -0,0 +1,4 @@ +headers = depot.h +package = com.russhwolf.settings.cinterop.qdbm.depot +compilerOpts = -I/usr/include/qdbm +linkerOpts = -lqdbm -L/usr/lib diff --git a/multiplatform-settings/src/nativeInterop/cinterop/qdbm-relic.def b/multiplatform-settings/src/nativeInterop/cinterop/qdbm-relic.def new file mode 100644 index 00000000..5adee5be --- /dev/null +++ b/multiplatform-settings/src/nativeInterop/cinterop/qdbm-relic.def @@ -0,0 +1,4 @@ +headers = relic.h +package = com.russhwolf.settings.cinterop.qdbm.relic +compilerOpts = -I/usr/include/qdbm +linkerOpts = -lqdbm -L/usr/lib diff --git a/multiplatform-settings/src/nativeInterop/cinterop/qdbm-villa.def b/multiplatform-settings/src/nativeInterop/cinterop/qdbm-villa.def new file mode 100644 index 00000000..fc66314c --- /dev/null +++ b/multiplatform-settings/src/nativeInterop/cinterop/qdbm-villa.def @@ -0,0 +1,4 @@ +headers = villa.h +package = com.russhwolf.settings.cinterop.qdbm.villa +compilerOpts = -I/usr/include/qdbm +linkerOpts = -lqdbm -L/usr/lib