Skip to content

Commit d442e91

Browse files
committed
WIP QDBM multiple implementations
1 parent 11e544b commit d442e91

File tree

11 files changed

+780
-0
lines changed

11 files changed

+780
-0
lines changed

.github/workflows/build-linux.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ jobs:
2424
java-version: 17
2525
distribution: corretto
2626

27+
- name: QDBM install
28+
run: |
29+
sudo apt-get install libqdbm-dev
30+
2731
- name: Linux build
2832
run: |
2933
./gradlew build publishToMavenLocal --no-daemon --stacktrace

multiplatform-settings/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
18+
1719
plugins {
1820
id("com.android.library")
1921
kotlin("multiplatform")
@@ -25,6 +27,11 @@ plugins {
2527
standardConfiguration()
2628

2729
kotlin {
30+
targets.getByName<KotlinNativeTarget>("linuxX64") {
31+
compilations["main"].cinterops.create("qdbm-depot")
32+
compilations["main"].cinterops.create("qdbm-relic")
33+
compilations["main"].cinterops.create("qdbm-villa")
34+
}
2835
sourceSets {
2936
commonMain {
3037
dependencies {
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright 2022 Russell Wolf
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.russhwolf.settings
18+
19+
import com.russhwolf.settings.cinterop.qdbm.depot.DEPOT
20+
import com.russhwolf.settings.cinterop.qdbm.depot.DP_DOVER
21+
import com.russhwolf.settings.cinterop.qdbm.depot.DP_OCREAT
22+
import com.russhwolf.settings.cinterop.qdbm.depot.DP_OREADER
23+
import com.russhwolf.settings.cinterop.qdbm.depot.DP_OWRITER
24+
import com.russhwolf.settings.cinterop.qdbm.depot.dpclose
25+
import com.russhwolf.settings.cinterop.qdbm.depot.dpecode
26+
import com.russhwolf.settings.cinterop.qdbm.depot.dperrmsg
27+
import com.russhwolf.settings.cinterop.qdbm.depot.dpget
28+
import com.russhwolf.settings.cinterop.qdbm.depot.dpiterinit
29+
import com.russhwolf.settings.cinterop.qdbm.depot.dpiternext
30+
import com.russhwolf.settings.cinterop.qdbm.depot.dpopen
31+
import com.russhwolf.settings.cinterop.qdbm.depot.dpout
32+
import com.russhwolf.settings.cinterop.qdbm.depot.dpput
33+
import kotlinx.cinterop.CPointer
34+
import kotlinx.cinterop.ExperimentalForeignApi
35+
import kotlinx.cinterop.MemScope
36+
import kotlinx.cinterop.memScoped
37+
import kotlinx.cinterop.toKString
38+
39+
// TODO clean up error checking?
40+
// TODO allow specifying directory
41+
@OptIn(ExperimentalForeignApi::class)
42+
@ExperimentalSettingsImplementation
43+
public class QdbmDepotSettings(private val path: String) : Settings {
44+
45+
override val keys: Set<String>
46+
get() = depotOperation { depot ->
47+
depot.foldKeys(mutableListOf<String>()) { list, key -> list.apply { add(key) } }.toSet()
48+
}
49+
50+
override val size: Int get() = depotOperation { depot -> depot.foldKeys(0) { size, _ -> size + 1 } }
51+
52+
public override fun clear(): Unit = depotOperation { depot -> depot.forEachKey { dpout(depot, it, -1) } }
53+
public override fun remove(key: String): Unit = depotOperation { depot -> dpout(depot, key, -1) }
54+
public override fun hasKey(key: String): Boolean = depotOperation { depot ->
55+
depot.forEachKey { if (key == it) return true }
56+
return false
57+
}
58+
59+
public override fun putInt(key: String, value: Int): Unit = saveString(key, value.toString())
60+
public override fun getInt(key: String, defaultValue: Int): Int = getIntOrNull(key) ?: defaultValue
61+
public override fun getIntOrNull(key: String): Int? = loadString(key)?.toInt()
62+
63+
public override fun putLong(key: String, value: Long): Unit = saveString(key, value.toString())
64+
public override fun getLong(key: String, defaultValue: Long): Long = getLongOrNull(key) ?: defaultValue
65+
public override fun getLongOrNull(key: String): Long? = loadString(key)?.toLong()
66+
67+
public override fun putString(key: String, value: String): Unit = saveString(key, value)
68+
public override fun getString(key: String, defaultValue: String): String = getStringOrNull(key) ?: defaultValue
69+
public override fun getStringOrNull(key: String): String? = loadString(key)
70+
71+
public override fun putFloat(key: String, value: Float): Unit = saveString(key, value.toString())
72+
public override fun getFloat(key: String, defaultValue: Float): Float = getFloatOrNull(key) ?: defaultValue
73+
public override fun getFloatOrNull(key: String): Float? = loadString(key)?.toFloat()
74+
75+
public override fun putDouble(key: String, value: Double): Unit = saveString(key, value.toString())
76+
public override fun getDouble(key: String, defaultValue: Double): Double = getDoubleOrNull(key) ?: defaultValue
77+
public override fun getDoubleOrNull(key: String): Double? = loadString(key)?.toDouble()
78+
79+
public override fun putBoolean(key: String, value: Boolean): Unit = saveString(key, value.toString())
80+
public override fun getBoolean(key: String, defaultValue: Boolean): Boolean = getBooleanOrNull(key) ?: defaultValue
81+
public override fun getBooleanOrNull(key: String): Boolean? = loadString(key)?.toBoolean()
82+
83+
private inline fun saveString(key: String, value: String): Unit = depotOperation { depot ->
84+
dpput(depot, key, -1, value, -1, DP_DOVER.toInt())
85+
}
86+
87+
private inline fun loadString(key: String): String? = depotOperation { depot ->
88+
val output = dpget(depot, key, -1, 0, -1, null)
89+
output?.toKString()
90+
}
91+
92+
private inline fun CPointer<DEPOT>.forEachKey(block: (key: String) -> Unit) {
93+
val depot = this
94+
if (dpiterinit(depot) != 0) {
95+
while (true) {
96+
val key = dpiternext(depot, null)?.toKString()
97+
if (key != null) {
98+
block(key)
99+
} else {
100+
break
101+
}
102+
}
103+
}
104+
}
105+
106+
private inline fun <A> CPointer<DEPOT>.foldKeys(initial: A, block: (accumulator: A, key: String) -> A): A {
107+
var accumulator = initial
108+
forEachKey { accumulator = block(accumulator, it) }
109+
return accumulator
110+
}
111+
112+
private inline fun <T> depotOperation(action: MemScope.(depot: CPointer<DEPOT>) -> T): T = memScoped {
113+
val depot = dpopen(path, (DP_OWRITER or DP_OREADER or DP_OCREAT).toInt(), 0)
114+
if (depot == null) {
115+
val message = dperrmsg(dpecode)?.toKString()
116+
error("error on depot open: $message")
117+
}
118+
val out = action(depot)
119+
dpclose(depot)
120+
out
121+
}
122+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright 2022 Russell Wolf
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.russhwolf.settings
18+
19+
import com.russhwolf.settings.cinterop.qdbm.relic.DBM
20+
import com.russhwolf.settings.cinterop.qdbm.relic.DBM_REPLACE
21+
import com.russhwolf.settings.cinterop.qdbm.relic.datum
22+
import com.russhwolf.settings.cinterop.qdbm.relic.dbm_clearerr
23+
import com.russhwolf.settings.cinterop.qdbm.relic.dbm_close
24+
import com.russhwolf.settings.cinterop.qdbm.relic.dbm_delete
25+
import com.russhwolf.settings.cinterop.qdbm.relic.dbm_error
26+
import com.russhwolf.settings.cinterop.qdbm.relic.dbm_fetch
27+
import com.russhwolf.settings.cinterop.qdbm.relic.dbm_firstkey
28+
import com.russhwolf.settings.cinterop.qdbm.relic.dbm_nextkey
29+
import com.russhwolf.settings.cinterop.qdbm.relic.dbm_open
30+
import com.russhwolf.settings.cinterop.qdbm.relic.dbm_store
31+
import kotlinx.cinterop.ByteVar
32+
import kotlinx.cinterop.CPointer
33+
import kotlinx.cinterop.CValue
34+
import kotlinx.cinterop.ExperimentalForeignApi
35+
import kotlinx.cinterop.MemScope
36+
import kotlinx.cinterop.cValue
37+
import kotlinx.cinterop.cstr
38+
import kotlinx.cinterop.memScoped
39+
import kotlinx.cinterop.plus
40+
import kotlinx.cinterop.pointed
41+
import kotlinx.cinterop.reinterpret
42+
import kotlinx.cinterop.toCValues
43+
import kotlinx.cinterop.useContents
44+
import kotlinx.cinterop.value
45+
import platform.posix.O_CREAT
46+
import platform.posix.O_RDWR
47+
import platform.posix.S_IRGRP
48+
import platform.posix.S_IROTH
49+
import platform.posix.S_IRUSR
50+
import platform.posix.S_IWUSR
51+
import platform.posix.errno
52+
53+
// TODO clean up error checking?
54+
// TODO allow specifying directory
55+
@OptIn(ExperimentalForeignApi::class)
56+
@ExperimentalSettingsImplementation
57+
public class QdbmRelicSettings(private val path: String) : Settings {
58+
59+
override val keys: Set<String>
60+
get() = dbmOperation { dbm ->
61+
dbm.foldKeys(mutableListOf<String>()) { list, key -> list.apply { add(key.toKString()!!) } }.toSet()
62+
}
63+
64+
override val size: Int get() = dbmOperation { dbm -> dbm.foldKeys(0) { size, _ -> size + 1 } }
65+
66+
public override fun clear(): Unit = dbmOperation { dbm -> dbm.forEachKey { dbm_delete(dbm, it) } }
67+
public override fun remove(key: String): Unit = dbmOperation { dbm -> dbm_delete(dbm, datumOf(key)) }
68+
public override fun hasKey(key: String): Boolean = dbmOperation { dbm ->
69+
dbm.forEachKey { if (key == it.toKString()) return true }
70+
return false
71+
}
72+
73+
public override fun putInt(key: String, value: Int): Unit = saveBytes(key, value.toByteArray())
74+
public override fun getInt(key: String, defaultValue: Int): Int = getIntOrNull(key) ?: defaultValue
75+
public override fun getIntOrNull(key: String): Int? = loadBytes(key)?.toInt()
76+
77+
public override fun putLong(key: String, value: Long): Unit = saveBytes(key, value.toByteArray())
78+
public override fun getLong(key: String, defaultValue: Long): Long = getLongOrNull(key) ?: defaultValue
79+
public override fun getLongOrNull(key: String): Long? = loadBytes(key)?.toLong()
80+
81+
public override fun putString(key: String, value: String): Unit = saveBytes(key, value.encodeToByteArray())
82+
public override fun getString(key: String, defaultValue: String): String = getStringOrNull(key) ?: defaultValue
83+
public override fun getStringOrNull(key: String): String? = loadBytes(key)?.decodeToString()
84+
85+
public override fun putFloat(key: String, value: Float): Unit = saveBytes(key, value.toRawBits().toByteArray())
86+
public override fun getFloat(key: String, defaultValue: Float): Float = getFloatOrNull(key) ?: defaultValue
87+
public override fun getFloatOrNull(key: String): Float? = loadBytes(key)?.toInt()?.let { Float.fromBits(it) }
88+
89+
public override fun putDouble(key: String, value: Double): Unit = saveBytes(key, value.toRawBits().toByteArray())
90+
public override fun getDouble(key: String, defaultValue: Double): Double = getDoubleOrNull(key) ?: defaultValue
91+
public override fun getDoubleOrNull(key: String): Double? = loadBytes(key)?.toLong()?.let { Double.fromBits(it) }
92+
93+
public override fun putBoolean(key: String, value: Boolean): Unit = saveBytes(key, byteArrayOf(if (value) 1 else 0))
94+
public override fun getBoolean(key: String, defaultValue: Boolean): Boolean = getBooleanOrNull(key) ?: defaultValue
95+
public override fun getBooleanOrNull(key: String): Boolean? = loadBytes(key)?.get(0)?.equals(0.toByte())?.not()
96+
97+
private inline fun saveBytes(key: String, bytes: ByteArray): Unit = dbmOperation { dbm ->
98+
dbm_store(dbm, datumOf(key), datumOf(bytes), DBM_REPLACE.toInt())
99+
}
100+
101+
private inline fun loadBytes(key: String): ByteArray? = dbmOperation { dbm ->
102+
val datum = dbm_fetch(dbm, datumOf(key))
103+
datum.toByteArray()
104+
}
105+
106+
private inline fun CPointer<DBM>.forEachKey(block: (key: CValue<datum>) -> Unit) {
107+
val dbm = this
108+
var key = dbm_firstkey(dbm)
109+
while (key.useContents { dptr != null }) {
110+
block(key)
111+
key = dbm_nextkey(dbm)
112+
}
113+
}
114+
115+
private inline fun <A> CPointer<DBM>.foldKeys(initial: A, block: (accumulator: A, key: CValue<datum>) -> A): A {
116+
var accumulator = initial
117+
forEachKey { accumulator = block(accumulator, it) }
118+
return accumulator
119+
}
120+
121+
private inline fun <T> dbmOperation(action: MemScope.(dbm: CPointer<DBM>) -> T): T = memScoped {
122+
val dbm = dbm_open(path.cstr, O_RDWR or O_CREAT, S_IRUSR or S_IWUSR or S_IRGRP or S_IROTH)
123+
?: error("Error on dbm_open: $errno")
124+
val out = action(dbm)
125+
val error = dbm_error(dbm)
126+
if (error != 0) {
127+
try {
128+
error("error: $error")
129+
} finally {
130+
dbm_clearerr(dbm)
131+
}
132+
}
133+
dbm_close(dbm)
134+
out
135+
}
136+
137+
private inline fun ByteArray.toLong(): Long = foldIndexed(0) { index, total: Long, byte: Byte ->
138+
((0xff.toLong() and byte.toLong()) shl index * Byte.SIZE_BITS) or total
139+
}
140+
141+
private inline fun ByteArray.toInt(): Int = foldIndexed(0) { index, total: Int, byte: Byte ->
142+
((0xff and byte.toInt()) shl index * Byte.SIZE_BITS) or total
143+
}
144+
145+
private inline fun Long.toByteArray(): ByteArray = ByteArray(Long.SIZE_BYTES) { index ->
146+
((this shr (Byte.SIZE_BITS * index)) and 0xff).toByte()
147+
}
148+
149+
private inline fun Int.toByteArray(): ByteArray = ByteArray(Int.SIZE_BYTES) { index ->
150+
((this shr (Byte.SIZE_BITS * index)) and 0xff).toByte()
151+
}
152+
153+
private inline fun CValue<datum>.toKString(): String? = toByteArray()?.decodeToString()
154+
private inline fun CValue<datum>.toByteArray(): ByteArray? = useContents {
155+
val size = dsize.toInt()
156+
val firstPtr: CPointer<ByteVar> = dptr?.reinterpret()
157+
?: return null
158+
return ByteArray(size) {
159+
val pointedValue = firstPtr.plus(it)?.pointed?.value
160+
pointedValue ?: 0
161+
}
162+
}
163+
164+
private inline fun MemScope.datumOf(string: String): CValue<datum> = datumOf(string.encodeToByteArray())
165+
private inline fun MemScope.datumOf(bytes: ByteArray): CValue<datum> = cValue {
166+
val cValues = bytes.toCValues()
167+
dptr = cValues.ptr
168+
dsize = cValues.size.toULong()
169+
}
170+
}

0 commit comments

Comments
 (0)