| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- /* 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 <https://www.gnu.org/licenses/>.
- */
- use std::{
- io::Cursor,
- sync::{Arc, Mutex as SyncMutex},
- };
- use darkfi_serial::{Decodable, Encodable};
- use crate::{
- db::AppDbPtr,
- error::{Error, Result},
- prop::{
- Property, PropertyAtomicGuard, PropertyPermission, PropertyPtr, PropertySubType,
- PropertyType, PropertyValue, Role,
- },
- scene::{CallArgType, Pimpl, SceneNode, SceneNodeType, SceneNodeWeak},
- theme, 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 `/setting` root has no knowledge of property paths underneath there
- /// is a convention of using `foo.bar.baz` to namespace the settings.
- /// Testnet endpoint from drk_config.toml
- const DARKFID_ENDPOINT_TCP: &str = "tcp+tls://node0.testnet.dark.fi:18345";
- /// TODO: replace with the real darkfid tor endpoint
- const DARKFID_ENDPOINT_TOR: &str = "tor://darkfid-tor-placeholder.onion:18345";
- pub fn create_setting(name: &str) -> SceneNode {
- let mut node = SceneNode::new(name, SceneNodeType::Setting);
- let mut prop = Property::new(
- "chat.is_enabled",
- PropertyType::Bool,
- PropertySubType::Flag,
- PropertyPermission::default(),
- );
- prop.set_defaults_bool(vec![true]).unwrap();
- node.add_property(prop).unwrap();
- let mut prop = Property::new(
- "net.transport",
- PropertyType::Enum,
- PropertySubType::Null,
- PropertyPermission::default(),
- );
- prop.set_enum_items(vec!["tcp", "tor"]).unwrap();
- prop.set_default_enum(0, "tcp").unwrap();
- node.add_property(prop).unwrap();
- let mut prop = Property::new(
- "wallet.tcp.rpc",
- PropertyType::Str,
- PropertySubType::Null,
- PropertyPermission::default(),
- );
- prop.set_defaults_str(vec![DARKFID_ENDPOINT_TCP.to_string()]).unwrap();
- node.add_property(prop).unwrap();
- let mut prop = Property::new(
- "wallet.tor.rpc",
- PropertyType::Str,
- PropertySubType::Null,
- PropertyPermission::default(),
- );
- prop.set_defaults_str(vec![DARKFID_ENDPOINT_TOR.to_string()]).unwrap();
- node.add_property(prop).unwrap();
- let mut prop = Property::new(
- "theme",
- PropertyType::Enum,
- PropertySubType::Null,
- PropertyPermission::default(),
- );
- prop.set_enum_items(theme::registry().iter().map(|t| t.name()).collect::<Vec<_>>()).unwrap();
- // Default scifi: the shipped look. A proper Enum variant default
- // (set_default_enum), unlike the Str-on-Enum latent mismatch above.
- prop.set_default_enum(0, theme::DEFAULT_THEME).unwrap();
- node.add_property(prop).unwrap();
- let mut prop = Property::new(
- "win.scale",
- PropertyType::Float32,
- PropertySubType::Null,
- PropertyPermission::default(),
- );
- 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<Setting>;
- pub struct Setting {
- tasks: SyncMutex<Vec<smol::Task<()>>>,
- }
- impl Setting {
- pub async fn new(node: SceneNodeWeak, app_db: AppDbPtr, ex: ExecutorPtr) -> Pimpl {
- let node_ref = node.upgrade().unwrap();
- // Load any persisted properties from the db in a fail-safe way
- let rows = match app_db.settings_all().await {
- Ok(rows) => rows,
- Err(e) => {
- error!(target: "app::setting", "Failed to read settings from db, using defaults: {e}");
- vec![]
- }
- };
- for (name, idx, data) in rows {
- let Some(prop) = node_ref.get_property(&name) else {
- warn!(target: "app::setting", "Skipping unknown persisted setting: {name}");
- continue
- };
- if let Err(e) = Self::load_prop(&prop, idx, &data) {
- warn!(target: "app::setting", "Skipping persisted setting {name}[{idx}]: {e}");
- }
- }
- // Spawn tasks persisting our properties to the db when they change.
- let mut tasks = vec![];
- for prop in &node_ref.props {
- let app_db2 = app_db.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, &app_db2).await.unwrap();
- }
- });
- tasks.push(task);
- }
- Pimpl::Setting(Arc::new(Self { tasks: SyncMutex::new(tasks) }))
- }
- /// Persist the state of a property: one row per set index keyed by
- /// (prop name, idx). Unset indexes have their row deleted.
- async fn save_prop(prop: &PropertyPtr, app_db: &AppDbPtr) -> Result<()> {
- assert!(prop.is_bounded());
- for i in 0..prop.get_len() {
- if prop.is_unset(i)? {
- app_db.setting_remove_idx(&prop.name, i as u32).await?;
- continue
- }
- let val = prop.get_value(i)?;
- let mut data = vec![];
- Self::encode_value(prop, &val, &mut data)?;
- app_db.setting_put(&prop.name, i as u32, "prop", &data).await?;
- }
- 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<X>`): a single tag byte followed
- /// by the value only when it is not null.
- fn encode_value(prop: &Property, val: &PropertyValue, data: &mut Vec<u8>) -> 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 the value of a property at `idx` previously written by
- /// `Self::save_prop()`.
- fn load_prop(prop: &PropertyPtr, idx: u32, data: &[u8]) -> Result<()> {
- assert!(prop.is_bounded());
- let mut cur = Cursor::new(data);
- let atom = &mut PropertyAtomicGuard::none();
- let val = Self::decode_value(prop, &mut cur)?;
- Self::apply_value(prop, atom, idx as usize, val)?;
- Ok(())
- }
- /// Decode a value serialized by `Self::encode_value()`.
- fn decode_value(prop: &Property, cur: &mut Cursor<&[u8]>) -> Result<PropertyValue> {
- 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 | PropertyType::VectorShape => {
- 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(_) | PropertyValue::VectorShape(_) => {
- Err(Error::PropertyWrongType)
- }
- }
- }
- }
- impl Drop for Setting {
- fn drop(&mut self) {
- self.tasks.lock().unwrap().clear();
- }
- }
|