changes saved in my editor

This commit is contained in:
Sebastian Moser
2026-10-22 18:25:57 +02:00
parent 105eca862d
commit 679f5e51e5
10 changed files with 3503 additions and 2 deletions

View File

@@ -23,9 +23,13 @@ All code written for, with or as part as mize (except for the small core) is wri
To understand the main components of the architecture you can refer to `docs/architecture.md`. To understand the main components of the architecture you can refer to `docs/architecture.md`.
## Require clarification and plan approval before making code changes Any proc macros should go into `./macros`.
the core mize library is in `./core`
any mize parts are in `./parts` or in their own repositories.
Before making any code changes other than the changelog, you must follow this two-step process: ## Require clarification and plan approval before making architectural code changes
Before making any code changes which could be considered to influence the architekture of the application, you must follow this two-step process:
### Step 1: Ask Clarifying Questions ### Step 1: Ask Clarifying Questions
@@ -34,6 +38,7 @@ Before making any code changes other than the changelog, you must follow this tw
- Clarify any ambiguous requirements or edge cases - Clarify any ambiguous requirements or edge cases
- Ask about preferred approaches if multiple solutions exist - Ask about preferred approaches if multiple solutions exist
- Confirm the expected behavior and user experience - Confirm the expected behavior and user experience
- Ask the clarifying questions by numbering them, so the user can number their replies.
### Step 2: Present Implementation Plan ### Step 2: Present Implementation Plan
@@ -45,6 +50,11 @@ Before making any code changes other than the changelog, you must follow this tw
- Estimate the complexity and scope of changes - Estimate the complexity and scope of changes
- **Wait for explicit user approval** before proceeding with any code changes - **Wait for explicit user approval** before proceeding with any code changes
### What are considered architectural changes
TODO: add better examples.
eg: adding a seccond line displaying a different value from the DB is not considered architectural. as it was achievable by only modifying the existing function and rsx! code.
### Approval Requirements ### Approval Requirements
- User must explicitly approve the plan with words like "yes", "approved", "proceed", "go ahead", or similar - User must explicitly approve the plan with words like "yes", "approved", "proceed", "go ahead", or similar

128
core/src/config.rs Normal file
View File

@@ -0,0 +1,128 @@
use std::any::Any;
use crate::MizeResult;
use crate::c_api::get_global_mize;
use crate::id::MizeId;
use crate::item::{IntoItemData, ItemData};
use crate::Mize;
type ConfigFun = Box<dyn Fn() -> MizeResult<ItemData> + Send + Sync + 'static>;
pub struct ConfigOpt {
pub name: String,
pub val: Option<ItemData>,
pub fun: Option<ConfigFun>,
pub deps: Vec<String>,
}
#[derive(Clone)]
#[repr(C)]
pub struct ConfigOptId {
pub id: MizeId,
}
impl ConfigOptId {
pub fn default_val(self, val: ItemData) -> Self {
let mize = get_global_mize().unwrap();
let mut config_opts = mize.config_opts.lock().unwrap();
let opt = config_opts.get_mut(&self.id).unwrap();
opt.val = Some(val);
self.clone()
}
pub fn fun(self, fun: ConfigFun) -> Self {
let mize = get_global_mize().unwrap();
let mut config_opts = mize.config_opts.lock().unwrap();
println!("get_mut: {}", self.id);
let opt = config_opts.get_mut(&self.id).unwrap();
opt.fun = Some(fun);
self.clone()
}
pub fn method<F: Fn(ItemData) -> MizeResult<ItemData>>(self, _name: &str, _fun: F) -> ConfigOptId {
self.clone()
}
}
//pub trait ToItemDataHelper {
//fn into_item_data(self) -> ItemData;
//}
pub fn opt(name: &str) -> crate::config::ConfigOptId {
let mize = get_global_mize().unwrap();
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
let mut opts = mize.config_opts.lock().unwrap();
let id = cur_part.join(name).unwrap();
let opt = ConfigOpt {
name: name.to_owned(),
val: None,
fun: None,
deps: Vec::new(),
};
opts.insert(id.clone(), opt);
ConfigOptId { id }
}
pub fn method<F: Fn(ItemData) -> MizeResult<ItemData>>(_name: &str, _fun: F) {
}
pub fn effect<F: Fn(ItemData) -> MizeResult<ItemData>>(_name: &str, _fun: F) {
}
pub fn thread<F: Fn(ItemData) -> MizeResult<ItemData>>(_name: &str, _fun: F) {
}
type CFun = unsafe extern "C" fn (*mut ItemData) -> *mut ItemData;
pub mod c_api {
use std::ffi::c_char;
use crate::config::{CFun, ConfigOptId};
use std::ffi::CStr;
pub extern "C" fn opt(name: *const c_char) -> ConfigOptId {
unsafe {
let c_str = CStr::from_ptr(name);
let string = c_str.to_string_lossy();
super::opt(string.as_str())
}
}
pub extern "C" fn method(name: *const c_char, c_fun: CFun) {
unsafe {
let c_str = CStr::from_ptr(name);
let string = c_str.to_string_lossy();
super::method(string.as_str(), move |args| {
let raw_ptr = Box::into_raw(Box::new(args));
let ret_ptr = c_fun(raw_ptr);
let data = *Box::from_raw(ret_ptr);
Ok(data)
})
}
}
pub extern "C" fn effect(name: *const c_char, c_fun: CFun) {
unsafe {
let c_str = CStr::from_ptr(name);
let string = c_str.to_string_lossy();
super::effect(string.as_str(), move |args| {
let raw_ptr = Box::into_raw(Box::new(args));
let ret_ptr = c_fun(raw_ptr);
let data = *Box::from_raw(ret_ptr);
Ok(data)
})
}
}
pub extern "C" fn thread(name: *const c_char, c_fun: CFun) {
unsafe {
let c_str = CStr::from_ptr(name);
let string = c_str.to_string_lossy();
super::thread(string.as_str(), move |args| {
let raw_ptr = Box::into_raw(Box::new(args));
let ret_ptr = c_fun(raw_ptr);
let data = *Box::from_raw(ret_ptr);
Ok(data)
})
}
}
}

203
core/src/id.rs Normal file
View File

@@ -0,0 +1,203 @@
use core::fmt;
use interner::{
shared::{SharedString, SharedVecString},
};
use std::hash::Hash;
use crate::{c_api::get_global_mize, error::MizeResult};
use crate::{instance::Mize, mize_err};
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct MizeId {
pub path: SharedVecString,
pub namespace: Namespace,
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct Namespace(pub SharedString);
impl Namespace {
pub fn as_string(&self) -> SharedString {
self.0.clone()
}
pub fn as_real_string(&self) -> String {
let a = self.as_string();
let b = a.to_string();
return b;
}
}
pub trait IntoMizeId {
fn to_mize_id(self, instance: &Mize) -> MizeResult<MizeId>;
}
impl MizeId {
pub fn join(&mut self, to_add: &str) -> MizeResult<MizeId> {
let path = self.path();
let mut new_path = Vec::new();
for el in path.iter() {
new_path.push(el.to_owned());
}
new_path.push(to_add.to_owned());
let mize = get_global_mize()?;
Ok(new_path.to_mize_id(&mize)?)
}
pub fn store_part(&self) -> &str {
self.path
.iter()
.nth(0)
.expect("an empty MizeId found, that should absolutely not be possible!!!!")
.as_str()
}
pub fn nth_part(&self, n: usize) -> MizeResult<&str> {
match self.path.iter().nth(n) {
Some(val) => Ok(val.as_str()),
None => Err(mize_err!("id does not have {} parts", n)),
}
}
pub fn path(&self) -> SharedVecString {
self.path.clone()
}
pub fn after_store_part(&self) -> Vec<String> {
let tmp = self.path();
let mut path_iter = <Box<[std::string::String]> as Clone>::clone(&tmp).into_iter();
path_iter.next();
let new_path: Vec<String> = path_iter.map(|v| v.to_owned()).collect();
return new_path;
}
pub fn namespace(&self) -> Namespace {
self.namespace.clone()
}
pub fn namespace_str(&self) -> &str {
&self.namespace.0
}
}
impl fmt::Display for MizeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.path.join("/"))
}
}
impl IntoMizeId for &str {
fn to_mize_id(self, instance: &Mize) -> MizeResult<MizeId> {
instance.id_from_string(self.to_owned())
}
}
impl IntoMizeId for String {
fn to_mize_id(self, instance: &Mize) -> MizeResult<MizeId> {
instance.id_from_string(self)
}
}
impl IntoMizeId for &String {
fn to_mize_id(self, instance: &Mize) -> MizeResult<MizeId> {
instance.id_from_string(self.to_owned())
}
}
impl IntoMizeId for Vec<String> {
fn to_mize_id(self, instance: &Mize) -> MizeResult<MizeId> {
instance.id_from_vec_string(self)
}
}
impl IntoMizeId for Vec<&String> {
fn to_mize_id(self, instance: &Mize) -> MizeResult<MizeId> {
let owned = self
.into_iter()
.map(|s| s.to_owned())
.collect::<Vec<String>>();
instance.id_from_vec_string(owned)
}
}
impl IntoMizeId for &[&str] {
fn to_mize_id(self, instance: &Mize) -> MizeResult<MizeId> {
let mut vec: Vec<String> = Vec::new();
for i in self {
vec.push((*i).to_owned())
}
instance.id_from_vec_string(vec)
}
}
impl IntoMizeId for Vec<&str> {
fn to_mize_id(self, instance: &Mize) -> MizeResult<MizeId> {
let mut vec: Vec<String> = Vec::new();
for i in self {
vec.push((*i).to_owned())
}
instance.id_from_vec_string(vec)
}
}
impl IntoMizeId for MizeId {
fn to_mize_id(self, _instance: &Mize) -> MizeResult<MizeId> {
Ok(self)
}
}
// would only work with a global id_pool
// for now use Instance::new_id()
//impl<T: Into<String>> From<T> for MizeId {
//fn from(value: T) -> Self {
//let string = value.into();
//let path: Vec<String> = string.split("/").map(|v| v.to_owned()).collect();
//return MizeId { MIZE_ID_POOL.get(path) }
//}
//}
#[cfg(test)]
mod test {
use crate::instance::Mize;
use super::*;
#[test]
fn test_id() -> MizeResult<()> {
let instance = Mize::empty()?;
let id_one = instance.new_id("0/config/hi")?;
assert_eq!(instance.get_namespace()?, id_one.namespace());
assert_eq!("0", id_one.store_part());
assert_eq!(
vec!["config".to_owned(), "hi".to_owned()],
id_one.after_store_part()
);
let id_two = instance.new_id("test.ns:hello/config/hi")?;
assert_eq!(
instance.namespace_from_string("test.ns".to_owned())?,
id_two.namespace()
);
assert_eq!("hello", id_two.store_part());
assert_eq!(
vec!["config".to_owned(), "hi".to_owned()],
id_one.after_store_part()
);
let id_three = instance.new_id("0/")?;
assert_eq!(instance.get_namespace()?, id_three.namespace());
assert_eq!("0", id_three.store_part());
assert_eq!(Vec::<String>::new(), id_three.after_store_part());
let id_four = instance.new_id("test.ns:0/")?;
assert_eq!(
instance.namespace_from_string("test.ns".to_owned())?,
id_four.namespace()
);
assert_eq!("0", id_four.store_part());
assert_eq!(Vec::<String>::new(), id_four.after_store_part());
let id_fife = instance.new_id("0")?;
assert_eq!(instance.get_namespace()?, id_fife.namespace());
assert_eq!("0", id_fife.store_part());
assert_eq!(Vec::<String>::new(), id_fife.after_store_part());
//TODO: add the parsing form mize:// urls....
Ok(())
}
}

869
core/src/instance/mod.rs Normal file
View File

