# HG changeset patch # User sam # Date 1712413591 -25200 # Node ID 7283373b3a223e8dcd193acd308205b0087b9fac # Parent 10da4ef8d9e1ec40a5f0ae5f2c6be771f2c29ba7 add: inital version of storage api (untested) diff -r 10da4ef8d9e1 -r 7283373b3a22 semicongine.nim --- 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 diff -r 10da4ef8d9e1 -r 7283373b3a22 semicongine.nimble --- 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" diff -r 10da4ef8d9e1 -r 7283373b3a22 semicongine/core/utils.nim --- 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))) diff -r 10da4ef8d9e1 -r 7283373b3a22 semicongine/storage.nim --- /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)