Просмотр исходного кода

bin/darkwiki: make more clean cli command

ghassmo 4 лет назад
Родитель
Сommit
fe552d5158
3 измененных файлов с 304 добавлено и 206 удалено
  1. 70 42
      bin/darkwiki/src/main.rs
  2. 21 22
      bin/darkwikid/src/jsonrpc.rs
  3. 213 142
      bin/darkwikid/src/main.rs

+ 70 - 42
bin/darkwiki/src/main.rs

@@ -12,12 +12,8 @@ use darkfi::{
 #[derive(Clone, Debug, StructOpt)]
 #[structopt(name = "darkwikiupdate")]
 struct Args {
-    #[structopt(long)]
-    /// Merge/Update without applying the changes
-    dry_run: bool,
-    #[structopt(long)]
-    /// Show all patches info
-    log: bool,
+    #[structopt(subcommand)]
+    sub_command: ArgsSubCommand,
     #[structopt(short, parse(from_occurrences))]
     /// Increase verbosity (-vvv supported)
     verbose: u8,
@@ -26,6 +22,31 @@ struct Args {
     endpoint: Url,
 }
 
+#[derive(Debug, Clone, PartialEq, StructOpt)]
+enum ArgsSubCommand {
+    /// Publish local patches and merging received patches
+    Update {
+        #[structopt(long, short)]
+        /// Run without applying the changes
+        dry_run: bool,
+        /// Names of files to update (Note: Will update all the documents if left empty)
+        values: Vec<String>,
+    },
+    /// Show the history of patches  
+    Log {
+        /// Names of files to log (Note: Will show all the log if left empty)
+        values: Vec<String>,
+    },
+    /// Undo the local changes
+    Restore {
+        #[structopt(long, short)]
+        /// Run without applying the changes
+        dry_run: bool,
+        /// Names of files to restore (Note: Will restore all the documents if left empty)
+        values: Vec<String>,
+    },
+}
+
 fn print_patches(value: &Vec<serde_json::Value>) {
     for res in value {
         let res = res.as_array().unwrap();
@@ -46,46 +67,53 @@ async fn main() -> Result<()> {
 
     let rpc_client = RpcClient::new(args.endpoint).await?;
 
-    let req = if args.dry_run {
-        JsonRequest::new("dry_run", json!([]))
-    } else if args.log {
-        JsonRequest::new("log", json!([]))
-    } else {
-        JsonRequest::new("update", json!([]))
-    };
-
-    let result = rpc_client.request(req).await?;
-
-    if !args.log {
-        let result = result.as_array().unwrap();
-        let local_patches = result[0].as_array().unwrap();
-        let sync_patches = result[1].as_array().unwrap();
-        let merge_patches = result[2].as_array().unwrap();
-
-        if !local_patches.is_empty() {
-            println!("");
-            println!("PUBLISH LOCAL PATCHES:");
-            println!("");
-            print_patches(local_patches);
-        }
+    match args.sub_command {
+        ArgsSubCommand::Update { dry_run, values } => {
+            let req = JsonRequest::new("update", json!([dry_run, values]));
 
-        if !sync_patches.is_empty() {
-            println!("");
-            println!("RECEIVED PATCHES:");
-            println!("");
-            print_patches(sync_patches);
-        }
+            let result = rpc_client.request(req).await?;
+
+            let result = result.as_array().unwrap();
+            let local_patches = result[0].as_array().unwrap();
+            let sync_patches = result[1].as_array().unwrap();
+            let merge_patches = result[2].as_array().unwrap();
 
-        if !merge_patches.is_empty() {
-            println!("");
-            println!("MERGE:");
-            println!("");
-            print_patches(merge_patches);
+            if !local_patches.is_empty() {
+                println!();
+                println!("PUBLISH LOCAL PATCHES:");
+                println!();
+                print_patches(local_patches);
+            }
+
+            if !sync_patches.is_empty() {
+                println!();
+                println!("RECEIVED PATCHES:");
+                println!();
+                print_patches(sync_patches);
+            }
+
+            if !merge_patches.is_empty() {
+                println!();
+                println!("MERGE:");
+                println!();
+                print_patches(merge_patches);
+            }
         }
-    }
+        ArgsSubCommand::Restore { dry_run, values } => {
+            let req = JsonRequest::new("restore", json!([dry_run, values]));
+            let result = rpc_client.request(req).await?;
 
-    if args.log {
-        todo!("TODO");
+            let result = result.as_array().unwrap();
+            let patches = result[0].as_array().unwrap();
+
+            if !patches.is_empty() {
+                println!();
+                println!("AFTER RESTORE:");
+                println!();
+                print_patches(patches);
+            }
+        }
+        _ => unimplemented!(),
     }
 
     rpc_client.close().await

+ 21 - 22
bin/darkwikid/src/jsonrpc.rs

@@ -12,7 +12,7 @@ use darkfi::{
 };
 
 pub struct JsonRpcInterface {
-    sender: async_channel::Sender<String>,
+    sender: async_channel::Sender<(String, bool, Vec<String>)>,
     receiver: async_channel::Receiver<Vec<Vec<(String, String)>>>,
 }
 
@@ -27,7 +27,7 @@ impl RequestHandler for JsonRpcInterface {
 
         let rep = match req.method.as_str() {
             Some("update") => self.update(req.id, params).await,
-            Some("dry_run") => self.dry_run(req.id, params).await,
+            Some("restore") => self.restore(req.id, params).await,
             Some("log") => self.log(req.id, params).await,
             Some(_) | None => return JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
         };
@@ -38,18 +38,21 @@ impl RequestHandler for JsonRpcInterface {
 
 impl JsonRpcInterface {
     pub fn new(
-        sender: async_channel::Sender<String>,
+        sender: async_channel::Sender<(String, bool, Vec<String>)>,
         receiver: async_channel::Receiver<Vec<Vec<(String, String)>>>,
     ) -> Self {
         Self { sender, receiver }
     }
 
     // RPCAPI:
-    // Update files in ~/darkwiki
+    // Update files
     // --> {"jsonrpc": "2.0", "method": "update", "params": [], "id": 1}
     // <-- {"jsonrpc": "2.0", "result": [[(String, String)]], "id": 1}
-    async fn update(&self, id: Value, _params: &[Value]) -> JsonResult {
-        let res = self.sender.send("update".into()).await.map_err(Error::from);
+    async fn update(&self, id: Value, params: &[Value]) -> JsonResult {
+        let dry = params[0].as_bool().unwrap();
+        let files: Vec<String> =
+            params[1].as_array().unwrap().iter().map(|f| f.as_str().unwrap().to_string()).collect();
+        let res = self.sender.send(("update".into(), dry, files)).await.map_err(Error::from);
 
         if let Err(e) = res {
             error!("Failed to update: {}", e);
@@ -61,14 +64,18 @@ impl JsonRpcInterface {
     }
 
     // RPCAPI:
-    // Update files in darkwiki (dry_run)
-    // --> {"jsonrpc": "2.0", "method": "dry_run", "params": [], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": [[(String, String)]], "id": 1}
-    async fn dry_run(&self, id: Value, _params: &[Value]) -> JsonResult {
-        let res = self.sender.send("dry_run".into()).await.map_err(Error::from);
+    // Undo the local changes
+    // --> {"jsonrpc": "2.0", "method": "restore", "params": [dry, files], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": [String, ..], "id": 1}
+    async fn restore(&self, id: Value, params: &[Value]) -> JsonResult {
+        let dry = params[0].as_bool().unwrap();
+        let files: Vec<String> =
+            params[1].as_array().unwrap().iter().map(|f| f.as_str().unwrap().to_string()).collect();
+
+        let res = self.sender.send(("restore".into(), dry, files)).await.map_err(Error::from);
 
         if let Err(e) = res {
-            error!("Failed to update(dry run): {}", e);
+            error!("Failed to restore: {}", e);
             return JsonError::new(ErrorCode::InternalError, None, id).into()
         }
 
@@ -78,17 +85,9 @@ impl JsonRpcInterface {
 
     // RPCAPI:
     // Show all patches
-    // --> {"jsonrpc": "2.0", "method": "log", "params": [], "id": 1}
+    // --> {"jsonrpc": "2.0", "method": "log", "params": [dry, files], "id": 1}
     // <-- {"jsonrpc": "2.0", "result": [[(String, String)]], "id": 1}
     async fn log(&self, id: Value, _params: &[Value]) -> JsonResult {
-        let res = self.sender.send("log".into()).await.map_err(Error::from);
-
-        if let Err(e) = res {
-            error!("Failed to show all patches: {}", e);
-            return JsonError::new(ErrorCode::InternalError, None, id).into()
-        }
-
-        let response = self.receiver.recv().await.unwrap();
-        JsonResponse::new(json!(response), id).into()
+        JsonResponse::new(json!(true), id).into()
     }
 }

+ 213 - 142
bin/darkwikid/src/main.rs

@@ -114,190 +114,250 @@ fn lcs(a: &str, b: &str) -> Vec<OpMethod> {
     result
 }
 
-fn on_receive_patch(received_patch: &Patch, settings: &DarkWikiSettings) -> DarkWikiResult<()> {
-    let sync_id_path = settings.datastore_path.join("sync").join(&received_patch.id);
-    let local_id_path = settings.datastore_path.join("local").join(&received_patch.id);
+fn title_to_id(title: &str) -> String {
+    let mut hasher = sha2::Sha256::new();
+    hasher.update(title);
+    hex::encode(hasher.finalize())
+}
 
-    if let Ok(mut sync_patch) = load_json_file::<Patch>(&sync_id_path) {
-        if sync_patch.timestamp == received_patch.timestamp {
-            return Ok(())
+struct Darkwiki {
+    settings: DarkWikiSettings,
+    rpc: (
+        async_channel::Sender<Vec<Vec<(String, String)>>>,
+        async_channel::Receiver<(String, bool, Vec<String>)>,
+    ),
+    raft: (async_channel::Sender<Patch>, async_channel::Receiver<Patch>),
+}
+
+impl Darkwiki {
+    async fn start(&self) -> DarkWikiResult<()> {
+        loop {
+            select! {
+                val = self.rpc.1.recv().fuse() => {
+                    let (cmd, dry, files) = val.map_err(Error::from)?;
+                    match cmd.as_str() {
+                        "update" => {
+                            self.on_receive_update(dry, files).await?;
+                        },
+                        "restore" => {
+                            self.on_receive_restore(dry, files).await?;
+                        },
+                        _ => {}
+                    }
+                }
+                patch = self.raft.1.recv().fuse() => {
+                    let patch = patch.map_err(Error::from)?;
+                    info!("Receive new patch from Raft {:?}", patch);
+                    self.on_receive_patch(&patch)?;
+                }
+
+            }
         }
+    }
 
-        if let Ok(local_patch) = load_json_file::<Patch>(&local_id_path) {
-            if local_patch.timestamp == sync_patch.timestamp {
-                sync_patch.base = local_patch.to_string();
-                sync_patch.set_ops(received_patch.ops());
-            } else {
-                sync_patch.extend_ops(received_patch.ops());
+    fn on_receive_patch(&self, received_patch: &Patch) -> DarkWikiResult<()> {
+        let sync_id_path = self.settings.datastore_path.join("sync").join(&received_patch.id);
+        let local_id_path = self.settings.datastore_path.join("local").join(&received_patch.id);
+
+        if let Ok(mut sync_patch) = load_json_file::<Patch>(&sync_id_path) {
+            if sync_patch.timestamp == received_patch.timestamp {
+                return Ok(())
+            }
+
+            if let Ok(local_patch) = load_json_file::<Patch>(&local_id_path) {
+                if local_patch.timestamp == sync_patch.timestamp {
+                    sync_patch.base = local_patch.to_string();
+                    sync_patch.set_ops(received_patch.ops());
+                } else {
+                    sync_patch.extend_ops(received_patch.ops());
+                }
             }
+
+            sync_patch.timestamp = received_patch.timestamp;
+            sync_patch.author = received_patch.author.clone();
+            save_json_file::<Patch>(&sync_id_path, &sync_patch)?;
+        } else if !received_patch.base.is_empty() {
+            save_json_file::<Patch>(&sync_id_path, received_patch)?;
         }
 
-        sync_patch.timestamp = received_patch.timestamp;
-        sync_patch.author = received_patch.author.clone();
-        save_json_file::<Patch>(&sync_id_path, &sync_patch)?;
-    } else if !received_patch.base.is_empty() {
-        save_json_file::<Patch>(&sync_id_path, received_patch)?;
+        Ok(())
     }
 
-    Ok(())
-}
-
-fn title_to_id(title: &str) -> String {
-    let mut hasher = sha2::Sha256::new();
-    hasher.update(title);
-    hex::encode(hasher.finalize())
-}
+    async fn on_receive_update(&self, dry: bool, files: Vec<String>) -> DarkWikiResult<()> {
+        let (patches, local, sync, merge) = self.update(dry, files)?;
 
-fn on_receive_update(settings: &DarkWikiSettings, dry: bool) -> DarkWikiResult<Patches> {
-    let mut patches: Vec<Patch> = vec![];
-    let mut local_patches: Vec<Patch> = vec![];
-    let mut sync_patches: Vec<Patch> = vec![];
-    let mut merge_patches: Vec<Patch> = vec![];
+        if !dry {
+            for patch in patches {
+                info!("Send a patch to Raft {:?}", patch);
+                self.raft.0.send(patch.clone()).await.map_err(Error::from)?;
+            }
+        }
 
-    let local_path = settings.datastore_path.join("local");
-    let sync_path = settings.datastore_path.join("sync");
-    let docs_path = settings.docs_path.clone();
+        let local: Vec<(String, String)> =
+            local.iter().map(|p| (p.title.to_owned(), p.colorize())).collect();
 
-    // save and compare docs in darkwiki and local dirs
-    // then merged with sync patches if any received
-    let docs = read_dir(&docs_path).map_err(Error::from)?;
-    for doc in docs {
-        let doc_title = doc.as_ref().unwrap().file_name();
-        let doc_title = doc_title.to_str().unwrap();
+        let sync: Vec<(String, String)> =
+            sync.iter().map(|p| (p.title.to_owned(), p.colorize())).collect();
 
-        // load doc content
-        let edit = load_file(&docs_path.join(doc_title)).map_err(Error::from)?;
-        let edit = edit.trim();
+        let merge: Vec<(String, String)> =
+            merge.iter().map(|p| (p.title.to_owned(), p.colorize())).collect();
 
-        let doc_id = title_to_id(doc_title);
+        self.rpc.0.send(vec![local, sync, merge]).await.map_err(Error::from)?;
 
-        // create new patch
-        let mut new_patch = Patch::new(doc_title, &doc_id, &settings.author);
+        Ok(())
+    }
 
-        // check for any changes found with local doc and darkwiki doc
-        if let Ok(local_patch) = load_json_file::<Patch>(&local_path.join(&doc_id)) {
-            // no changes found
-            if local_patch.to_string() == edit {
-                continue
-            }
+    async fn on_receive_restore(&self, dry: bool, files_name: Vec<String>) -> DarkWikiResult<()> {
+        let patches = self.restore(dry, files_name)?;
+        let patches: Vec<(String, String)> =
+            patches.iter().map(|p| (p.title.to_owned(), p.to_string())).collect();
 
-            // check the differences with LCS algorithm
-            let lcs_ops = lcs(&local_patch.to_string(), edit);
+        self.rpc.0.send(vec![patches]).await.map_err(Error::from)?;
 
-            // add the change ops to the new patch
-            for op in lcs_ops {
-                new_patch.add_op(&op);
-            }
+        Ok(())
+    }
 
-            new_patch.base = local_patch.to_string();
+    fn restore(&self, dry: bool, files_name: Vec<String>) -> DarkWikiResult<Vec<Patch>> {
+        let local_path = self.settings.datastore_path.join("local");
+        let docs_path = self.settings.docs_path.clone();
+        let local_files = read_dir(&local_path).map_err(Error::from)?;
 
-            local_patches.push(new_patch.clone());
+        let mut patches = vec![];
 
-            let mut b_patch = new_patch.clone();
-            b_patch.base = "".to_string();
-            patches.push(b_patch);
+        for file in local_files {
+            let file_id = file.as_ref().unwrap().file_name();
+            let file_id = file_id.to_str().unwrap();
+            let file_path = local_path.join(&file_id);
+            let local_patch: Patch = load_json_file(&file_path)?;
 
-            // check if the same doc has received patch from the network
-            if let Ok(sync_patch) = load_json_file::<Patch>(&sync_path.join(&doc_id)) {
-                if sync_patch.timestamp != local_patch.timestamp {
-                    sync_patches.push(sync_patch.clone());
+            if !files_name.is_empty() && !files_name.contains(&local_patch.title.to_string()) {
+                continue
+            }
 
-                    let sync_patch_t = new_patch.transform(&sync_patch);
-                    new_patch = new_patch.merge(&sync_patch_t);
-                    if !dry {
-                        save_file(&docs_path.join(doc_title), &new_patch.to_string())?;
-                    }
-                    merge_patches.push(new_patch.clone());
+            if let Ok(doc) = load_file(&docs_path.join(&local_patch.title)) {
+                if local_patch.to_string() == doc {
+                    continue
                 }
             }
-        } else {
-            new_patch.base = edit.to_string();
-            local_patches.push(new_patch.clone());
-            patches.push(new_patch.clone());
-        };
 
-        if !dry {
-            save_json_file(&local_path.join(&doc_id), &new_patch)?;
-            save_json_file(&sync_path.join(doc_id), &new_patch)?;
+            if !dry {
+                save_file(&docs_path.join(&local_patch.title), &local_patch.to_string())?;
+            }
+
+            patches.push(local_patch);
         }
+
+        Ok(patches)
     }
 
-    // check if a new patch received
-    // and save the new changes in both local and darkwiki dirs
-    let sync_files = read_dir(&sync_path).map_err(Error::from)?;
-    for file in sync_files {
-        let file_id = file.as_ref().unwrap().file_name();
-        let file_id = file_id.to_str().unwrap();
-        let file_path = sync_path.join(&file_id);
-        let sync_patch: Patch = load_json_file(&file_path)?;
-
-        if let Ok(local_patch) = load_json_file::<Patch>(&local_path.join(&file_id)) {
-            if local_patch.timestamp == sync_patch.timestamp {
+    fn update(&self, dry: bool, files_name: Vec<String>) -> DarkWikiResult<Patches> {
+        let mut patches: Vec<Patch> = vec![];
+        let mut local_patches: Vec<Patch> = vec![];
+        let mut sync_patches: Vec<Patch> = vec![];
+        let mut merge_patches: Vec<Patch> = vec![];
+
+        let local_path = self.settings.datastore_path.join("local");
+        let sync_path = self.settings.datastore_path.join("sync");
+        let docs_path = self.settings.docs_path.clone();
+
+        // save and compare docs in darkwiki and local dirs
+        // then merged with sync patches if any received
+        let docs = read_dir(&docs_path).map_err(Error::from)?;
+        for doc in docs {
+            let doc_title = doc.as_ref().unwrap().file_name();
+            let doc_title = doc_title.to_str().unwrap();
+
+            if !files_name.is_empty() && !files_name.contains(&doc_title.to_string()) {
                 continue
             }
-        }
 
-        if !dry {
-            save_file(&docs_path.join(&sync_patch.title), &sync_patch.to_string())?;
-            save_json_file(&local_path.join(file_id), &sync_patch)?;
-        }
+            // load doc content
+            let edit = load_file(&docs_path.join(doc_title)).map_err(Error::from)?;
+            let edit = edit.trim();
 
-        if !sync_patches.contains(&sync_patch) {
-            sync_patches.push(sync_patch);
-        }
-    }
+            let doc_id = title_to_id(doc_title);
 
-    Ok((patches, local_patches, sync_patches, merge_patches))
-}
+            // create new patch
+            let mut new_patch = Patch::new(doc_title, &doc_id, &self.settings.author);
 
-async fn start(
-    rpc_rv: async_channel::Receiver<String>,
-    notify_sx: async_channel::Sender<Vec<Vec<(String, String)>>>,
-    raft_sender: async_channel::Sender<Patch>,
-    raft_receiver: async_channel::Receiver<Patch>,
-    settings: DarkWikiSettings,
-) -> DarkWikiResult<()> {
-    loop {
-        select! {
-            command = rpc_rv.recv().fuse() => {
-                let command = command.unwrap();
-                match command.as_str() {
-                    "update" | "dry_run" => {
-                        let dry = command.as_str() == "dry_run";
-                        let (patches, local, sync, merge) = on_receive_update(&settings, dry)?;
+            // check for any changes found with local doc and darkwiki doc
+            if let Ok(local_patch) = load_json_file::<Patch>(&local_path.join(&doc_id)) {
+                // no changes found
+                if local_patch.to_string() == edit {
+                    continue
+                }
 
-                        if !dry {
-                            for patch in patches {
-                                info!("Send a patch to Raft {:?}", patch);
-                                raft_sender.send(patch.clone()).await.map_err(Error::from)?;
-                            }
-                        }
+                // check the differences with LCS algorithm
+                let lcs_ops = lcs(&local_patch.to_string(), edit);
 
-                        let local: Vec<(String, String)> =
-                            local.iter().map(|p| (p.title.to_owned(), p.colorize())).collect();
+                // add the change ops to the new patch
+                for op in lcs_ops {
+                    new_patch.add_op(&op);
+                }
 
-                        let sync: Vec<(String, String)> =
-                            sync.iter().map(|p| (p.title.to_owned(), p.colorize())).collect();
+                new_patch.base = local_patch.to_string();
 
-                        let merge: Vec<(String, String)> =
-                            merge.iter().map(|p| (p.title.to_owned(), p.colorize())).collect();
+                local_patches.push(new_patch.clone());
 
-                        notify_sx.send(vec![local, sync, merge]).await.map_err(Error::from)?;
-                    }
-                    "log" => {
-                        // TODO
-                        notify_sx.send(vec![]).await.map_err(Error::from)?;
+                let mut b_patch = new_patch.clone();
+                b_patch.base = "".to_string();
+                patches.push(b_patch);
+
+                // check if the same doc has received patch from the network
+                if let Ok(sync_patch) = load_json_file::<Patch>(&sync_path.join(&doc_id)) {
+                    if sync_patch.timestamp != local_patch.timestamp {
+                        sync_patches.push(sync_patch.clone());
+
+                        let sync_patch_t = new_patch.transform(&sync_patch);
+                        new_patch = new_patch.merge(&sync_patch_t);
+                        if !dry {
+                            save_file(&docs_path.join(doc_title), &new_patch.to_string())?;
+                        }
+                        merge_patches.push(new_patch.clone());
                     }
-                    _ => {}
                 }
+            } else {
+                new_patch.base = edit.to_string();
+                local_patches.push(new_patch.clone());
+                patches.push(new_patch.clone());
+            };
+
+            if !dry {
+                save_json_file(&local_path.join(&doc_id), &new_patch)?;
+                save_json_file(&sync_path.join(doc_id), &new_patch)?;
             }
-            patch = raft_receiver.recv().fuse() => {
-                let patch = patch.map_err(Error::from)?;
-                info!("Receive new patch from Raft {:?}", patch);
-                on_receive_patch(&patch, &settings)?;
+        }
+
+        // check if a new patch received
+        // and save the new changes in both local and darkwiki dirs
+        let sync_files = read_dir(&sync_path).map_err(Error::from)?;
+        for file in sync_files {
+            let file_id = file.as_ref().unwrap().file_name();
+            let file_id = file_id.to_str().unwrap();
+            let file_path = sync_path.join(&file_id);
+            let sync_patch: Patch = load_json_file(&file_path)?;
+
+            if let Ok(local_patch) = load_json_file::<Patch>(&local_path.join(&file_id)) {
+                if local_patch.timestamp == sync_patch.timestamp {
+                    continue
+                }
             }
 
+            if !files_name.is_empty() && !files_name.contains(&sync_patch.title.to_string()) {
+                continue
+            }
+
+            if !dry {
+                save_file(&docs_path.join(&sync_patch.title), &sync_patch.to_string())?;
+                save_json_file(&local_path.join(file_id), &sync_patch)?;
+            }
+
+            if !sync_patches.contains(&sync_patch) {
+                sync_patches.push(sync_patch);
+            }
         }
+
+        Ok((patches, local_patches, sync_patches, merge_patches))
     }
 }
 
@@ -310,7 +370,7 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
     create_dir_all(datastore_path.join("local"))?;
     create_dir_all(datastore_path.join("sync"))?;
 
-    let (rpc_sx, rpc_rv) = async_channel::unbounded::<String>();
+    let (rpc_sx, rpc_rv) = async_channel::unbounded::<(String, bool, Vec<String>)>();
     let (notify_sx, notify_rv) = async_channel::unbounded::<Vec<Vec<(String, String)>>>();
 
     //
@@ -359,9 +419,20 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
     //
     // Darkwiki start
     //
-    let darkwiki_settings = DarkWikiSettings { author: settings.author, datastore_path, docs_path };
+
+    let raft_sx = raft.sender();
+    let raft_rv = raft.receiver();
     executor
-        .spawn(start(rpc_rv, notify_sx, raft.sender(), raft.receiver(), darkwiki_settings))
+        .spawn(async move {
+            let darkwiki_settings =
+                DarkWikiSettings { author: settings.author, datastore_path, docs_path };
+            let darkwiki = Darkwiki {
+                settings: darkwiki_settings,
+                raft: (raft_sx, raft_rv),
+                rpc: (notify_sx, rpc_rv),
+            };
+            darkwiki.start().await.unwrap_or(());
+        })
         .detach();
 
     //