@@ -0,0 +1,869 @@
use async_trait::async_trait;
use colored::Colorize;
use core::fmt;
use std::time::Duration;
use flume::{bounded, unbounded, Sender};
use interner::shared::{StringPool, VecStringPool};
use std::any::Any;
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Mutex};
use std::{thread, vec};
use tracing::{debug, info, trace};
use crate::c_api::set_global_mize;
use crate::config::{ConfigOpt, ConfigOptId};
use crate::error::{MizeError, MizeResult};
use crate::id::{IntoMizeId, MizeId, Namespace};
use crate::instance::store::Store;
use crate::instance::subscription::Subscription;
use crate::instance::updater::Operation;
use crate::instance::updater::updater_thread;
use crate::item::{Item, ItemData};
use crate::memstore::MemStore;
use crate::mize_err;
use crate::proto::MizeMessage;
use crate::error::IntoMizeResult;
use self::connection::{ConnListener, Connection};
use self::updater::handle_operation;
#[cfg(feature = "async")]
use tokio::runtime::Handle;
#[cfg(feature = "async")]
use tokio::runtime::Runtime;
use core::future::Future;
use std::thread::JoinHandle;
pub mod connection;
pub mod msg_thread;
pub mod store;
pub mod subscription;
pub mod updater;
#[cfg(test)]
mod tests;
static MSG_CHANNEL_SIZE: usize = 200;
static BUILD_TIME_CONFIG: &str = include_str!(std::env!("MIZE_BUILD_CONFIG"));
/// The Instance type is the heart of the mize system
#[derive(Clone)]
pub struct Mize {
// a bit a lot of Mutexes isn't it???
pub(crate) store: Arc<Mutex<Box<dyn Store>>>,
connections: Arc<Mutex<Vec<Connection>>>,
next_con_id: Arc<Mutex<u64>>,
subs: Arc<Mutex<HashMap<MizeId, Vec<Subscription>>>>,
pub(crate) id_pool: Arc<Mutex<VecStringPool>>,
pub(crate) namespace_pool: Arc<Mutex<StringPool>>,
pub(crate) listeners: Arc<
Mutex<HashMap<String, Arc<dyn Fn(MizeMessage, &mut Mize) -> MizeResult<()> + Send + Sync>>>,
>,
pub parts: Arc<Mutex<HashMap<MizeId, Option<Box<dyn MizePart + Sync + Send>>>>>,
//pub(crate) platform: Arc<Mutex<Box<dyn MizePlatform>>>, // should we do that instead of all the #cfg(faturs=targt-smth) in lib.rs
part_names: Arc<Mutex<Vec<&'static str>>>, // exists, so that we know the ordering in which parts were loaded
pub(crate) config_opts: Arc<Mutex<HashMap<MizeId, ConfigOpt>>>,
// the namespace the instance operates in
pub(crate) namespace: Arc<Mutex<Namespace>>,
// the namespace of the instance itself
// TODO: set to a random uuid
pub(crate) self_namespace: Arc<Mutex<Namespace>>,
pub(crate) op_tx: Sender<Operation>,
threads: Arc<Mutex<Vec<(u32, String, Option<JoinHandle<MizeResult<()>>>)>>>,
next_thread_id: Arc<Mutex<u32>>,
give_msg_wait: Arc<Mutex<HashMap<MizeId, Vec<Sender<ItemData>>>>>,
create_msg_wait: Arc<Mutex<Option<Sender<MizeId>>>>,
pub cur_eval_part: Arc<Mutex<Option<MizeId>>>,
pub native_values: Arc<Mutex<HashMap<u32, Box<dyn Any + Send + Sync>>>>,
pub next_native_value_id: Arc<Mutex<u32>>,
#[cfg(feature = "async")]
pub runtime: Arc<Mutex<Runtime>>,
}
pub struct InstanceRef {
inner: Arc<Mutex<Mize>>,
}
pub struct PartRef {
id: MizeId,
}
impl PartRef {
pub fn into_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
impl MizePart for PartRef {
fn id(&self) -> MizeId {
self.id.clone()
}
}
pub trait PartToNative {
fn from_mize_id(id: MizeId) -> Self;
}
pub struct InstanceAsync {
inner: Arc<Mutex<Mize>>,
}
pub trait MizePart {
fn id(&self) -> MizeId;
}
impl Mize {
pub fn empty() -> MizeResult<Mize> {
let id_pool = Arc::new(Mutex::new(VecStringPool::default()));
let namespace_pool_raw = StringPool::default();
let connections = Arc::new(Mutex::new(Vec::new()));
let subs = Arc::new(Mutex::new(HashMap::new()));
let (op_tx, op_rx) = unbounded();
let give_msg_wait = Arc::new(Mutex::new(HashMap::new()));
let create_msg_wait = Arc::new(Mutex::new(None));
let namespace = Arc::new(Mutex::new(Namespace(
namespace_pool_raw.get("mize.default.namespace"),
)));
let self_namespace = Arc::new(Mutex::new(Namespace(
namespace_pool_raw.get("mize.default.namespace"),
)));
let mut instance = Mize {
store: Arc::new(Mutex::new(Box::new(MemStore::new()))),
parts: Arc::new(Mutex::new(HashMap::new())),
part_names: Arc::new(Mutex::new(Vec::new())),
config_opts: Arc::new(Mutex::new(HashMap::new())),
listeners: Arc::new(Mutex::new(HashMap::new())),
connections,
subs,
id_pool,
namespace,
self_namespace,
op_tx,
namespace_pool: Arc::new(Mutex::new(namespace_pool_raw)),
threads: Arc::new(Mutex::new(Vec::new())),
next_thread_id: Arc::new(Mutex::new(0)),
next_con_id: Arc::new(Mutex::new(1)),
give_msg_wait,
create_msg_wait,
cur_eval_part: Arc::new(Mutex::new(None)),
native_values: Arc::new(Mutex::new(HashMap::new())),
next_native_value_id: Arc::new(Mutex::new(0)),
#[cfg(feature = "async")]
runtime: Arc::new(Mutex::new(
Runtime::new().mres_msg("Could not create async runtime")?,
)),
};
#[cfg(feature = "target-os")]
{
let instance_clone = instance.clone();
let op_rx_clone = op_rx.clone();
let closure = move || updater_thread(op_rx_clone, &instance_clone);
instance.spawn_background("updater_thread", closure)?;
let instance_clone_two = instance.clone();
let closure_two = move || updater_thread(op_rx, &instance_clone_two);
instance.spawn_background("updater_thread", closure_two)?;
}
// set up async update "threads" when using wasm
#[cfg(feature = "target-wasm ")]
{
let instance_clone = instance.clone();
let op_rx_clone = op_rx.clone();
wasm_bindgen_futures::spawn_local(updater_thread_async(op_rx_clone, instance_clone));
let instance_clone_two = instance.clone();
wasm_bindgen_futures::spawn_local(updater_thread_async(op_rx, instance_clone_two));
}
// will move the msg stuff into it's own thread
// like this it can deadlock.... if a msg waits on an operation to complete
//let msg_instance_clone = instance.clone();
//let msg_closure = move || msg_thread(op_rx, instance_clone);
//instance.spawn("msg_thread", closure)?;
#[cfg(feature = "target-wasm ")]
console_log!("before loading build time config");
// load the config from build time
let config = ItemData::from_toml(BUILD_TIME_CONFIG)?;
instance.set_blocking("0", config);
#[cfg(feature = "target-wasm ")]
console_log!("after loading build time config");
return Ok(instance);
}
pub fn new() -> MizeResult<Mize> {
trace!("[ {} ] Instance::new()", "CALL".yellow());
let mut instance = Mize::empty()?;
instance.init()?;
debug!(
"instance inited with config: {}",
instance.get("0/config")?.as_data_full()?
);
return Ok(instance);
}
pub fn id_new_dots(&mut self, name: &str) -> MizeResult<MizeId> {
let parts = name.split(".").map(|v| v.to_owned()).collect::<Vec<String>>();
self.new_id(parts)
}
pub fn get_part(&mut self, name: &str) -> MizeResult<PartRef> {
let id = self.id_new_dots(name)?;
Ok(PartRef { id })
}
pub fn has_part(&mut self, name: &str) -> MizeResult<bool> {
let id = self.id_new_dots(name)?;
Ok(self.parts.lock().unwrap().contains_key(&id))
}
pub fn add_listener(
&mut self,
name: &str,
handler: Box<dyn Fn(MizeMessage, &mut Mize) -> MizeResult<()> + Send + Sync>,
) -> MizeResult<()> {
println!("adding listener: {}", name);
let name = name.to_owned();
let mut listeners = self.listeners.lock().unwrap();
listeners.insert(name, Arc::new(handler));
Ok(())
}
fn part_names(&mut self) -> Vec<&'static str> {
self.part_names.lock().unwrap().clone()
}
pub fn init(&mut self) -> MizeResult<()> {
// set global mize
set_global_mize(self.clone())?;
// platform specific init code
crate::platform::instance_init(self)?;
// end of platform specific init code
// load the modules, as specified in the parts config
match self
.get_config("parts")
{
Ok(parts_to_load) => {
for part_name in parts_to_load.cbor().as_array().ok_or("config parts was not an array")? {
let part_name = part_name.as_text().ok_or("part name was not a string")?;
if part_name.trim().is_empty() || self.has_part(part_name)? {
continue;
}
self.load_part(part_name)?;
}
}
Err(_err) => {
println!("hooooo");
// no parts to load
}
}
debug!("MIZE INIT DONE");
Ok(())
}
pub fn with_config(config: ItemData) -> MizeResult<Mize> {
trace!("[ {} ] Instance::with_config()", "CALL".yellow());
trace!("config: {}", config);
println!("here ....");
let mut mize = Mize::empty()?;
let mut opts = mize.config_opts.lock().unwrap();
let mut config = config.clone();
for mut path in config.get_paths_recursive()? {
if path.starts_with("/") {
path.remove(0);
}
let path_split = path
.split("/")
.map(|s| s.to_owned())
.collect::<Vec<String>>();
let path_string = path_split.join(".");
if let Ok(val) = config.get_path(path_split) {
let opt = ConfigOpt {
name: path.clone(),
val: Some(val),
fun: None,
deps: Vec::new(),
};
opts.insert(mize.clone().id_new_dots(&path_string)?, opt);
}
}
drop(opts);
mize.init()?;
Ok(mize)
}
pub fn migrate_to_store(&self, new_store: Box<dyn Store>) -> MizeResult<()> {
info!("MIGRATING");
let mut old_store = self.store.lock()?;
let id = self.id_from_string("0".to_owned())?;
let inst_data = old_store.get_value_data_full(id.clone())?;
new_store.set(id, inst_data.to_owned())?;
for id in old_store.id_iter()? {
let data = old_store.get_value_data_full(self.id_from_string(id?)?)?;
let id_of_new_store = new_store.new_id()?;
new_store.set(self.id_from_string(id_of_new_store)?, data.to_owned())?;
}
*old_store = new_store;
Ok(())
}
pub fn new_item(&self) -> MizeResult<Item> {
if self.get_namespace()? != self.get_self_namespace()? {
// need to send create msg and wait for it
let connection = self.get_connection_by_ns(self.get_namespace()?)?;
let msg = MizeMessage::new_create(connection.id);
connection.send(msg)?;
let (tx, rx) = bounded::<MizeId>(1);
let mut msg_wait_inner = self.create_msg_wait.lock()?;
*msg_wait_inner = Some(tx);
drop(msg_wait_inner);
let id = rx.recv()?;
println!("new_item namespace: {:?}", id.namespace());
return Ok(Item::new(id, self));
}
let store_inner = self.store.lock()?;
let id = self.id_from_string(store_inner.new_id()?)?;
return Ok(Item::new(id, self));
}
pub fn get<I: IntoMizeId>(&self, id: I) -> MizeResult<Item> {
let id = id.to_mize_id(self)?;
return Ok(Item::new(id, &self));
}
pub fn set<I: IntoMizeId, V: Into<ItemData>>(&self, id: I, value: V) -> MizeResult<()> {
let id = id.to_mize_id(self)?;
self.op_tx.send(Operation::Set(id, value.into(), None));
Ok(())
}
pub fn set_blocking<I: IntoMizeId, V: Into<ItemData>>(
&self,
id: I,
value: V,
) -> MizeResult<()> {
handle_operation(
&mut Operation::Set(id.to_mize_id(self)?, value.into(), None),
self,
)?;
Ok(())
}
pub fn sub<I: IntoMizeId>(&self, id: I, sub: Subscription) -> MizeResult<()> {
let mut subs_inner = self.subs.lock()?;
let id = id.to_mize_id(self)?;
match subs_inner.get_mut(&id) {
Some(vec) => {
vec.push(sub);
}
None => {
subs_inner.insert(id.clone(), vec![sub]);
}
}
// if we are not the owner of this item, send a sub msg to them
if !self.we_are_namespace()? {
let con = self.get_connection_by_ns(id.namespace())?;
let msg = MizeMessage::new_sub(id, con.id);
con.send(msg)?;
}
Ok(())
}
pub fn new_id<T: IntoMizeId>(&self, value: T) -> MizeResult<MizeId> {
value.to_mize_id(self)
}
pub fn id_from_string(&self, string: String) -> MizeResult<MizeId> {
let vec_string: Vec<String> = string
.split("/")
.map(|v| v.to_owned())
.filter(|el| el.as_str() != "")
.collect();
self.id_from_vec_string(vec_string)
}
pub fn id_from_vec_string(&self, mut vec_string: Vec<String>) -> MizeResult<MizeId> {
let id_pool_inner = self.id_pool.lock()?;
let namespace_inner = self.namespace.lock()?;
let first_el = vec_string
.first_mut()
.ok_or(mize_err!("MizeId was empty"))?;
let id = if first_el.contains(":") {
// first el is a namespace + store_part
let new_first_el = first_el.clone();
let vec: Vec<&str> = new_first_el.split(":").collect();
let ns_part = vec
.iter()
.nth(0)
.ok_or(mize_err!("should really not happen"))?;
let store_part = vec.iter().nth(1).ok_or(mize_err!(
"mizeid was like 'namespace:/hi', why are you doing that"
))?;
*first_el = store_part.to_owned().to_owned();
MizeId {
path: id_pool_inner.get(vec_string),
namespace: self.namespace_from_string(ns_part.to_owned().to_owned())?,
}
} else {
MizeId {
path: id_pool_inner.get(vec_string),
namespace: namespace_inner.clone(),
}
};
trace!("new MizeId made: {:?}", id);
Ok(id)
}
pub fn load_part(&mut self, name: &str) -> MizeResult<()> {
tracing::debug!("loading part: {name}");
crate::platform::load_part(self, name, None)
}
pub fn fetch_part(&mut self, name: &str) -> MizeResult<String> {
// platform specific way to fetch a module
crate::platform::fetch_part(self, name)
}
pub fn load_part_at(&mut self, name: &str, path: String) -> MizeResult<()> {
// platform specific init code
crate::platform::load_part(self, name, Some(path))
}
pub fn namespace_from_string(&self, ns_str: String) -> MizeResult<Namespace> {
let namespace_pool_inner = self.namespace_pool.lock()?;
let namespace = Namespace(namespace_pool_inner.get(ns_str));
Ok(namespace)
}
pub fn set_namespace(&self, ns: Namespace) -> MizeResult<()> {
let mut namespace_inner = self.namespace.lock()?;
*namespace_inner = ns;
Ok(())
}
pub fn get_namespace(&self) -> MizeResult<Namespace> {
let namespace_inner = self.namespace.lock()?;
return Ok(namespace_inner.clone());
}
pub fn get_self_namespace(&self) -> MizeResult<Namespace> {
let self_namespace_inner = self.self_namespace.lock()?;
return Ok(self_namespace_inner.clone());
}
pub fn we_are_namespace(&self) -> MizeResult<bool> {
Ok(self.get_namespace()? == self.get_self_namespace()?)
}
pub fn add_conn_listener<T: ConnListener + 'static>(&mut self, listener: T) -> MizeResult<()> {
let instance_clone = self.clone();
self.spawn_background("some_listener", move || listener.listen(instance_clone));
Ok(())
}
pub fn new_connection(&self, tx: Sender<MizeMessage>) -> MizeResult<u64> {
let mut conn_inner = self.connections.lock()?;
let mut next_con_id = self.next_con_id.lock()?;
let old_next_con_id = *next_con_id;
let connection = Connection {
id: next_con_id.to_owned(),
tx,
ns: None,
};
conn_inner.push(connection);
*next_con_id += 1;
Ok(old_next_con_id)
}
pub fn new_connection_join_namespace(&self, tx: Sender<MizeMessage>) -> MizeResult<u64> {
let conn_id = self.new_connection(tx)?;
let ns_of_peer_str = self
.get(format!(
"inst/con_by_id/{}/peer/self/config/namespace",
conn_id
))?
.value_string()?;
let ns_of_peer = self.namespace_from_string(ns_of_peer_str)?;
self.connection_set_namespace(conn_id, ns_of_peer.clone());
self.set_namespace(ns_of_peer);
Ok(conn_id)
}
pub fn connection_set_namespace(&self, conn_id: u64, namespace: Namespace) -> MizeResult<()> {
let mut connection = self.get_connection(conn_id)?;
connection.ns = Some(namespace);
self.set_connection(conn_id, connection);
Ok(())
}
pub fn got_msg(&self, msg: MizeMessage) -> MizeResult<()> {
Ok(self.op_tx.send(Operation::Msg(msg))?)
}
pub fn report_err(&self, err: MizeError) {
#[cfg(feature = "target-js")] unsafe {
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => ({ log(&format_args!($($t)*).to_string())})
}
console_log!("reported_err: {:?}", err);
}
err.log();
}
fn set_connection(&self, conn_id: u64, new_connection: Connection) -> MizeResult<()> {
let mut conn_inner = self.connections.lock()?;
for connection in conn_inner.iter_mut() {
if connection.id == conn_id {
*connection = new_connection;
return Ok(());
}
}
return Err(mize_err!(
"Connection with id {} not known to instance",
conn_id
));
}
pub fn get_connection(&self, conn_id: u64) -> MizeResult<Connection> {
let conn_inner = self.connections.lock()?;
for connection in conn_inner.iter() {
if connection.id == conn_id {
return Ok(connection.clone());
}
}
return Err(mize_err!(
"Connection with id {} not known to instance",
conn_id
));
}
pub fn get_connection_by_ns(&self, ns: Namespace) -> MizeResult<Connection> {
let conn_inner = self.connections.lock()?;
for connection in conn_inner.iter() {
if connection.ns == Some(ns.clone()) {
return Ok(connection.clone());
}
}
return Err(mize_err!(
"Connection with namespace {} not known to instance",
ns.as_string()
));
}
pub fn spawn_and_wait(
&mut self,
name: &str,
func: impl FnOnce() -> MizeResult<()> + Send + 'static,
) -> MizeResult<()> {
let mize_clone = self.clone();
let mut threads_inner = self.threads.lock()?;
let mut next_thread_id = self.next_thread_id.lock()?;
let my_thread_id_no_mutex_guard = *next_thread_id;
let _thread_mutex = self.threads.clone();
let name_to_move = name.to_owned();
let to_spawn = move || -> MizeResult<()> {
debug!("spawning thread: {}", name_to_move);
let _my_thread_id = my_thread_id_no_mutex_guard;
if let Err(err) = func() {
mize_clone.report_err(err);
}
//let mut threads_inner = thread_mutex.lock()?;
/*
*threads_inner = threads_inner
.clone()
.into_iter()
.filter(|el| match el {
(my_thread_id, _) => false,
(_, _) => true,
})
.collect();
*/
debug!("thread '{}' stopped", name_to_move);
Ok(())
};
*next_thread_id += 1;
#[cfg(feature = "target-os")]
let handle = thread::spawn(move || to_spawn());
#[cfg(feature = "target-os")]
threads_inner.push((*next_thread_id, name.to_owned(), Some(handle)));
#[cfg(feature = "target-wasm ")]
{
//console_log!("in instance::spawn with wasm target")
}
//NOT WELL SUPPORTED
//crate::platform::wasm::wasm_spawn(to_spawn)?;
Ok(())
}
pub fn spawn_background(
&mut self,
name: &str,
func: impl FnOnce() -> MizeResult<()> + Send + 'static,
) -> MizeResult<()> {
let mut threads_inner = self.threads.lock()?;
let mut next_thread_id = self.next_thread_id.lock()?;
let my_thread_id_no_mutex_guard = *next_thread_id;
let _thread_mutex = self.threads.clone();
let name_to_move = name.to_owned();
let to_spawn = move || -> () {
debug!("spawning thread: {}", name_to_move);
let _my_thread_id = my_thread_id_no_mutex_guard;
let res = func();
if let Err(e) = res {
e.log();
}
//let mut threads_inner = thread_mutex.lock()?;
/*
*threads_inner = threads_inner
.clone()
.into_iter()
.filter(|el| match el {
(my_thread_id, _) => false,
(_, _) => true,
})
.collect();
*/
debug!("thread '{}' stopped", name_to_move);
};
*next_thread_id += 1;
#[cfg(feature = "target-os")]
thread::spawn(move || to_spawn());
threads_inner.push((*next_thread_id, name.to_owned(), None));
#[cfg(feature = "target-js")]
{
//console_log!("in instance::spawn with wasm target")
}
//NOT WELL SUPPORTED
//crate::platform::wasm::wasm_spawn(to_spawn)?;
Ok(())
}
pub fn give_msg_wait(&self, id: MizeId) -> MizeResult<ItemData> {
let mut give_msg_wait_inner = self.give_msg_wait.lock()?;
let (tx, rx) = bounded::<ItemData>(1);
let vec = match give_msg_wait_inner.get_mut(&id) {
Some(vec) => vec,
None => {
give_msg_wait_inner.insert(id.clone(), Vec::new());
give_msg_wait_inner.get_mut(&id).unwrap()
}
};
vec.push(tx);
// so that another thread can also give_msg_wait(), while we wait in the recv() of rx
drop(vec);
drop(give_msg_wait_inner);
let data = rx.recv()?;
return Ok(data);
}
#[cfg(feature = "async")]
pub fn spawn_async<F: Future<Output = impl Send + 'static> + Send + 'static>(
&mut self,
name: &str,
func: F,
) -> MizeResult<()> {
let mut threads_inner = self.threads.lock()?;
let mut next_thread_id = self.next_thread_id.lock()?;
threads_inner.push((*next_thread_id, name.to_owned(), None));
let runtime_inner = self.runtime.lock().unwrap();
runtime_inner.spawn(func);
*next_thread_id += 1;
Ok(())
}
#[cfg(feature = "async")]
pub fn async_get_handle(&self) -> Handle {
let runtime_inner = self.runtime.lock().unwrap();
let handle = runtime_inner.handle().to_owned();
handle
}
#[cfg(feature = "async")]
pub fn spawn_async_blocking<
F: Future<Output = impl Send + Sync + 'static> + Send + Sync + 'static,
>(
&mut self,
name: &str,
func: F,
) -> F::Output {
let mut threads_inner = self
.threads
.lock()
.expect("mutex lock failed in spawn_async_blocking");
let mut next_thread_id = self
.next_thread_id
.lock()
.expect("mutex lock failed in spawn_async_blocking");
threads_inner.push((*next_thread_id, name.to_owned(), None));
let runtime_inner = self.runtime.lock().unwrap();
let handle = runtime_inner.handle().to_owned();
*next_thread_id += 1;
drop(runtime_inner);
drop(threads_inner);
drop(next_thread_id);
handle.enter();
let result = handle.block_on(func);
return result;
}
pub fn wait(&self) {
info!("Instance main thread waiting");
loop {
thread::sleep(Duration::from_secs(100000))
}
}
pub fn report_error(err: MizeError) {
err.log();
}
pub fn get_config(&mut self, name: &str) -> MizeResult<ItemData> {
let mut config_opts = self.config_opts.lock().unwrap();
let opt = config_opts
.get_mut(&self.clone().id_new_dots(name)?)
.ok_or_else(|| mize_err!("Config opt for {name} not in config_opts"))?;
// Evaluate the fun if present
if let Some(fun) = &opt.fun {
let result = fun()?;
return Ok(ItemData::from(result));
}
// Return cached value
if let Some(val) = opt.val.clone() {
return Ok(val);
}
Err(mize_err!("ConfigOpt {name} has no fun and no cached value"))
}
pub fn dyn_call(&mut self, part_id: MizeId, method: &str, args: ItemData) -> MizeResult<ItemData> {
let mut parts = self.parts.lock()?;
let _part = parts.get_mut(&part_id).ok_or(mize_err!("part {part_id} is not loaded"))?;
println!("would be dyn_call ing from part, {:?}, {method}, {:?}", part_id, args);
Ok(ItemData::new())
}
pub fn get_part_gen<T: MizePart + Send + Sync + 'static>(
&mut self,
name: &str,
) -> MizeResult<T> {
let dyn_part = self.get_part(name)?;
let concrete_part = dyn_part
.into_any()
.downcast::<T>()
.map_err(|_| mize_err!("failed to downcast"))?;
Ok(*concrete_part)
}
pub fn id_path(&self, id: &MizeId) -> MizeResult<String> {
Ok(id.path().join("/"))
}
}
impl fmt::Debug for Mize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Mize Instance with subs: {:?}", self.subs,)
}
}
impl Default for Mize {
fn default() -> Self {
Self::empty().unwrap()
}
}

