changeset 984:4e487f06378b

add: inital version of storage api (untested)
author sam <sam@basx.dev>
date Sat, 06 Apr 2024 21:26:31 +0700
parents 83925a516f88
children 0c02ca5de8e6
files semicongine.nim semicongine.nimble semicongine/core/utils.nim semicongine/storage.nim
diffstat 4 files changed, 96 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine.nim	Sat Apr 06 17:45:20 2024 +0700
+++ b/semicongine.nim	Sat Apr 06 21:26:31 2024 +0700
@@ -18,6 +18,7 @@
 import semicongine/resources
 import semicongine/settings
 import semicongine/steam
+import semicongine/storage
 import semicongine/text
 import semicongine/platform/window
 import semicongine/vulkan
@@ -36,6 +37,7 @@
 export resources
 export settings
 export steam
+export storage
 export text
 export window
 export vulkan
--- a/semicongine.nimble	Sat Apr 06 17:45:20 2024 +0700
+++ b/semicongine.nimble	Sat Apr 06 21:26:31 2024 +0700
@@ -18,4 +18,5 @@
 requires "winim"
 requires "x11" # also requires libx11-dev e.g. on debian systems
 requires "zippy"
+requires "db_connector"
 
--- a/semicongine/core/utils.nim	Sat Apr 06 17:45:20 2024 +0700
+++ b/semicongine/core/utils.nim	Sat Apr 06 21:26:31 2024 +0700
@@ -1,5 +1,7 @@
 import std/typetraits
 import std/strutils
+import std/paths
+import std/os
 import std/strformat
 
 type
@@ -29,5 +31,8 @@
     raise newException(Exception, &"Running '{command}' produced exit code: {exitcode}" & output)
   return output
 
+proc AppName*(): string =
+  return string(Path(getAppFilename()).splitFile.name)
+
 func size*[T: seq](list: T): uint64 =
   uint64(list.len * sizeof(get(genericParams(typeof(list)), 0)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semicongine/storage.nim	Sat Apr 06 21:26:31 2024 +0700
@@ -0,0 +1,88 @@
+import std/marshal
+import std/strformat
+import std/paths
+import std/os
+
+import db_connector/db_sqlite
+
+import ./core
+
+const STORAGE_NAME = Path("storage.db")
+const KEY_VALUE_TABLE_NAME = "shelf"
+const KILL_SIGNAL_KEY = "__semicongine__kill_worker"
+
+type
+  StorageType* = enum
+    SystemStorage
+    UserStorage
+    # ? level storage type ?
+  StorageOperation = enum
+    Read
+    Write
+    KillWorker
+  Storage*[T] = object
+    storageType: StorageType
+    keyChannel: Channel[(StorageOperation, string)] # false is read, true is write
+    dataChannel: Channel[T]
+    thread: Thread[(ptr Channel[string], ptr Channel[T])]
+
+proc path(storageType: StorageType): Path =
+  case storageType:
+    of SystemStorage:
+      Path(getAppDir()) / STORAGE_NAME
+    of UserStorage:
+      Path(getDataDir()) / Path(AppName()) / STORAGE_NAME
+
+proc openDb(storageType: StorageType): DbConn =
+  result = open(string(storageType.path), "", "", "")
+  result.exec(sql(&"""CREATE TABLE IF NOT EXISTS {KEY_VALUE_TABLE_NAME} (
+    key TEXT NOT NULL UNIQUE,
+    value TEXT NOT NULL,
+  )"""))
+
+proc store[T](db: DbConn, key: string, value: T) =
+  db.exec(sql(f"""INSERT INTO {KEY_VALUE_TABLE_NAME} VALUES(?, ?)
+  ON CONFLICT(key) DO UPDATE SET value=excluded.value
+  """), key, $$value)
+
+proc load[T](db: DbConn, key: string, default = default(T)): T =
+  let dbResult = db.getValue(sql(f"""SELECT value FROM {KEY_VALUE_TABLE_NAME} WHERE key = ? """), key)
+  if dbResult == "":
+    return default
+  return to[T](dbResult)
+
+proc storageWorker[T](params: tuple[storageType: StorageType, keyChannel: ptr Channel[(StorageOperation, string)], dataChannel: ptr Channel[T]]) =
+  var db = params.storageType.openDb()
+  defer: db.close()
+  var key: (string, bool)
+  while key[0] != KILL_SIGNAL_KEY:
+    key = params.keyChannel[].recv()
+    case key:
+      of Read: params.dataChannel[].send(db.load(key))
+      of Write: db.store(key, params.dataChannel[].recv())
+      of KillWorker: break
+
+proc openStorage*[T](storageType: StorageType): Storage[T] =
+  result.keyChannel = cast[ptr Channel[(string, bool)]](allocShared0(sizeof(Channel[(string, bool)])))
+  result.keyChannel[].open()
+  result.dataChannel = cast[ptr Channel[T]](allocShared0(sizeof(Channel[T])))
+  result.dataChannel[].open()
+  createThread(result.thread, storageWorker, (storageType, result.keyChannel, result.dataChannel))
+
+proc get[T](storage: Storage[T], key: string): Channel[T] =
+  storage.keyChannel.send((Read, key))
+  return storage.dataChannel[]
+
+proc set[T](storage: Storage[T], key: string, value: T) =
+  storage.keyChannel.send((Write, key))
+  storage.dataChannel.send(value)
+
+proc closeStorage*[T](storage: var Storage[T]) =
+  storage.keyChannel[].send((KillWorker, ""))
+  storage.thread.joinThread()
+
+  storage.keyChannel[].close()
+  storage.deallocShared(storage.keyChannel)
+
+  storage.dataChannel[].close()
+  storage.deallocShared(storage.dataChannel)