1+ /*
2+ * Copyright 2020 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+ import com.russhwolf.settings.Settings
18+ import com.russhwolf.settings.cinterop.qdbm.DBM
19+ import com.russhwolf.settings.cinterop.qdbm.DBM_REPLACE
20+ import com.russhwolf.settings.cinterop.qdbm.datum
21+ import com.russhwolf.settings.cinterop.qdbm.dbm_clearerr
22+ import com.russhwolf.settings.cinterop.qdbm.dbm_close
23+ import com.russhwolf.settings.cinterop.qdbm.dbm_delete
24+ import com.russhwolf.settings.cinterop.qdbm.dbm_error
25+ import com.russhwolf.settings.cinterop.qdbm.dbm_fetch
26+ import com.russhwolf.settings.cinterop.qdbm.dbm_firstkey
27+ import com.russhwolf.settings.cinterop.qdbm.dbm_nextkey
28+ import com.russhwolf.settings.cinterop.qdbm.dbm_open
29+ import com.russhwolf.settings.cinterop.qdbm.dbm_store
30+ import kotlinx.cinterop.ByteVar
31+ import kotlinx.cinterop.CPointer
32+ import kotlinx.cinterop.CValue
33+ import kotlinx.cinterop.MemScope
34+ import kotlinx.cinterop.cValue
35+ import kotlinx.cinterop.cstr
36+ import kotlinx.cinterop.memScoped
37+ import kotlinx.cinterop.plus
38+ import kotlinx.cinterop.pointed
39+ import kotlinx.cinterop.reinterpret
40+ import kotlinx.cinterop.toCValues
41+ import kotlinx.cinterop.useContents
42+ import kotlinx.cinterop.value
43+ import platform.posix.O_CREAT
44+ import platform.posix.O_RDWR
45+ import platform.posix.S_IRGRP
46+ import platform.posix.S_IROTH
47+ import platform.posix.S_IRUSR
48+ import platform.posix.S_IWUSR
49+ import platform.posix.errno
50+
51+ // TODO clean up error checking?
52+ // TODO allow specifying directory
53+ @OptIn(ExperimentalUnsignedTypes ::class )
54+ public class DbmSettings (private val path : String ) : Settings {
55+
56+ override val keys: Set <String >
57+ get() = dbmOperation { dbm ->
58+ dbm.foldKeys(mutableListOf<String >()) { list, key -> list.apply { add(key.toKString()!! ) } }.toSet()
59+ }
60+
61+ override val size: Int get() = dbmOperation { dbm -> dbm.foldKeys(0 ) { size, _ -> size + 1 } }
62+
63+ public override fun clear (): Unit = dbmOperation { dbm -> dbm.forEachKey { dbm_delete(dbm, it) } }
64+ public override fun remove (key : String ): Unit = dbmOperation { dbm -> dbm_delete(dbm, datumOf(key)) }
65+ public override fun hasKey (key : String ): Boolean = dbmOperation { dbm ->
66+ dbm.foldKeys(false ) { out , thisKey -> if (key == thisKey.toKString()) true else out }
67+ }
68+
69+ public override fun putInt (key : String , value : Int ): Unit = saveBytes(key, value.toByteArray())
70+ public override fun getInt (key : String , defaultValue : Int ): Int = getIntOrNull(key) ? : defaultValue
71+ public override fun getIntOrNull (key : String ): Int? = loadBytes(key)?.toInt()
72+
73+ public override fun putLong (key : String , value : Long ): Unit = saveBytes(key, value.toByteArray())
74+ public override fun getLong (key : String , defaultValue : Long ): Long = getLongOrNull(key) ? : defaultValue
75+ public override fun getLongOrNull (key : String ): Long? = loadBytes(key)?.toLong()
76+
77+ public override fun putString (key : String , value : String ): Unit = saveBytes(key, value.encodeToByteArray())
78+ public override fun getString (key : String , defaultValue : String ): String = getStringOrNull(key) ? : defaultValue
79+ public override fun getStringOrNull (key : String ): String? = loadBytes(key)?.decodeToString()
80+
81+ public override fun putFloat (key : String , value : Float ): Unit = saveBytes(key, value.toRawBits().toByteArray())
82+ public override fun getFloat (key : String , defaultValue : Float ): Float = getFloatOrNull(key) ? : defaultValue
83+ public override fun getFloatOrNull (key : String ): Float? = loadBytes(key)?.toInt()?.let { Float .fromBits(it) }
84+
85+ public override fun putDouble (key : String , value : Double ): Unit = saveBytes(key, value.toRawBits().toByteArray())
86+ public override fun getDouble (key : String , defaultValue : Double ): Double = getDoubleOrNull(key) ? : defaultValue
87+ public override fun getDoubleOrNull (key : String ): Double? = loadBytes(key)?.toLong()?.let { Double .fromBits(it) }
88+
89+ public override fun putBoolean (key : String , value : Boolean ): Unit = saveBytes(key, byteArrayOf(if (value) 1 else 0 ))
90+ public override fun getBoolean (key : String , defaultValue : Boolean ): Boolean = getBooleanOrNull(key) ? : defaultValue
91+ public override fun getBooleanOrNull (key : String ): Boolean? = loadBytes(key)?.get(0 )?.equals(0 )?.not ()
92+
93+ private inline fun <T > dbmOperation (action : MemScope .(dbm: CPointer <DBM >) -> T ): T = memScoped {
94+ val dbm = dbm_open(path.cstr, O_RDWR or O_CREAT , S_IRUSR or S_IWUSR or S_IRGRP or S_IROTH )
95+ ? : error(" Error on dbm_open: $errno " )
96+ val out = action(dbm)
97+ val error = dbm_error(dbm)
98+ if (error != 0 ) {
99+ try {
100+ error(" error: $error " )
101+ } finally {
102+ dbm_clearerr(dbm)
103+ }
104+ }
105+ dbm_close(dbm)
106+ out
107+ }
108+
109+ private inline fun saveBytes (key : String , bytes : ByteArray ): Unit = dbmOperation { dbm ->
110+ dbm_store(dbm, datumOf(key), datumOf(bytes), DBM_REPLACE .toInt())
111+ }
112+
113+ private inline fun loadBytes (key : String ): ByteArray? = dbmOperation { dbm ->
114+ val datum = dbm_fetch(dbm, datumOf(key))
115+ datum.toByteArray()
116+ }
117+
118+ private inline fun ByteArray.toLong (): Long = foldIndexed(0 ) { index, total: Long , byte: Byte ->
119+ ((0xff .toLong() and byte.toLong()) shl index * Byte .SIZE_BITS ) or total
120+ }
121+
122+ private inline fun ByteArray.toInt (): Int = foldIndexed(0 ) { index, total: Int , byte: Byte ->
123+ ((0xff and byte.toInt()) shl index * Byte .SIZE_BITS ) or total
124+ }
125+
126+ private inline fun Long.toByteArray (): ByteArray = ByteArray (Long .SIZE_BYTES ) { index ->
127+ ((this shr (Byte .SIZE_BITS * index)) and 0xff ).toByte()
128+ }
129+
130+ private inline fun Int.toByteArray (): ByteArray = ByteArray (Int .SIZE_BYTES ) { index ->
131+ ((this shr (Byte .SIZE_BITS * index)) and 0xff ).toByte()
132+ }
133+
134+ private inline fun CPointer<DBM>.forEachKey (block : (key: CValue <datum>) -> Unit ) {
135+ val dbm = this
136+ var nextKey = dbm_firstkey(dbm)
137+ while (nextKey.useContents { dptr != null }) {
138+ block(nextKey)
139+ nextKey = dbm_nextkey(dbm)
140+ }
141+ }
142+
143+ private inline fun <A > CPointer<DBM>.foldKeys (initial : A , block : (accumulator: A , key: CValue <datum>) -> A ): A {
144+ var accumulator = initial
145+ forEachKey { accumulator = block(accumulator, it) }
146+ return accumulator
147+ }
148+
149+ private inline fun CValue<datum>.toKString (): String? = toByteArray()?.decodeToString()
150+ private inline fun CValue<datum>.toByteArray (): ByteArray? = useContents {
151+ val size = dsize.toInt()
152+ val firstPtr: CPointer <ByteVar > = dptr?.reinterpret()
153+ ? : return null
154+ return ByteArray (size) {
155+ val pointedValue = firstPtr.plus(it)?.pointed?.value
156+ pointedValue ? : 0
157+ }
158+ }
159+
160+ private inline fun MemScope.datumOf (string : String ): CValue <datum> = datumOf(string.encodeToByteArray())
161+ private inline fun MemScope.datumOf (bytes : ByteArray ): CValue <datum> = cValue {
162+ val cValues = bytes.toCValues()
163+ dptr = cValues.ptr
164+ dsize = cValues.size.toULong()
165+ }
166+ }
0 commit comments