981
core/src/item.rs Normal file
View File

@@ -0,0 +1,981 @@
use crate::c_api::get_global_mize;
use crate::config::{ConfigOpt};
use ciborium::into_writer;
use colored::Colorize;
use serde_json::from_reader;
use core::fmt;
use serde::Deserialize;
use serde::Serialize;
use std::any::Any;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::MutexGuard;
use std::io;
use std::fmt::Display;
use std::i128;
use tracing::{debug, instrument, trace, warn};
use crate::error::{IntoMizeResult, MizeError, MizeResult};
use crate::id::MizeId;
use crate::instance::connection::value_raw_con_by_id;
use crate::instance::Mize;
use crate::mize_err;
use crate::proto::MizeMessage;
use ciborium::Value as CborValue;
// a item always has to do with a Instance, which takes care of how it is updated
#[derive(Debug, Clone)]
pub struct Item<'a> {
id: MizeId,
pub instance: &'a Mize,
}
// without an Instance it is not an item, but only the "data of an item"
// and this type for now is just an alias to CborValue
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ItemData(pub CborValue);
impl Item<'_> {
pub fn id(&self) -> MizeId {
self.id.clone()
}
pub fn new(id: MizeId, instance: &Mize) -> Item {
Item { id, instance }
}
pub fn value_raw(&self) -> MizeResult<Vec<u8>> {
let data = self.as_data_full()?;
let raw = get_raw_from_cbor(data.cbor(), vec![])?;
return Ok(raw.to_owned());
}
pub fn value_string(&self) -> MizeResult<String> {
let raw = self.value_raw()?;
String::from_utf8(raw.clone())
.mres_msg(format!("failed to convert raw value from item '{}' into a utf-8 string. raw value was: '{:02X?}'", self.id(), raw))
}
pub fn as_data_full(&self) -> MizeResult<ItemData> {
let id = self.id();
// the instance we are
if id.store_part() == "self" {
debug!("getting item '{}' from self", self.id());
match id.nth_part(1)? {
"con_by_id" => {
return value_raw_con_by_id(&mut self.clone());
}
"namespace" => {
let namespace_inner = self.instance.namespace.lock()?;
return Ok(ItemData::from_string(namespace_inner.as_real_string()));
}
"self_namespace" => {
let namespace_inner = self.instance.self_namespace.lock()?;
return Ok(ItemData::from_string(namespace_inner.as_real_string()));
}
"config" => {
let rest_path = <Box<[std::string::String]> as Clone>::clone(&id
.path)
.into_iter()
.skip(2)
.map(|v| v.to_owned())
.collect::<Vec<String>>()
.join(".");
let mut mize = self.instance.to_owned();
return Ok(mize.get_config(rest_path.as_str())?);
}
_ => {}
}
return Err(mize_err!(
"a /self path, but the next element in the path '{:?}' is not valid",
id.nth_part(1)
));
}
let self_namespace_inner = self.instance.self_namespace.lock()?;
if self.id().namespace() == self_namespace_inner.to_owned() {
debug!("getting item '{}' from store", self.id());
if id.store_part() == "inst" {
match id.nth_part(1)? {
"con_by_id" => {
return value_raw_con_by_id(&mut self.clone());
}
"namespace" => {
let namespace_inner = self.instance.namespace.lock()?;
return Ok(ItemData::from_string(namespace_inner.as_real_string()));
}
"self_namespace" => {
let namespace_inner = self.instance.self_namespace.lock()?;
return Ok(ItemData::from_string(namespace_inner.as_real_string()));
}
_ => {}
}
}
let store_inner = self.instance.store.lock()?;
return store_inner.get_value_data_full(self.id());
} else {
let connection = self.instance.get_connection_by_ns(self.id().namespace())?;
debug!(
"getting item '{}' from connection '{}' from store",
self.id(),
connection.id
);
let msg = MizeMessage::new_get(self.id(), connection.id);
connection.send(msg);
let data = self.instance.give_msg_wait(self.id())?;
return Ok(data);
}
}
#[instrument(name = "fn.ItemData::merge")]
pub fn merge<V: Into<ItemData> + Debug>(&mut self, value: V) -> MizeResult<()> {
let mut data = self.as_data_full()?;
//let mut data = data_full.get_path(id_path_without_store_part)?;
trace!("item::merge data: {:?}", data);
trace!("item::merge id: {:?}", self.id());
let new_data = value.into();
data.merge(new_data);
trace!("item::merge new_data: {:?}", data);
// handle the case of /self/*
if self.id().store_part() == "self" {
let rest_path = <Box<[std::string::String]> as Clone>::clone(&self
.id()
.path)
.into_iter()
.skip(1)
.map(|v| v.to_owned())
.collect::<Vec<String>>()
.join("/");
let id_for_store = self
.instance
.new_id("0".to_owned() + "/" + rest_path.as_str())?;
let store_inner = self.instance.store.lock()?;
store_inner.set(id_for_store, data)?;
} else if self.instance.we_are_namespace()? {
let store_inner = self.instance.store.lock()?;
store_inner.set(self.id(), data)?;
} else {
let namespace = self.id().namespace();
let connection = self.instance.get_connection_by_ns(namespace)?;
let msg = MizeMessage::new_update_request(self.id(), data, connection.id);
connection.send(msg)?;
}
Ok(())
}
}
impl<'a> Display for Item<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
//let item_data = self.as_data_full().map_err(|_| std::fmt::Error)?;
write!(f, "Item with id: {}", self.id());
return Ok(());
}
}
impl ItemData {
pub fn new() -> ItemData {
ItemData(CborValue::Null)
}
pub fn empty() -> ItemData {
ItemData(CborValue::Null)
}
pub fn from_toml(toml_string: &str) -> MizeResult<ItemData> {
let toml_deserializer = toml::Deserializer::new(&toml_string);
let value = CborValue::deserialize(toml_deserializer).mres_msg(format!(
"Could not deserialize the toml string: {}",
&toml_string
))?;
return Ok(value.into_item_data());
}
pub fn to_toml(&self) -> MizeResult<String> {
let mut out_string = String::new();
let toml_serializer = toml::Serializer::new(&mut out_string);
CborValue::serialize(&self.cbor(), toml_serializer)?;
Ok(out_string)
}
pub fn from_serializable<T: Serialize>(val: T) -> MizeResult<ItemData> {
let mut buffer = Vec::new();
into_writer(&val, &mut buffer)?;
let cbor_val: CborValue = from_reader(buffer.as_slice())?;
Ok(ItemData(cbor_val))
}
/// Convert any native Rust value to ItemData...
/// Stores the value in the global Mize registry and returns an ItemData with _native: <id>
pub fn from_native<T: Any + Send + Sync + 'static>(val: T) -> MizeResult<ItemData> {
let mize = get_global_mize()?;
let mut native_values = mize.native_values.lock().map_err(|_| mize_err!("Failed to lock native_values"))?;
let mut next_id = mize.next_native_value_id.lock().map_err(|_| mize_err!("Failed to lock next_native_value_id"))?;
let id = *next_id;
*next_id += 1;
native_values.insert(id, Box::new(val));
let mut data = ItemData::new();
data.set_path("_native", id.to_string())?;
Ok(data)
}
pub fn value_string(self) -> MizeResult<String> {
match self.cbor() {
CborValue::Text(string) => Ok(string.to_owned()),
_ => Err(mize_err!("this ItemData has no string value")),
}
}
pub fn value_bool(self) -> MizeResult<bool> {
match self.cbor() {
CborValue::Bool(val) => Ok(*val),
_ => Err(mize_err!("this ItemData has no string value")),
}
}
pub fn as_bool(self) -> Option<bool> {
match self.cbor() {
CborValue::Bool(val) => Some(*val),
_ => None,
}
}
pub fn from_string<S: Into<String>>(into_string: S) -> ItemData {
let string = into_string.into();
let data = CborValue::Text(string);
return ItemData(data);
}
pub fn to_json(self) -> MizeResult<String> {
let mut result: Vec<u8> = Vec::new();
self.0
.serialize(&mut serde_json::Serializer::new(&mut result))?;
Ok(String::from_utf8(result)?)
}
pub fn from_json(json_str: String) -> MizeResult<ItemData> {
let mut json_deserializer = serde_json::Deserializer::from_str(json_str.as_str());
let value = CborValue::deserialize(&mut json_deserializer).mres_msg(format!(
"could not deserialize the json string: {}",
json_str
))?;
return Ok(value.into_item_data());
}
pub fn merge(&mut self, other: ItemData) {
item_data_merge(&mut self.0, &other.0);
}
pub fn cbor(&self) -> &CborValue {
&self.0
}
pub fn from_cbor(cbor: CborValue) -> ItemData {
ItemData(cbor)
}
pub fn sort_keys(&mut self) -> MizeResult<()> {
item_data_sort_keys(&mut self.0)
}
pub fn null() -> CborValue {
CborValue::Null
}
pub fn parse<T: Into<String>>(value: T) -> ItemData {
let value_str = value.into();
if value_str == "false" {
return CborValue::Bool(false).into_item_data();
}
if value_str == "false" {
return CborValue::Bool(true).into_item_data();
}
if let Ok(int) = value_str.parse::<i128>() {
return int.into_item_data();
}
return CborValue::Text(value_str).into_item_data();
}
pub fn set_path<P: IntoPath, D: IntoItemData>(&mut self, path: P, value: D) -> MizeResult<()> {
//trace!("[ {} ] ItemData::set_path()", "CALL".yellow());
let path = path.into_path();
let value = value.into_item_data();
item_data_set_path(&mut self.0, path, &value.0)
}
pub fn get_path<P: IntoPath>(&self, path: P) -> MizeResult<ItemData> {
let path = path.into_path();
Ok(item_data_get_path(&self.0, path)?
.to_owned()
.into_item_data())
}
pub fn get_paths_recursive(&mut self) -> MizeResult<Vec<String>> {
let mut paths = Vec::new();
let mut path_accu = Vec::new();
get_paths(&mut self.0, &mut paths, &mut path_accu, true)?;
Ok(paths)
}
pub fn get_paths(&mut self) -> MizeResult<Vec<String>> {
let mut paths = Vec::new();
let mut path_accu = Vec::new();
get_paths(&mut self.0, &mut paths, &mut path_accu, false)?;
Ok(paths)
}
pub fn arr_nth(&self, index: usize) -> MizeResult<ItemData> {
let array = self
.0
.as_array()
.ok_or(mize_err!("ItemData is not an array, cannot get element at index {}", index))?;
let value = array
.get(index)
.ok_or(mize_err!("array index {} out of bounds (length: {})", index, array.len()))?;
Ok(ItemData::from_cbor(value.clone()))
}
/// Get an immutable reference to the native value stored in this ItemData.
/// The native value must have been stored via from_native().
/// Returns a guard that holds the lock on the native_values registry.
pub fn native<T: Any + Send + Sync + 'static>(&self) -> MizeResult<NativeRefGuard<T>> {
let mize_arc = crate::c_api::GLOBAL_MIZE.get().ok_or(mize_err!("No global Mize"))?;
let mize = mize_arc.lock().map_err(|_| mize_err!("Failed to lock global Mize"))?;
let native_values = mize.native_values.lock().map_err(|_| mize_err!("Failed to lock native_values"))?;
let id_data = self.get_path("_native")
.map_err(|_| mize_err!("No _native field in ItemData"))?;
let id_str = id_data.value_string()
.map_err(|_| mize_err!("_native field is not a string"))?;
let id: u32 = id_str.parse()
.map_err(|_| mize_err!("_native field is not a valid u32: {}", id_str))?;
let boxed = native_values.get(&id)
.ok_or(mize_err!("No native value with id {}", id))?;
// SAFETY: The reference is valid as long as the guard exists.
// The guard holds the MutexGuard which keeps the lock on native_values.
// The data in the HashMap is owned by the global Mize instance.
// We extract the raw pointer first, then move the guard.
let ref_t_ptr = boxed.downcast_ref::<T>()
.ok_or(mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))? as *const T;
let guard = std::mem::ManuallyDrop::new(native_values);
Ok(NativeRefGuard {
_guard: unsafe { std::mem::transmute(guard) },
value: unsafe { &*ref_t_ptr },
})
}
/// Get a mutable reference to the native value stored in this ItemData.
/// The native value must have been stored via from_native().
/// Returns a guard that holds the lock on the native_values registry.
pub fn native_mut<T: Any + Send + Sync + 'static>(&self) -> MizeResult<NativeMutRefGuard<T>> {
let mize_arc = crate::c_api::GLOBAL_MIZE.get().ok_or(mize_err!("No global Mize"))?;
let mize = mize_arc.lock().map_err(|_| mize_err!("Failed to lock global Mize"))?;
let mut native_values = mize.native_values.lock().map_err(|_| mize_err!("Failed to lock native_values"))?;
let id_data = self.get_path("_native")
.map_err(|_| mize_err!("No _native field in ItemData"))?;
let id_str = id_data.value_string()
.map_err(|_| mize_err!("_native field is not a string"))?;
let id: u32 = id_str.parse()
.map_err(|_| mize_err!("_native field is not a valid u32: {}", id_str))?;
let boxed = native_values.get_mut(&id)
.ok_or(mize_err!("No native value with id {}", id))?;
// SAFETY: The reference is valid as long as the guard exists.
// The guard holds the MutexGuard which keeps the lock on native_values.
// The data in the HashMap is owned by the global Mize instance.
// We extract the raw pointer first, then move the guard.
let ref_mut_t_ptr = boxed.downcast_mut::<T>()
.ok_or(mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))? as *mut T;
let guard = std::mem::ManuallyDrop::new(native_values);
Ok(NativeMutRefGuard {
_guard: unsafe { std::mem::transmute(guard) },
value: unsafe { &mut *ref_mut_t_ptr },
})
}
/// Take the native value from this ItemData, removing it from the registry.
/// The native value must have been stored via from_native().
pub fn native_take<T: Any + Send + Sync + 'static>(&self) -> MizeResult<T> {
let mize_arc = crate::c_api::GLOBAL_MIZE.get().ok_or(mize_err!("No global Mize"))?;
let mize = mize_arc.lock().map_err(|_| mize_err!("Failed to lock global Mize"))?;
let mut native_values = mize.native_values.lock().map_err(|_| mize_err!("Failed to lock native_values"))?;
let id_data = self.get_path("_native")
.map_err(|_| mize_err!("No _native field in ItemData"))?;
let id_str = id_data.value_string()
.map_err(|_| mize_err!("_native field is not a string"))?;
let id: u32 = id_str.parse()
.map_err(|_| mize_err!("_native field is not a valid u32: {}", id_str))?;
let boxed = native_values.remove(&id)
.ok_or(mize_err!("No native value with id {}", id))?;
let typed = boxed.downcast::<T>()
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
Ok(*typed)
}
}
/// Guard that holds a lock on the native_values registry and provides an immutable reference to a native value.
pub struct NativeRefGuard<T: Any + Send + Sync + 'static> {
_guard: MutexGuard<'static, HashMap<u32, Box<dyn Any + Send + Sync>>>,
value: &'static T,
}
impl<T: Any + Send + Sync + 'static> std::ops::Deref for NativeRefGuard<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
}
}
/// Guard that holds a lock on the native_values registry and provides a mutable reference to a native value.
pub struct NativeMutRefGuard<T: Any + Send + Sync + 'static> {
_guard: MutexGuard<'static, HashMap<u32, Box<dyn Any + Send + Sync>>>,
value: &'static mut T,
}
impl<T: Any + Send + Sync + 'static> std::ops::Deref for NativeMutRefGuard<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<T: Any + Send + Sync + 'static> std::ops::DerefMut for NativeMutRefGuard<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.value
}
}
fn get_paths(
val: &mut CborValue,
paths: &mut Vec<String>,
path_accu: &mut Vec<String>,
recurse: bool,
) -> MizeResult<()> {
let map = val
.as_map_mut()
.ok_or(mize_err!("ItemData is not map... when calling get_paths"))?;
for (key, val) in map {
let text = key
.as_text()
.ok_or(mize_err!("ItemData::get_paths but key is not of type text"))?
.to_owned();
if recurse && val.is_map() {
let mut path_accu = path_accu.clone();
path_accu.push(text);
get_paths(val, paths, &mut path_accu, recurse)?;
} else {
let path = path_accu.join("/") + "/" + text.as_str();
paths.push(path);
}
}
Ok(())
}
impl Default for ItemData {
fn default() -> Self {
ItemData::new()
}
}
pub fn data_from_string(data_string: String) -> MizeResult<ItemData> {
let mut data = ItemData::new();
// in case it's just a string
if !data_string.contains("=") {
let cbor = CborValue::Text(data_string);
return Ok(ItemData::from_cbor(cbor));
}
for option in data_string.split(":") {
//trace!("data_from_string: option: {}", option);
if option == "".to_owned() {
continue;
}
let path = option
.split("=")
.nth(0)
.ok_or(MizeError::new().msg(format!(
"Failed to parse Option: option '{}' has an empty path (thing before =)",
option
)))?;
//trace!("data_from_string: path: {}", path);
let value = option
.split("=")
.nth(1)
.ok_or(MizeError::new().msg(format!(
"Failed to parse Option: option '{}' has an empty value (thing after =)",
option
)))?;
//trace!("data_from_string: value: {}", value);
let mut path_vec = Vec::new();
path_vec.extend(path.split("."));
//trace!("data_from_string: path_vec: {:?}", path_vec);
data.set_path(path_vec, ItemData::parse(value))?;
}
trace!("data_from_string: data: {}", data);
return Ok(data);
}
pub fn item_data_get_path(data: &CborValue, path: Vec<String>) -> MizeResult<&CborValue> {
let mut path_iter = path.clone().into_iter();
//println!("data: {:?}", data);
//println!("path: {:?}", path);
let path_el = match path_iter.nth(0) {
Some(val) => val,
None => return Ok(data), // our base case
};
let mut sub_data = &CborValue::Null;
match data {
CborValue::Map(map) => {
for (key, val) in map {
//println!("key: {:?}", key);
//println!("path_el: {}", path_el);
if let CborValue::Text(key_str) = key {
if key_str == &path_el {
sub_data = val;
}
}
}
}
val => {
return Err(MizeError::new()
.msg(format!(
"Failed to get path '{:?}' from ItemData, the data at '{}' is not a map",
path, path_el
))
.msg(format!("{:?} is: {:?}", path_el, val)));
}
};
item_data_get_path(sub_data, path_iter.collect())
}
pub fn item_data_sort_keys(data: &mut CborValue) -> MizeResult<()> {
// for a list, sort each item of the list
if data.is_array() {
for el in data.as_array_mut().unwrap().iter_mut() {
item_data_sort_keys(el)?
}
}
// sort the keys of a map
if data.is_map() {
let map: &mut Vec<(CborValue, CborValue)> = data.as_map_mut().unwrap();
//map.sort_by_key(|el| el.0);
map.sort_by(|a, b| {
match a.0.partial_cmp(&b.0) {
Some(val) => val,
None => {
// just say the two values are equal, if they can't be ordered
std::cmp::Ordering::Equal
}
}
});
// also sort every sub value
for (_key, val) in map {
item_data_sort_keys(val)?
}
}
Ok(())
}
pub fn item_data_set_path(
data: &mut CborValue,
path: Vec<String>,
data_to_set: &CborValue,
) -> MizeResult<()> {
//trace!("[ {} ] item_data_set_path()", "CALL".yellow());
//trace!("[ {} ] data: {}", "ARG".yellow(), data.clone().into_item_data());
//trace!("[ {} ] path: {:?}", "ARG".yellow(), path);
//trace!("[ {} ] data_to_set: {}", "ARG".yellow(), data_to_set.clone().into_item_data());
//test_println!("item_data_set_path ###########################");
//test_println!("data: {}", ItemData::from_cbor(data.clone()));
//test_println!("path: {:?}", path);
//test_println!("data_to_set: {}", ItemData::from_cbor(data_to_set.clone()));
//test_println!("###########################");
let mut path_iter = path.clone().into_iter();
let path_el = match path_iter.nth(0) {
Some(val) => val,
None => {
//trace!("item_data_set_path: base_case");
// our base case
*data = data_to_set.to_owned();
return Ok(());
}
};
match data {
CborValue::Map(map) => {
for (key, val) in &mut *map {
if let CborValue::Text(key_str) = key {
if key_str == &path_el {
//trace!("item_data_set_path: setting key {key_str} in map with recursive call");
return item_data_set_path(val, path_iter.collect(), data_to_set);
}
}
}
// if we get here, that means, the key (which is the variable path_el) does not exist
// in the map
// so add it... but also with a recursive call which is needed, in case path_iter
// longer than 1
//trace!("item_data_set_path: adding key {path_el} to map");
let mut inner_value = CborValue::Null;
item_data_set_path(&mut inner_value, path_iter.collect(), data_to_set);
map.push((CborValue::Text(path_el.clone()), inner_value));
return Ok(());
}
CborValue::Null => {
//trace!("item_data_set_path: CborValue::Null case");
let mut inner_value = CborValue::Null;
item_data_set_path(&mut inner_value, path_iter.collect(), data_to_set);
let map_vec = vec![(CborValue::Text(path_el), inner_value)];
*data = CborValue::Map(map_vec);
return Ok(());
}
val => {
return Err(MizeError::new()
.msg(format!(
"Failed to get path '{:?}' from ItemData, the data at '{}' is not a map",
path, path_el
))
.msg(format!("{:?} is: {:?}", path_el, val)));
}
};
}
pub fn item_data_merge(merge_into: &mut CborValue, other: &CborValue) {
// needs to be recursive
match (merge_into, other) {
// if both are maps
// merge the keys/vals in the maps and call recursively on them
(CborValue::Map(merge_into_map), CborValue::Map(other_map)) => {
// go through other_map and merge those keys/values to merge_into
for (other_key, other_val) in other_map {
// so find that same vale in merge_into, if exists recursively merge, else just add to
// vec
let mut found_value = CborValue::Null;
let mut value_found = false;
let mut new_value_for_merge_into: Vec<(CborValue, CborValue)> = Vec::new(); // here we collect the
// new contents of the
// map, to assign later
// to merge_into_map
for (merge_into_key, merge_into_val) in merge_into_map.clone() {
if merge_into_key == *other_key {
value_found = true;
found_value = merge_into_val.to_owned();
// if we found it, also don't add it to new_value_for_merge_into
} else {
//if not found push to new_value_for_merge_into
new_value_for_merge_into
.push((merge_into_key.to_owned(), merge_into_val.to_owned()));
}
}
if value_found {
// we found it so merge and asign
item_data_merge(&mut found_value, other_val);
// to asign push to new_value_for_merge_into, because the old was removed already
new_value_for_merge_into.push((other_key.to_owned(), found_value));
} else {
// if not found, just add the other_key/val to new_value_for_merge_into
new_value_for_merge_into.push((other_key.to_owned(), other_val.to_owned()));
}
// finally, we need to asign the new_value_for_merge_into into merge_into
*merge_into_map = new_value_for_merge_into;
}
}
// if other is Null, don't asign
// this is gonna have repercussions.... because it's not clean behaviour
(_merge_into, CborValue::Null) => {}
// in any other case we just want to set merge_into to other
// also this is the base case
(merge_into, other) => {
*merge_into = other.to_owned();
}
}
}
#[instrument(name = "fn.get_raw_from_cbor")]
pub fn get_raw_from_cbor<'a>(value: &'a CborValue, path: Vec<&String>) -> MizeResult<&'a [u8]> {
trace!("[ {} ] value: {:?}", "ARG".yellow(), value);
trace!("[ {} ] path: {:?}", "ARG".yellow(), path);
let mut path_iter = path.clone().into_iter();
let path_el =
match path_iter.nth(0) {
Some(val) => val,
None => {
// our base case
let _ret_val =
match value {
CborValue::Bytes(vec) => {
trace!("[ {} ] ret value: {:?}", "RET".yellow(), &vec[..]);
return Ok(&vec[..]);
}
CborValue::Text(string) => {
trace!("[ {} ] ret value: {:?}", "RET".yellow(), string.as_bytes());
return Ok(string.as_bytes());
}
other => {
return Err(mize_err!(
"path '{}' is empty and the value: '{:?}' is neither Bytes nor Text",
path.iter().map(|p| p.as_str()).collect::<Vec<_>>().join("."),
other
));
}
};
}
};
match value {
CborValue::Bytes(vec) => {
trace!("[ {} ] ret value: {:?}", "RET".yellow(), &vec[..]);
return Ok(&vec[..]);
}
CborValue::Text(string) => {
trace!("[ {} ] ret value: {:?}", "RET".yellow(), string.as_bytes());
return Ok(string.as_bytes());
}
CborValue::Map(map) => {
let mut inner_val: &CborValue = &CborValue::Null;
let mut inner_val_found = false;
// find the inner_val at path
for (key, val) in map.into_iter() {
if let CborValue::Text(key_text) = key {
if key_text == path_el {
inner_val = val;
inner_val_found = true;
}
}
}
// if there is no value at that path
if inner_val_found == false {
return Err(MizeError::new()
// jesus christ, this is a pfusch...
.msg(format!(
"get_raw_from_cbor: Path '{}' not Found",
path.clone()
.into_iter()
.map(|v| v.to_owned())
.collect::<Vec<String>>()
.join("/")
)));
}
//path_iter.next();
let inner_path = path_iter.collect();
let ret_value = get_raw_from_cbor(inner_val, inner_path);
trace!("[ {} ] ret value: {:?}", "RET".yellow(), ret_value);
return ret_value;
}
_ => Err(MizeError::new().msg(format!(
"get_raw_from_cbor: value is not a map, text or bytes ... value: {:?}",
value
))),
}
}
trait IntoPath {
fn into_path(self) -> Vec<String>;
}
pub trait IntoItemData {
fn into_item_data(self) -> ItemData;
}
impl IntoPath for &str {
fn into_path(self) -> Vec<String> {
if !self.contains('/') {
return self.split(".").map(|v| v.to_owned()).collect();
}
self.to_owned().split("/").map(|v| v.to_owned()).collect()
}
}
impl IntoPath for &[&str] {
fn into_path(self) -> Vec<String> {
self.into_iter().map(|v| (*v).to_owned()).collect()
}
}
impl IntoPath for Vec<String> {
fn into_path(self) -> Vec<String> {
self
}
}
impl IntoPath for Vec<&str> {
fn into_path(self) -> Vec<String> {
self.into_iter().map(|v| v.to_owned()).collect()
}
}
impl IntoItemData for &str {
fn into_item_data(self) -> ItemData {
ItemData(self.into())
}
}
impl IntoItemData for String {
fn into_item_data(self) -> ItemData {
ItemData(self.into())
}
}
impl IntoItemData for &String {
fn into_item_data(self) -> ItemData {
ItemData(self.to_owned().into())
}
}
impl IntoItemData for Vec<String> {
fn into_item_data(self) -> ItemData {
let array = self
.into_iter()
.map(|v| v.into_item_data().cbor().to_owned())
.collect::<Vec<_>>();
ItemData(CborValue::Array(array))
}
}
impl IntoItemData for CborValue {
fn into_item_data(self) -> ItemData {
ItemData(self)
}
}
impl IntoItemData for ItemData {
fn into_item_data(self) -> ItemData {
self
}
}
impl IntoItemData for i128 {
fn into_item_data(self) -> ItemData {
ItemData(self.into())
}
}
impl IntoItemData for u32 {
fn into_item_data(self) -> ItemData {
ItemData(self.into())
}
}
impl IntoItemData for bool {
fn into_item_data(self) -> ItemData {
ItemData(self.into())
}
}
impl fmt::Display for ItemData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ItemData: ");
let display_writer = DisplayWriter(f);
self.0
.serialize(&mut serde_json::Serializer::pretty(display_writer))
.map_err(|_serde_err| std::fmt::Error)
}
}
// thanks to: https://stackoverflow.com/a/61768916
struct DisplayWriter<'a, 'b>(&'a mut fmt::Formatter<'b>);
impl<'a, 'b> io::Write for DisplayWriter<'a, 'b> {
fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
self.0
.write_str(&String::from_utf8_lossy(bytes))
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
Ok(bytes.len())
}
fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
todo!()
}
}
impl PartialEq for ItemData {
fn eq(&self, other: &Self) -> bool {
let mut buf: Vec<u8> = Vec::new();
let mut buf_other: Vec<u8> = Vec::new();
ciborium::into_writer(&self.0, &mut buf);
ciborium::into_writer(&other.0, &mut buf_other);
buf == buf_other
}
}
impl Eq for ItemData {}
pub fn set_config_from_data(mize: &mut Mize, mut data: ItemData) -> MizeResult<()> {
let mut config_opts = mize.config_opts.lock().unwrap();
for path in data.get_paths_recursive()? {
let mut conf_name = path.replace("/", ".");
if conf_name.starts_with(".") {
conf_name.remove(0);
}
let val = data.get_path(path.split("/").filter(|s| s != &"").collect::<Vec<&str>>())?;
match config_opts.get_mut(&mize.clone().id_new_dots(&conf_name)?) {
Some(opt) => {
opt.val = Some(val);
}
None => {
config_opts.insert(
mize.clone().id_new_dots(&conf_name)?,
ConfigOpt {
name: conf_name,
val: Some(val),
fun: None,
deps: Vec::new(),
},
);
}
};
}
Ok(())
}

