/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2026 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::{ io::Cursor, sync::{Arc, Mutex as SyncMutex}, }; use darkfi_serial::{Decodable, Encodable, VarInt}; use kvdb_overlay::Tree; use crate::{ error::{Error, Result}, prop::{ Property, PropertyAtomicGuard, PropertyPtr, PropertySubType, PropertyType, PropertyValue, Role, }, scene::{CallArgType, Pimpl, SceneNode, SceneNodeType, SceneNodeWeak}, ExecutorPtr, }; /// Settings when modified are persisted otherwise they use their default /// as expected by properties. /// /// Settings can be used directly by other nodes or as a dependency which /// can be used in expressions. /// /// Settings are set by the user with `Role::User`. /// /// For example `net.enable_tor` might be implicitly used by `/plugin/darkirc` /// while some other setting might be used in the schema itself with the node /// not being aware its depending on an external property. /// /// In both cases modifying the setting should propagate the changes to that node. /// /// Although the `/setting2` root has no knowledge of property paths underneath there /// is a convention of using `foo.bar.baz` to namespace the settings. pub fn create_setting(name: &str) -> SceneNode { let mut node = SceneNode::new(name, SceneNodeType::Setting); // Example let prop = Property::new("net.enable_tor", PropertyType::Bool, PropertySubType::Null); node.add_property(prop).unwrap(); let mut prop = Property::new("win.scale", PropertyType::Float32, PropertySubType::Null); prop.set_defaults_f32(vec![1.]).unwrap(); prop.set_range_f32(0., f32::MAX); node.add_property(prop).unwrap(); node.add_method( "search", vec![("filter", "Filter string to search keys", CallArgType::Str)], None, ) .unwrap(); node } pub type SettingPtr = Arc; pub struct Setting { tasks: SyncMutex>>, } impl Setting { pub async fn new(node: SceneNodeWeak, db_tree: Tree, ex: ExecutorPtr) -> Pimpl { let node_ref = node.upgrade().unwrap(); // Load any persisted properties from the db. for entry in db_tree.iter() { let (key, data) = entry.unwrap(); let key = String::from_utf8(key).unwrap(); let prop = node_ref.get_property(&key).unwrap(); Self::load_prop(&prop, &data).unwrap(); } // Spawn tasks persisting our properties to the db when they change. let mut tasks = vec![]; for prop in &node_ref.props { let db_tree2 = db_tree.clone(); let prop2 = prop.clone(); let on_modify_sub = prop.subscribe_modify(); let task = ex.spawn(async move { while let Ok((_role, _action, _guard)) = on_modify_sub.receive().await { Self::save_prop(&prop2, &db_tree2).unwrap(); } }); tasks.push(task); } Pimpl::Setting(Arc::new(Self { tasks: SyncMutex::new(tasks) })) } /// Persist the state of a property under its name as the db key. /// The db value is a list of (prop_idx, prop_value) pairs exactly /// specifying which idxs inside the property are set. If none are /// set the key is dropped. fn save_prop(prop: &PropertyPtr, db_tree: &Tree) -> Result<()> { assert!(prop.is_bounded()); let mut data = vec![]; let mut is_set = false; for i in 0..prop.get_len() { if prop.is_unset(i)? { continue } is_set = true; let val = prop.get_value(i)?; VarInt(i as u64).encode(&mut data)?; Self::encode_value(prop, &val, &mut data)?; } if is_set { db_tree.insert(prop.name.as_bytes(), &data)?; } else { db_tree.remove(prop.name.as_bytes())?; } Ok(()) } /// Serialize a single value. The property type determines the binary /// format of the value itself. If the property allows null values then /// it is written as an option (`Option`): a single tag byte followed /// by the value only when it is not null. fn encode_value(prop: &Property, val: &PropertyValue, data: &mut Vec) -> Result<()> { if prop.is_null_allowed { match val { PropertyValue::Null => { false.encode(data)?; } val => { true.encode(data)?; val.encode(data)?; } } } else { val.encode(data)?; } Ok(()) } /// Restore a property state previously written by `Self::save_prop()`. fn load_prop(prop: &PropertyPtr, data: &[u8]) -> Result<()> { assert!(prop.is_bounded()); let mut cur = Cursor::new(data); let atom = &mut PropertyAtomicGuard::none(); while (cur.position() as usize) < data.len() { let i = VarInt::decode(&mut cur)?.0 as usize; let val = Self::decode_value(prop, &mut cur)?; Self::apply_value(prop, atom, i, val)?; } Ok(()) } /// Decode a value serialized by `Self::encode_value()`. fn decode_value(prop: &Property, cur: &mut Cursor<&[u8]>) -> Result { macro_rules! decode_ty { ($typ:ty, $variant:ident) => {{ if prop.is_null_allowed { match Option::<$typ>::decode(cur)? { Some(v) => PropertyValue::$variant(v), None => PropertyValue::Null, } } else { PropertyValue::$variant(<$typ>::decode(cur)?) } }}; } let val = match prop.typ { PropertyType::Bool => decode_ty!(bool, Bool), PropertyType::Uint32 => decode_ty!(u32, Uint32), PropertyType::Float32 => decode_ty!(f32, Float32), PropertyType::Str => decode_ty!(String, Str), PropertyType::Enum => decode_ty!(String, Enum), PropertyType::SceneNodeId => decode_ty!(u32, SceneNodeId), PropertyType::Null | PropertyType::SExpr => return Err(Error::PropertyWrongType), }; Ok(val) } /// Set the value at index `i` as `Role::User`. fn apply_value( prop: &PropertyPtr, atom: &mut PropertyAtomicGuard, i: usize, val: PropertyValue, ) -> Result<()> { match val { PropertyValue::Bool(v) => prop.set_bool(atom, Role::User, i, v), PropertyValue::Uint32(v) => prop.set_u32(atom, Role::User, i, v), PropertyValue::Float32(v) => prop.set_f32(atom, Role::User, i, v), PropertyValue::Str(v) => prop.set_str(atom, Role::User, i, v), PropertyValue::Enum(v) => prop.set_enum(atom, Role::User, i, v), PropertyValue::SceneNodeId(v) => prop.set_node_id(atom, Role::User, i, v), PropertyValue::Null => prop.set_null(atom, Role::User, i), PropertyValue::Unset | PropertyValue::SExpr(_) => Err(Error::PropertyWrongType), } } } impl Drop for Setting { fn drop(&mut self) { self.tasks.lock().unwrap().clear(); } }