|
@@ -1,200 +1,107 @@
|
|
|
use std::{
|
|
use std::{
|
|
|
- fs::{create_dir_all, read_dir, remove_dir_all, remove_file},
|
|
|
|
|
|
|
+ fs::{create_dir_all, read_dir, remove_file},
|
|
|
io::stdin,
|
|
io::stdin,
|
|
|
path::{Path, PathBuf},
|
|
path::{Path, PathBuf},
|
|
|
|
|
+ process::exit,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
use async_executor::Executor;
|
|
use async_executor::Executor;
|
|
|
-use async_std::sync::{Arc, Mutex};
|
|
|
|
|
-use crypto_box::{
|
|
|
|
|
- aead::{Aead, AeadCore},
|
|
|
|
|
- rand_core::OsRng,
|
|
|
|
|
- SalsaBox, SecretKey,
|
|
|
|
|
|
|
+use async_std::{
|
|
|
|
|
+ stream::StreamExt,
|
|
|
|
|
+ sync::{Arc, Mutex, RwLock},
|
|
|
|
|
+ task,
|
|
|
};
|
|
};
|
|
|
|
|
+use dryoc::classic::crypto_secretbox::{crypto_secretbox_keygen, Key};
|
|
|
use futures::{select, FutureExt};
|
|
use futures::{select, FutureExt};
|
|
|
use fxhash::FxHashMap;
|
|
use fxhash::FxHashMap;
|
|
|
-use log::{error, info, warn};
|
|
|
|
|
-use serde::Deserialize;
|
|
|
|
|
-use sha2::Digest;
|
|
|
|
|
|
|
+use lazy_static::lazy_static;
|
|
|
|
|
+use log::{debug, error, info, warn};
|
|
|
|
|
+use signal_hook::consts::{SIGHUP, SIGINT, SIGQUIT, SIGTERM};
|
|
|
|
|
+use signal_hook_async_std::Signals;
|
|
|
use smol::future;
|
|
use smol::future;
|
|
|
-use structopt::StructOpt;
|
|
|
|
|
-use structopt_toml::StructOptToml;
|
|
|
|
|
-use unicode_segmentation::UnicodeSegmentation;
|
|
|
|
|
|
|
+use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
|
|
|
use url::Url;
|
|
use url::Url;
|
|
|
|
|
|
|
|
use darkfi::{
|
|
use darkfi::{
|
|
|
- async_daemonize,
|
|
|
|
|
- net::{self, settings::SettingsOpt},
|
|
|
|
|
|
|
+ async_daemonize, cli_desc, net,
|
|
|
raft::{NetMsg, ProtocolRaft, Raft, RaftSettings},
|
|
raft::{NetMsg, ProtocolRaft, Raft, RaftSettings},
|
|
|
rpc::server::listen_and_serve,
|
|
rpc::server::listen_and_serve,
|
|
|
- serial::{deserialize, serialize, SerialDecodable, SerialEncodable},
|
|
|
|
|
util::{
|
|
util::{
|
|
|
cli::{get_log_config, get_log_level, spawn_config},
|
|
cli::{get_log_config, get_log_level, spawn_config},
|
|
|
file::{load_file, load_json_file, save_file, save_json_file},
|
|
file::{load_file, load_json_file, save_file, save_json_file},
|
|
|
path::{expand_path, get_config_path},
|
|
path::{expand_path, get_config_path},
|
|
|
},
|
|
},
|
|
|
- Error, Result,
|
|
|
|
|
|
|
+ Result,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
mod jsonrpc;
|
|
mod jsonrpc;
|
|
|
-mod lcs;
|
|
|
|
|
-mod patch;
|
|
|
|
|
-
|
|
|
|
|
use jsonrpc::JsonRpcInterface;
|
|
use jsonrpc::JsonRpcInterface;
|
|
|
|
|
+mod lcs;
|
|
|
use lcs::Lcs;
|
|
use lcs::Lcs;
|
|
|
-use patch::{OpMethod, Patch};
|
|
|
|
|
|
|
+mod patch;
|
|
|
|
|
+use patch::{EncryptedPatch, OpMethod, Patch};
|
|
|
|
|
+mod util;
|
|
|
|
|
+use util::{decrypt_patch, encrypt_patch, get_docs_paths, parse_workspaces, path_to_id};
|
|
|
|
|
|
|
|
type Patches = (Vec<Patch>, Vec<Patch>, Vec<Patch>, Vec<Patch>);
|
|
type Patches = (Vec<Patch>, Vec<Patch>, Vec<Patch>, Vec<Patch>);
|
|
|
|
|
|
|
|
-pub const CONFIG_FILE: &str = "darkwiki.toml";
|
|
|
|
|
-pub const CONFIG_FILE_CONTENTS: &str = include_str!("../../darkwiki.toml");
|
|
|
|
|
-
|
|
|
|
|
-/// darkwikid cli
|
|
|
|
|
-#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
|
|
|
|
|
-#[serde(default)]
|
|
|
|
|
-#[structopt(name = "darkwikid")]
|
|
|
|
|
-pub struct Args {
|
|
|
|
|
- /// Sets a custom config file
|
|
|
|
|
- #[structopt(long)]
|
|
|
|
|
- pub config: Option<String>,
|
|
|
|
|
- /// Sets Docs Path
|
|
|
|
|
- #[structopt(long, default_value = "~/darkwiki")]
|
|
|
|
|
- pub docs: String,
|
|
|
|
|
- /// Sets Author Name for Patch
|
|
|
|
|
- #[structopt(long, default_value = "NONE")]
|
|
|
|
|
- pub author: String,
|
|
|
|
|
- /// Secret Key To Encrypt/Decrypt Patches
|
|
|
|
|
- #[structopt(long)]
|
|
|
|
|
- pub workspaces: Vec<String>,
|
|
|
|
|
- /// Generate A New Secret Key
|
|
|
|
|
- #[structopt(long)]
|
|
|
|
|
- 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)]
|
|
|
|
|
- pub refresh: bool,
|
|
|
|
|
- /// JSON-RPC Listen URL
|
|
|
|
|
- #[structopt(long = "rpc", default_value = "tcp://127.0.0.1:24330")]
|
|
|
|
|
- pub rpc_listen: Url,
|
|
|
|
|
- #[structopt(flatten)]
|
|
|
|
|
- pub net: SettingsOpt,
|
|
|
|
|
- /// Increase Verbosity
|
|
|
|
|
- #[structopt(short, parse(from_occurrences))]
|
|
|
|
|
- pub verbose: u8,
|
|
|
|
|
|
|
+lazy_static! {
|
|
|
|
|
+ /// This is where we hold our workspaces, so we are also able to refresh them on SIGHUP.
|
|
|
|
|
+ static ref WORKSPACES: RwLock<FxHashMap<String, Key>> = RwLock::new(FxHashMap::default());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
|
|
|
|
|
-pub struct EncryptedPatch {
|
|
|
|
|
- nonce: Vec<u8>,
|
|
|
|
|
- payload: Vec<u8>,
|
|
|
|
|
-}
|
|
|
|
|
|
|
+pub const CONFIG_FILE: &str = "darkwikid_config.toml";
|
|
|
|
|
+pub const CONFIG_FILE_CONTENTS: &str = include_str!("../darkwikid_config.toml");
|
|
|
|
|
|
|
|
-fn get_workspaces(settings: &Args, docs_path: &Path) -> Result<FxHashMap<String, SalsaBox>> {
|
|
|
|
|
- let mut workspaces = FxHashMap::default();
|
|
|
|
|
|
|
+const SYNC_ID_PATH: &str = "sync";
|
|
|
|
|
+const LOCAL_ID_PATH: &str = "local";
|
|
|
|
|
|
|
|
- for workspace in settings.workspaces.iter() {
|
|
|
|
|
- let workspace: Vec<&str> = workspace.split(':').collect();
|
|
|
|
|
- let (workspace, secret) = (workspace[0], workspace[1]);
|
|
|
|
|
|
|
+#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
|
|
|
|
|
+#[serde(default)]
|
|
|
|
|
+#[structopt(name = "darkwikid", about = cli_desc!())]
|
|
|
|
|
+struct Args {
|
|
|
|
|
+ /// Increase verbosity (-vvv supported)
|
|
|
|
|
+ #[structopt(short, parse(from_occurrences))]
|
|
|
|
|
+ verbose: u8,
|
|
|
|
|
|
|
|
- let bytes: [u8; 32] = bs58::decode(secret)
|
|
|
|
|
- .into_vec()?
|
|
|
|
|
- .try_into()
|
|
|
|
|
- .map_err(|_| Error::ParseFailed("Parse secret key failed"))?;
|
|
|
|
|
|
|
+ /// Configuration file to use
|
|
|
|
|
+ #[structopt(short, long)]
|
|
|
|
|
+ config: Option<String>,
|
|
|
|
|
|
|
|
- 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))?;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ /// Workspace configuration (repeatable flag)
|
|
|
|
|
+ #[structopt(short, long)]
|
|
|
|
|
+ workspace: Vec<String>,
|
|
|
|
|
|
|
|
- Ok(workspaces)
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ /// Path where to store wiki's files
|
|
|
|
|
+ #[structopt(short, long, default_value = "~/darkwiki")]
|
|
|
|
|
+ docs: String,
|
|
|
|
|
|
|
|
-fn encrypt_patch(
|
|
|
|
|
- patch: &Patch,
|
|
|
|
|
- salsa_box: &SalsaBox,
|
|
|
|
|
- rng: &mut crypto_box::rand_core::OsRng,
|
|
|
|
|
-) -> Result<EncryptedPatch> {
|
|
|
|
|
- let nonce = SalsaBox::generate_nonce(rng);
|
|
|
|
|
- let payload = &serialize(patch)[..];
|
|
|
|
|
- let payload = salsa_box
|
|
|
|
|
- .encrypt(&nonce, payload)
|
|
|
|
|
- .map_err(|_| Error::ParseFailed("Encrypting Patch failed"))?;
|
|
|
|
|
-
|
|
|
|
|
- let nonce = nonce.to_vec();
|
|
|
|
|
- Ok(EncryptedPatch { nonce, payload })
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ /// Sets author's name for patches
|
|
|
|
|
+ #[structopt(long, default_value = "Anonymous")]
|
|
|
|
|
+ author: String,
|
|
|
|
|
|
|
|
-fn decrypt_patch(encrypt_patch: &EncryptedPatch, salsa_box: &SalsaBox) -> Result<Patch> {
|
|
|
|
|
- let nonce = encrypt_patch.nonce.as_slice();
|
|
|
|
|
- let decrypted_patch = salsa_box
|
|
|
|
|
- .decrypt(nonce.into(), &encrypt_patch.payload[..])
|
|
|
|
|
- .map_err(|_| Error::ParseFailed("Decrypting Patch failed"))?;
|
|
|
|
|
|
|
+ /// Generate a new secret for a workspace
|
|
|
|
|
+ #[structopt(long)]
|
|
|
|
|
+ gen_secret: bool,
|
|
|
|
|
|
|
|
- let patch = deserialize(&decrypted_patch)?;
|
|
|
|
|
|
|
+ /// JSON-RPC listen URL
|
|
|
|
|
+ #[structopt(long, default_value = "tcp://localhost:24330")]
|
|
|
|
|
+ rpc_listen: Url,
|
|
|
|
|
|
|
|
- Ok(patch)
|
|
|
|
|
|
|
+ /// Network settings
|
|
|
|
|
+ #[structopt(flatten)]
|
|
|
|
|
+ net: net::settings::SettingsOpt,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-pub struct DarkWikiSettings {
|
|
|
|
|
|
|
+/// Settings struct used to hold some metadata for DarkWiki
|
|
|
|
|
+struct DarkWikiSettings {
|
|
|
author: String,
|
|
author: String,
|
|
|
docs_path: PathBuf,
|
|
docs_path: PathBuf,
|
|
|
- datastore_path: PathBuf,
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-fn str_to_chars(s: &str) -> Vec<&str> {
|
|
|
|
|
- s.graphemes(true).collect::<Vec<&str>>()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-fn path_to_id(path: &str, workspace: &str) -> String {
|
|
|
|
|
- let mut hasher = sha2::Sha256::new();
|
|
|
|
|
- hasher.update(&format!("{}{}", path, workspace));
|
|
|
|
|
- bs58::encode(hex::encode(hasher.finalize())).into_string()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-fn get_docs_paths(files: &mut Vec<PathBuf>, path: &Path, parent: Option<&Path>) -> Result<()> {
|
|
|
|
|
- let docs = read_dir(&path)?;
|
|
|
|
|
- let docs = docs.filter(|d| d.is_ok()).map(|d| d.unwrap().path()).collect::<Vec<PathBuf>>();
|
|
|
|
|
-
|
|
|
|
|
- for doc in docs {
|
|
|
|
|
- if let Some(f) = doc.file_name() {
|
|
|
|
|
- let file_name = PathBuf::from(f);
|
|
|
|
|
- let file_name =
|
|
|
|
|
- if let Some(parent) = parent { parent.join(file_name) } else { file_name };
|
|
|
|
|
- if doc.is_file() {
|
|
|
|
|
- if let Some(ext) = doc.extension() {
|
|
|
|
|
- if ext == "md" {
|
|
|
|
|
- files.push(file_name);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } else if doc.is_dir() {
|
|
|
|
|
- if f == ".log" {
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
- get_docs_paths(files, &doc, Some(&file_name))?;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Ok(())
|
|
|
|
|
|
|
+ store_path: PathBuf,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-fn is_delete_patch(patch: &Patch) -> bool {
|
|
|
|
|
- if patch.ops().0.len() != 1 {
|
|
|
|
|
- return false
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if let OpMethod::Delete(d) = patch.ops().0[0] {
|
|
|
|
|
- if patch.base.len() as u64 == d {
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- false
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-struct Darkwiki {
|
|
|
|
|
|
|
+/// DarkWiki object
|
|
|
|
|
+struct DarkWiki {
|
|
|
settings: DarkWikiSettings,
|
|
settings: DarkWikiSettings,
|
|
|
#[allow(clippy::type_complexity)]
|
|
#[allow(clippy::type_complexity)]
|
|
|
rpc: (
|
|
rpc: (
|
|
@@ -202,43 +109,69 @@ struct Darkwiki {
|
|
|
async_channel::Receiver<(String, bool, Vec<String>)>,
|
|
async_channel::Receiver<(String, bool, Vec<String>)>,
|
|
|
),
|
|
),
|
|
|
raft: (async_channel::Sender<EncryptedPatch>, async_channel::Receiver<EncryptedPatch>),
|
|
raft: (async_channel::Sender<EncryptedPatch>, async_channel::Receiver<EncryptedPatch>),
|
|
|
- workspaces: FxHashMap<String, SalsaBox>,
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-impl Darkwiki {
|
|
|
|
|
|
|
+impl DarkWiki {
|
|
|
async fn start(&self) -> Result<()> {
|
|
async fn start(&self) -> Result<()> {
|
|
|
- let mut rng = crypto_box::rand_core::OsRng;
|
|
|
|
|
loop {
|
|
loop {
|
|
|
select! {
|
|
select! {
|
|
|
val = self.rpc.1.recv().fuse() => {
|
|
val = self.rpc.1.recv().fuse() => {
|
|
|
- let (cmd, dry, files) = val?;
|
|
|
|
|
|
|
+ let (cmd, dry, files) = match val {
|
|
|
|
|
+ Ok(v) => v,
|
|
|
|
|
+ Err(e) => {
|
|
|
|
|
+ error!("Failed unwrapping val received from RPC: {}", e);
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
match cmd.as_str() {
|
|
match cmd.as_str() {
|
|
|
"update" => {
|
|
"update" => {
|
|
|
- self.on_receive_update(dry, files, &mut rng).await?;
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ if let Err(e) = self.on_receive_update(dry, files).await {
|
|
|
|
|
+ error!("on_receive_update returned error: {}", e);
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
"restore" => {
|
|
"restore" => {
|
|
|
- self.on_receive_restore(dry, files).await?;
|
|
|
|
|
- },
|
|
|
|
|
- _ => {}
|
|
|
|
|
|
|
+ if let Err(e) = self.on_receive_restore(dry, files).await {
|
|
|
|
|
+ error!("on_receive_restore returned error: {}", e);
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ x => {
|
|
|
|
|
+ warn!("Received unsupported command: {}", x);
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
patch = self.raft.1.recv().fuse() => {
|
|
patch = self.raft.1.recv().fuse() => {
|
|
|
- for (workspace, salsa_box) in self.workspaces.iter() {
|
|
|
|
|
- if let Ok(mut patch) = decrypt_patch(&patch.clone()?, salsa_box) {
|
|
|
|
|
|
|
+ let patch = match patch {
|
|
|
|
|
+ Ok(v) => v,
|
|
|
|
|
+ Err(e) => {
|
|
|
|
|
+ error!("Failed unwrapping patch received from raft: {}", e);
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ for (workspace, key) in WORKSPACES.read().await.iter() {
|
|
|
|
|
+ if let Ok(mut patch) = decrypt_patch(&patch, key) {
|
|
|
info!("[{}] Receive a {:?}", workspace, patch);
|
|
info!("[{}] Receive a {:?}", workspace, patch);
|
|
|
patch.workspace = workspace.clone();
|
|
patch.workspace = workspace.clone();
|
|
|
- self.on_receive_patch(&patch)?;
|
|
|
|
|
|
|
+ if let Err(e) = self.on_receive_patch(&patch) {
|
|
|
|
|
+ error!("on_receive_patch returned error: {}", e);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
fn on_receive_patch(&self, received_patch: &Patch) -> Result<()> {
|
|
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);
|
|
|
|
|
|
|
+ let sync_id_path = self.settings.store_path.join(SYNC_ID_PATH).join(&received_patch.id);
|
|
|
|
|
+ let local_id_path = self.settings.store_path.join(LOCAL_ID_PATH).join(&received_patch.id);
|
|
|
|
|
|
|
|
if let Ok(mut sync_patch) = load_json_file::<Patch>(&sync_id_path) {
|
|
if let Ok(mut sync_patch) = load_json_file::<Patch>(&sync_id_path) {
|
|
|
if sync_patch.timestamp == received_patch.timestamp {
|
|
if sync_patch.timestamp == received_patch.timestamp {
|
|
@@ -252,8 +185,6 @@ impl Darkwiki {
|
|
|
} else {
|
|
} else {
|
|
|
sync_patch.extend_ops(received_patch.ops());
|
|
sync_patch.extend_ops(received_patch.ops());
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- sync_patch.extend_ops(received_patch.ops());
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
sync_patch.timestamp = received_patch.timestamp;
|
|
sync_patch.timestamp = received_patch.timestamp;
|
|
@@ -266,17 +197,10 @@ impl Darkwiki {
|
|
|
Ok(())
|
|
Ok(())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- async fn on_receive_update(
|
|
|
|
|
- &self,
|
|
|
|
|
- dry: bool,
|
|
|
|
|
- files: Vec<String>,
|
|
|
|
|
- rng: &mut OsRng,
|
|
|
|
|
- ) -> Result<()> {
|
|
|
|
|
- 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() {
|
|
|
|
|
|
|
+ async fn on_receive_update(&self, dry: bool, files: Vec<String>) -> Result<()> {
|
|
|
|
|
+ let (mut local, mut sync, mut merge) = (vec![], vec![], vec![]);
|
|
|
|
|
+
|
|
|
|
|
+ for (workspace, key) in WORKSPACES.read().await.iter() {
|
|
|
let (patches, l, s, m) = self.update(
|
|
let (patches, l, s, m) = self.update(
|
|
|
dry,
|
|
dry,
|
|
|
&self.settings.docs_path.join(workspace),
|
|
&self.settings.docs_path.join(workspace),
|
|
@@ -291,32 +215,29 @@ impl Darkwiki {
|
|
|
if !dry {
|
|
if !dry {
|
|
|
for patch in patches {
|
|
for patch in patches {
|
|
|
info!("Send a {:?}", patch);
|
|
info!("Send a {:?}", patch);
|
|
|
- let encrypt_patch = encrypt_patch(&patch, salsa_box, rng)?;
|
|
|
|
|
|
|
+ let encrypt_patch = encrypt_patch(&patch, key)?;
|
|
|
self.raft.0.send(encrypt_patch).await?;
|
|
self.raft.0.send(encrypt_patch).await?;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
self.rpc.0.send(vec![local, sync, merge]).await?;
|
|
self.rpc.0.send(vec![local, sync, merge]).await?;
|
|
|
-
|
|
|
|
|
Ok(())
|
|
Ok(())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- async fn on_receive_restore(&self, dry: bool, files_name: Vec<String>) -> Result<()> {
|
|
|
|
|
- let mut patches: Vec<Patch> = vec![];
|
|
|
|
|
|
|
+ async fn on_receive_restore(&self, dry: bool, filenames: Vec<String>) -> Result<()> {
|
|
|
|
|
+ let mut patches = vec![];
|
|
|
|
|
|
|
|
- for (workspace, _) in self.workspaces.iter() {
|
|
|
|
|
- let ps = self.restore(
|
|
|
|
|
|
|
+ for (workspace, _) in WORKSPACES.read().await.iter() {
|
|
|
|
|
+ patches.extend(self.restore(
|
|
|
dry,
|
|
dry,
|
|
|
&self.settings.docs_path.join(workspace),
|
|
&self.settings.docs_path.join(workspace),
|
|
|
- &files_name,
|
|
|
|
|
|
|
+ &filenames,
|
|
|
workspace,
|
|
workspace,
|
|
|
- )?;
|
|
|
|
|
- patches.extend(ps);
|
|
|
|
|
|
|
+ )?);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
self.rpc.0.send(vec![patches]).await?;
|
|
self.rpc.0.send(vec![patches]).await?;
|
|
|
-
|
|
|
|
|
Ok(())
|
|
Ok(())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -324,11 +245,10 @@ impl Darkwiki {
|
|
|
&self,
|
|
&self,
|
|
|
dry: bool,
|
|
dry: bool,
|
|
|
docs_path: &Path,
|
|
docs_path: &Path,
|
|
|
- files_name: &[String],
|
|
|
|
|
|
|
+ filenames: &[String],
|
|
|
workspace: &str,
|
|
workspace: &str,
|
|
|
) -> Result<Vec<Patch>> {
|
|
) -> Result<Vec<Patch>> {
|
|
|
- let local_path = self.settings.datastore_path.join("local");
|
|
|
|
|
-
|
|
|
|
|
|
|
+ let local_path = self.settings.store_path.join(LOCAL_ID_PATH);
|
|
|
let mut patches = vec![];
|
|
let mut patches = vec![];
|
|
|
|
|
|
|
|
let local_files = read_dir(&local_path)?;
|
|
let local_files = read_dir(&local_path)?;
|
|
@@ -341,7 +261,8 @@ impl Darkwiki {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if !files_name.is_empty() && !files_name.contains(&local_patch.path.to_string()) {
|
|
|
|
|
|
|
+ // TODO: FIXME: Simplify this logic, what is this? Add comments.
|
|
|
|
|
+ if !filenames.is_empty() && !filenames.contains(&local_patch.path.to_string()) {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -361,72 +282,70 @@ impl Darkwiki {
|
|
|
Ok(patches)
|
|
Ok(patches)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // TODO: Add debug/info statements and refactor this function, there's too many things going on here.
|
|
|
fn update(
|
|
fn update(
|
|
|
&self,
|
|
&self,
|
|
|
dry: bool,
|
|
dry: bool,
|
|
|
docs_path: &Path,
|
|
docs_path: &Path,
|
|
|
- files_name: Vec<String>,
|
|
|
|
|
|
|
+ filenames: Vec<String>,
|
|
|
workspace: &str,
|
|
workspace: &str,
|
|
|
) -> Result<Patches> {
|
|
) -> Result<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 (mut patches, mut local_patches, mut sync_patches, mut merge_patches) =
|
|
|
|
|
+ (vec![], vec![], vec![], vec![]);
|
|
|
|
|
|
|
|
- let local_path = self.settings.datastore_path.join("local");
|
|
|
|
|
- let sync_path = self.settings.datastore_path.join("sync");
|
|
|
|
|
|
|
+ let local_path = self.settings.store_path.join(LOCAL_ID_PATH);
|
|
|
|
|
+ let sync_path = self.settings.store_path.join(SYNC_ID_PATH);
|
|
|
|
|
|
|
|
- // save and compare docs in darkwiki and local dirs
|
|
|
|
|
- // then merged with sync patches if any received
|
|
|
|
|
|
|
+ // Save and compare docs in darkwiki and local dirs, then
|
|
|
|
|
+ // merge with sync patches if any have been received.
|
|
|
let mut docs = vec![];
|
|
let mut docs = vec![];
|
|
|
get_docs_paths(&mut docs, docs_path, None)?;
|
|
get_docs_paths(&mut docs, docs_path, None)?;
|
|
|
for doc in docs {
|
|
for doc in docs {
|
|
|
let doc_path = doc.to_str().unwrap();
|
|
let doc_path = doc.to_str().unwrap();
|
|
|
|
|
|
|
|
- if !files_name.is_empty() && !files_name.contains(&doc_path.to_string()) {
|
|
|
|
|
|
|
+ // FIXME: IDGI
|
|
|
|
|
+ if !filenames.is_empty() && !filenames.contains(&doc_path.to_string()) {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // load doc content
|
|
|
|
|
|
|
+ // Load doc content
|
|
|
let edit = load_file(&docs_path.join(doc_path))?;
|
|
let edit = load_file(&docs_path.join(doc_path))?;
|
|
|
-
|
|
|
|
|
if edit.is_empty() {
|
|
if edit.is_empty() {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
let doc_id = path_to_id(doc_path, workspace);
|
|
let doc_id = path_to_id(doc_path, workspace);
|
|
|
|
|
|
|
|
- // create new patch
|
|
|
|
|
|
|
+ // Create new patch
|
|
|
let mut new_patch = Patch::new(doc_path, &doc_id, &self.settings.author, workspace);
|
|
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
|
|
|
|
|
|
|
+ // 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)) {
|
|
if let Ok(local_patch) = load_json_file::<Patch>(&local_path.join(&doc_id)) {
|
|
|
- // no changes found
|
|
|
|
|
|
|
+ // No changes found
|
|
|
if local_patch.to_string() == edit {
|
|
if local_patch.to_string() == edit {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // check the differences with LCS algorithm
|
|
|
|
|
|
|
+ // Check the differences with LCS algorithm
|
|
|
let local_patch_str = local_patch.to_string();
|
|
let local_patch_str = local_patch.to_string();
|
|
|
let lcs = Lcs::new(&local_patch_str, &edit);
|
|
let lcs = Lcs::new(&local_patch_str, &edit);
|
|
|
let lcs_ops = lcs.ops();
|
|
let lcs_ops = lcs.ops();
|
|
|
|
|
|
|
|
- // add the change ops to the new patch
|
|
|
|
|
|
|
+ // Add the change ops to the new patch
|
|
|
for op in lcs_ops {
|
|
for op in lcs_ops {
|
|
|
new_patch.add_op(&op);
|
|
new_patch.add_op(&op);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
new_patch.base = local_patch.to_string();
|
|
new_patch.base = local_patch.to_string();
|
|
|
-
|
|
|
|
|
local_patches.push(new_patch.clone());
|
|
local_patches.push(new_patch.clone());
|
|
|
|
|
|
|
|
let mut b_patch = new_patch.clone();
|
|
let mut b_patch = new_patch.clone();
|
|
|
b_patch.base = "".to_string();
|
|
b_patch.base = "".to_string();
|
|
|
patches.push(b_patch);
|
|
patches.push(b_patch);
|
|
|
|
|
|
|
|
- // check if the same doc has received patch from the network
|
|
|
|
|
|
|
+ // Check if the same doc has received a patch from the network
|
|
|
if let Ok(sync_patch) = load_json_file::<Patch>(&sync_path.join(&doc_id)) {
|
|
if let Ok(sync_patch) = load_json_file::<Patch>(&sync_path.join(&doc_id)) {
|
|
|
- if !is_delete_patch(&sync_patch) {
|
|
|
|
|
|
|
+ if !Self::is_delete_patch(&sync_patch) {
|
|
|
if sync_patch.timestamp != local_patch.timestamp {
|
|
if sync_patch.timestamp != local_patch.timestamp {
|
|
|
sync_patches.push(sync_patch.clone());
|
|
sync_patches.push(sync_patch.clone());
|
|
|
|
|
|
|
@@ -450,12 +369,12 @@ impl Darkwiki {
|
|
|
|
|
|
|
|
if !dry {
|
|
if !dry {
|
|
|
save_json_file(&local_path.join(&doc_id), &new_patch)?;
|
|
save_json_file(&local_path.join(&doc_id), &new_patch)?;
|
|
|
- save_json_file(&sync_path.join(doc_id), &new_patch)?;
|
|
|
|
|
|
|
+ save_json_file(&sync_path.join(&doc_id), &new_patch)?;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // check if a new patch received
|
|
|
|
|
- // and save the new changes in both local and darkwiki dirs
|
|
|
|
|
|
|
+ // Check if a new patch is received and save the new changes
|
|
|
|
|
+ // in both local and darkwiki dirs.
|
|
|
let sync_files = read_dir(&sync_path)?;
|
|
let sync_files = read_dir(&sync_path)?;
|
|
|
for file in sync_files {
|
|
for file in sync_files {
|
|
|
let file_id = file?.file_name();
|
|
let file_id = file?.file_name();
|
|
@@ -466,15 +385,15 @@ impl Darkwiki {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if is_delete_patch(&sync_patch) {
|
|
|
|
|
|
|
+ if Self::is_delete_patch(&sync_patch) {
|
|
|
if local_path.join(&sync_patch.id).exists() {
|
|
if local_path.join(&sync_patch.id).exists() {
|
|
|
sync_patches.push(sync_patch.clone());
|
|
sync_patches.push(sync_patch.clone());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if !dry {
|
|
if !dry {
|
|
|
- remove_file(docs_path.join(&sync_patch.path)).unwrap_or(());
|
|
|
|
|
- remove_file(local_path.join(&sync_patch.id)).unwrap_or(());
|
|
|
|
|
- remove_file(file_path).unwrap_or(());
|
|
|
|
|
|
|
+ remove_file(docs_path.join(&sync_patch.path))?;
|
|
|
|
|
+ remove_file(local_path.join(&sync_patch.id))?;
|
|
|
|
|
+ remove_file(file_path)?;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
continue
|
|
continue
|
|
@@ -486,7 +405,8 @@ impl Darkwiki {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if !files_name.is_empty() && !files_name.contains(&sync_patch.path.to_string()) {
|
|
|
|
|
|
|
+ // TODO: FIXME: IDGI AGAIN, HALP
|
|
|
|
|
+ if !filenames.is_empty() && !filenames.contains(&sync_patch.path.to_string()) {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -500,7 +420,7 @@ impl Darkwiki {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // check if any doc removed from ~/darkwiki
|
|
|
|
|
|
|
+ // Check if any doc is removed from darkwiki filesystem.
|
|
|
let local_files = read_dir(&local_path)?;
|
|
let local_files = read_dir(&local_path)?;
|
|
|
for file in local_files {
|
|
for file in local_files {
|
|
|
let file_id = file?.file_name();
|
|
let file_id = file?.file_name();
|
|
@@ -511,7 +431,8 @@ impl Darkwiki {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if !files_name.is_empty() && !files_name.contains(&local_patch.path.to_string()) {
|
|
|
|
|
|
|
+ // TODO: FIXME: Is it just supposed to check that filenames doesn't contain the local_patch?
|
|
|
|
|
+ if !filenames.is_empty() && !filenames.contains(&local_patch.path.to_string()) {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -529,7 +450,7 @@ impl Darkwiki {
|
|
|
local_patches.push(new_patch);
|
|
local_patches.push(new_patch);
|
|
|
|
|
|
|
|
if !dry {
|
|
if !dry {
|
|
|
- remove_file(file_path).unwrap_or(());
|
|
|
|
|
|
|
+ remove_file(file_path)?;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -546,151 +467,189 @@ impl Darkwiki {
|
|
|
}
|
|
}
|
|
|
save_file(&path, edit)
|
|
save_file(&path, edit)
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-async_daemonize!(realmain);
|
|
|
|
|
-async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
|
|
|
|
|
- let docs_path = expand_path(&settings.docs)?;
|
|
|
|
|
- let datastore_path = expand_path(docs_path.join(".log").to_str().unwrap())?;
|
|
|
|
|
-
|
|
|
|
|
- if settings.refresh {
|
|
|
|
|
- println!("Removing local docs in: {:?} (yes/no)? ", docs_path);
|
|
|
|
|
- let mut confirm = String::new();
|
|
|
|
|
- stdin().read_line(&mut confirm).expect("Failed to read line");
|
|
|
|
|
-
|
|
|
|
|
- let confirm = confirm.to_lowercase();
|
|
|
|
|
- let confirm = confirm.trim();
|
|
|
|
|
-
|
|
|
|
|
- if confirm == "yes" || confirm == "y" {
|
|
|
|
|
- remove_dir_all(docs_path).unwrap_or(());
|
|
|
|
|
- println!("Local data removed successfully.");
|
|
|
|
|
- } else {
|
|
|
|
|
- error!("Unexpected Value: {}", confirm);
|
|
|
|
|
|
|
+ fn is_delete_patch(patch: &Patch) -> bool {
|
|
|
|
|
+ if patch.ops().0.len() != 1 {
|
|
|
|
|
+ return false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return Ok(())
|
|
|
|
|
|
|
+ if let OpMethod::Delete(d) = patch.ops().0[0] {
|
|
|
|
|
+ if patch.base.len() as u64 == d {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ false
|
|
|
}
|
|
}
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- create_dir_all(docs_path.clone())?;
|
|
|
|
|
- create_dir_all(datastore_path.clone())?;
|
|
|
|
|
- create_dir_all(datastore_path.join("local"))?;
|
|
|
|
|
- create_dir_all(datastore_path.join("sync"))?;
|
|
|
|
|
|
|
+async fn handle_signals(
|
|
|
|
|
+ mut signals: Signals,
|
|
|
|
|
+ cfg_path: PathBuf,
|
|
|
|
|
+ term_tx: async_channel::Sender<()>,
|
|
|
|
|
+) {
|
|
|
|
|
+ debug!("Started signal handler");
|
|
|
|
|
+ while let Some(signal) = signals.next().await {
|
|
|
|
|
+ match signal {
|
|
|
|
|
+ SIGHUP => {
|
|
|
|
|
+ info!("Caught SIGHUP");
|
|
|
|
|
+ let toml_contents = match std::fs::read_to_string(cfg_path.clone()) {
|
|
|
|
|
+ Ok(v) => v,
|
|
|
|
|
+ Err(e) => {
|
|
|
|
|
+ error!("Couldn't load configuration file: {}", e);
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ *WORKSPACES.write().await = parse_workspaces(&toml_contents);
|
|
|
|
|
+ info!("Reloaded workspaces");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if settings.generate {
|
|
|
|
|
- println!("Generating a new workspace");
|
|
|
|
|
|
|
+ SIGTERM | SIGINT | SIGQUIT => {
|
|
|
|
|
+ term_tx.send(()).await.unwrap();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _ => unreachable!(),
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async_daemonize!(realmain);
|
|
|
|
|
+async fn realmain(args: Args, executor: Arc<Executor<'_>>) -> Result<()> {
|
|
|
|
|
+ let cfg_path = get_config_path(args.config, CONFIG_FILE)?;
|
|
|
|
|
+ let docs_path = expand_path(&args.docs)?;
|
|
|
|
|
+ let store_path = expand_path(docs_path.join(".log").to_str().unwrap())?;
|
|
|
|
|
|
|
|
|
|
+ create_dir_all(docs_path.clone())?;
|
|
|
|
|
+ create_dir_all(store_path.clone())?;
|
|
|
|
|
+ create_dir_all(store_path.join(LOCAL_ID_PATH))?;
|
|
|
|
|
+ create_dir_all(store_path.join(SYNC_ID_PATH))?;
|
|
|
|
|
+
|
|
|
|
|
+ if args.gen_secret {
|
|
|
|
|
+ eprintln!("Generating a new workspace");
|
|
|
loop {
|
|
loop {
|
|
|
- println!("Name for the new workspace: ");
|
|
|
|
|
|
|
+ eprint!("Input the name for the new workspace (use ascii chars): ");
|
|
|
let mut workspace = String::new();
|
|
let mut workspace = String::new();
|
|
|
- stdin().read_line(&mut workspace).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");
|
|
|
|
|
|
|
+ stdin().read_line(&mut workspace)?;
|
|
|
|
|
+ // Non-exhaustive
|
|
|
|
|
+ let workspace = workspace
|
|
|
|
|
+ .replace('\n', "")
|
|
|
|
|
+ .replace('\t', "_")
|
|
|
|
|
+ .replace('\r', "_")
|
|
|
|
|
+ .replace(' ', "_")
|
|
|
|
|
+ .replace('/', "_")
|
|
|
|
|
+ .replace('\\', "_")
|
|
|
|
|
+ .replace('\'', "_")
|
|
|
|
|
+ .replace('&', "_")
|
|
|
|
|
+ .replace('~', "_")
|
|
|
|
|
+ .replace(':', "_");
|
|
|
|
|
+
|
|
|
|
|
+ if workspace.is_empty() || workspace.len() < 3 {
|
|
|
|
|
+ eprintln!("Error: Workspace name is empty or less than 3 characters. Try again.");
|
|
|
continue
|
|
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))?;
|
|
|
|
|
|
|
+ let secret = bs58::encode(crypto_secretbox_keygen()).into_string();
|
|
|
|
|
+ create_dir_all(docs_path.join(workspace.clone()))?;
|
|
|
|
|
|
|
|
- println!("workspace: {}:{}", workspace, encoded.into_string());
|
|
|
|
|
- println!("Please add it to the config file.");
|
|
|
|
|
- break
|
|
|
|
|
|
|
+ println!("Created workspace: {}:{}", workspace, secret);
|
|
|
|
|
+ eprintln!("Please add it to the config file.");
|
|
|
|
|
+ return Ok(())
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- return Ok(())
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- 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(())
|
|
|
|
|
|
|
+ // Signal handling for config reload and graceful termination.
|
|
|
|
|
+ let signals = Signals::new(&[SIGHUP, SIGTERM, SIGINT, SIGQUIT])?;
|
|
|
|
|
+ let handle = signals.handle();
|
|
|
|
|
+ let (term_tx, term_rx) = async_channel::bounded::<()>(1);
|
|
|
|
|
+ let signals_task = task::spawn(handle_signals(signals, cfg_path.clone(), term_tx));
|
|
|
|
|
+ info!("Set up signal handling");
|
|
|
|
|
+
|
|
|
|
|
+ {
|
|
|
|
|
+ info!("Parsing configuration file for workspaces");
|
|
|
|
|
+ let toml_contents = std::fs::read_to_string(cfg_path.clone())?;
|
|
|
|
|
+ *WORKSPACES.write().await = parse_workspaces(&toml_contents);
|
|
|
|
|
+ if WORKSPACES.read().await.is_empty() {
|
|
|
|
|
+ eprintln!("Please add atleast one workspace to the config file.");
|
|
|
|
|
+ eprintln!("Run \"$ darkwikid --gen-secret\" to create a new workspace.");
|
|
|
|
|
+ exit(1);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- let (rpc_sx, rpc_rv) = async_channel::unbounded::<(String, bool, Vec<String>)>();
|
|
|
|
|
- let (notify_sx, notify_rv) = async_channel::unbounded::<Vec<Vec<Patch>>>();
|
|
|
|
|
|
|
+ let (rpc_tx, rpc_rx) = async_channel::unbounded::<(String, bool, Vec<String>)>();
|
|
|
|
|
+ let (notify_tx, notify_rx) = async_channel::unbounded::<Vec<Vec<Patch>>>();
|
|
|
|
|
|
|
|
- //
|
|
|
|
|
- // RPC
|
|
|
|
|
- //
|
|
|
|
|
- let rpc_interface = Arc::new(JsonRpcInterface::new(rpc_sx, notify_rv));
|
|
|
|
|
- executor.spawn(listen_and_serve(settings.rpc_listen.clone(), rpc_interface)).detach();
|
|
|
|
|
|
|
+ // ===============
|
|
|
|
|
+ // JSON-RPC server
|
|
|
|
|
+ // ===============
|
|
|
|
|
+ let rpc_iface = Arc::new(JsonRpcInterface::new(rpc_tx, notify_rx));
|
|
|
|
|
+ executor.spawn(listen_and_serve(args.rpc_listen, rpc_iface)).detach();
|
|
|
|
|
|
|
|
- //
|
|
|
|
|
|
|
+ // ====
|
|
|
// Raft
|
|
// Raft
|
|
|
- //
|
|
|
|
|
|
|
+ // ====
|
|
|
let seen_net_msgs = Arc::new(Mutex::new(FxHashMap::default()));
|
|
let seen_net_msgs = Arc::new(Mutex::new(FxHashMap::default()));
|
|
|
-
|
|
|
|
|
- let datastore_raft = datastore_path.join("darkwiki.db");
|
|
|
|
|
- let raft_settings = RaftSettings { datastore_path: datastore_raft, ..RaftSettings::default() };
|
|
|
|
|
-
|
|
|
|
|
- let mut raft = Raft::<EncryptedPatch>::new(raft_settings, seen_net_msgs.clone())?;
|
|
|
|
|
-
|
|
|
|
|
- //
|
|
|
|
|
- // P2p setup
|
|
|
|
|
- //
|
|
|
|
|
- let mut net_settings = settings.net.clone();
|
|
|
|
|
|
|
+ let store_raft = store_path.join("darkwiki.db");
|
|
|
|
|
+ let raft_settings = RaftSettings { datastore_path: store_raft, ..RaftSettings::default() };
|
|
|
|
|
+ // FIXME: This is a bad design, and needs a proper rework.
|
|
|
|
|
+ let raft =
|
|
|
|
|
+ Arc::new(Mutex::new(Raft::<EncryptedPatch>::new(raft_settings, seen_net_msgs.clone())?));
|
|
|
|
|
+
|
|
|
|
|
+ // =========
|
|
|
|
|
+ // P2P setup
|
|
|
|
|
+ // =========
|
|
|
|
|
+ let mut net_settings = args.net.clone();
|
|
|
net_settings.app_version = Some(option_env!("CARGO_PKG_VERSION").unwrap_or("").to_string());
|
|
net_settings.app_version = Some(option_env!("CARGO_PKG_VERSION").unwrap_or("").to_string());
|
|
|
- let (p2p_send_channel, p2p_recv_channel) = async_channel::unbounded::<NetMsg>();
|
|
|
|
|
-
|
|
|
|
|
|
|
+ let (p2p_tx, p2p_rx) = async_channel::unbounded::<NetMsg>();
|
|
|
let p2p = net::P2p::new(net_settings.into()).await;
|
|
let p2p = net::P2p::new(net_settings.into()).await;
|
|
|
- let p2p = p2p.clone();
|
|
|
|
|
-
|
|
|
|
|
let registry = p2p.protocol_registry();
|
|
let registry = p2p.protocol_registry();
|
|
|
|
|
|
|
|
- let raft_node_id = raft.id();
|
|
|
|
|
- registry
|
|
|
|
|
- .register(net::SESSION_ALL, move |channel, p2p| {
|
|
|
|
|
- let raft_node_id = raft_node_id.clone();
|
|
|
|
|
- let sender = p2p_send_channel.clone();
|
|
|
|
|
- let seen_net_msgs_cloned = seen_net_msgs.clone();
|
|
|
|
|
- async move {
|
|
|
|
|
- ProtocolRaft::init(raft_node_id, channel, sender, p2p, seen_net_msgs_cloned).await
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- .await;
|
|
|
|
|
|
|
+ let raft_node_id = raft.lock().await.id();
|
|
|
|
|
+ registry.register(net::SESSION_ALL, move | channel, p2p| {
|
|
|
|
|
+ let raft_node_id = raft_node_id.clone();
|
|
|
|
|
+ let sender = p2p_tx.clone();
|
|
|
|
|
+ let seen_net_msgs = seen_net_msgs.clone();
|
|
|
|
|
+ async move {
|
|
|
|
|
+ ProtocolRaft::init(raft_node_id, channel, sender, p2p, seen_net_msgs).await
|
|
|
|
|
+ }
|
|
|
|
|
+ }).await;
|
|
|
|
|
|
|
|
p2p.clone().start(executor.clone()).await?;
|
|
p2p.clone().start(executor.clone()).await?;
|
|
|
-
|
|
|
|
|
executor.spawn(p2p.clone().run(executor.clone())).detach();
|
|
executor.spawn(p2p.clone().run(executor.clone())).detach();
|
|
|
|
|
|
|
|
- //
|
|
|
|
|
|
|
+ // ==============
|
|
|
// Darkwiki start
|
|
// Darkwiki start
|
|
|
- //
|
|
|
|
|
- let raft_sx = raft.sender();
|
|
|
|
|
- let raft_rv = raft.receiver();
|
|
|
|
|
|
|
+ // ==============
|
|
|
|
|
+ let raft_tx = raft.lock().await.sender();
|
|
|
|
|
+ let raft_rx = raft.lock().await.receiver();
|
|
|
executor
|
|
executor
|
|
|
.spawn(async move {
|
|
.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),
|
|
|
|
|
- workspaces,
|
|
|
|
|
- };
|
|
|
|
|
- darkwiki.start().await.unwrap_or(());
|
|
|
|
|
|
|
+ let settings = DarkWikiSettings { author: args.author, store_path, docs_path };
|
|
|
|
|
+ let dw = DarkWiki { settings, raft: (raft_tx, raft_rx), rpc: (notify_tx, rpc_rx) };
|
|
|
|
|
+ dw.start().await.unwrap();
|
|
|
})
|
|
})
|
|
|
.detach();
|
|
.detach();
|
|
|
|
|
|
|
|
- //
|
|
|
|
|
- // Waiting Exit signal
|
|
|
|
|
- //
|
|
|
|
|
- let (signal, shutdown) = async_channel::bounded::<()>(1);
|
|
|
|
|
- ctrlc::set_handler(move || {
|
|
|
|
|
- warn!(target: "darkwiki", "Catch exit signal");
|
|
|
|
|
- // cleaning up tasks running in the background
|
|
|
|
|
- if let Err(e) = async_std::task::block_on(signal.send(())) {
|
|
|
|
|
- error!("Error on sending exit signal: {}", e);
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- .unwrap();
|
|
|
|
|
|
|
+ let (raft_term_tx, raft_term_rx) = async_channel::bounded::<()>(1);
|
|
|
|
|
+ let _p2p = p2p.clone();
|
|
|
|
|
+ let _ex = executor.clone();
|
|
|
|
|
+ executor
|
|
|
|
|
+ .spawn(async move { raft.lock().await.run(_p2p, p2p_rx, _ex, raft_term_rx).await.unwrap() })
|
|
|
|
|
+ .detach();
|
|
|
|
|
+
|
|
|
|
|
+ // Wait for termination signal
|
|
|
|
|
+ term_rx.recv().await?;
|
|
|
|
|
+ eprint!("\r");
|
|
|
|
|
+ info!("Caught termination signal, cleaning up and exiting...");
|
|
|
|
|
+ handle.close();
|
|
|
|
|
+ signals_task.await;
|
|
|
|
|
+
|
|
|
|
|
+ info!("Stopping Raft...");
|
|
|
|
|
+ raft_term_tx.send(()).await.unwrap();
|
|
|
|
|
|
|
|
- raft.run(p2p.clone(), p2p_recv_channel.clone(), executor.clone(), shutdown.clone()).await?;
|
|
|
|
|
|
|
+ info!("Stopping P2P network...");
|
|
|
|
|
+ p2p.stop().await;
|
|
|
|
|
|
|
|
|
|
+ info!("Bye.");
|
|
|
Ok(())
|
|
Ok(())
|
|
|
}
|
|
}
|