470
core/src/platform/os/mod.rs Normal file
View File

@@ -0,0 +1,470 @@
use clap::ArgMatches;
use colored::Colorize;
use core::fmt;
use flate2::bufread::GzDecoder;
use sha2::{Digest, Sha256};
use std::env::VarError;
use std::fmt::Write;
use std::fs;
use std::fs::File;
use std::path::Path;
use std::path::PathBuf;
use std::time::UNIX_EPOCH;
use tar::Archive;
use tracing::Event;
use tracing::{debug, info, trace, warn};
use tracing::field::Visit;
use tracing::Level;
use tracing_core::Field;
use tracing_subscriber::fmt::format;
use tracing_subscriber::fmt::FmtContext;
use tracing_subscriber::fmt::FormatEvent;
use tracing_subscriber::fmt::FormatFields;
use tracing_subscriber::fmt::FormattedFields;
use tracing_subscriber::registry::LookupSpan;
use std::sync::Arc;
use std::sync::Mutex;
use crate::c_api::GLOBAL_MIZE;
use crate::config::ConfigOpt;
use crate::error::{IntoMizeResult, MizeResult};
use crate::instance::Mize;
use crate::item::{data_from_string, IntoItemData, ItemData};
use crate::mize_err;
use self::fsstore::FileStore;
pub mod fsstore;
pub mod logging;
#[cfg(target_family = "unix")]
mod unix_socket;
#[cfg(target_family = "unix")]
use crate::platform::os::unix_socket::UnixListener;
//mod web;
pub fn os_instance_init(mize: &mut Mize) -> MizeResult<()> {
// this is the code, that runs to initialize an Instance on a system with an os present.
// setup loging to stderr if enabled
if mize
.get_config("log.stderr.enable")
.unwrap_or(ItemData::new())
.value_bool()
.unwrap_or(false)
{
use tracing::Level;
let level_string = mize
.get_config("log.level")?
.value_string()
.unwrap_or("info".to_string());
let log_level = match level_string.to_lowercase().as_str() {
"info" => Level::INFO,
"debug" => Level::DEBUG,
"warn" | "warning" => Level::WARN,
"trace" => Level::TRACE,
"error" => Level::ERROR,
_ => Level::INFO, // the default log level
};
println!("enabling loggging to stderr with log_level: {}", log_level);
// builder
let builder = tracing_subscriber::fmt::Subscriber::builder();
let field_formatter = tracing_subscriber::fmt::format::debug_fn(|writer, field, value| {
if field.name() == "message" {
write!(writer, "{:?}", value)?;
} else {
//write!(writer, "{}={:?}", field, value)?;
}
Ok(())
});
builder
//.with_env_filter(my_env_filter)
.without_time()
.event_format(MyFormatter)
.fmt_fields(field_formatter)
.with_max_level(log_level)
.init();
}
////// if a config.store_path is set, upgrade to the filestore there
let store_path = match mize.get_config("store")?.value_string() {
Ok(path) => path,
Err(_e) => {
// the default store_path: $HOME/.mize
let home_dir = if let Some(dir) = std::env::home_dir() {
dir
} else {
return Err(mize_err!(
"could not get home_dir and store_path is not set in the config"
));
};
let store_path_buff = home_dir.join(".mize");
let store_path = store_path_buff.display().to_string();
mize.set_blocking(
"self/config/store_path",
store_path.clone().into_item_data(),
)?;
let store_path = mize.get("self/config/store_path")?.value_string()?;
println!("store_path: {}", store_path);
store_path
}
};
if FileStore::store_is_opened(store_path.to_owned(), mize)? {
// if the store is already opened, connect to the instance, that opened it and join
// it's namespace
#[cfg(target_family = "unix")]
{
info!("CONNECTING");
use crate::platform::os::unix_socket;
unix_socket::connect(mize, store_path.into())?;
return Ok(());
}
#[cfg(target_family = "windows")]
{
error!("CONNECTING... would connect, but that is not implemented on windows yet");
}
} else {
// else open it ourselves
let file_store = FileStore::new(store_path.as_str())?;
mize.migrate_to_store(Box::new(file_store))?;
let path = Path::new(&store_path).to_owned();
#[cfg(target_family = "unix")]
{
mize.add_conn_listener(UnixListener::new(path)?)?;
}
#[cfg(target_family = "windows")]
{
warn!("would add a Listener on a local socket, but that is not yet implemented for windows");
}
}
Ok(())
}
pub fn seconds_since_modification(path: &Path) -> MizeResult<u64> {
let metadata = fs::metadata(path)?;
let modified = metadata.modified()?;
let duration = modified.duration_since(UNIX_EPOCH)?;
Ok(duration.as_secs())
}
pub fn get_part_hash(
mize: &mut Mize,
name: &str,
options: ItemData,
) -> MizeResult<(String, String)> {
let mut selector_data = mize.get("self/config/selector")?.as_data_full()?;
// handle the case, when we want a module for not the system we are running on...
println!("get_module_hash: name: {}", name);
let mut name_parts = name.split(".");
if name_parts
.next()
.ok_or(mize_err!("no 0th element in the modName"))?
== "cross"
{
let system = name_parts
.next()
.ok_or(mize_err!("no 1th element in the modName"))?;
let mod_name: String = name_parts.collect();
println!("get_module_hash: mod_name: {}", mod_name);
if mod_name == "" {
return Err(mize_err!("no 2nd element in the modName"));
}
selector_data.set_path("system", system)?;
selector_data.set_path("modName", mod_name)?;
} else {
selector_data.set_path("modName", name)?;
}
selector_data.merge(options);
// important to produce the same hash for the sama selector data
selector_data.sort_keys();
let selector_str = selector_data.to_json()?;
let mut hasher = Sha256::new();
hasher.update(selector_str.clone().as_bytes());
let result = hasher.finalize();
let mut hex: String = result
.iter()
.map(|b| format!("{:02x}", b).to_string())
.collect::<Vec<String>>()
.join("");
hex.truncate(32);
return Ok((hex, selector_str));
}
pub fn fetch_part(mize: &mut Mize, part_name: &str) -> MizeResult<String> {
let (part_hash, part_selector) = get_part_hash(mize, part_name, ItemData::new())?;
let part_url = "https://mize.works/mize/dist/{part_hash}-{part_name}.tar.gz";
let store_path = mize.get_config("store")?.value_string()?;
let parts_path = format!("{store_path}/parts");
let target_dir = format!("{parts_path}/{part_hash}-{part_name}");
let tmp_file_path_gz = format!("{parts_path}/{part_hash}-{part_name}.tar.gz");
let tmp_gz_file = if !PathBuf::from(tmp_file_path_gz.clone()).exists() {
let mut tmp_gz_file = File::create(&tmp_file_path_gz)?;
debug!("downloading part '{}' with hash: {}", part_name, part_hash);
let response = http_req::request::get(part_url, &mut tmp_gz_file)?;
let code = response.status_code();
if code != 200.into() {
return Err(mize_err!(
"failed to get part '{}' with hash '{}' and selector: {}",
part_name,
part_hash,
part_selector
)
.msg(format!(
"the http request to fetch the part returned a status code of: {}",
code
)));
}
tmp_gz_file
} else {
std::fs::File::open(tmp_file_path_gz.clone())?
};
let tar = GzDecoder::new(std::io::BufReader::new(tmp_gz_file));
let mut archive = Archive::new(tar);
//archive.set_mask(umask::Mode::parse("rwxrwxrwx")?.into());
std::fs::create_dir_all(target_dir.as_str())?;
archive.unpack(target_dir.as_str())?;
fs::remove_file(&tmp_file_path_gz)?;
Ok(format!("{parts_path}/{part_hash}-{part_name}"))
}
fn get_part_path(mize: &mut Mize, part_name: &str, _path: Option<String>) -> MizeResult<String> {
// check if $name.src is set
if let Ok(itm) = mize.get_config(format!("{part_name}.src").as_str()) {
let part_src = itm.value_string().map_err(|e| e.msg(format!("opt {part_name}.src was not a string")))?;
// will only work for rust for now
let part_path = format!("{part_src}/target/debug/libmize_part_{part_name}.so");
return Ok(part_path);
}
// check if $name.out is set
if let Ok(itm) = mize.get_config(format!("{part_name}.out").as_str()) {
let part_out = itm.value_string().map_err(|e| e.msg(format!("opt {part_name}.out was not a string")))?;
let part_path = format!("{part_out}/lib/libmize_part_{part_name}.so");
return Ok(part_path);
}
// check if we find the part inside mize.src
if let Ok(itm) = mize.get_config("mize.src") {
let mize_src = itm.value_string().map_err(|e| e.msg("opt mize.src was not a string"))?;
let part_path = format!("{mize_src}/target/debug/libmize_part_{part_name}.so");
return Ok(part_path);
}
// fetch the part from https://mize.works
let out_path = fetch_part(mize, part_name)?;
return Ok(format!("{out_path}/lib/libmize_part_{part_name}.so"));
//Err(mize_err!("part {part_name} not found"))
}
pub fn load_part(mize: &mut Mize, part_name: &str, path: Option<String>) -> MizeResult<()> {
let part_path = get_part_path(mize, part_name, path)?;
let lib = unsafe { libloading::Library::new(part_path)? };
unsafe {
if let Ok(rust_hack_func) = lib.get::<libloading::Symbol<unsafe extern "C" fn(Arc<Mutex<Mize>>)>>("mize_rust_hack_set_global".as_bytes()) {
rust_hack_func(GLOBAL_MIZE.get().unwrap().clone())
};
}
let func: libloading::Symbol<
unsafe extern "C" fn() -> MizeResult<()>,
> = unsafe { lib.get(format!("part_{}", part_name).as_bytes())? };
let mut cur_eval_part = mize.cur_eval_part.lock()?;
*cur_eval_part = Some(mize.clone().id_new_dots(part_name)?);
println!("running part: {}", cur_eval_part.clone().unwrap());
drop(cur_eval_part);
unsafe { func()? };
let mut cur_eval_part = mize.cur_eval_part.lock()?;
*cur_eval_part = None;
// dropping the lib, would (i suspect) free all memory, of the library's code, which would
// make the part's vtable point into empty memory
std::mem::forget(lib);
Ok(())
}
pub fn config_from_cli_args(matches: &ArgMatches) -> MizeResult<ItemData> {
let mut config = ItemData::new();
////// populate values from $MIZE_CONFIG_FILES
if let Ok(config_file_paths) = std::env::var("MIZE_CONFIG_FILES") {
for config_file_path in config_file_paths.split(":") {
println!("reading config file: {config_file_path}");
let content = std::fs::read_to_string(config_file_path)?;
let data = ItemData::from_toml(content.as_str())?;
config.merge(data);
}
}
////// load config from MIZE_CONFIG env var
match std::env::var("MIZE_CONFIG") {
Ok(config_string) => {
let data = data_from_string(config_string)?;
config.merge(data);
}
Err(var_err) => match var_err {
VarError::NotPresent => {
debug!("env var MIZE_CONFIG NOT present")
}
VarError::NotUnicode(_) => {
warn!("env var MIZE_CONFIG is not Unicode, so not reading it")
}
},
};
if let Some(config_file_path) = matches.get_one::<String>("config") {
config.merge(config_from_file(config_file_path.to_string())?);
trace!("config after --config arg: {}", config);
}
if let Some(config_string) = matches.get_one::<String>("opt") {
config.merge(data_from_string(config_string.to_string())?);
trace!("config after --opt arg: {}", config);
}
return Ok(config);
}
pub fn config_from_file(file_path: String) -> MizeResult<ItemData> {
let toml_string = fs::read_to_string(&file_path)
.mres_msg(format!("Could not read file {} as mize config", &file_path))?;
let item_data = ItemData::from_toml(toml_string.as_str())?;
return Ok(item_data);
}
struct MyFormatter;
impl<S, N> FormatEvent<S, N> for MyFormatter
where
S: tracing::Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: format::Writer<'_>,
event: &Event<'_>,
) -> fmt::Result {
// Format values from the event's's metadata:
let metadata = event.metadata();
// set format
//builder.format(|buf, record| match record.level() {});
let level_str = match metadata.level() {
&Level::TRACE => format!("[ {} ]", "TRACE".yellow()),
&Level::DEBUG => format!("[ {} ]", "DEBUG".green()),
&Level::INFO => format!("[ {} ]", "INFO".blue()),
&Level::WARN => format!("[ {} ]", "WARN".truecolor(245, 164, 66)),
&Level::ERROR => format!("[ {} ]", "ERROR".red()),
};
// with target
// write!(&mut writer, "{} {}: ", level_str, metadata.target())?;
write!(&mut writer, "{} ", level_str)?;
// Format all the spans in the event's span context.
if let Some(scope) = ctx.event_scope() {
for span in scope.from_root() {
write!(writer, "{}", span.name())?;
// `FormattedFields` is a formatted representation of the span's
// fields, which is stored in its extensions by the `fmt` layer's
// `new_span` method. The fields will have been formatted
// by the same field formatter that's provided to the event
// formatter in the `FmtContext`.
let ext = span.extensions();
let fields = &ext
.get::<FormattedFields<N>>()
.expect("will never be `None`");
// Skip formatting the fields if the span had no fields.
if !fields.is_empty() {
//write!(writer, "{{{}}}", fields)?;
}
write!(writer, ": ")?;
}
}
// Write fields on the event
ctx.field_format().format_fields(writer.by_ref(), event)?;
writeln!(writer);
Ok(())
}
}
// thanks: https://stackoverflow.com/a/75546824
pub struct StringVisitor<'a> {
string: &'a mut String,
}
impl<'a> StringVisitor<'a> {
pub(crate) fn new(string: &'a mut String) -> Self {
StringVisitor { string }
}
}
impl<'a> Visit for StringVisitor<'a> {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
if field.name() == "message" {
write!(self.string, "{value:?} ").unwrap();
} else {
write!(self.string, "{} = {:?}; ", field.name(), value).unwrap();
}
}
}

