From 679f5e51e57413df699aa832fdedb842acb1c804 Mon Sep 17 00:00:00 2001 From: Sebastian Moser Date: Thu, 22 Oct 2026 18:25:57 +0200 Subject: [PATCH] changes saved in my editor --- AGENTS.md | 14 +- core/src/config.rs | 128 +++++ core/src/id.rs | 203 +++++++ core/src/instance/mod.rs | 869 ++++++++++++++++++++++++++++ core/src/item.rs | 981 ++++++++++++++++++++++++++++++++ core/src/platform/os/mod.rs | 470 +++++++++++++++ parts/auth/src/client.rs | 697 +++++++++++++++++++++++ parts/cli/src/lib.rs | 10 + parts/mize.dev/src/generated.rs | 70 +++ parts/mize.dev/src/lib.rs | 63 ++ 10 files changed, 3503 insertions(+), 2 deletions(-) create mode 100644 core/src/config.rs create mode 100644 core/src/id.rs create mode 100644 core/src/instance/mod.rs create mode 100644 core/src/item.rs create mode 100644 core/src/platform/os/mod.rs create mode 100644 parts/auth/src/client.rs create mode 100644 parts/cli/src/lib.rs create mode 100644 parts/mize.dev/src/generated.rs create mode 100644 parts/mize.dev/src/lib.rs diff --git a/AGENTS.md b/AGENTS.md index ee51b72..485fd02 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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`. -## 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 @@ -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 - Ask about preferred approaches if multiple solutions exist - 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 @@ -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 - **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 - User must explicitly approve the plan with words like "yes", "approved", "proceed", "go ahead", or similar diff --git a/core/src/config.rs b/core/src/config.rs new file mode 100644 index 0000000..7ab3241 --- /dev/null +++ b/core/src/config.rs @@ -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 MizeResult + Send + Sync + 'static>; + +pub struct ConfigOpt { + pub name: String, + pub val: Option, + pub fun: Option, + pub deps: Vec, +} + +#[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 MizeResult>(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 MizeResult>(_name: &str, _fun: F) { +} + +pub fn effect MizeResult>(_name: &str, _fun: F) { +} + +pub fn thread MizeResult>(_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) + }) + } + } +} diff --git a/core/src/id.rs b/core/src/id.rs new file mode 100644 index 0000000..4324036 --- /dev/null +++ b/core/src/id.rs @@ -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; +} + +impl MizeId { + pub fn join(&mut self, to_add: &str) -> MizeResult { + 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 { + let tmp = self.path(); + let mut path_iter = as Clone>::clone(&tmp).into_iter(); + path_iter.next(); + let new_path: Vec = 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 { + instance.id_from_string(self.to_owned()) + } +} + +impl IntoMizeId for String { + fn to_mize_id(self, instance: &Mize) -> MizeResult { + instance.id_from_string(self) + } +} +impl IntoMizeId for &String { + fn to_mize_id(self, instance: &Mize) -> MizeResult { + instance.id_from_string(self.to_owned()) + } +} +impl IntoMizeId for Vec { + fn to_mize_id(self, instance: &Mize) -> MizeResult { + instance.id_from_vec_string(self) + } +} +impl IntoMizeId for Vec<&String> { + fn to_mize_id(self, instance: &Mize) -> MizeResult { + let owned = self + .into_iter() + .map(|s| s.to_owned()) + .collect::>(); + instance.id_from_vec_string(owned) + } +} +impl IntoMizeId for &[&str] { + fn to_mize_id(self, instance: &Mize) -> MizeResult { + let mut vec: Vec = 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 { + let mut vec: Vec = 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 { + Ok(self) + } +} + +// would only work with a global id_pool +// for now use Instance::new_id() +//impl> From for MizeId { +//fn from(value: T) -> Self { +//let string = value.into(); +//let path: Vec = 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::::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::::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::::new(), id_fife.after_store_part()); + + //TODO: add the parsing form mize:// urls.... + + Ok(()) + } +} diff --git a/core/src/instance/mod.rs b/core/src/instance/mod.rs new file mode 100644 index 0000000..8849043 --- /dev/null +++ b/core/src/instance/mod.rs @@ -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>>, + connections: Arc>>, + next_con_id: Arc>, + subs: Arc>>>, + pub(crate) id_pool: Arc>, + pub(crate) namespace_pool: Arc>, + + pub(crate) listeners: Arc< + Mutex MizeResult<()> + Send + Sync>>>, + >, + + pub parts: Arc>>>>, + //pub(crate) platform: Arc>>, // should we do that instead of all the #cfg(faturs=targt-smth) in lib.rs + part_names: Arc>>, // exists, so that we know the ordering in which parts were loaded + + pub(crate) config_opts: Arc>>, + + // the namespace the instance operates in + pub(crate) namespace: Arc>, + + // the namespace of the instance itself + // TODO: set to a random uuid + pub(crate) self_namespace: Arc>, + pub(crate) op_tx: Sender, + threads: Arc>>)>>>, + next_thread_id: Arc>, + give_msg_wait: Arc>>>>, + create_msg_wait: Arc>>>, + + pub cur_eval_part: Arc>>, + + pub native_values: Arc>>>, + pub next_native_value_id: Arc>, + + #[cfg(feature = "async")] + pub runtime: Arc>, +} + +pub struct InstanceRef { + inner: Arc>, +} + +pub struct PartRef { + id: MizeId, +} + +impl PartRef { + pub fn into_any(self) -> Box { + 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>, +} + +pub trait MizePart { + fn id(&self) -> MizeId; +} + +impl Mize { + pub fn empty() -> MizeResult { + 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 { + 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 { + let parts = name.split(".").map(|v| v.to_owned()).collect::>(); + self.new_id(parts) + } + + pub fn get_part(&mut self, name: &str) -> MizeResult { + let id = self.id_new_dots(name)?; + Ok(PartRef { id }) + } + pub fn has_part(&mut self, name: &str) -> MizeResult { + 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 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 { + 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::>(); + 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) -> 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 { + 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::(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(&self, id: I) -> MizeResult { + let id = id.to_mize_id(self)?; + return Ok(Item::new(id, &self)); + } + + pub fn set>(&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>( + &self, + id: I, + value: V, + ) -> MizeResult<()> { + handle_operation( + &mut Operation::Set(id.to_mize_id(self)?, value.into(), None), + self, + )?; + Ok(()) + } + + pub fn sub(&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(&self, value: T) -> MizeResult { + value.to_mize_id(self) + } + + pub fn id_from_string(&self, string: String) -> MizeResult { + let vec_string: Vec = 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) -> MizeResult { + 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 { + // 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 { + 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 { + let namespace_inner = self.namespace.lock()?; + return Ok(namespace_inner.clone()); + } + + pub fn get_self_namespace(&self) -> MizeResult { + let self_namespace_inner = self.self_namespace.lock()?; + return Ok(self_namespace_inner.clone()); + } + + pub fn we_are_namespace(&self) -> MizeResult { + Ok(self.get_namespace()? == self.get_self_namespace()?) + } + + pub fn add_conn_listener(&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) -> MizeResult { + 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) -> MizeResult { + 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 { + 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 { + 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 { + let mut give_msg_wait_inner = self.give_msg_wait.lock()?; + + let (tx, rx) = bounded::(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 + 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 + 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 { + 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 { + 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( + &mut self, + name: &str, + ) -> MizeResult { + let dyn_part = self.get_part(name)?; + let concrete_part = dyn_part + .into_any() + .downcast::() + .map_err(|_| mize_err!("failed to downcast"))?; + Ok(*concrete_part) + } + pub fn id_path(&self, id: &MizeId) -> MizeResult { + 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() + } +} diff --git a/core/src/item.rs b/core/src/item.rs new file mode 100644 index 0000000..64e7016 --- /dev/null +++ b/core/src/item.rs @@ -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> { + 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 { + 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 { + 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 = as Clone>::clone(&id + .path) + .into_iter() + .skip(2) + .map(|v| v.to_owned()) + .collect::>() + .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 + 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 = as Clone>::clone(&self + .id() + .path) + .into_iter() + .skip(1) + .map(|v| v.to_owned()) + .collect::>() + .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 { + 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 { + 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(val: T) -> MizeResult { + 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: + pub fn from_native(val: T) -> MizeResult { + 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 { + 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 { + match self.cbor() { + CborValue::Bool(val) => Ok(*val), + _ => Err(mize_err!("this ItemData has no string value")), + } + } + pub fn as_bool(self) -> Option { + match self.cbor() { + CborValue::Bool(val) => Some(*val), + _ => None, + } + } + + pub fn from_string>(into_string: S) -> ItemData { + let string = into_string.into(); + let data = CborValue::Text(string); + return ItemData(data); + } + + pub fn to_json(self) -> MizeResult { + let mut result: Vec = 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 { + 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>(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::() { + return int.into_item_data(); + } + return CborValue::Text(value_str).into_item_data(); + } + + pub fn set_path(&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(&self, path: P) -> MizeResult { + 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> { + 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> { + 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 { + 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(&self) -> MizeResult> { + 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::() + .ok_or(mize_err!("Failed to downcast native value to {}", std::any::type_name::()))? 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(&self) -> MizeResult> { + 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::() + .ok_or(mize_err!("Failed to downcast native value to {}", std::any::type_name::()))? 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(&self) -> MizeResult { + 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::() + .map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::()))?; + + Ok(*typed) + } +} + +/// Guard that holds a lock on the native_values registry and provides an immutable reference to a native value. +pub struct NativeRefGuard { + _guard: MutexGuard<'static, HashMap>>, + value: &'static T, +} + +impl std::ops::Deref for NativeRefGuard { + 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 { + _guard: MutexGuard<'static, HashMap>>, + value: &'static mut T, +} + +impl std::ops::Deref for NativeMutRefGuard { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value + } +} + +impl std::ops::DerefMut for NativeMutRefGuard { + fn deref_mut(&mut self) -> &mut Self::Target { + self.value + } +} + +fn get_paths( + val: &mut CborValue, + paths: &mut Vec, + path_accu: &mut Vec, + 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 { + 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) -> 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, + 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::>().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::>() + .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; +} + +pub trait IntoItemData { + fn into_item_data(self) -> ItemData; +} + +impl IntoPath for &str { + fn into_path(self) -> Vec { + 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 { + self.into_iter().map(|v| (*v).to_owned()).collect() + } +} +impl IntoPath for Vec { + fn into_path(self) -> Vec { + self + } +} +impl IntoPath for Vec<&str> { + fn into_path(self) -> Vec { + 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 { + fn into_item_data(self) -> ItemData { + let array = self + .into_iter() + .map(|v| v.into_item_data().cbor().to_owned()) + .collect::>(); + 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 { + 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 = Vec::new(); + let mut buf_other: Vec = 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::>())?; + 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(()) +} diff --git a/core/src/platform/os/mod.rs b/core/src/platform/os/mod.rs new file mode 100644 index 0000000..3945f81 --- /dev/null +++ b/core/src/platform/os/mod.rs @@ -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 { + 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::>() + .join(""); + + hex.truncate(32); + + return Ok((hex, selector_str)); +} + +pub fn fetch_part(mize: &mut Mize, part_name: &str) -> MizeResult { + 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) -> MizeResult { + + // 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) -> 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::>)>>("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 { + 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::("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::("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 { + 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 FormatEvent 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::>() + .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(); + } + } +} diff --git a/parts/auth/src/client.rs b/parts/auth/src/client.rs new file mode 100644 index 0000000..175e6fb --- /dev/null +++ b/parts/auth/src/client.rs @@ -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, +} + +#[derive(Default, Serialize, Deserialize, Clone, Debug)] +pub struct ClientSecrets { + id_token: String, + access_token: String, + refresh_token: String, + expiry: DateTime, +} + +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::("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::("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::("cli")?; + cli.set_command("ppc"); + cli.subcommand(Command::new("login"), move |_, mut mize| { + let mut auth = mize.get_part_native::("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::("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::("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::("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::("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::("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); + + // 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 { + 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 { + #[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 { + 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 { + 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, _> = + 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::("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::().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(()) +} diff --git a/parts/cli/src/lib.rs b/parts/cli/src/lib.rs new file mode 100644 index 0000000..0b9db94 --- /dev/null +++ b/parts/cli/src/lib.rs @@ -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() +} diff --git a/parts/mize.dev/src/generated.rs b/parts/mize.dev/src/generated.rs new file mode 100644 index 0000000..f36c1e0 --- /dev/null +++ b/parts/mize.dev/src/generated.rs @@ -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 { + let mut mize = get_global_mize()?; + let part = mize.get_part(name)?; + Ok(CliPart { id: part.id() }) + } + pub fn run(&self, cmdline: Vec) -> 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 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 { + 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 }) + } +} diff --git a/parts/mize.dev/src/lib.rs b/parts/mize.dev/src/lib.rs new file mode 100644 index 0000000..45c5cb4 --- /dev/null +++ b/parts/mize.dev/src/lib.rs @@ -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(()) +}