Browse Source

darkwiki: add workspaces

ghassmo 3 years ago
parent
commit
44fceece00

+ 3 - 2
bin/darkwiki/src/main.rs

@@ -50,8 +50,9 @@ enum ArgsSubCommand {
 fn print_patches(value: &Vec<serde_json::Value>) {
     for res in value {
         let res = res.as_array().unwrap();
-        let (title, changes) = (res[0].as_str().unwrap(), res[1].as_str().unwrap());
-        println!("FILE: {}", title);
+        let res: Vec<&str> = res.iter().map(|r| r.as_str().unwrap()).collect();
+        let (title, workspace, changes) = (res[0], res[1], res[2]);
+        println!("WORKSPACE: {} FILE: {}", workspace, title);
         println!("{}", changes);
         println!("----------------------------------");
     }

+ 2 - 2
bin/darkwikid/darkwiki.toml

@@ -7,8 +7,8 @@
 ## Sets Author name
 # author="name"
 
-## Secret Key To Encrypt/Decrypt Patches
-#secret = "86MGNN31r3VxT4ULMmhQnMtV8pDnod339KwHwHCfabG2"
+## Workspaces 
+# workspaces = ["darkfi:86MGNN31r3VxT4ULMmhQnMtV8pDnod339KwHwHCfabG2"]
 
 ## Raft net settings
 [net]

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

@@ -11,9 +11,11 @@ use darkfi::{
     Error,
 };
 
+use crate::Patch;
+
 pub struct JsonRpcInterface {
     sender: async_channel::Sender<(String, bool, Vec<String>)>,
-    receiver: async_channel::Receiver<Vec<Vec<(String, String)>>>,
+    receiver: async_channel::Receiver<Vec<Vec<Patch>>>,
 }
 
 #[async_trait]
@@ -36,10 +38,25 @@ impl RequestHandler for JsonRpcInterface {
     }
 }
 
+fn patch_to_tuple(p: &Patch, colorize: bool) -> (String, String, String) {
+    (p.path.to_owned(), p.workspace.to_owned(), if colorize { p.colorize() } else { p.to_string() })
+}
+
+fn printable_patches(
+    patches: Vec<Vec<Patch>>,
+    colorize: bool,
+) -> Vec<Vec<(String, String, String)>> {
+    let mut response = vec![];
+    for ps in patches {
+        response.push(ps.iter().map(|p| patch_to_tuple(p, colorize)).collect())
+    }
+    response
+}
+
 impl JsonRpcInterface {
     pub fn new(
         sender: async_channel::Sender<(String, bool, Vec<String>)>,
-        receiver: async_channel::Receiver<Vec<Vec<(String, String)>>>,
+        receiver: async_channel::Receiver<Vec<Vec<Patch>>>,
     ) -> Self {
         Self { sender, receiver }
     }
@@ -60,6 +77,7 @@ impl JsonRpcInterface {
         }
 
         let response = self.receiver.recv().await.unwrap();
+        let response = printable_patches(response, true);
         JsonResponse::new(json!(response), id).into()
     }
 
@@ -80,6 +98,7 @@ impl JsonRpcInterface {
         }
 
         let response = self.receiver.recv().await.unwrap();
+        let response = printable_patches(response, false);
         JsonResponse::new(json!(response), id).into()
     }
 

+ 141 - 59
bin/darkwikid/src/main.rs