697
parts/auth/src/client.rs Normal file
View File

@@ -0,0 +1,697 @@
use chrono::{DateTime, TimeDelta, Utc};
use mize::config::OptVal;
use mize::config::thread;
use mize::include_native;
use mize::prelude::*;
use mize::out;
use mize::{
item::ItemData, mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult,
};
use openidconnect::{
ClientId, DeviceAuthorizationResponse, EmptyExtraDeviceAuthorizationFields, IssuerUrl,
OAuth2TokenResponse, RefreshToken, Scope, TokenResponse,
};
use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::Write;
use std::{path::PathBuf, thread};
use tracing::{info, warn};
use mize_part_cli::CliPartState;
// console_log macro
// that can be copied into other files for debugging purposes
#[cfg(feature = "target-obsidian")]
use wasm_bindgen::prelude::*;
#[cfg(feature = "target-obsidian")]
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[cfg(feature = "target-obsidian")]
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => (unsafe { log(&format_args!($($t)*).to_string())})
}
#[cfg(feature = "target-os")]
use mize_part_cli::clap::Command;
#[derive(Default)]
#[mize_part]
pub struct ClientAuth {
mize: Mize,
secrets: Option<ClientSecrets>,
}
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct ClientSecrets {
id_token: String,
access_token: String,
refresh_token: String,
expiry: DateTime<Utc>,
}
impl MizePartCreate for ClientAuth {}
impl MizePart for ClientAuth {
fn name(&self) -> &'static str {
"client_auth"
}
}
#[cfg(feature = "target-js")]
pub fn store_secrets_js(mize: &Mize, secrets: &ClientSecrets) -> MizeResult<()> {
use web_sys::window;
// Serialize struct to JSON string
let serialized = serde_json::to_string(&secrets)?;
// Get browser window object
let window = window().ok_or_else(|| mize_err!("No global window found"))?;
// Access local storage
let storage = window
.local_storage().map_err(|_| mize_err!("couldn't get local_storage"))?
.ok_or_else(|| mize_err!("LocalStorage is not available"))?;
// Store the JSON string
storage.set_item("mize_client_secrets", &serialized).map_err(|_| mize_err!("couldn't set item in local_storage"))?;
Ok(())
}
impl ClientAuth {
pub fn secrets(&self) -> Option<&ClientSecrets> {
self.secrets.as_ref()
}
pub fn id_token(&self) -> MizeResult<&str> {
if self.secrets.is_none() {
return Err(mize_err!("Client not logged in"));
}
Ok(&self.secrets.as_ref().unwrap().id_token)
}
pub fn access_token(&self) -> MizeResult<&str> {
if self.secrets.is_none() {
return Err(mize_err!("Client not logged in"));
}
Ok(&self.secrets.as_ref().unwrap().access_token)
}
pub fn refresh_token(&self) -> MizeResult<&str> {
if self.secrets.is_none() {
return Err(mize_err!("Client not logged in"));
}
Ok(&self.secrets.as_ref().unwrap().refresh_token)
}
pub fn refresh(&mut self) -> MizeResult<()> {
#[cfg(feature = "target-js")] {
console_log!("refresh is not implemented yet on wasm");
}
#[cfg(feature = "target-os")] {
let secrets = self.secrets.clone().ok_or(mize_err!(
"no client secrets, meaning client_auth.refresh() thread can't use the refresh token"
))?;
let new_secrets = self.mize.spawn_async_blocking(
"auth.refresh_flow",
refresh_flow(self.mize.clone(), secrets),
)?;
store_secrets(&mut self.mize, &new_secrets)?;
self.secrets = Some(new_secrets);
}
Ok(())
}
pub fn login(&mut self) -> MizeResult<()> {
#[cfg(feature = "target-os")] {
if self.secrets().is_none() {
let secrets = self
.mize
.spawn_async_blocking("auth.login_flow", auth_flow(self.mize.clone()))?;
store_secrets(&mut self.mize, &secrets)?;
self.secrets = Some(secrets);
}
}
#[cfg(feature = "target-js")] {
if self.secrets().is_none() {
let mut mize_clone = self.mize.clone();
wasm_bindgen_futures::spawn_local(async move {
console_log!("before auth_flow");
let secrets = match auth_flow(mize_clone.clone()).await {
Ok(secrets) => secrets,
Err(e) => {
mize_clone.report_err(e);
return ();
}
};
console_log!("after auth_flow");
if let Err(e) = store_secrets_js(&mut mize_clone, &secrets) {
mize_clone.report_err(e);
return ();
}
let mut auth = match mize_clone.get_part_native::<ClientAuth>("client_auth") {
Ok(auth) => auth,
Err(e) => {
mize_clone.report_err(e);
return ();
}
};
auth.secrets = Some(secrets);
});
}
}
Ok(())
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn part_auth() -> MizeResult<()> {
opt("cred")
.fun(|| {
let mize = get_global_mize()?;
let secrets = do_login()?;
ItemData::from_serializable(secrets)
});
let mize = get_global_mize()?;
// start refresh tread if configured
if mize
.get_config("auth.client.refresh_thread")
.unwrap_or(ItemData::new())
.value_bool()
.unwrap_or(false)
{
thread("auth.refresh_thread", start_refresh_thread());
}
let cli = get_part_gen::<generated::CliPart>("cli");
// cli command for os
#[cfg(feature = "target-os")] {
println!("hasdfaslkjfasldkfj");
let cli_dyn = mize.get_part("cli")?;
println!("hasdfaslkjfasldkfj three ");
let mut cli = mize_part_cli::downcast_cli_part(cli_dyn)?;
println!("hasdfaslkjfasldkfj two ");
//let mut cli = mize.get_part_native::<CliPart>("cli")?;
cli.set_command("ppc");
cli.subcommand(Command::new("login"), move |_, mut mize| {
let mut auth = mize.get_part_native::<ClientAuth>("client_auth")?;
let secrets = if auth.secrets.is_some() {
match mize.spawn_async_blocking(
"client_auth",
refresh_flow(mize.clone(), auth.secrets.clone().unwrap()),
) {
Ok(secrets) => secrets,
Err(err) => {
out!("err refreshing secrets: {:?}", err);
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone.clone()))?
}
}
} else {
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone.clone()))?
};
store_secrets(&mut mize, &secrets)?;
auth.secrets = Some(secrets);
Ok(())
})?;
}
// register command for obsidian
#[cfg(feature = "target-obsidian")] {
use crate::MartsCommand;
console_log!("register obsidian command...");
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian")?;
obsidian.add_command(MartsCommand {
id: "mize-login".to_string(),
name: "Mize Login".to_string(),
callback: Box::new(|mut mize| {
let mut auth = mize.get_part_native::<crate::ClientAuth>("client_auth")?;
let res = auth.login();
console_log!("auth.login done: {:?}", res);
res
}),
})?;
}
auth_part.secrets = secrets;
mize.register_part(Box::new(auth_part))?;
Ok(())
}
pub fn start_refresh_thread() -> MizeResult<()> {
// background thread for os targets
#[cfg(feature = "target-os")] {
let mut mize_clone = self.mize.clone();
self.mize.spawn_background("auth.refresh", move || loop {
info!("hi from auth.refresh thread...");
thread::sleep(Duration::from_secs(33)); // TERRIBLE hack
let auth = mize_clone.get_part_native::<ClientAuth>("client_auth")?;
let expiry = auth.secrets.as_ref().unwrap().expiry.clone();
info!("auth.refresh thread: now: {}", Utc::now());
info!("auth.refresh thread: expiry: {}", expiry);
let to_sleep = expiry - Utc::now() - TimeDelta::minutes(20);
/*
if to_sleep < TimeDelta::from_std(Duration::from_secs(0))? {
if let Err(e) = auth.refresh() {
e.log();
}
}
*/
drop(auth);
info!("auth.refresh: thread going to sleep for: {}", to_sleep);
thread::sleep(to_sleep.to_std()?);
info!("refresh_thread: sleeping done");
let mut auth = mize_clone.get_part_native::<ClientAuth>("auth")?;
auth.refresh()?;
drop(auth);
})?;
Ok(())
}
/*
// spawn an async background task doing the refresh
#[cfg(feature = "target-js")] {
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
// Get the window object
let window = web_sys::window().expect("no global `window` exists");
// Clone mize for the closure
let mize_clone = self.mize.clone();
// Create a closure that will be called periodically
let refresh_closure = Closure::wrap(Box::new(move || {
let mut mize_clone = mize_clone.clone();
wasm_bindgen_futures::spawn_local(async move {
let mut auth = mize_clone.get_part_native::<ClientAuth>("client_auth");
match auth {
Ok(mut auth_part) => {
if let Some(secrets) = auth_part.secrets() {
let now = Utc::now();
let expiry = secrets.expiry;
let time_until_expiry = expiry - now;
// Refresh if token will expire in less than 20 minutes
if time_until_expiry < TimeDelta::minutes(20) {
if let Err(e) = auth_part.refresh() {
console_log!("Error refreshing token: {:?}", e);
} else {
console_log!("Token refreshed successfully");
}
}
}
}
Err(e) => {
console_log!("Error getting auth part: {:?}", e);
}
}
});
}) as Box<dyn FnMut()>);
// Set up interval to run every 50 minutes (300000 ms)
let interval_ms = 3000000;
let _ = window.set_interval_with_callback_and_timeout_and_arguments_0(
refresh_closure.as_ref().unchecked_ref(),
interval_ms,
);
// Leak the closure to prevent it from being dropped
refresh_closure.forget();
}
*/
Ok(())
}
fn do_login() -> MizeResult<ClientSecrets> {
let mut mize = get_global_mize()?;
let mut secrets;
if let Ok(val) = load_secrets(&mut mize) {
if val.clone().unwrap().expiry < Utc::now() {
// refresh if secrets are expired
secrets = mize.spawn_async_blocking("auth.refresh", refresh_flow(mize, val))?;
} else {
secrets = val;
}
} else {
secrets = mize.spawn_async_blocking("auth.login", auth_flow(mize))?;
}
Ok(secrets)
}
fn load_secrets(mize: &mut Mize) -> MizeResult<ClientSecrets> {
#[cfg(feature = "target-os")] {
let mize_dir = mize.get_config("data_dir")?.value_string()?;
let secrets_file_path = PathBuf::from(mize_dir).join("client_secrets.json");
let secrets_string = std::fs::read_to_string(secrets_file_path)?;
let secrets: ClientSecrets = serde_json::from_str(&secrets_string)?;
return Ok(secrets);
}
#[cfg(feature = "target-js")] {
use web_sys::window;
// Get browser window object
let window = window().ok_or_else(|| mize_err!("No global window found"))?;
// Access local storage
let storage = window
.local_storage().map_err(|_| mize_err!("couldn't get local_storage"))?
.ok_or_else(|| mize_err!("LocalStorage is not available"))?;
// Retrieve the string item
return match storage.get_item("mize_client_secrets").map_err(|_| mize_err!("couldn't get item from local_storage"))? {
Some(json_str) => {
// Deserialize JSON string back into Client struct
let secrets: ClientSecrets = serde_json::from_str(json_str.as_str())?;
Ok(secrets)
}
None => Err(mize_err!("no secrets")), // Key does not exist
}
}
}
fn store_secrets(mize: &mut Mize, secrets: &ClientSecrets) -> MizeResult<()> {
#[cfg(feature = "target-os")] {
let mize_dir = mize.get_config("store")?.value_string()?;
let secrets_file_path = PathBuf::from(mize_dir.clone())
.join("misc")
.join("client_secrets.json");
std::fs::create_dir_all(PathBuf::from(mize_dir).join("misc"))?;
let secrets_string = serde_json::to_string(secrets)?;
tracing::info!("secrets_file_path: {}", secrets_file_path.display());
//std::fs::write(secrets_file_path, secrets_string)?;
OpenOptions::new()
.write(true)
.create(true)
.open(secrets_file_path)?
.write_all(&secrets_string.as_bytes())?;
// update the spacetimedb_token in XDG_CONFIG_HOME/spacetime/cli.toml config file if configured to do so
if mize
.get_config("auth.client.update_spacetime_token")
.unwrap_or(ItemData::empty())
.as_bool()
.unwrap_or(false)
{
update_spacetimedb_token(&secrets.id_token.as_str())?;
}
}
Ok(())
}
use openidconnect::reqwest::Client as HttpClient;
use openidconnect::DeviceAuthorizationUrl;
use std::time::Duration;
//use wasm_bindgen::JsCast;
async fn refresh_flow(mut mize: Mize, old_secrets: ClientSecrets) -> MizeResult<ClientSecrets> {
out!("Refresh flow...");
let issuer = mize.get_config("auth.client.issuer")?.value_string()?;
let client_id = mize.get_config("auth.client.client_id")?.value_string()?;
let issuer = IssuerUrl::new(issuer)?;
let client_id = ClientId::new(client_id);
let http_client = HttpClient::new();
// Teach openidconnect-rs about the device authorization url.
#[derive(Clone, Debug, Deserialize, Serialize)]
struct MyExtraProviderMetadata {
device_authorization_endpoint: String,
}
use openidconnect::core::{
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,
CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType,
CoreSubjectIdentifierType,
};
use openidconnect::AdditionalProviderMetadata;
use openidconnect::ProviderMetadata;
impl AdditionalProviderMetadata for MyExtraProviderMetadata {}
type MyProviderMetadata = ProviderMetadata<
MyExtraProviderMetadata,
CoreAuthDisplay,
CoreClientAuthMethod,
CoreClaimName,
CoreClaimType,
CoreGrantType,
CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm,
CoreJsonWebKey,
CoreResponseMode,
CoreResponseType,
CoreSubjectIdentifierType,
>;
// Discover provider metadata
let provider_metadata: MyProviderMetadata =
MyProviderMetadata::discover_async(issuer, &http_client).await?;
let device_auth_url = provider_metadata
.additional_metadata()
.device_authorization_endpoint
.clone();
let client = CoreClient::from_provider_metadata(
provider_metadata,
client_id,
None, // public client
)
.set_device_authorization_url(DeviceAuthorizationUrl::new(device_auth_url)?);
let refresh_token = RefreshToken::new(old_secrets.refresh_token);
let res = client
.exchange_refresh_token(&refresh_token)?
.request_async(&http_client)
.await?;
let expires_in = res.expires_in().ok_or(mize_err!(
"no expiry information was in token response of the refresh flow"
))?;
return Ok(ClientSecrets {
id_token: res.id_token().unwrap().to_string(),
access_token: res.access_token().secret().to_owned(),
refresh_token: res.refresh_token().unwrap().secret().to_owned(),
expiry: Utc::now() + expires_in,
});
}
async fn auth_flow(mut mize: Mize) -> MizeResult<ClientSecrets> {
let issuer = mize.get_config("auth.client.issuer")?.value_string()?;
let client_id = mize.get_config("auth.client.client_id")?.value_string()?;
let issuer = IssuerUrl::new(issuer)?;
let client_id = ClientId::new(client_id);
let http_client = HttpClient::new();
// Teach openidconnect-rs about the device authorization url.
#[derive(Clone, Debug, Deserialize, Serialize)]
struct MyExtraProviderMetadata {
device_authorization_endpoint: String,
}
use openidconnect::core::{
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,
CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType,
CoreSubjectIdentifierType,
};
use openidconnect::AdditionalProviderMetadata;
use openidconnect::ProviderMetadata;
impl AdditionalProviderMetadata for MyExtraProviderMetadata {}
type MyProviderMetadata = ProviderMetadata<
MyExtraProviderMetadata,
CoreAuthDisplay,
CoreClientAuthMethod,
CoreClaimName,
CoreClaimType,
CoreGrantType,
CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm,
CoreJsonWebKey,
CoreResponseMode,
CoreResponseType,
CoreSubjectIdentifierType,
>;
// Discover provider metadata
let provider_metadata: MyProviderMetadata =
MyProviderMetadata::discover_async(issuer, &http_client).await?;
let device_auth_url = provider_metadata
.additional_metadata()
.device_authorization_endpoint
.clone();
let client = CoreClient::from_provider_metadata(
provider_metadata,
client_id,
None, // public client
)
.set_device_authorization_url(DeviceAuthorizationUrl::new(device_auth_url)?);
// ===== DEVICE AUTH REQUEST =====
let details: Result<DeviceAuthorizationResponse<EmptyExtraDeviceAuthorizationFields>, _> =
client
.exchange_device_code()
.add_scope(Scope::new("openid".into()))
.add_scope(Scope::new("profile".into()))
.add_scope(Scope::new("offline_access".into()))
.request_async(&http_client)
.await;
let details = match details {
Ok(val) => val,
Err(err) => {
out!("err: {:?}", err);
out!("err: {}", err);
panic!();
}
};
#[cfg(feature = "target-obsidian")] {
console_log!("details: {:?}", details);
use std::str::FromStr;
use js_sys::{Array, Function, JsString, Reflect};
use mize::error::IntoMizeResultJsValue;
use wasm_bindgen::convert::TryFromJsValue;
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian")?;
let show_device_code = Reflect::get(&obsidian.plugin(), &JsValue::from_str("showDeviceCode")).mres()?;
let args = Array::new();
args.push(&JsValue::from_str(details.user_code().secret().as_str()));
args.push(&JsValue::from_str(details.verification_uri_complete().unwrap().secret().as_str()));
Reflect::apply(&show_device_code.dyn_into::<Function>().mres()?, obsidian.plugin(), &args).mres()?;
}
#[cfg(feature = "target-os")] {
out!(
"If your browser does not open automatically, open this URL in it:\n{}\n",
details.verification_uri_complete().unwrap().secret()
);
out!(
"Or enter this PPC login code:\n{}\n",
details.user_code().secret()
);
if let Some(_) = details.verification_uri_complete() {
let _ = webbrowser::open(details.verification_uri_complete().unwrap().secret());
}
}
let token_response = client
.exchange_device_access_token(&details)?
.request_async(
&http_client,
async |dur: Duration| {
#[cfg(feature = "target-os")] {
tokio::time::sleep(dur).await;
}
#[cfg(feature = "target-js")] {
console_log!("sleeping");
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
let promise = js_sys::Promise::new(&mut |resolve, _| {
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, dur.as_millis() as i32)
.unwrap();
});
// Await the JS Promise like a Rust Future
let _ = JsFuture::from(promise).await;
}
},
None,
)
.await?;
// ===== TOKENS =====
let id_token = token_response
.extra_fields()
.id_token()
.ok_or(mize_err!("no id_token"))?;
let refresh = token_response
.refresh_token()
.ok_or(mize_err!("no refresh token present"))?;
let expires_in = token_response.expires_in().ok_or(mize_err!(
"no expiry information was in token response of the refresh flow"
))?;
Ok(ClientSecrets {
id_token: id_token.to_string(),
access_token: token_response.access_token().secret().to_string(),
refresh_token: refresh.secret().to_owned(),
expiry: Utc::now() + expires_in,
})
}
fn update_spacetimedb_token(new_token: &str) -> MizeResult<()> {
// Resolve config directory
let config_dir = match std::env::var("XDG_CONFIG_HOME") {
Ok(val) => PathBuf::from(val),
Err(_) => {
let home = std::env::var("HOME")?;
std::path::PathBuf::from(home).join(".config")
}
};
let file_path = config_dir.join("spacetime").join("cli.toml");
if !file_path.exists() {
warn!("spacetime config file does not exist, not updating token there");
return Ok(());
};
// Read existing file (or create empty document if it doesn't exist)
let content = std::fs::read_to_string(&file_path).unwrap_or_default();
let mut data = ItemData::from_toml(content.as_str())?;
// Update the field
data.set_path("spacetimedb_token", new_token)?;
// Write back
std::fs::write(&file_path, data.to_toml()?)?;
Ok(())
}

