feat: add base module
- Add base module with context, session, and configurator. - Implement session management and configuration loading/saving. - Introduce error handling and logging mechanisms. - Include template files for Lua scripts.
This commit is contained in:
25
lib/core/base/base.v
Normal file
25
lib/core/base/base.v
Normal file
@@ -0,0 +1,25 @@
|
||||
module base
|
||||
|
||||
@[heap]
|
||||
pub struct Base {
|
||||
configtype string @[required]
|
||||
mut:
|
||||
instance string
|
||||
session_ ?&Session
|
||||
}
|
||||
|
||||
pub fn (mut self Base) session() !&Session {
|
||||
mut mysession := self.session_ or {
|
||||
// mut c := context()!
|
||||
// mut r := c.redis()!
|
||||
panic('sdsdsd')
|
||||
// incrkey := 'sessions:base:latest:${self.type_name}:${self.instance}'
|
||||
// latestid:=r.incr(incrkey)!
|
||||
// name:="${self.type_name}_${self.instance}_${latestid}"
|
||||
// mut s:=c.session_new(name:name)!
|
||||
// self.session_ = &s
|
||||
// &s
|
||||
}
|
||||
|
||||
return mysession
|
||||
}
|
||||
164
lib/core/base/baseconfig.v
Normal file
164
lib/core/base/baseconfig.v
Normal file
@@ -0,0 +1,164 @@
|
||||
module base
|
||||
|
||||
import json
|
||||
// import freeflowuniverse.crystallib.ui.console
|
||||
|
||||
// is an object which has a configurator, session and config object which is unique for the model
|
||||
// T is the Config Object
|
||||
|
||||
pub struct BaseConfig[T] {
|
||||
mut:
|
||||
configurator_ ?Configurator[T] @[skip; str: skip]
|
||||
config_ ?&T
|
||||
session_ ?&Session @[skip; str: skip]
|
||||
configtype string
|
||||
pub mut:
|
||||
instance string
|
||||
}
|
||||
|
||||
pub fn (mut self BaseConfig[T]) session() !&Session {
|
||||
mut mysession := self.session_ or {
|
||||
mut c := context()!
|
||||
mut r := c.redis()!
|
||||
incrkey := 'sessions:base:latest:${self.configtype}:${self.instance}'
|
||||
latestid := r.incr(incrkey)!
|
||||
name := '${self.configtype}_${self.instance}_${latestid}'
|
||||
mut s := c.session_new(name: name)!
|
||||
self.session_ = &s
|
||||
&s
|
||||
}
|
||||
|
||||
return mysession
|
||||
}
|
||||
|
||||
// management class of the configs of this obj
|
||||
pub fn (mut self BaseConfig[T]) configurator() !&Configurator[T] {
|
||||
if self.configurator_ == none {
|
||||
mut c := configurator_new[T](
|
||||
instance: self.instance
|
||||
)!
|
||||
self.configurator_ = c
|
||||
}
|
||||
return &(self.configurator_ or { return error('configurator not initialized') })
|
||||
}
|
||||
|
||||
// will overwrite the config
|
||||
pub fn (mut self BaseConfig[T]) config_set(myconfig T) ! {
|
||||
self.config_ = &myconfig
|
||||
self.config_save()!
|
||||
}
|
||||
|
||||
pub fn (mut self BaseConfig[T]) config_new() !&T {
|
||||
config := self.config_ or {
|
||||
mut configurator := self.configurator()!
|
||||
mut c := configurator.new()!
|
||||
self.config_ = &c
|
||||
&c
|
||||
}
|
||||
|
||||
self.config_save()!
|
||||
return config
|
||||
}
|
||||
|
||||
pub fn (mut self BaseConfig[T]) config() !&T {
|
||||
mut config := self.config_ or { return error('config was not initialized yet') }
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
pub fn (mut self BaseConfig[T]) config_get() !&T {
|
||||
mut mycontext := context()!
|
||||
mut config := self.config_ or {
|
||||
mut configurator := self.configurator()!
|
||||
if !(configurator.exists()!) {
|
||||
mut mycfg := self.config_new()!
|
||||
return mycfg
|
||||
}
|
||||
|
||||
mut db := mycontext.db_config_get()!
|
||||
if !db.exists(key: configurator.config_key())! {
|
||||
return error("can't find configuration with name: ${configurator.config_key()} in context:'${mycontext.config.name}'")
|
||||
}
|
||||
data := db.get(key: configurator.config_key())!
|
||||
|
||||
mut c := json.decode(T, data)!
|
||||
$for field in T.fields {
|
||||
field_attrs := attrs_get(field.attrs)
|
||||
if 'secret' in field_attrs {
|
||||
// QUESTION: is it ok if we only support encryption for string fields
|
||||
$if field.typ is string {
|
||||
v := c.$(field.name)
|
||||
c.$(field.name) = mycontext.secret_decrypt(v)!
|
||||
// console.print_debug('FIELD DECRYPTED: ${field} ${field.name}')
|
||||
}
|
||||
}
|
||||
}
|
||||
self.config_ = &c
|
||||
&c
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
pub fn (mut self BaseConfig[T]) config_save() ! {
|
||||
mut config2 := *self.config()! // dereference so we don't modify the original
|
||||
mut mycontext := context()!
|
||||
// //walk over the properties see where they need to be encrypted, if yes encrypt
|
||||
$for field in T.fields {
|
||||
field_attrs := attrs_get(field.attrs)
|
||||
if 'secret' in field_attrs {
|
||||
// QUESTION: is it ok if we only support encryption for string fields
|
||||
$if field.typ is string {
|
||||
v := config2.$(field.name)
|
||||
config2.$(field.name) = mycontext.secret_encrypt(v)!
|
||||
}
|
||||
// console.print_debug('FIELD ENCRYPTED: ${field.name}')
|
||||
}
|
||||
}
|
||||
mut configurator := self.configurator()!
|
||||
configurator.set(config2)!
|
||||
}
|
||||
|
||||
pub fn (mut self BaseConfig[T]) config_delete() ! {
|
||||
mut configurator := self.configurator()!
|
||||
configurator.delete()!
|
||||
self.config_ = none
|
||||
}
|
||||
|
||||
pub enum Action {
|
||||
set
|
||||
get
|
||||
new
|
||||
delete
|
||||
}
|
||||
|
||||
// init our class with the base session_args
|
||||
pub fn (mut self BaseConfig[T]) init(configtype string, instance string, action Action, myconfig T) ! {
|
||||
self.instance = instance
|
||||
self.configtype = configtype
|
||||
if action == .get {
|
||||
self.config_get()!
|
||||
} else if action == .new {
|
||||
self.config_new()!
|
||||
} else if action == .delete {
|
||||
self.config_delete()!
|
||||
} else if action == .set {
|
||||
self.config_set(myconfig)!
|
||||
} else {
|
||||
panic('bug')
|
||||
}
|
||||
}
|
||||
|
||||
// will return {'name': 'teststruct', 'params': ''}
|
||||
fn attrs_get(attrs []string) map[string]string {
|
||||
mut out := map[string]string{}
|
||||
for i in attrs {
|
||||
if i.contains('=') {
|
||||
kv := i.split('=')
|
||||
out[kv[0].trim_space().to_lower()] = kv[1].trim_space().to_lower()
|
||||
} else {
|
||||
out[i.trim_space().to_lower()] = ''
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
121
lib/core/base/configurator.v
Normal file
121
lib/core/base/configurator.v
Normal file
@@ -0,0 +1,121 @@
|
||||
module base
|
||||
|
||||
import json
|
||||
import freeflowuniverse.crystallib.ui.console
|
||||
|
||||
@[heap]
|
||||
pub struct Configurator[T] {
|
||||
pub mut:
|
||||
// context &Context @[skip; str: skip]
|
||||
instance string
|
||||
description string
|
||||
configured bool
|
||||
configtype string // e.g. sshclient
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ConfiguratorArgs {
|
||||
pub mut:
|
||||
// context &Context // optional context for the configurator
|
||||
instance string @[required]
|
||||
}
|
||||
|
||||
// name is e.g. mailclient (the type of configuration setting)
|
||||
// instance is the instance of the config e.g. kds
|
||||
// the context defines the context in which we operate, is optional will get the default one if not set
|
||||
pub fn configurator_new[T](args ConfiguratorArgs) !Configurator[T] {
|
||||
return Configurator[T]{
|
||||
// context: args.context
|
||||
configtype: T.name.to_lower()
|
||||
instance: args.instance
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut self Configurator[T]) config_key() string {
|
||||
return '${self.configtype}_config_${self.instance}'
|
||||
}
|
||||
|
||||
// set the full configuration as one object to dbconfig
|
||||
pub fn (mut self Configurator[T]) set(args T) ! {
|
||||
mut mycontext := context()!
|
||||
mut db := mycontext.db_config_get()!
|
||||
data := json.encode_pretty(args)
|
||||
db.set(key: self.config_key(), value: data)!
|
||||
}
|
||||
|
||||
pub fn (mut self Configurator[T]) exists() !bool {
|
||||
mut mycontext := context()!
|
||||
mut db := mycontext.db_config_get()!
|
||||
return db.exists(key: self.config_key())
|
||||
}
|
||||
|
||||
pub fn (mut self Configurator[T]) new() !T {
|
||||
return T{
|
||||
instance: self.instance
|
||||
description: self.description
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut self Configurator[T]) get() !T {
|
||||
mut mycontext := context()!
|
||||
mut db := mycontext.db_config_get()!
|
||||
if !db.exists(key: self.config_key())! {
|
||||
return error("can't find configuration with name: ${self.config_key()} in context:'${mycontext.config.name}'")
|
||||
}
|
||||
data := db.get(key: self.config_key())!
|
||||
return json.decode(T, data)!
|
||||
}
|
||||
|
||||
pub fn (mut self Configurator[T]) delete() ! {
|
||||
mut mycontext := context()!
|
||||
mut db := mycontext.db_config_get()!
|
||||
db.delete(key: self.config_key())!
|
||||
}
|
||||
|
||||
pub fn (mut self Configurator[T]) getset(args T) !T {
|
||||
mut mycontext := context()!
|
||||
mut db := mycontext.db_config_get()!
|
||||
if db.exists(key: self.config_key())! {
|
||||
return self.get()!
|
||||
}
|
||||
self.set(args)!
|
||||
return self.get()!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PrintArgs {
|
||||
pub mut:
|
||||
name string
|
||||
}
|
||||
|
||||
pub fn (mut self Configurator[T]) list() ![]string {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
pub fn (mut self Configurator[T]) configprint(args PrintArgs) ! {
|
||||
mut mycontext := context()!
|
||||
mut db := mycontext.db_config_get()!
|
||||
if args.name.len > 0 {
|
||||
if db.exists(key: self.config_key())! {
|
||||
data := db.get(key: self.config_key())!
|
||||
c := json.decode(T, data)!
|
||||
console.print_debug('${c}')
|
||||
console.print_debug('')
|
||||
} else {
|
||||
return error("Can't find connection with name: ${args.name}")
|
||||
}
|
||||
} else {
|
||||
panic('implement')
|
||||
// for item in list()! {
|
||||
// // console.print_debug(" ==== $item")
|
||||
// configprint(name: item)!
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// init our class with the base session_args
|
||||
// pub fn (mut self Configurator[T]) init(session_args_ SessionNewArgs) ! {
|
||||
// self.session_=session_args.session or {
|
||||
// session_new(session_args)!
|
||||
// }
|
||||
// }
|
||||
187
lib/core/base/context.v
Normal file
187
lib/core/base/context.v
Normal file
@@ -0,0 +1,187 @@
|
||||
module base
|
||||
|
||||
import freeflowuniverse.crystallib.data.paramsparser
|
||||
import freeflowuniverse.crystallib.clients.redisclient
|
||||
import freeflowuniverse.crystallib.data.dbfs
|
||||
// import freeflowuniverse.crystallib.crypt.secp256k1
|
||||
import freeflowuniverse.crystallib.crypt.aes_symmetric
|
||||
import freeflowuniverse.crystallib.ui
|
||||
import freeflowuniverse.crystallib.ui.console
|
||||
import freeflowuniverse.crystallib.core.pathlib
|
||||
import freeflowuniverse.crystallib.core.texttools
|
||||
import freeflowuniverse.crystallib.core.rootpath
|
||||
import json
|
||||
import os
|
||||
import crypto.md5
|
||||
|
||||
@[heap]
|
||||
pub struct Context {
|
||||
mut:
|
||||
// priv_key_ ?&secp256k1.Secp256k1 @[skip; str: skip]
|
||||
params_ ?¶msparser.Params
|
||||
dbcollection_ ?&dbfs.DBCollection @[skip; str: skip]
|
||||
redis_ ?&redisclient.Redis @[skip; str: skip]
|
||||
pub mut:
|
||||
// snippets map[string]string
|
||||
config ContextConfig
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ContextConfig {
|
||||
pub mut:
|
||||
id u32 @[required]
|
||||
name string = 'default'
|
||||
params string
|
||||
coderoot string
|
||||
interactive bool
|
||||
secret string // is hashed secret
|
||||
priv_key string // encrypted version
|
||||
db_path string // path to dbcollection
|
||||
encrypt bool
|
||||
}
|
||||
|
||||
// return the gistructure as is being used in context
|
||||
pub fn (mut self Context) params() !¶msparser.Params {
|
||||
mut p := self.params_ or {
|
||||
mut p := paramsparser.new(self.config.params)!
|
||||
self.params_ = &p
|
||||
&p
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
pub fn (self Context) id() string {
|
||||
return self.config.id.str()
|
||||
}
|
||||
|
||||
pub fn (self Context) name() string {
|
||||
return self.config.name
|
||||
}
|
||||
|
||||
pub fn (self Context) guid() string {
|
||||
return '${self.id()}:${self.name()}'
|
||||
}
|
||||
|
||||
pub fn (mut self Context) redis() !&redisclient.Redis {
|
||||
mut r2 := self.redis_ or {
|
||||
mut r := redisclient.core_get()!
|
||||
if self.config.id > 0 {
|
||||
// make sure we are on the right db
|
||||
r.selectdb(int(self.config.id))!
|
||||
}
|
||||
self.redis_ = &r
|
||||
&r
|
||||
}
|
||||
|
||||
return r2
|
||||
}
|
||||
|
||||
pub fn (mut self Context) save() ! {
|
||||
jsonargs := json.encode_pretty(self.config)
|
||||
mut r := self.redis()!
|
||||
// console.print_debug("save")
|
||||
// console.print_debug(jsonargs)
|
||||
r.set('context:config', jsonargs)!
|
||||
}
|
||||
|
||||
// get context from out of redis
|
||||
pub fn (mut self Context) load() ! {
|
||||
mut r := self.redis()!
|
||||
d := r.get('context:config')!
|
||||
// console.print_debug("load")
|
||||
// console.print_debug(d)
|
||||
if d.len > 0 {
|
||||
self.config = json.decode(ContextConfig, d)!
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut self Context) cfg_redis_exists() !bool {
|
||||
mut r := self.redis()!
|
||||
return r.exists('context:config')!
|
||||
}
|
||||
|
||||
// return db collection
|
||||
pub fn (mut self Context) dbcollection() !&dbfs.DBCollection {
|
||||
mut dbc2 := self.dbcollection_ or {
|
||||
if self.config.db_path.len == 0 {
|
||||
self.config.db_path = '${os.home_dir()}/hero/db/${self.config.id}'
|
||||
}
|
||||
mut dbc := dbfs.get(
|
||||
contextid: self.config.id
|
||||
dbpath: self.config.db_path
|
||||
secret: self.config.secret
|
||||
)!
|
||||
self.dbcollection_ = &dbc
|
||||
&dbc
|
||||
}
|
||||
|
||||
return dbc2
|
||||
}
|
||||
|
||||
pub fn (mut self Context) db_get(dbname string) !dbfs.DB {
|
||||
mut dbc := self.dbcollection()!
|
||||
return dbc.db_get_create(name: dbname, withkeys: true)!
|
||||
}
|
||||
|
||||
// always return the config db which is the same for all apps in context
|
||||
pub fn (mut self Context) db_config_get() !dbfs.DB {
|
||||
mut dbc := self.dbcollection()!
|
||||
return dbc.db_get_create(name: 'config', withkeys: true)!
|
||||
}
|
||||
|
||||
pub fn (mut self Context) hero_config_set(cat string, name string, content_ string) ! {
|
||||
mut content := texttools.dedent(content_)
|
||||
content = rootpath.shell_expansion(content)
|
||||
path := '${os.home_dir()}/hero/context/${self.config.name}/${cat}__${name}.yaml'
|
||||
mut config_file := pathlib.get_file(path: path)!
|
||||
config_file.write(content)!
|
||||
}
|
||||
|
||||
pub fn (mut self Context) hero_config_exists(cat string, name string) bool {
|
||||
path := '${os.home_dir()}/hero/context/${self.config.name}/${cat}__${name}.yaml'
|
||||
return os.exists(path)
|
||||
}
|
||||
|
||||
pub fn (mut self Context) hero_config_get(cat string, name string) !string {
|
||||
path := '${os.home_dir()}/hero/context/${self.config.name}/${cat}__${name}.yaml'
|
||||
mut config_file := pathlib.get_file(path: path, create: false)!
|
||||
return config_file.read()!
|
||||
}
|
||||
|
||||
pub fn (mut self Context) secret_encrypt(txt string) !string {
|
||||
return aes_symmetric.encrypt_str(txt, self.secret_get()!)
|
||||
}
|
||||
|
||||
pub fn (mut self Context) secret_decrypt(txt string) !string {
|
||||
return aes_symmetric.decrypt_str(txt, self.secret_get()!)
|
||||
}
|
||||
|
||||
pub fn (mut self Context) secret_get() !string {
|
||||
mut secret := self.config.secret
|
||||
if secret == '' {
|
||||
self.secret_configure()!
|
||||
secret = self.config.secret
|
||||
self.save()!
|
||||
}
|
||||
if secret == '' {
|
||||
return error("can't get secret")
|
||||
}
|
||||
return secret
|
||||
}
|
||||
|
||||
// show a UI in console to configure the secret
|
||||
pub fn (mut self Context) secret_configure() ! {
|
||||
mut myui := ui.new()!
|
||||
console.clear()
|
||||
secret_ := myui.ask_question(question: 'Please enter your hero secret string:')!
|
||||
self.secret_set(secret_)!
|
||||
}
|
||||
|
||||
// unhashed secret
|
||||
pub fn (mut self Context) secret_set(secret_ string) ! {
|
||||
secret := secret_.trim_space()
|
||||
secret2 := md5.hexhash(secret)
|
||||
self.config.secret = secret2
|
||||
self.save()!
|
||||
}
|
||||
89
lib/core/base/context_session.v
Normal file
89
lib/core/base/context_session.v
Normal file
@@ -0,0 +1,89 @@
|
||||
module base
|
||||
|
||||
import freeflowuniverse.crystallib.data.ourtime
|
||||
import freeflowuniverse.crystallib.data.paramsparser
|
||||
import json
|
||||
|
||||
@[params]
|
||||
pub struct SessionConfig {
|
||||
pub mut:
|
||||
name string // unique name for session (id), there can be more than 1 session per context
|
||||
start string // can be e.g. +1h
|
||||
description string
|
||||
params string
|
||||
}
|
||||
|
||||
// get a session object based on the name /
|
||||
// params:
|
||||
// ```
|
||||
// name string
|
||||
// ```
|
||||
pub fn (mut context Context) session_new(args_ SessionConfig) !Session {
|
||||
mut args := args_
|
||||
if args.name == '' {
|
||||
args.name = ourtime.now().key()
|
||||
}
|
||||
|
||||
if args.start == '' {
|
||||
t := ourtime.new(args.start)!
|
||||
args.start = t.str()
|
||||
}
|
||||
|
||||
mut r := context.redis()!
|
||||
|
||||
rkey := 'sessions:config:${args.name}'
|
||||
|
||||
config_json := json.encode(args)
|
||||
|
||||
r.set(rkey, config_json)!
|
||||
|
||||
rkey_latest := 'sessions:config:latest'
|
||||
r.set(rkey_latest, args.name)!
|
||||
|
||||
return context.session_get(name: args.name)!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ContextSessionGetArgs {
|
||||
pub mut:
|
||||
name string
|
||||
}
|
||||
|
||||
pub fn (mut context Context) session_get(args_ ContextSessionGetArgs) !Session {
|
||||
mut args := args_
|
||||
mut r := context.redis()!
|
||||
|
||||
if args.name == '' {
|
||||
rkey_latest := 'sessions:config:latest'
|
||||
args.name = r.get(rkey_latest)!
|
||||
}
|
||||
rkey := 'sessions:config:${args.name}'
|
||||
mut datajson := r.get(rkey)!
|
||||
if datajson == '' {
|
||||
if args.name == '' {
|
||||
return context.session_new()!
|
||||
} else {
|
||||
return error("can't find session with name ${args.name}")
|
||||
}
|
||||
}
|
||||
config := json.decode(SessionConfig, datajson)!
|
||||
t := ourtime.new(config.start)!
|
||||
mut s := Session{
|
||||
name: args.name
|
||||
start: t
|
||||
context: &context
|
||||
params: paramsparser.new(config.params)!
|
||||
config: config
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
pub fn (mut context Context) session_latest() !Session {
|
||||
mut r := context.redis()!
|
||||
rkey_latest := 'sessions:config:latest'
|
||||
latestname := r.get(rkey_latest)!
|
||||
if latestname == '' {
|
||||
return context.session_new()!
|
||||
}
|
||||
return context.session_get(name: latestname)!
|
||||
}
|
||||
6
lib/core/base/factory.v
Normal file
6
lib/core/base/factory.v
Normal file
@@ -0,0 +1,6 @@
|
||||
module base
|
||||
|
||||
__global (
|
||||
contexts map[u32]&Context
|
||||
context_current u32
|
||||
)
|
||||
103
lib/core/base/factory_context.v
Normal file
103
lib/core/base/factory_context.v
Normal file
@@ -0,0 +1,103 @@
|
||||
module base
|
||||
|
||||
import freeflowuniverse.crystallib.data.paramsparser
|
||||
import freeflowuniverse.crystallib.ui
|
||||
import freeflowuniverse.crystallib.ui.console
|
||||
import crypto.md5
|
||||
|
||||
@[params]
|
||||
pub struct ContextConfigArgs {
|
||||
pub mut:
|
||||
id u32
|
||||
name string = 'default'
|
||||
params string
|
||||
coderoot string
|
||||
interactive bool
|
||||
secret string
|
||||
encrypt bool
|
||||
priv_key_hex string // hex representation of private key
|
||||
}
|
||||
|
||||
// configure a context object
|
||||
// params: .
|
||||
// ```
|
||||
// id u32 //if not set then redis will get a new context id
|
||||
// name string = 'default'
|
||||
// params string
|
||||
// coderoot string
|
||||
// interactive bool
|
||||
// secret string
|
||||
// priv_key_hex string //hex representation of private key
|
||||
// ```
|
||||
pub fn context_new(args_ ContextConfigArgs) !&Context {
|
||||
mut args := ContextConfig{
|
||||
id: args_.id
|
||||
name: args_.name
|
||||
params: args_.params
|
||||
coderoot: args_.coderoot
|
||||
interactive: args_.interactive
|
||||
secret: args_.secret
|
||||
encrypt: args_.encrypt
|
||||
}
|
||||
|
||||
if args.encrypt && args.secret == '' && args.interactive {
|
||||
mut myui := ui.new()!
|
||||
console.clear()
|
||||
args.secret = myui.ask_question(question: 'Please enter your hero secret string:')!
|
||||
}
|
||||
|
||||
if args.encrypt && args.secret.len > 0 {
|
||||
args.secret = md5.hexhash(args.secret)
|
||||
}
|
||||
|
||||
mut c := Context{
|
||||
config: args
|
||||
}
|
||||
|
||||
// if args_.priv_key_hex.len > 0 {
|
||||
// c.privkey_set(args_.priv_key_hex)!
|
||||
// }
|
||||
|
||||
// c.save()!
|
||||
|
||||
if args.params.len > 0 {
|
||||
mut p := paramsparser.new('')!
|
||||
c.params_ = &p
|
||||
}
|
||||
|
||||
c.save()!
|
||||
contexts[args.id] = &c
|
||||
|
||||
return contexts[args.id] or { panic('bug') }
|
||||
}
|
||||
|
||||
pub fn context_get(id u32) !&Context {
|
||||
context_current = id
|
||||
|
||||
if id in contexts {
|
||||
return contexts[id] or { panic('bug') }
|
||||
}
|
||||
|
||||
mut mycontext := Context{
|
||||
config: ContextConfig{
|
||||
id: id
|
||||
}
|
||||
}
|
||||
|
||||
if mycontext.cfg_redis_exists()! {
|
||||
mycontext.load()!
|
||||
return &mycontext
|
||||
}
|
||||
|
||||
mut mycontext2 := context_new(id: id)!
|
||||
return mycontext2
|
||||
}
|
||||
|
||||
pub fn context_select(id u32) !&Context {
|
||||
context_current = id
|
||||
return context()!
|
||||
}
|
||||
|
||||
pub fn context() !&Context {
|
||||
return context_get(context_current)!
|
||||
}
|
||||
39
lib/core/base/readme.md
Normal file
39
lib/core/base/readme.md
Normal file
@@ -0,0 +1,39 @@
|
||||
## context & sessions
|
||||
|
||||
Everything we do in hero lives in a context, each context has a unique name.
|
||||
|
||||
Redis is used to manage the contexts and the sessions.
|
||||
|
||||
- redis db 0
|
||||
- `context:current` curent id of the context, is also the DB if redis if redis is used
|
||||
- redis db X, x is nr of context
|
||||
- `context:name` name of this context
|
||||
- `context:secret` secret as is used in context (is md5 of original config secret)
|
||||
- `context:privkey` secp256k1 privkey as is used in context (encrypted by secret)
|
||||
- `context:params` params for a context, can have metadata
|
||||
- `context:lastid` last id for our db
|
||||
- `session:$id` the location of session
|
||||
- `session:$id:params` params for the session, can have metadata
|
||||
|
||||
Session id is $contextid:$sessionid (e.g. 10:111)
|
||||
|
||||
**The SessionNewArgs:**
|
||||
|
||||
- context_name string = 'default'
|
||||
- session_name string //default will be an incremental nr if not filled in
|
||||
- interactive bool = true //can ask questions, default on true
|
||||
- coderoot string //this will define where all code is checked out
|
||||
|
||||
```v
|
||||
import freeflowuniverse.crystallib.core.base
|
||||
|
||||
mut session:=context_new(
|
||||
coderoot:'/tmp/code'
|
||||
interactive:true
|
||||
)!
|
||||
|
||||
mut session:=session_new(context:'default',session:'mysession1')!
|
||||
mut session:=session_new()! //returns default context & incremental new session
|
||||
|
||||
```
|
||||
|
||||
98
lib/core/base/session.v
Normal file
98
lib/core/base/session.v
Normal file
@@ -0,0 +1,98 @@
|
||||
module base
|
||||
|
||||
import freeflowuniverse.crystallib.data.ourtime
|
||||
// import freeflowuniverse.crystallib.core.texttools
|
||||
import freeflowuniverse.crystallib.data.paramsparser
|
||||
import freeflowuniverse.crystallib.data.dbfs
|
||||
import json
|
||||
// import freeflowuniverse.crystallib.core.pathlib
|
||||
// import freeflowuniverse.crystallib.develop.gittools
|
||||
// import freeflowuniverse.crystallib.ui.console
|
||||
|
||||
@[heap]
|
||||
pub struct Session {
|
||||
pub mut:
|
||||
name string // unique id for session (session id), can be more than one per context
|
||||
interactive bool = true
|
||||
params paramsparser.Params
|
||||
start ourtime.OurTime
|
||||
end ourtime.OurTime
|
||||
context &Context @[skip; str: skip]
|
||||
config SessionConfig
|
||||
env map[string]string
|
||||
}
|
||||
|
||||
///////// LOAD & SAVE
|
||||
|
||||
// fn (mut self Session) key() string {
|
||||
// return 'hero:sessions:${self.guid()}'
|
||||
// }
|
||||
|
||||
// get db of the session, is unique per session
|
||||
pub fn (mut self Session) db_get() !dbfs.DB {
|
||||
return self.context.db_get('session_${self.name}')!
|
||||
}
|
||||
|
||||
// get the db of the config, is unique per context
|
||||
pub fn (mut self Session) db_config_get() !dbfs.DB {
|
||||
return self.context.db_get('config')!
|
||||
}
|
||||
|
||||
// load the params from redis
|
||||
pub fn (mut self Session) load() ! {
|
||||
mut r := self.context.redis()!
|
||||
rkey := 'sessions:config:${self.name}'
|
||||
mut datajson := r.get(rkey)!
|
||||
if datajson == '' {
|
||||
return error("can't find session with name ${self.name}")
|
||||
}
|
||||
self.config = json.decode(SessionConfig, datajson)!
|
||||
self.params = paramsparser.new(self.config.params)!
|
||||
}
|
||||
|
||||
// save the params to redis
|
||||
pub fn (mut self Session) save() ! {
|
||||
self.check()!
|
||||
rkey := 'sessions:config:${self.name}'
|
||||
mut r := self.context.redis()!
|
||||
self.config.params = self.params.str()
|
||||
config_json := json.encode(self.config)
|
||||
r.set(rkey, config_json)!
|
||||
}
|
||||
|
||||
// Set an environment variable
|
||||
pub fn (mut self Session) env_set(key string, value string) ! {
|
||||
self.env[key] = value
|
||||
self.save()!
|
||||
}
|
||||
|
||||
// Get an environment variable
|
||||
pub fn (mut self Session) env_get(key string) !string {
|
||||
return self.env[key] or { return error("can't find env in session ${self.name}") }
|
||||
}
|
||||
|
||||
// Delete an environment variable
|
||||
pub fn (mut self Session) env_delete(key string) {
|
||||
self.env.delete(key)
|
||||
}
|
||||
|
||||
////////// REPRESENTATION
|
||||
|
||||
pub fn (self Session) check() ! {
|
||||
if self.name.len < 3 {
|
||||
return error('name should be at least 3 char')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (self Session) guid() string {
|
||||
return '${self.context.guid()}:${self.name}'
|
||||
}
|
||||
|
||||
fn (self Session) str2() string {
|
||||
mut out := 'session:${self.guid()}'
|
||||
out += ' start:\'${self.start}\''
|
||||
if !self.end.empty() {
|
||||
out += ' end:\'${self.end}\''
|
||||
}
|
||||
return out
|
||||
}
|
||||
42
lib/core/base/session_error.v
Normal file
42
lib/core/base/session_error.v
Normal file
@@ -0,0 +1,42 @@
|
||||
module base
|
||||
|
||||
import freeflowuniverse.crystallib.data.ourtime
|
||||
import freeflowuniverse.crystallib.core.texttools
|
||||
|
||||
pub struct ErrorArgs {
|
||||
pub mut:
|
||||
cat string
|
||||
error string
|
||||
errortype ErrorType
|
||||
}
|
||||
|
||||
pub struct ErrorItem {
|
||||
pub mut:
|
||||
time ourtime.OurTime
|
||||
cat string
|
||||
error string
|
||||
errortype ErrorType
|
||||
session string // the unique name for the session
|
||||
}
|
||||
|
||||
pub enum ErrorType {
|
||||
uknown
|
||||
value
|
||||
}
|
||||
|
||||
pub fn (mut session Session) error(args_ ErrorArgs) !ErrorItem {
|
||||
mut args := args_
|
||||
args.cat = texttools.name_fix(args.cat)
|
||||
|
||||
mut l := ErrorItem{
|
||||
cat: args.cat
|
||||
error: args.error
|
||||
errortype: args.errortype
|
||||
time: ourtime.now()
|
||||
session: session.name
|
||||
}
|
||||
|
||||
// TODO: get string output and put to redis
|
||||
|
||||
return l
|
||||
}
|
||||
61
lib/core/base/session_logger.v
Normal file
61
lib/core/base/session_logger.v
Normal file
@@ -0,0 +1,61 @@
|
||||
module base
|
||||
|
||||
import freeflowuniverse.crystallib.data.ourtime
|
||||
import freeflowuniverse.crystallib.core.texttools
|
||||
|
||||
@[heap]
|
||||
pub struct Logger {
|
||||
pub mut:
|
||||
session string
|
||||
}
|
||||
|
||||
pub struct LogItem {
|
||||
pub mut:
|
||||
time ourtime.OurTime
|
||||
cat string
|
||||
log string
|
||||
logtype LogType
|
||||
session string
|
||||
}
|
||||
|
||||
pub enum LogType {
|
||||
stdout
|
||||
error
|
||||
}
|
||||
|
||||
pub fn (session Session) logger_new() !Logger {
|
||||
// mut l:=log.Log{}
|
||||
// l.set_full_logpath('./info.log')
|
||||
// l.log_to_console_too()
|
||||
return Logger{}
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct LogArgs {
|
||||
pub mut:
|
||||
cat string
|
||||
log string @[required]
|
||||
logtype LogType
|
||||
}
|
||||
|
||||
// cat & log are the arguments .
|
||||
// category can be any well chosen category e.g. vm
|
||||
pub fn (mut session Session) log(args_ LogArgs) !LogItem {
|
||||
mut args := args_
|
||||
args.cat = texttools.name_fix(args.cat)
|
||||
|
||||
mut l := LogItem{
|
||||
cat: args.cat
|
||||
log: args.log
|
||||
time: ourtime.now()
|
||||
// session: session.guid()
|
||||
}
|
||||
|
||||
// TODO: get string output and put to redis
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
pub fn (li LogItem) str() string {
|
||||
return '${li.session}'
|
||||
}
|
||||
4
lib/core/base/templates/load.sh
Normal file
4
lib/core/base/templates/load.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
#redis-cli SCRIPT LOAD "$(cat logger.lua)"
|
||||
export LOGGER_ADD=$(redis-cli SCRIPT LOAD "$(cat logger_add.lua)")
|
||||
export LOGGER_DEL=$(redis-cli SCRIPT LOAD "$(cat logger_del.lua)")
|
||||
export STATS_ADD=$(redis-cli SCRIPT LOAD "$(cat stats_add.lua)")
|
||||
51
lib/core/base/templates/logger_add.lua
Normal file
51
lib/core/base/templates/logger_add.lua
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
local function normalize(str)
|
||||
return string.gsub(string.lower(str), "%s+", "_")
|
||||
end
|
||||
|
||||
local src = normalize(ARGV[1])
|
||||
local category = normalize(ARGV[2])
|
||||
local message = ARGV[3]
|
||||
local logHashKey = "logs:" .. src
|
||||
local lastIdKey = "logs:" .. src .. ":lastid"
|
||||
|
||||
-- redis.log(redis.LOG_NOTICE, "...")
|
||||
|
||||
-- Increment the log ID using Redis INCR command
|
||||
local logId = redis.call('INCR', lastIdKey)
|
||||
|
||||
-- Get the current epoch time
|
||||
local epoch = redis.call('TIME')[1]
|
||||
|
||||
-- Prepare the log entry with a unique ID, epoch time, and message
|
||||
local logEntry = category .. ":" .. epoch .. ":" .. message
|
||||
|
||||
-- Add the new log entry to the hash set
|
||||
redis.call('HSET', logHashKey, logId, logEntry)
|
||||
|
||||
-- Optionally manage the size of the hash to keep the latest 2000 entries only
|
||||
local hlen = redis.call('HLEN', logHashKey)
|
||||
if hlen > 5000 then
|
||||
-- Find the smallest logId
|
||||
local smallestId = logId
|
||||
local cursor = "0"
|
||||
repeat
|
||||
local scanResult = redis.call('HSCAN', logHashKey, cursor, "COUNT", 5)
|
||||
cursor = scanResult[1]
|
||||
local entries = scanResult[2]
|
||||
for i = 1, #entries, 2 do
|
||||
local currentId = tonumber(entries[i])
|
||||
if currentId < smallestId then
|
||||
smallestId = currentId
|
||||
end
|
||||
end
|
||||
until cursor == "0"
|
||||
-- redis.log(redis.LOG_NOTICE, "smallest id: " .. smallestId)
|
||||
|
||||
-- Remove the oldest entries
|
||||
for i = smallestId, smallestId + 500 do
|
||||
redis.call('HDEL', logHashKey, i)
|
||||
end
|
||||
end
|
||||
|
||||
return logEntry
|
||||
22
lib/core/base/templates/logger_del.lua
Normal file
22
lib/core/base/templates/logger_del.lua
Normal file
@@ -0,0 +1,22 @@
|
||||
-- Function to normalize strings (convert to lower case and replace spaces with underscores)
|
||||
local function normalize(str)
|
||||
return string.gsub(string.lower(str), "%s+", "_")
|
||||
end
|
||||
|
||||
local src = ARGV[1] and normalize(ARGV[1]) or nil
|
||||
|
||||
if src then
|
||||
-- Delete logs for specified source and category
|
||||
local logHashKey = "logs:" .. src
|
||||
local lastIdKey = logHashKey .. ":lastid"
|
||||
redis.call('DEL', logHashKey)
|
||||
redis.call('DEL', lastIdKey)
|
||||
else
|
||||
-- Delete all logs for all sources and categories
|
||||
local keys = redis.call('KEYS', "logs:*")
|
||||
for i, key in ipairs(keys) do
|
||||
redis.call('DEL', key)
|
||||
end
|
||||
end
|
||||
|
||||
return "Logs deleted"
|
||||
18
lib/core/base/templates/logger_example.sh
Executable file
18
lib/core/base/templates/logger_example.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -x
|
||||
cd "$(dirname "$0")"
|
||||
source load.sh
|
||||
|
||||
# for i in $(seq 1 1000)
|
||||
# do
|
||||
# redis-cli EVALSHA $LOGHASH 0 "AAA" "CAT1" "Example log message"
|
||||
# redis-cli EVALSHA $LOGHASH 0 "AAA" "CAT2" "Example log message"
|
||||
# done
|
||||
|
||||
redis-cli EVALSHA $LOGGER_DEL 0
|
||||
|
||||
for i in $(seq 1 200)
|
||||
do
|
||||
redis-cli EVALSHA $LOGGER_ADD 0 "BBB" "CAT2" "Example log message"
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user