@@ -65,11 +65,11 @@ pub struct Args {
     #[structopt(long, default_value = "NONE")]
     pub author: String,
     /// Secret Key To Encrypt/Decrypt Patches
-    #[structopt(long, default_value = "")]
-    pub secret: String,
+    #[structopt(long)]
+    pub workspaces: Vec<String>,
     /// Generate A New Secret Key
     #[structopt(long)]
-    pub keygen: bool,
+    pub generate: bool,
     ///  Clean all the local data in docs path
     /// (BE CAREFULL) Check the docs path in the config file before running this
     #[structopt(long)]
@@ -90,6 +90,28 @@ pub struct EncryptedPatch {
     payload: Vec<u8>,
 }
 
+fn get_workspaces(settings: &Args, docs_path: &Path) -> Result<FxHashMap<String, SalsaBox>> {
+    let mut workspaces = FxHashMap::default();
+
+    for workspace in settings.workspaces.iter() {
+        let workspace: Vec<&str> = workspace.split(':').collect();
+        let (workspace, secret) = (workspace[0], workspace[1]);
+
+        let bytes: [u8; 32] = bs58::decode(secret)
+            .into_vec()?
+            .try_into()
+            .map_err(|_| Error::ParseFailed("Parse secret key failed"))?;
+
+        let secret = crypto_box::SecretKey::from(bytes);
+        let public = secret.public_key();
+        let salsa_box = crypto_box::SalsaBox::new(&public, &secret);
+        workspaces.insert(workspace.to_string(), salsa_box);
+        create_dir_all(docs_path.join(workspace))?;
+    }
+
+    Ok(workspaces)
+}
+
 fn encrypt_patch(
     patch: &Patch,
     salsa_box: &SalsaBox,
@@ -126,9 +148,9 @@ fn str_to_chars(s: &str) -> Vec<&str> {
     s.graphemes(true).collect::<Vec<&str>>()
 }
 
-fn path_to_id(path: &str) -> String {
+fn path_to_id(path: &str, workspace: &str) -> String {
     let mut hasher = sha2::Sha256::new();
-    hasher.update(path);
+    hasher.update(&format!("{}{}", path, workspace));
     bs58::encode(hex::encode(hasher.finalize())).into_string()
 }
 
@@ -177,11 +199,11 @@ struct Darkwiki {
     settings: DarkWikiSettings,
     #[allow(clippy::type_complexity)]
     rpc: (
-        async_channel::Sender<Vec<Vec<(String, String)>>>,
+        async_channel::Sender<Vec<Vec<Patch>>>,
         async_channel::Receiver<(String, bool, Vec<String>)>,
     ),
     raft: (async_channel::Sender<EncryptedPatch>, async_channel::Receiver<EncryptedPatch>),
-    salsa_box: SalsaBox,
+    workspaces: FxHashMap<String, SalsaBox>,
 }
 
 impl Darkwiki {
@@ -202,17 +224,19 @@ impl Darkwiki {
                     }
                 }
                 patch = self.raft.1.recv().fuse() => {
-                    self.on_receive_patch(&patch?)?;
+                    for (workspace, salsa_box) in self.workspaces.iter() {
+                        if let Ok(patch) = decrypt_patch(&patch.clone()?, &salsa_box) {
+                            info!("[{}] Receive a {:?}", workspace, patch);
+                            self.on_receive_patch(&patch)?;
+                        }
+                    }
                 }
 
             }
         }
     }
 