10
parts/cli/src/lib.rs Normal file
View File

@@ -0,0 +1,10 @@
use mize_macros::{include_native_simple, part_impl};
use clap::Command;
#[part_impl]
pub fn part() -> mize::Instance {
// Register the command with the new macro syntax
include_native_simple!("Command", clap::Command, [new]);
mize::Instance::new()
}

View File

@@ -0,0 +1,70 @@
use mize::prelude::*;
pub struct CliPart {
id: MizeId
}
impl MizePart for CliPart {
fn id(&self) -> MizeId {
self.id.clone()
}
}
impl CliPart {
pub fn get(name: &str) -> MizeResult<CliPart> {
let mut mize = get_global_mize()?;
let part = mize.get_part(name)?;
Ok(CliPart { id: part.id() })
}
pub fn run(&self, cmdline: Vec<String>) -> MizeResult<()> {
let cmdline = cmdline.into_item_data();
let mut args = ItemData::new();
args.set_path("0", cmdline)?;
dyn_call(self.id.clone(), "run", args)?;
Ok(())
}
pub fn set_command(&self, name: &str) -> MizeResult<()> {
let name = name.into_item_data();
let mut args = ItemData::new();
args.set_path("0", name)?;
dyn_call(self.id.clone(), "set_command", args)?;
Ok(())
}
pub fn subcommand<T: Fn(ArgMatches) -> MizeResult<()> + Send + Sync + 'static>(&self, subcmd: Command, action: T) -> MizeResult<()> {
let mut subcmd_arg = ItemData::new();
subcmd_arg.set_path("_native", subcmd.native_id)?;
let mut action_arg = ItemData::from_native(action)?;
let mut args = ItemData::new();
args.set_path("0", subcmd_arg)?;
args.set_path("1", action_arg)?;
dyn_call(self.id.clone(), "subcommand", args)?;
Ok(())
}
}
// the clap::Command native stuff
pub struct Command {
native_id: u32
}
pub struct ArgMatches {
native_id: u32
}
pub struct Action {
native_id: u32
}
impl Command {
pub fn new(name: &str) -> MizeResult<Command> {
let name = name.into_item_data();
let mut args = ItemData::new();
args.set_path("0", name)?;
let mut mize = get_global_mize()?;
let id = mize.id_new_dots("cli.Command")?;
let data = dyn_call(id.clone(), "new", args)?;
let native_id: u32 = data.get_path("_native")?.native_take()?;
Ok(Command { native_id })
}
}

