浏览代码

app: add new /setting node impl. all changes to properties belonging to this node are persisted.

darkfi 1 天之前
父节点
当前提交
75a91c8bae

+ 9 - 19
bin/app/src/app/mod.rs

@@ -25,14 +25,12 @@ use std::sync::{
 
 #[cfg(target_os = "android")]
 use crate::android;
-
-#[cfg(any(feature = "enable-plugin-darkirc", feature = "enable-plugin-fud"))]
-use crate::plugin::PluginSettings;
 use crate::{
     error::Error,
     gfx::{EpochIndex, GraphicsEventPublisherPtr, Renderer},
-    prop::{PropertyAtomicGuard, PropertyValue, Role},
-    scene::{Pimpl, SceneNode, SceneNodePtr, SceneNodeType},
+    prop::{PropertyAtomicGuard, Role},
+    scene::{Pimpl, SceneNodePtr},
+    setting::{create_setting, Setting},
     sfx,
     ui::{RedrawTrigger, Window},
     util::i18n::I18nBabelFish,
@@ -44,7 +42,6 @@ use locale::read_locale_ftl;
 mod node;
 use node::create_window;
 pub mod schema;
-use schema::get_settingsdb_path;
 
 macro_rules! d { ($($arg:tt)*) => { debug!(target: "app", $($arg)*); } }
 macro_rules! t { ($($arg:tt)*) => { trace!(target: "app", $($arg)*); } }
@@ -95,8 +92,6 @@ impl App {
     pub async fn setup(&self, db: Database) -> Result<Option<i32>, Error> {
         t!("App::setup()");
 
-        let setting_root = SceneNode::new("setting", SceneNodeType::SettingRoot);
-        let setting_root = setting_root.setup_null();
         let settings_tree = db.open_tree_default("settings").unwrap();
         let flags_tree = db.open_tree_default("app_flags").unwrap();
         let is_first_time = !flags_tree.contains_key(IS_FIRST_TIME_KEY).unwrap();
@@ -104,13 +99,12 @@ impl App {
             flags_tree.insert(IS_FIRST_TIME_KEY, b"").unwrap();
         }
         self.is_first_time.store(is_first_time, Ordering::Relaxed);
-        // Commenting this out since it doesnt compile when enable-plugins isnt enabled.
-        /*
-        let settings = Arc::new(PluginSettings {
-            setting_root: setting_root.clone(),
-            kvdb_tree: settings_tree,
-        });
-        */
+
+        let ex = self.ex.clone();
+        let setting = create_setting("setting");
+        let setting =
+            setting.setup(|me| async move { Setting::new(me, settings_tree, ex).await }).await;
+        self.sg_root.link(setting);
 
         let i18n_fish = self.setup_locale();
 
@@ -144,7 +138,6 @@ impl App {
                     me,
                     self.renderer.clone(),
                     i18n_fish.clone(),
-                    setting_root.clone(),
                     self.redraw_trigger.clone(),
                     self.redraw_rx.clone(),
                 )
@@ -152,7 +145,6 @@ impl App {
             .await;
 
         self.sg_root.link(window.clone());
-        self.sg_root.link(setting_root.clone());
 
         #[cfg(feature = "schema-app")]
         schema::make(&self, window.clone(), &i18n_fish, db).await;
@@ -169,8 +161,6 @@ impl App {
         #[cfg(all(feature = "schema-app", feature = "schema-test"))]
         compile_error!("Only one schema can be selected");
 
-        //settings::make(&self, window, self.ex.clone()).await;
-
         d!("Schema loaded");
 
         Ok(None)

+ 0 - 187
bin/app/src/app/schema/mod.rs

@@ -40,7 +40,6 @@ use crate::{
 mod chat;
 pub mod menu;
 use menu::channel::Channel;
-//mod settings;
 pub mod test;
 pub mod test_edit;
 pub mod test_scroll_layer;
@@ -80,10 +79,6 @@ mod ui_consts {
         get_external_storage_path().join("chatdb")
     }
 
-    pub fn get_settingsdb_path() -> PathBuf {
-        get_appdata_path().join("settings")
-    }
-
     pub fn get_main_db_path() -> PathBuf {
         get_appdata_path().join("db")
     }
@@ -104,10 +99,6 @@ mod desktop_paths {
         dirs::data_local_dir().unwrap().join("darkfi/app/chatdb")
     }
 
-    pub fn get_settingsdb_path() -> PathBuf {
-        dirs::cache_dir().unwrap().join("darkfi/app/settings")
-    }
-
     pub fn get_main_db_path() -> PathBuf {
         dirs::data_local_dir().unwrap().join("darkfi/app/db")
     }
@@ -226,93 +217,6 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish, db
         0,
     )
     .unwrap();
-    /*
-    let node = create_shortcut("zoom_out_shortcut");
-    node.set_property_str(atom, Role::App, "key", "ctrl+-").unwrap();
-    // Not sure what was eating my keys. This is a workaround.
-    node.set_property_u32(atom, Role::App, "priority", 10).unwrap();
-    let (slot, recvr) = Slot::new("zoom_out_pressed");
-    node.register("shortcut", slot).unwrap();
-    let window_scale = app.sg_root.lookup_node("/setting/scale").unwrap();
-    let window_scale2 = window_scale.clone();
-    let redraw = app.redraw_trigger.clone();
-    let listen_zoom = app.ex.spawn(async move {
-        while let Ok(_) = recvr.recv().await {
-            let scale = 0.9 * window_scale2.get_property_f32("value").unwrap();
-
-            let filename = get_window_scale_filename();
-            if let Some(parent) = filename.parent() {
-                let _ = std::fs::create_dir_all(parent);
-            }
-            if let Ok(mut file) = File::create(filename) {
-                scale.encode(&mut file).unwrap();
-            }
-
-            let atom = &mut redraw.make_guard(gfxtag!("zoom_out shortcut"));
-            window_scale2.set_property_f32(atom, Role::User, "value", scale).unwrap();
-        }
-    });
-    app.tasks.lock().unwrap().push(listen_zoom);
-    let node = node.setup(|me| Shortcut::new(me)).await;
-    window.link(node);
-
-    let node = create_shortcut("zoom_in_shortcut");
-    node.set_property_str(atom, Role::App, "key", "ctrl+=").unwrap();
-    // Not sure what was eating my keys. This is a workaround.
-    node.set_property_u32(atom, Role::App, "priority", 10).unwrap();
-    let (slot, recvr) = Slot::new("zoom_in_pressed");
-    node.register("shortcut", slot).unwrap();
-    let window_scale2 = window_scale.clone();
-    let redraw = app.redraw_trigger.clone();
-    let listen_zoom = app.ex.spawn(async move {
-        while let Ok(_) = recvr.recv().await {
-            let scale = 1.1 * window_scale2.get_property_f32("value").unwrap();
-
-            let filename = get_window_scale_filename();
-            if let Some(parent) = filename.parent() {
-                let _ = std::fs::create_dir_all(parent);
-            }
-            if let Ok(mut file) = File::create(filename) {
-                scale.encode(&mut file).unwrap();
-            }
-
-            let atom = &mut redraw.make_guard(gfxtag!("zoom_in shortcut"));
-            window_scale2.set_property_f32(atom, Role::User, "value", scale).unwrap();
-        }
-    });
-    app.tasks.lock().unwrap().push(listen_zoom);
-    let node = node.setup(|me| Shortcut::new(me)).await;
-    window.link(node);
-    */
-
-    /*
-    let node = create_gesture("zoom_gesture");
-    node.set_property_u32(atom, Role::App, "priority", 10).unwrap();
-    let (slot, recvr) = Slot::new("zoom_gesture");
-    node.register("gesture", slot).unwrap();
-    let listen_zoom = app.ex.spawn(async move {
-        while let Ok(data) = recvr.recv().await {
-            let distance: f32 = deserialize(&data).unwrap();
-            // Dampen it a little
-            let r = (distance - 1.) / 2. + 1.;
-            let scale = r * window_scale.get_property_f32("value").unwrap();
-
-            let filename = get_window_scale_filename();
-            if let Some(parent) = filename.parent() {
-                let _ = std::fs::create_dir_all(parent);
-            }
-            if let Ok(mut file) = File::create(filename) {
-                scale.encode(&mut file).unwrap();
-            }
-
-            let atom = &mut PropertyAtomicGuard::new();
-            window_scale.set_property_f32(atom, Role::User, "value", scale);
-        }
-    });
-    app.tasks.lock().unwrap().push(listen_zoom);
-    let node = node.setup(|me| Gesture::new(me)).await;
-    window.link(node);
-    */
 
     // Root content layer
     let content = create_layer("content");
@@ -423,97 +327,6 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish, db
         window.link(node);
     }
 
-    // Navbar Settings Button
-
-    /*
-    // Layer
-    let settingslayer_node = create_layer("settings_button_layer");
-    let prop = settingslayer_node.get_property("rect").unwrap();
-    let code = cc.compile("w - NETSTATUS_ICON_SIZE - SETTINGS_ICON_SIZE").unwrap();
-    prop.set_expr(atom, Role::App, 0, code).unwrap();
-    prop.set_f32(atom, Role::App, 1, 0.).unwrap();
-    prop.set_f32(atom, Role::App, 2, 1000.).unwrap();
-    prop.set_f32(atom, Role::App, 3, 1000.).unwrap();
-    settingslayer_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
-    settingslayer_node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
-    let settingslayer_node =
-        settingslayer_node.setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    content.link(settingslayer_node.clone());
-
-    // Background
-    let node = create_vector_art("settings_btn_bg");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, NETSTATUS_ICON_SIZE / 2.).unwrap();
-    prop.set_f32(atom, Role::App, 1, NETSTATUS_ICON_SIZE / 2.).unwrap();
-    prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
-    prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
-    node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
-    node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
-    let shape = shape::create_settings([0., 0.94, 1., 1.]).scaled(20.);
-    let node =
-        node.setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    settingslayer_node.link(node);
-
-    // Button
-    let node = create_button("settings_btn");
-    node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
-    prop.set_f32(atom, Role::App, 1, 0.).unwrap();
-    prop.set_f32(atom, Role::App, 2, NETSTATUS_ICON_SIZE).unwrap();
-    prop.set_f32(atom, Role::App, 3, NETSTATUS_ICON_SIZE).unwrap();
-
-    let sg_root = app.sg_root.clone();
-    let settings = move || {
-        info!(target: "app::chat", "clicked settings");
-        let atom = &mut PropertyAtomicGuard::new();
-
-        // Hide all relevant window children nodes
-        // Messy.
-        //
-        // Some suggestions:
-        //  1. Something closer to a router, that would be a accessible globally,
-        //  which essentially holds a vector of references to SceneNodes
-        //  representing the app navigation history.
-        //  When the user changes the route, it would make invisible (or later remove
-        //  elements from the tree for optimization purposes) the node of the last SceneNodes
-        //  in the vector and all its children, recursively;
-        //  and append a new SceneNode pointer, which is the new "route" chosen by the user,
-        //  and draw it and its children recursively.
-        //  Note that this would implicitly handle nested routes (like
-        //  /window/somewhere1/view1 to /window/somewhere1/view2, if the last element of the
-        //  router currently points to view1 and we call router.goto("./view2")).
-        //
-        //  2. Support of wildcard in lookups in .get_children() or another method, like this "*_chat_layer".
-        let windows = sg_root.lookup_node("/window/content/chat").unwrap().get_children();
-        let target_substrings = vec!["_chat_layer", "menu_layer"];
-        for node in windows.iter() {
-            if target_substrings.iter().any(|&s| node.name.contains(s)) {
-                if let Err(e) = node.set_property_bool(atom, Role::App, "is_visible", false) {
-                    debug!("Failed to set property 'is_visible' on node: {:?}", e);
-                }
-            }
-        }
-
-        // Show settings
-        let settings_node = sg_root.lookup_node("/window/content/settings_layer").unwrap();
-        settings_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
-    };
-
-    let (slot, recvr) = Slot::new("settings_clicked");
-    node.register("click", slot).unwrap();
-    let settings2 = settings.clone();
-    let listen_click = app.ex.spawn(async move {
-        while let Ok(_) = recvr.recv().await {
-            settings2();
-        }
-    });
-    app.tasks.lock().unwrap().push(listen_click);
-
-    let node = node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    settingslayer_node.link(node);
-    */
-
     let emoji_meshes = emoji_picker::EmojiMeshes::new(app.renderer.clone(), EMOJI_PICKER_ICON_SIZE);
 
     let emoji_meshes2 = emoji_meshes.clone();

+ 9 - 0
bin/app/src/error.rs

@@ -140,6 +140,9 @@ pub enum Error {
 
     #[error("Contact not found")]
     ContactNotFound = 47,
+
+    #[error("Serialization error")]
+    SerialErr = 48,
 }
 
 impl From<kvdb_overlay::Error> for Error {
@@ -147,3 +150,9 @@ impl From<kvdb_overlay::Error> for Error {
         Error::KvdbErr
     }
 }
+
+impl From<std::io::Error> for Error {
+    fn from(_: std::io::Error) -> Error {
+        Error::SerialErr
+    }
+}

+ 1 - 0
bin/app/src/main.rs

@@ -48,6 +48,7 @@ mod pubsub;
 //mod py;
 //mod ringbuf;
 mod scene;
+mod setting;
 mod sfx;
 mod shape;
 mod text;

+ 1 - 44
bin/app/src/plugin/darkirc.rs

@@ -59,7 +59,7 @@ use crate::{
     },
     error::{Error, Result},
     prop::{BatchGuardPtr, PropertyAtomicGuard, PropertyPtr, PropertyStr, Role},
-    scene::{MethodCallSub, Pimpl, SceneNode, SceneNodePtr, SceneNodeType, SceneNodeWeak, Slot},
+    scene::{MethodCallSub, Pimpl, SceneNodePtr, SceneNodeWeak, Slot},
     ui::{
         chatview::{MessageId, Timestamp},
         OnModify,
@@ -67,8 +67,6 @@ use crate::{
     ExecutorPtr,
 };
 
-use super::PluginSettings;
-
 const P2P_RETRY_TIME: u64 = 20;
 const COOLOFF_SLEEP_TIME: u64 = 20;
 const COOLOFF_SYNC_ATTEMPTS: usize = 6;
@@ -178,7 +176,6 @@ pub struct DarkIrc {
     contacts_tree: Tree,
     nick_tree: Tree,
     dm_secret: SecretKey,
-    settings: PluginSettings,
     ex: ExecutorPtr,
 }
 
@@ -192,14 +189,8 @@ impl DarkIrc {
         let node_ref = &node.upgrade().unwrap();
         let nick = PropertyStr::wrap(node_ref, Role::Internal, "nick", 0).unwrap();
 
-        let setting_root = Arc::new(SceneNode::new("setting", SceneNodeType::SettingRoot));
-        node_ref.link(setting_root.clone());
-
         i!("Starting DarkIRC backend");
 
-        let setting_tree = db.open_tree_default("settings")?;
-        i!("Opened darkirc_settings tree from unified db");
-
         // Use the unified db for reading channels (UI stores channels there)
         let channels_tree = db.open_tree_default("channels")?;
         i!("Opened channels tree from unified db");
@@ -223,8 +214,6 @@ impl DarkIrc {
             .unwrap();
         i!("DM identity public key (share with contacts): {dm_public_b58}");
 
-        let settings = PluginSettings { setting_root, kvdb_tree: setting_tree };
-
         let mut p2p_settings: NetSettings = Default::default();
         p2p_settings.magic_bytes = MagicBytes([251, 229, 199, 181]);
         p2p_settings.app_version = semver::Version::parse("0.5.0").unwrap();
@@ -266,11 +255,6 @@ impl DarkIrc {
         p2p_settings.p2p_datastore = p2p_datastore_path().into_os_string().into_string().ok();
         p2p_settings.hostlist = hostlist_path().into_os_string().into_string().ok();
 
-        settings.add_p2p_settings(&p2p_settings);
-
-        settings.load_settings();
-        settings.update_p2p_settings(&mut p2p_settings);
-
         let p2p = match P2p::new(p2p_settings.clone(), ex.clone()).await {
             Ok(p2p) => p2p,
             Err(err) => {
@@ -331,7 +315,6 @@ impl DarkIrc {
             nick_tree,
             dm_secret,
 
-            settings,
             ex: ex.clone(),
         });
 
@@ -887,14 +870,6 @@ impl DarkIrc {
         true
     }
 
-    async fn apply_settings(self_: Arc<Self>, _: BatchGuardPtr) {
-        self_.settings.save_settings();
-
-        let p2p_settings = self_.p2p.settings();
-        let mut write_guard = p2p_settings.write().await;
-        self_.settings.update_p2p_settings(&mut write_guard);
-    }
-
     async fn process_reconnect(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
         let Ok(method_call) = sub.receive().await else {
             d!("Reconnect method closed");
@@ -936,16 +911,6 @@ impl DarkIrc {
 
         p2p_settings.write().await.outbound_connections = count;
         self.p2p.clone().reload().await;
-
-        let setting = self.settings.get_setting("net.outbound_connections").unwrap();
-        setting
-            .set_property_u32(
-                &mut PropertyAtomicGuard::none(),
-                Role::Internal,
-                "value",
-                count as u32,
-            )
-            .unwrap();
     }
 
     async fn start(self: Arc<Self>, sg_root: SceneNodePtr, ex: ExecutorPtr) {
@@ -988,14 +953,6 @@ impl DarkIrc {
         }
         on_modify.when_change(self.nick.prop(), save_nick);
 
-        // `apply_settings` is triggered if any setting changes
-        for setting_node in self.settings.setting_root.get_children().iter() {
-            on_modify.when_change(
-                setting_node.get_property("value").clone().unwrap(),
-                Self::apply_settings,
-            );
-        }
-
         let ev_sub = self.event_graph.event_subscribe().await;
         let ev_task = ex.spawn(self.clone().relay_events(ev_sub));
 

+ 1 - 9
bin/app/src/plugin/drk.rs

@@ -35,8 +35,7 @@ use url::Url;
 
 use crate::{
     error::{Error, Result},
-    prop::BatchGuardPtr,
-    scene::{MethodCallSub, Pimpl, SceneNode, SceneNodePtr, SceneNodeType, SceneNodeWeak},
+    scene::{MethodCallSub, Pimpl, SceneNodePtr, SceneNodeWeak},
     ExecutorPtr,
 };
 
@@ -133,9 +132,6 @@ impl DrkPlugin {
     pub async fn new(node: SceneNodeWeak, sg_root: SceneNodePtr, ex: ExecutorPtr) -> Result<Pimpl> {
         let node_ref = node.upgrade().unwrap();
 
-        let setting_root = Arc::new(SceneNode::new("setting", SceneNodeType::SettingRoot));
-        node_ref.link(setting_root.clone());
-
         let endpoint = Url::parse(DARKFID_ENDPOINT).unwrap();
 
         let drk = match Drk::new(
@@ -280,10 +276,6 @@ impl DrkPlugin {
         Ok(Pimpl::Drk(self_))
     }
 
-    async fn apply_settings(_self: Arc<Self>, _batch: BatchGuardPtr) {
-        // TODO
-    }
-
     pub async fn get_default_address(&self) -> Result<String> {
         let drk = self.drk.read().await;
         let pubkey = drk.default_address().await.map_err(|e| {

+ 4 - 44
bin/app/src/plugin/fud.rs

@@ -45,16 +45,12 @@ use url::Url;
 
 use crate::{
     error::{Error, Result},
-    prop::{BatchGuardPtr, PropertyAtomicGuard, PropertyBool, Role},
-    scene::{
-        MethodCall, MethodCallSub, Pimpl, SceneNode, SceneNodePtr, SceneNodeType, SceneNodeWeak,
-    },
-    ui::{chatview::FileMessageStatus, OnModify},
+    prop::{PropertyAtomicGuard, PropertyBool, Role},
+    scene::{MethodCall, MethodCallSub, Pimpl, SceneNodePtr, SceneNodeWeak},
+    ui::chatview::FileMessageStatus,
     ExecutorPtr,
 };
 
-use super::PluginSettings;
-
 const P2P_RETRY_TIME: u64 = 20;
 
 #[cfg(target_os = "android")]
@@ -127,8 +123,6 @@ pub struct FudPlugin {
     fud: Arc<Fud>,
 
     tracked_files: Arc<Mutex<HashSet<Url>>>,
-
-    settings: PluginSettings,
 }
 
 impl FudPlugin {
@@ -138,9 +132,6 @@ impl FudPlugin {
         let fud_ready = PropertyBool::wrap(node_ref, Role::Internal, "ready", 0).unwrap();
         fud_ready.set(&mut PropertyAtomicGuard::none(), false);
 
-        let setting_root = Arc::new(SceneNode::new("setting", SceneNodeType::SettingRoot));
-        node_ref.clone().link(setting_root.clone());
-
         let basedir = get_base_path();
 
         i!("Starting Fud backend");
@@ -153,9 +144,6 @@ impl FudPlugin {
             }
         };
 
-        let setting_tree = db.open_tree_default("settings")?;
-        let settings = PluginSettings { setting_root, kvdb_tree: setting_tree };
-
         let mut fud_settings: FudSettings = Default::default();
         fud_settings.base_dir = basedir.to_string_lossy().to_string();
         let mut p2p_settings: NetSettings = Default::default();
@@ -242,12 +230,6 @@ impl FudPlugin {
         p2p_settings.p2p_datastore = p2p_datastore_path().into_os_string().into_string().ok();
         p2p_settings.hostlist = hostlist_path().into_os_string().into_string().ok();
 
-        settings.add_p2p_settings(&p2p_settings);
-        // TODO: add other fud settings
-
-        settings.load_settings();
-        settings.update_p2p_settings(&mut p2p_settings);
-
         let p2p = match P2p::new(p2p_settings.clone(), ex.clone()).await {
             Ok(p2p) => p2p,
             Err(err) => {
@@ -276,22 +258,11 @@ impl FudPlugin {
             event_pub,
             fud,
             tracked_files: Arc::new(Mutex::new(HashSet::new())),
-            settings,
         });
         self_.clone().start(ex).await;
         Ok(Pimpl::Fud(self_))
     }
 
-    async fn apply_settings(self_: Arc<Self>, _batch: BatchGuardPtr) {
-        self_.settings.save_settings();
-
-        let p2p_settings = self_.p2p.settings();
-        let mut write_guard = p2p_settings.write().await;
-        self_.settings.update_p2p_settings(&mut write_guard);
-
-        // TODO: add other fud settings
-    }
-
     async fn start(self: Arc<Self>, ex: ExecutorPtr) {
         i!("Registering Fud protocol");
         let registry = self.p2p.protocol_registry();
@@ -325,16 +296,6 @@ impl FudPlugin {
             Self::process_events(&me2, event_pub).await;
         });
 
-        let mut on_modify = OnModify::new(ex.clone(), self.node.clone(), me.clone());
-
-        // `apply_settings` is triggered if any setting changes
-        for setting_node in self.settings.setting_root.get_children().iter() {
-            on_modify.when_change(
-                setting_node.get_property("value").clone().unwrap(),
-                Self::apply_settings,
-            );
-        }
-
         let fud = self.fud.clone();
         let start_task = ex.spawn(async move {
             while fud.start().await.is_err() {
@@ -342,8 +303,7 @@ impl FudPlugin {
             }
         });
 
-        let mut tasks = vec![get_method_task, track_file_method_task, ev_task, start_task];
-        tasks.append(&mut on_modify.tasks);
+        let tasks = vec![get_method_task, track_file_method_task, ev_task, start_task];
         self.tasks.set(tasks).unwrap();
 
         i!("Starting Fud P2P");

+ 0 - 292
bin/app/src/plugin/mod.rs

@@ -16,9 +16,6 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use kvdb_overlay::Tree;
-use std::{array::TryFromSliceError, string::FromUtf8Error, sync::Arc};
-
 #[cfg(feature = "enable-plugin-darkirc")]
 pub mod darkirc;
 #[cfg(feature = "enable-plugin-darkirc")]
@@ -40,292 +37,3 @@ pub use darkirc::DarkIrc;
 pub use drk::DrkPlugin;
 #[cfg(feature = "enable-plugin-fud")]
 pub use fud::FudPlugin;
-
-#[cfg(any(feature = "enable-plugin-darkirc", feature = "enable-plugin-fud"))]
-use darkfi::net::Settings as NetSettings;
-
-#[cfg(any(feature = "enable-plugin-darkirc", feature = "enable-plugin-fud"))]
-use crate::{
-    prop::{Property, PropertyAtomicGuard, PropertySubType, PropertyType, PropertyValue, Role},
-    scene::{SceneNode, SceneNodePtr, SceneNodeType},
-};
-
-#[cfg(any(feature = "enable-plugin-darkirc", feature = "enable-plugin-fud"))]
-pub struct PluginSettings {
-    pub setting_root: SceneNodePtr,
-    pub kvdb_tree: Tree,
-}
-
-#[cfg(any(feature = "enable-plugin-darkirc", feature = "enable-plugin-fud"))]
-impl PluginSettings {
-    pub fn add_setting(&self, name: &str, default: PropertyValue) -> Option<SceneNodePtr> {
-        let atom = &mut PropertyAtomicGuard::none();
-        let node = match default {
-            PropertyValue::Bool(b) => {
-                let mut node = SceneNode::new(name, SceneNodeType::Setting);
-                let prop = Property::new("value", PropertyType::Bool, PropertySubType::Null);
-                node.add_property(prop).unwrap();
-                let prop = Property::new("default", PropertyType::Bool, PropertySubType::Null);
-                node.add_property(prop).unwrap();
-                node.set_property_bool(atom, Role::User, "value", b.clone()).unwrap();
-                node.set_property_bool(atom, Role::App, "default", b.clone()).unwrap();
-                Some(node)
-            }
-            PropertyValue::Uint32(u) => {
-                let mut node = SceneNode::new(name, SceneNodeType::Setting);
-                let prop = Property::new("value", PropertyType::Uint32, PropertySubType::Null);
-                node.add_property(prop).unwrap();
-                let prop = Property::new("default", PropertyType::Uint32, PropertySubType::Null);
-                node.add_property(prop).unwrap();
-                node.set_property_u32(atom, Role::User, "value", u.clone()).unwrap();
-                node.set_property_u32(atom, Role::App, "default", u.clone()).unwrap();
-                Some(node)
-            }
-            PropertyValue::Float32(f) => {
-                let mut node = SceneNode::new(name, SceneNodeType::Setting);
-                let prop = Property::new("value", PropertyType::Float32, PropertySubType::Null);
-                node.add_property(prop).unwrap();
-                let prop = Property::new("default", PropertyType::Float32, PropertySubType::Null);
-                node.add_property(prop).unwrap();
-                node.set_property_f32(atom, Role::User, "value", f.clone()).unwrap();
-                node.set_property_f32(atom, Role::App, "default", f.clone()).unwrap();
-                Some(node)
-            }
-            PropertyValue::Str(s) => {
-                let mut node = SceneNode::new(name, SceneNodeType::Setting);
-                let prop = Property::new("value", PropertyType::Str, PropertySubType::Null);
-                node.add_property(prop).unwrap();
-                let prop = Property::new("default", PropertyType::Str, PropertySubType::Null);
-                node.add_property(prop).unwrap();
-                node.set_property_str(atom, Role::User, "value", s.clone()).unwrap();
-                node.set_property_str(atom, Role::App, "default", s.clone()).unwrap();
-                Some(node)
-            }
-            _ => None,
-        };
-
-        match node {
-            Some(n) => {
-                let node_ptr = Arc::new(n);
-                self.setting_root.link(node_ptr.clone().into());
-                Some(node_ptr)
-            }
-            None => None,
-        }
-    }
-
-    // For all settings, copy the value from kvdb into the setting node's value property
-    pub fn load_settings(&self) {
-        let atom = &mut PropertyAtomicGuard::none();
-        for setting_node in self.setting_root.get_children().iter() {
-            if setting_node.typ != SceneNodeType::Setting {
-                continue
-            }
-
-            let value = setting_node.get_property("value").clone().unwrap();
-            match value.typ {
-                PropertyType::Bool => {
-                    let kvdb_result = self.kvdb_tree.get(setting_node.name.as_bytes());
-                    if let Ok(Some(kvdb_value)) = kvdb_result {
-                        setting_node
-                            .set_property_bool(atom, Role::User, "value", kvdb_value[0] != 0)
-                            .unwrap();
-                    }
-                }
-                PropertyType::Uint32 => {
-                    let kvdb_result = self.kvdb_tree.get(setting_node.name.as_bytes());
-                    if let Ok(Some(kvdb_value)) = kvdb_result {
-                        if kvdb_value.len() == 4 {
-                            if let Ok(b) = kvdb_value.try_into() {
-                                setting_node
-                                    .set_property_u32(
-                                        atom,
-                                        Role::User,
-                                        "value",
-                                        u32::from_le_bytes(b),
-                                    )
-                                    .unwrap();
-                            }
-                        }
-                    }
-                }
-                PropertyType::Float32 => {
-                    let kvdb_result = self.kvdb_tree.get(setting_node.name.as_bytes());
-                    if let Ok(Some(kvdb_value)) = kvdb_result {
-                        if kvdb_value.len() == 4 {
-                            if let Ok(b) = kvdb_value.try_into() {
-                                setting_node
-                                    .set_property_f32(
-                                        atom,
-                                        Role::User,
-                                        "value",
-                                        f32::from_le_bytes(b),
-                                    )
-                                    .unwrap();
-                            }
-                        }
-                    }
-                }
-                PropertyType::Str => {
-                    let kvdb_result = self.kvdb_tree.get(setting_node.name.as_bytes());
-                    if let Ok(Some(kvdb_value)) = kvdb_result {
-                        let string: Result<String, FromUtf8Error> =
-                            String::from_utf8(kvdb_value.to_vec());
-                        if let Ok(s) = string {
-                            setting_node.set_property_str(atom, Role::User, "value", s).unwrap();
-                        }
-                    }
-                }
-                _ => {}
-            }
-        }
-    }
-
-    // Save all settings to kvdb
-    pub fn save_settings(&self) {
-        for setting_node in self.setting_root.get_children().iter() {
-            if setting_node.typ != SceneNodeType::Setting {
-                continue
-            }
-
-            let value = setting_node.get_property("value").clone().unwrap();
-            match value.typ {
-                PropertyType::Bool => {
-                    let value_bytes = if value.get_bool(0).unwrap() { 1u8 } else { 0u8 };
-                    self.kvdb_tree
-                        .insert(setting_node.name.as_bytes(), &vec![value_bytes])
-                        .unwrap();
-                }
-                PropertyType::Uint32 => {
-                    self.kvdb_tree
-                        .insert(
-                            setting_node.name.as_bytes(),
-                            value.get_u32(0).unwrap().to_le_bytes().as_ref(),
-                        )
-                        .unwrap();
-                }
-                PropertyType::Float32 => {
-                    self.kvdb_tree
-                        .insert(
-                            setting_node.name.as_bytes(),
-                            value.get_f32(0).unwrap().to_le_bytes().as_ref(),
-                        )
-                        .unwrap();
-                }
-                PropertyType::Str => {
-                    self.kvdb_tree
-                        .insert(setting_node.name.as_bytes(), value.get_str(0).unwrap().as_bytes())
-                        .unwrap();
-                }
-                _ => {}
-            }
-        }
-    }
-
-    pub fn get_setting(&self, name: &str) -> Option<SceneNodePtr> {
-        self.setting_root
-            .clone()
-            .get_children()
-            .iter()
-            .find(|node| node.typ == SceneNodeType::Setting && node.name == name)
-            .cloned()
-    }
-
-    pub fn add_p2p_settings(&self, p2p_settings: &NetSettings) {
-        self.add_setting(
-            "net.outbound_connections",
-            PropertyValue::Uint32(p2p_settings.outbound_connections as u32),
-        );
-        self.add_setting(
-            "net.inbound_connections",
-            PropertyValue::Uint32(p2p_settings.inbound_connections as u32),
-        );
-        //TODO: Update this when multiple active_profiles at a time is supported
-        self.add_setting(
-            "net.outbound_connect_timeout",
-            PropertyValue::Uint32(
-                p2p_settings
-                    .outbound_connect_timeout(&p2p_settings.active_profiles.first().unwrap())
-                    as u32,
-            ),
-        );
-        self.add_setting(
-            "net.channel_handshake_timeout",
-            PropertyValue::Uint32(
-                p2p_settings
-                    .channel_handshake_timeout(&p2p_settings.active_profiles.first().unwrap())
-                    as u32,
-            ),
-        );
-        self.add_setting(
-            "net.channel_heartbeat_interval",
-            PropertyValue::Uint32(
-                p2p_settings
-                    .channel_heartbeat_interval(&p2p_settings.active_profiles.first().unwrap())
-                    as u32,
-            ),
-        );
-        self.add_setting(
-            "net.outbound_peer_discovery_cooloff_time",
-            PropertyValue::Uint32(p2p_settings.outbound_peer_discovery_cooloff_time as u32),
-        );
-        self.add_setting("net.localnet", PropertyValue::Bool(p2p_settings.localnet));
-        self.add_setting(
-            "net.greylist_refinery_interval",
-            PropertyValue::Uint32(p2p_settings.greylist_refinery_interval as u32),
-        );
-        self.add_setting(
-            "net.time_with_no_connections",
-            PropertyValue::Uint32(p2p_settings.time_with_no_connections as u32),
-        );
-    }
-
-    // Update a NetSettings from settings in the node tree
-    pub fn update_p2p_settings(&self, p2p_settings: &mut NetSettings) {
-        p2p_settings.outbound_connections = self
-            .get_setting("net.outbound_connections")
-            .unwrap()
-            .get_property_u32("value")
-            .unwrap() as usize;
-        p2p_settings.inbound_connections =
-            self.get_setting("net.inbound_connections").unwrap().get_property_u32("value").unwrap()
-                as usize;
-        //TODO: Update this when multiple active_profiles at a time is supported
-        if let Some(profile) =
-            p2p_settings.profiles.get_mut(p2p_settings.active_profiles.first().unwrap())
-        {
-            profile.outbound_connect_timeout = self
-                .get_setting("net.outbound_connect_timeout")
-                .unwrap()
-                .get_property_u32("value")
-                .unwrap() as u64;
-            profile.channel_handshake_timeout = self
-                .get_setting("net.channel_handshake_timeout")
-                .unwrap()
-                .get_property_u32("value")
-                .unwrap() as u64;
-            profile.channel_heartbeat_interval = self
-                .get_setting("net.channel_heartbeat_interval")
-                .unwrap()
-                .get_property_u32("value")
-                .unwrap() as u64;
-        }
-        p2p_settings.outbound_peer_discovery_cooloff_time = self
-            .get_setting("net.outbound_peer_discovery_cooloff_time")
-            .unwrap()
-            .get_property_u32("value")
-            .unwrap() as u64;
-        p2p_settings.localnet =
-            self.get_setting("net.localnet").unwrap().get_property_bool("value").unwrap();
-        p2p_settings.greylist_refinery_interval = self
-            .get_setting("net.greylist_refinery_interval")
-            .unwrap()
-            .get_property_u32("value")
-            .unwrap() as u64;
-        p2p_settings.time_with_no_connections = self
-            .get_setting("net.time_with_no_connections")
-            .unwrap()
-            .get_property_u32("value")
-            .unwrap() as u64;
-    }
-}

+ 4 - 3
bin/app/src/prop/mod.rs

@@ -201,6 +201,7 @@ pub enum ModifyAction {
     Set(usize),
     SetVec,
     SetCache(Vec<usize>),
+    Unset(usize),
     Push(usize),
     Insert(usize),
     Remove(usize, PropertyValue),
@@ -402,7 +403,7 @@ impl Property {
             }
             vals[i] = PropertyValue::Unset;
         }
-        atom.add(self.clone(), role, ModifyAction::Set(i));
+        atom.add(self.clone(), role, ModifyAction::Unset(i));
         Ok(())
     }
 
@@ -978,7 +979,7 @@ impl Property {
         for i in 0..self.get_len() {
             if self.get_str(i).unwrap() == item {
                 self.remove_str(atom, role, i).unwrap();
-                return Some(i);
+                return Some(i)
             }
         }
         None
@@ -1189,7 +1190,7 @@ impl Property {
         for i in 0..self.get_len() {
             if let Ok(item) = self.get_str(i) {
                 if item == s {
-                    return true;
+                    return true
                 }
             }
         }

+ 2 - 1
bin/app/src/scene.rs

@@ -34,6 +34,7 @@ use crate::{
     plugin,
     prop::{Property, PropertyAtomicGuard, PropertyPtr, Role},
     pubsub::{Publisher, PublisherPtr, Subscription},
+    setting::SettingPtr,
     ui,
 };
 
@@ -116,7 +117,6 @@ pub enum SceneNodeType {
     Shortcut = 17,
     Gesture = 18,
     EmojiPicker = 19,
-    SettingRoot = 20,
     Setting = 21,
     Menu = 22,
     TokenTable = 23,
@@ -598,6 +598,7 @@ pub enum Pimpl {
     EmojiPicker(ui::EmojiPickerPtr),
     Menu(ui::MenuPtr),
     TokenTable(ui::TokenTablePtr),
+    Setting(SettingPtr),
     #[cfg(feature = "enable-plugin-darkirc")]
     DarkIrc(plugin::DarkIrcPtr),
     #[cfg(feature = "enable-plugin-fud")]

+ 230 - 0
bin/app/src/setting.rs

@@ -0,0 +1,230 @@
+/* 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, 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<Setting>;
+
+pub struct Setting {
+    tasks: SyncMutex<Vec<smol::Task<()>>>,
+}
+
+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<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 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<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 => 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();
+    }
+}

+ 0 - 1
bin/app/src/ui/win/mod.rs

@@ -81,7 +81,6 @@ impl Window {
         node: SceneNodeWeak,
         renderer: Renderer,
         i18n_fish: I18nBabelFish,
-        _setting_root: SceneNodePtr,
         redraw_tx: RedrawTrigger,
         redraw_rx: async_channel::Receiver<()>,
     ) -> Pimpl {