-    fn on_receive_patch(&self, received_patch: &EncryptedPatch) -> Result<()> {
-        let received_patch = decrypt_patch(received_patch, &self.salsa_box)?;
-
-        info!("Receive a {:?}", received_patch);
+    fn on_receive_patch(&self, received_patch: &Patch) -> Result<()> {
         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);
 
@@ -233,7 +257,7 @@ impl Darkwiki {
             }
 
             sync_patch.timestamp = received_patch.timestamp;
-            sync_patch.author = received_patch.author;
+            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)?;
@@ -248,43 +272,62 @@ impl Darkwiki {
         files: Vec<String>,
         rng: &mut OsRng,
     ) -> Result<()> {
-        let (patches, local, sync, merge) = self.update(dry, files)?;
+        let mut local: Vec<Patch> = vec![];
+        let mut sync: Vec<Patch> = vec![];
+        let mut merge: Vec<Patch> = vec![];
+
+        for (workspace, salsa_box) in self.workspaces.iter() {
+            let (patches, l, s, m) = self.update(
+                dry,
+                &self.settings.docs_path.join(workspace),
+                files.clone(),
+                workspace,
+            )?;
+
+            local.extend(l);
+            sync.extend(s);
+            merge.extend(m);
 
-        if !dry {
-            for patch in patches {
-                info!("Send a {:?}", patch);
-                let encrypt_patch = encrypt_patch(&patch, &self.salsa_box, rng)?;
-                self.raft.0.send(encrypt_patch).await?;
+            if !dry {
+                for patch in patches {
+                    info!("Send a {:?}", patch);
+                    let encrypt_patch = encrypt_patch(&patch, &salsa_box, rng)?;
+                    self.raft.0.send(encrypt_patch).await?;
+                }
             }
         }
 
-        let local: Vec<(String, String)> =
-            local.iter().map(|p| (p.path.to_owned(), p.colorize())).collect();
-
-        let sync: Vec<(String, String)> =
-            sync.iter().map(|p| (p.path.to_owned(), p.colorize())).collect();
-
-        let merge: Vec<(String, String)> =
-            merge.iter().map(|p| (p.path.to_owned(), p.colorize())).collect();
-
         self.rpc.0.send(vec![local, sync, merge]).await?;
 
         Ok(())
     }
 
     async fn on_receive_restore(&self, dry: bool, files_name: Vec<String>) -> Result<()> {
-        let patches = self.restore(dry, files_name)?;
-        let patches: Vec<(String, String)> =
-            patches.iter().map(|p| (p.path.to_owned(), p.to_string())).collect();
+        let mut patches: Vec<Patch> = vec![];
+
+        for (workspace, _) in self.workspaces.iter() {
+            let ps = self.restore(
+                dry,
+                &self.settings.docs_path.join(workspace),
+                &files_name,
+                workspace,
+            )?;
+            patches.extend(ps);
+        }
 
         self.rpc.0.send(vec![patches]).await?;
 
         Ok(())
     }
 
-    fn restore(&self, dry: bool, files_name: Vec<String>) -> Result<Vec<Patch>> {
+    fn restore(
+        &self,
+        dry: bool,
+        docs_path: &Path,
+        files_name: &[String],
+        workspace: &str,
+    ) -> Result<Vec<Patch>> {
         let local_path = self.settings.datastore_path.join("local");
-        let docs_path = self.settings.docs_path.clone();
 
         let mut patches = vec![];
 
@@ -294,6 +337,10 @@ impl Darkwiki {
             let file_path = local_path.join(&file_id);
             let local_patch: Patch = load_json_file(&file_path)?;
 
+            if local_patch.workspace != workspace {
+                continue
+            }
+
             if !files_name.is_empty() && !files_name.contains(&local_patch.path.to_string()) {
                 continue
             }
@@ -305,7 +352,7 @@ impl Darkwiki {
             }
 
             if !dry {
-                self.save_doc(&local_patch.path, &local_patch.to_string())?;
+                self.save_doc(&local_patch.path, &local_patch.to_string(), workspace)?;
             }
 
             patches.push(local_patch);
@@ -314,7 +361,13 @@ impl Darkwiki {
         Ok(patches)
     }
 
-    fn update(&self, dry: bool, files_name: Vec<String>) -> Result<Patches> {
+    fn update(
+        &self,
+        dry: bool,
+        docs_path: &Path,
+        files_name: Vec<String>,
+        workspace: &str,
+    ) -> Result<Patches> {
         let mut patches: Vec<Patch> = vec![];
         let mut local_patches: Vec<Patch> = vec![];
         let mut sync_patches: Vec<Patch> = vec![];
@@ -322,7 +375,6 @@ impl Darkwiki {
 
         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
@@ -342,10 +394,10 @@ impl Darkwiki {
                 continue
             }
 
-            let doc_id = path_to_id(doc_path);
+            let doc_id = path_to_id(doc_path, workspace);
 
             // create new patch
-            let mut new_patch = Patch::new(doc_path, &doc_id, &self.settings.author);
+            let mut new_patch = Patch::new(doc_path, &doc_id, &self.settings.author, workspace);
 
             // 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)) {
@@ -381,7 +433,7 @@ impl Darkwiki {
                             let sync_patch_t = new_patch.transform(&sync_patch);
                             new_patch = new_patch.merge(&sync_patch_t);
                             if !dry {
-                                self.save_doc(doc_path, &new_patch.to_string())?;
+                                self.save_doc(doc_path, &new_patch.to_string(), workspace)?;
                             }
                             merge_patches.push(new_patch.clone());
                         }
@@ -410,6 +462,10 @@ impl Darkwiki {
             let file_path = sync_path.join(&file_id);
             let sync_patch: Patch = load_json_file(&file_path)?;
 
+            if sync_patch.workspace != workspace {
+                continue
+            }
+
             if is_delete_patch(&sync_patch) {
                 if local_path.join(&sync_patch.id).exists() {
                     sync_patches.push(sync_patch.clone());
@@ -435,7 +491,7 @@ impl Darkwiki {
             }
 
             if !dry {
-                self.save_doc(&sync_patch.path, &sync_patch.to_string())?;
+                self.save_doc(&sync_patch.path, &sync_patch.to_string(), workspace)?;
                 save_json_file(&local_path.join(file_id), &sync_patch)?;
             }
 
@@ -451,13 +507,21 @@ impl Darkwiki {
             let file_path = local_path.join(&file_id);
             let local_patch: Patch = load_json_file(&file_path)?;
 
+            if local_patch.workspace != workspace {
+                continue
+            }
+
             if !files_name.is_empty() && !files_name.contains(&local_patch.path.to_string()) {
                 continue
             }
 
             if !docs_path.join(&local_patch.path).exists() {
-                let mut new_patch =
-                    Patch::new(&local_patch.path, &local_patch.id, &self.settings.author);
+                let mut new_patch = Patch::new(
+                    &local_patch.path,
+                    &local_patch.id,
+                    &self.settings.author,
+                    &local_patch.workspace,
+                );
                 new_patch.add_op(&OpMethod::Delete(local_patch.to_string().len() as u64));
                 patches.push(new_patch.clone());
 
@@ -473,8 +537,8 @@ impl Darkwiki {
         Ok((patches, local_patches, sync_patches, merge_patches))
     }
 
-    fn save_doc(&self, path: &str, edit: &str) -> Result<()> {
-        let path = self.settings.docs_path.join(path);
+    fn save_doc(&self, path: &str, edit: &str, workspace: &str) -> Result<()> {
+        let path = self.settings.docs_path.join(workspace).join(path);
         if let Some(p) = path.parent() {
             if !p.exists() && !p.to_str().unwrap().is_empty() {
                 create_dir_all(p)?;
@@ -512,25 +576,43 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
     create_dir_all(datastore_path.join("local"))?;
     create_dir_all(datastore_path.join("sync"))?;
 
-    if settings.keygen {
-        info!("Generating a new secret key");
-        let mut rng = crypto_box::rand_core::OsRng;
-        let secret_key = SecretKey::generate(&mut rng);
-        let encoded = bs58::encode(secret_key.as_bytes());
-        println!("Secret key: {}", encoded.into_string());
+    if settings.generate {
+        println!("Generating a new workspace");
+
+        loop {
+            println!("Name for the new workspace: ");
+            let mut workspace = String::new();
+            stdin().read_line(&mut workspace).ok().expect("Failed to read line");
+            let workspace = workspace.to_lowercase();
+            let workspace = workspace.trim();
+            if workspace.is_empty() && workspace.len() < 3 {
+                error!("Wrong workspace try again");
+                continue
+            }
+            let mut rng = crypto_box::rand_core::OsRng;
+            let secret_key = SecretKey::generate(&mut rng);
+            let encoded = bs58::encode(secret_key.as_bytes());
+
+            create_dir_all(docs_path.join(workspace))?;
+
+            println!("workspace: {}:{}", workspace, encoded.into_string());
+            println!("Please add it to the config file.");
+            break
+        }
+
         return Ok(())
     }
 
-    let bytes: [u8; 32] = bs58::decode(settings.secret)
-        .into_vec()?
-        .try_into()
-        .map_err(|_| Error::ParseFailed("Parse secret key failed"))?;
-    let secret = crypto_box::SecretKey::from(bytes);
-    let public = secret.public_key();
-    let salsa_box = crypto_box::SalsaBox::new(&public, &secret);
+    let workspaces = get_workspaces(&settings, &docs_path)?;
+
+    if workspaces.is_empty() {
+        error!("Please add at least on workspace to the config file.");
+        println!("Run `$ darkwikid --generate` to generate new workspace.");
+        return Ok(())
+    }
 
     let (rpc_sx, rpc_rv) = async_channel::unbounded::<(String, bool, Vec<String>)>();
-    let (notify_sx, notify_rv) = async_channel::unbounded::<Vec<Vec<(String, String)>>>();
+    let (notify_sx, notify_rv) = async_channel::unbounded::<Vec<Vec<Patch>>>();
 
     //
     // RPC
@@ -588,7 +670,7 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
                 settings: darkwiki_settings,
                 raft: (raft_sx, raft_rv),
                 rpc: (notify_sx, rpc_rv),
-                salsa_box,
+                workspaces,
             };
             darkwiki.start().await.unwrap_or(());
         })

+ 10 - 8
bin/darkwikid/src/patch.rs

@@ -27,6 +27,7 @@ pub struct Patch {
     pub id: String,
     pub base: String,
     pub timestamp: Timestamp,
+    pub workspace: String,
     ops: OpMethods,
 }
 
@@ -66,12 +67,13 @@ impl std::string::ToString for Patch {
 }
 
 impl Patch {
-    pub fn new(path: &str, id: &str, author: &str) -> Self {
+    pub fn new(path: &str, id: &str, author: &str, workspace: &str) -> Self {
         Self {
             path: path.to_string(),
             id: id.to_string(),
             ops: OpMethods(vec![]),
             base: String::new(),
+            workspace: workspace.to_string(),
             author: author.to_string(),
             timestamp: Timestamp::current_time(),
         }
@@ -146,7 +148,7 @@ impl Patch {
     //
     // TODO need more work to get better performance with iterators
     pub fn transform(&self, other: &Self) -> Self {
-        let mut new_patch = Self::new(&self.path, &self.id, &self.author);
+        let mut new_patch = Self::new(&self.path, &self.id, &self.author, "");
         new_patch.base = self.base.clone();
 
         let mut ops1 = self.ops.0.iter().cloned();
@@ -253,7 +255,7 @@ impl Patch {
         let mut ops1 = ops1.iter().cloned();
         let mut ops2 = other.ops.0.iter().cloned();
 
-        let mut new_patch = Self::new(&self.path, &self.id, &self.author);
+        let mut new_patch = Self::new(&self.path, &self.id, &self.author, "");
         new_patch.base = self.base.clone();
 
         let mut op1 = ops1.next();
@@ -468,7 +470,7 @@ mod tests {
 
     #[test]
     fn test_to_string() {
-        let mut patch = Patch::new("", &gen_id(30), "");
+        let mut patch = Patch::new("", &gen_id(30), "", "");
         patch.base = "text example\n hello".to_string();
         patch.retain(14);
         patch.delete(5);
@@ -479,7 +481,7 @@ mod tests {
 
     #[test]
     fn test_merge() {
-        let mut patch_init = Patch::new("", &gen_id(30), "");
+        let mut patch_init = Patch::new("", &gen_id(30), "", "");
         let base = "text example\n hello";
         patch_init.base = base.to_string();
 
@@ -517,7 +519,7 @@ mod tests {
 
     #[test]
     fn test_transform() {
-        let mut patch_init = Patch::new("", &gen_id(30), "");
+        let mut patch_init = Patch::new("", &gen_id(30), "", "");
         let base = "text example\n hello";
         patch_init.base = base.to_string();
 
@@ -555,7 +557,7 @@ mod tests {
 
     #[test]
     fn test_transform2() {
-        let mut patch_init = Patch::new("", &gen_id(30), "");
+        let mut patch_init = Patch::new("", &gen_id(30), "", "");
         let base = "#hello\n hello";
         patch_init.base = base.to_string();
 
@@ -587,7 +589,7 @@ mod tests {
         assert_eq!(op_method, op_method_deser);
 
         // serialize & deserialize Patch
-        let mut patch = Patch::new("", &gen_id(30), "");
+        let mut patch = Patch::new("", &gen_id(30), "", "");
         patch.insert("hello");
         patch.delete(2);
 

+ 7 - 6
src/raft/consensus_candidate.rs

@@ -23,8 +23,6 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
             return Ok(())
         }
 
-        let self_id = self.id();
-
         self.set_current_term(&(self.current_term()? + 1))?;
 
         if self.role != Role::Candidate {
@@ -32,14 +30,13 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
             self.role = Role::Candidate;
         }
 
-        self.set_voted_for(&Some(self_id.clone()))?;
-        self.votes_received = vec![];
-        self.votes_received.push(self_id.clone());
+        self.set_voted_for(&Some(self.id()))?;
+        self.votes_received = vec![self.id()];
 
         self.reset_last_term()?;
 
         let request = VoteRequest {
-            node_id: self_id,
+            node_id: self.id(),
             current_term: self.current_term()?,
             log_length: self.logs_len(),
             last_term: self.last_term,
@@ -51,6 +48,10 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
 
     pub(super) async fn receive_vote_response(&mut self, vr: VoteResponse) -> Result<()> {
         if self.role == Role::Candidate && vr.current_term == self.current_term()? && vr.ok {
+            if self.votes_received.contains(&vr.node_id) {
+                return Ok(())
+            }
+
             self.votes_received.push(vr.node_id);
 
             let nodes = self.nodes.lock().await;