63
parts/mize.dev/src/lib.rs Normal file
View File

@@ -0,0 +1,63 @@
use std::fs::create_dir_all;
use mize::prelude::*;
use cmd_lib::run_cmd as sh;
use mize::tracing::info;
// a static version of what would be generated.
// as we need mize.dev to generate anything.
mod generated;
use generated::*;
// spacetime gen command: sh!(RUST_LOG=error RUST_BACKTRACE=0 spacetime generate --build-options="--lint-dir=" -p $SRC/mize/marts/spacetime -o $SRC/mize/marts/generated/src/spacetime --lang rust)?;
#[unsafe(no_mangle)]
extern "C" fn part_dev() -> MizeResult<()> {
let mut mize = get_global_mize()?;
let mut cli = generated::CliPart::get("cli")?;
cli.set_command("dev");
cli.subcommand(Command::new("test")?, |_| {println!("hello from test command"); Ok(())})?;
//cli.subcommand(Command::new("publish").alias("p"), publish_mize)?;
//cli.subcommand(Command::new("genBindings").alias("b"), gen_bindings)?;
Ok(())
}
pub fn publish_mize(_ : &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mize_src = mize.get_config("mize.src")?.value_string()?;
let cargo_dir = std::env::home_dir().unwrap().join(".cargo");
let targets = vec![
"x86_64-pc-windows-gnu",
"aarch64-pc-windows-gnullvm",
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"armv7-unknown-linux-gnueabihf",
"arm-unknown-linux-gnueabi",
"aarch64-apple-darwin",
"x86_64-apple-darwin",
];
for target_name in targets {
let docker_image = match target_name {
"aarch64-apple-darwin" => "darwin",
"x86_64-apple-darwin" => "darwin",
_ => "base",
};
sh!(docker build -t mize-build-$docker_image $mize_src/parts/mize.dev/targets/$docker_image)?;
let res = sh!(docker run -it --rm -v $cargo_dir/registry:/usr/local/cargo/registry -v $cargo_dir/git:/usr/local/cargo/git -v $mize_src:/src -v $mize_src/target:/src/target mize-build-$docker_image cargo build --target $target_name --release);
if res.is_err() {
info!("build for {target_name} failed...");
}
};
Ok(())
}
pub fn gen_bindings(_ : &ArgMatches, mize: Mize) -> MizeResult<()> {
Ok(())
}