script/research: create raft diagnostic tool

This commit is contained in:
ghassmo
2022-07-24 01:31:30 +04:00
parent 9df075dfb8
commit 6d9177691b
11 changed files with 279 additions and 824 deletions

View File

@@ -8,7 +8,6 @@ use fxhash::FxHashMap;
use log::{debug, error, info, warn};
use smol::future;
use structopt_toml::StructOptToml;
use fxhash::FxHashMap;
use darkfi::{
async_daemonize, net,

View File

@@ -1,3 +1,2 @@
/target
Cargo.lock
raft_db

View File

@@ -0,0 +1,39 @@
[package]
name = "raft-diag"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
darkfi = {path = "../../../", features = ["raft"]}
# Async
smol = "1.2.5"
async-std = {version = "1.12.0", features = ["attributes"]}
async-trait = "0.1.56"
async-channel = "1.6.1"
async-executor = "1.4.1"
easy-parallel = "3.2.0"
futures = "0.3.21"
# Misc
log = "0.4.17"
simplelog = "0.12.0"
rand = "0.8.5"
chrono = "0.4.19"
thiserror = "1.0.31"
ctrlc-async = {version= "3.2.2", default-features = false, features = ["async-std", "termination"]}
url = "2.2.2"
fxhash = "0.2.1"
# Encoding and parsing
serde = {version = "1.0.138", features = ["derive"]}
serde_json = "1.0.82"
structopt = "0.3.26"
hex = "0.4.3"
bs58 = "0.4.0"
toml = "0.5.9"
[workspace]

View File

@@ -0,0 +1,228 @@
use async_std::sync::{Arc, Mutex};
use std::path::Path;
use async_executor::Executor;
use fxhash::FxHashMap;
use log::{error, info, warn};
use smol::future;
use structopt::StructOpt;
use url::Url;
use darkfi::{
net,
raft::{DataStore, NetMsg, ProtocolRaft, Raft},
util::{
cli::{get_log_config, get_log_level},
expand_path,
serial::{SerialDecodable, SerialEncodable},
sleep,
},
Result,
};
#[derive(Clone, Debug, StructOpt)]
#[structopt(name = "raft-diag")]
pub struct Args {
/// JSON-RPC listen URL
#[structopt(long = "rpc", default_value = "tcp://127.0.0.1:12055")]
pub rpc_listen: Url,
/// Inbound listen URL
#[structopt(long = "inbound")]
pub inbound_url: Option<Url>,
/// Seed Urls
#[structopt(long = "seeds")]
pub seed_urls: Vec<Url>,
/// Outbound connections
#[structopt(long = "outbound", default_value = "0")]
pub outbound_connections: u32,
/// Sets Datastore Path
#[structopt(long = "path", default_value = "test1.db")]
pub datastore: String,
/// Check if all datastore paths provided are synced
#[structopt(long = "check")]
pub check: Vec<String>,
/// Datastore path to extract and print it
#[structopt(long = "extract")]
pub extract: Option<String>,
/// Number of messages to broadcast
#[structopt(short, default_value = "0")]
pub broadcast: u32,
/// Increase verbosity
#[structopt(short, parse(from_occurrences))]
pub verbose: u8,
}
#[derive(Debug, Clone, SerialEncodable, SerialDecodable, PartialEq, Eq)]
pub struct Message {
payload: String,
}
fn extract(path: &str) -> Result<()> {
if !Path::new(path).exists() {
return Ok(())
}
let db = DataStore::<Message>::new(path)?;
let commits = db.commits.get_all()?;
println!("{:?}", commits);
Ok(())
}
fn check(args: Args) -> Result<()> {
let mut commits_check = vec![];
for path in args.check {
if !Path::new(&path).exists() {
continue
}
let db = DataStore::<Message>::new(&path)?;
let commits = db.commits.get_all()?;
commits_check.push(commits);
}
let result = commits_check.windows(2).all(|w| w[0] == w[1]);
println!("Synced: {}", result);
Ok(())
}
async fn start_broadcasting(n: u32, sender: async_channel::Sender<Message>) -> Result<()> {
sleep(10).await;
info!("Start broadcasting...");
for id in 0..n {
sleep(3).await;
let msg = format!("msg_test_{}", id);
info!("Send a message {:?}", msg);
let msg = Message { payload: msg };
sender.send(msg).await?;
}
Ok(())
}
async fn receive_loop(receiver: async_channel::Receiver<Message>) -> Result<()> {
loop {
let msg = receiver.recv().await?;
info!("Receive new msg {:?}", msg);
}
}
async fn start(args: Args, executor: Arc<Executor<'_>>) -> Result<()> {
let net_settings = net::Settings {
outbound_connections: args.outbound_connections,
inbound: args.inbound_url.clone(),
external_addr: args.inbound_url,
seeds: args.seed_urls,
..net::Settings::default()
};
//
// Raft
//
let datastore_raft = expand_path(&args.datastore)?;
let seen_net_msgs = Arc::new(Mutex::new(FxHashMap::default()));
let mut raft =
Raft::<Message>::new(net_settings.inbound.clone(), datastore_raft, seen_net_msgs.clone())?;
//
// P2p setup
//
let (p2p_send_channel, p2p_recv_channel) = async_channel::unbounded::<NetMsg>();
let p2p = net::P2p::new(net_settings).await;
let p2p = p2p.clone();
let registry = p2p.protocol_registry();
let raft_node_id = raft.id.clone();
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;
p2p.clone().start(executor.clone()).await?;
executor.spawn(p2p.clone().run(executor.clone())).detach();
//
// Waiting Exit signal
//
let (signal, shutdown) = async_channel::bounded::<()>(1);
ctrlc_async::set_async_handler(async move {
warn!("Catch exit signal");
// cleaning up tasks running in the background
if let Err(e) = signal.send(()).await {
error!("Error on sending exit signal: {}", e);
}
})
.unwrap();
if args.broadcast != 0 {
executor.spawn(start_broadcasting(args.broadcast, raft.get_msgs_channel())).detach();
}
executor.spawn(receive_loop(raft.get_commits_channel())).detach();
raft.start(p2p.clone(), p2p_recv_channel.clone(), executor.clone(), shutdown.clone()).await?;
Ok(())
}
fn main() -> Result<()> {
let args = Args::from_args();
let log_level = get_log_level(args.verbose.into());
let log_config = get_log_config();
let mut log_path = expand_path(&args.datastore)?;
let log_name: String = log_path.file_name().as_ref().unwrap().to_str().unwrap().to_owned();
log_path.pop();
let log_path = log_path.join(&format!("{}.log", log_name));
let env_log_file_path = std::fs::File::create(log_path).unwrap();
simplelog::CombinedLogger::init(vec![
simplelog::TermLogger::new(
log_level,
log_config.clone(),
simplelog::TerminalMode::Mixed,
simplelog::ColorChoice::Auto,
),
simplelog::WriteLogger::new(log_level, log_config, env_log_file_path),
])?;
if !args.check.is_empty() {
return check(args)
}
if args.extract.is_some() {
return extract(&args.extract.unwrap())
}
// https://docs.rs/smol/latest/smol/struct.Executor.html#examples
let ex = Arc::new(async_executor::Executor::new());
let (signal, shutdown) = async_channel::unbounded::<()>();
let (_, result) = easy_parallel::Parallel::new()
// Run four executor threads
.each(0..4, |_| future::block_on(ex.run(shutdown.recv())))
// Run the main future on the current thread.
.finish(|| {
future::block_on(async {
start(args, ex.clone()).await?;
drop(signal);
Ok::<(), darkfi::Error>(())
})
});
result
}

View File

@@ -0,0 +1,12 @@
#!/bin/sh
export LOG_TARGETS='!net,!sled,!rustls'
tmux new-session -d "./target/release/raft-diag --inbound tcp://127.0.0.1:12001 --path test1.db -v"
sleep 3
tmux split-window -v "./target/release/raft-diag --inbound tcp://127.0.0.1:12002 --seeds tcp://127.0.0.1:12001 --outbound 3 --path test2.db -v"
sleep 2
tmux split-window -h "./target/release/raft-diag --seeds tcp://127.0.0.1:12001 --outbound 3 --path test3.db -v"
sleep 1
tmux select-pane -t 0
tmux split-window -h "./target/release/raft-diag --seeds tcp://127.0.0.1:12001 --outbound 3 --path test4.db -b 3 -v"
tmux attach

View File

@@ -1,28 +0,0 @@
[package]
name = "raft-tool"
version = "0.3.0"
edition = "2021"
[dependencies.darkfi]
path = "../../../"
features = ["raft"]
[dependencies]
async-std = "1.12.0"
sled = "0.34.7"
# for taud
crypto_box = {version = "0.7.2", features = ["std"]}
hex = "0.4.3"
log = "0.4.17"
simplelog = "0.12.0"
rand = "0.8.5"
chrono = "0.4.19"
url = "2.2.2"
serde = {version = "1.0.138", features = ["derive"]}
serde_json = "1.0.82"
thiserror = "1.0.31"
[workspace]

View File

@@ -1,45 +0,0 @@
use serde_json::Value;
use darkfi::rpc::jsonrpc::{ErrorCode, JsonError, JsonResponse, JsonResult};
#[derive(Debug, thiserror::Error)]
pub enum TaudError {
#[error("Due timestamp invalid")]
InvalidDueTime,
#[error("Invalid Id")]
InvalidId,
#[error("Invalid Data/Params: `{0}` ")]
InvalidData(String),
#[error("InternalError")]
Darkfi(#[from] darkfi::error::Error),
#[error("Json serialization error: `{0}`")]
SerdeJsonError(String),
}
pub type TaudResult<T> = std::result::Result<T, TaudError>;
impl From<serde_json::Error> for TaudError {
fn from(err: serde_json::Error) -> TaudError {
TaudError::SerdeJsonError(err.to_string())
}
}
pub fn to_json_result(res: TaudResult<Value>, id: Value) -> JsonResult {
match res {
Ok(v) => JsonResponse::new(v, id).into(),
Err(err) => match err {
TaudError::InvalidId => {
JsonError::new(ErrorCode::InvalidParams, Some("invalid task id".into()), id).into()
}
TaudError::InvalidData(e) | TaudError::SerdeJsonError(e) => {
JsonError::new(ErrorCode::InvalidParams, Some(e), id).into()
}
TaudError::InvalidDueTime => {
JsonError::new(ErrorCode::InvalidParams, Some("invalid due time".into()), id).into()
}
TaudError::Darkfi(e) => {
JsonError::new(ErrorCode::InternalError, Some(e.to_string()), id).into()
}
},
}
}

View File

@@ -1,203 +0,0 @@
use std::{fs::File, io::Write};
use crypto_box::{aead::Aead, Box, SecretKey, KEY_SIZE};
use darkfi::{
raft::DataStore,
util::{
expand_path,
serial::{deserialize, SerialDecodable, SerialEncodable},
},
Result,
};
mod error;
mod month_tasks;
mod task_info;
mod util;
use crate::{task_info::TaskInfo, util::load};
#[derive(Debug)]
pub struct Info<T> {
pub logs: Vec<Log<T>>,
pub commits: Vec<T>,
pub voted_for: Vec<Option<NodeId>>,
pub terms: Vec<u64>,
}
#[derive(Debug)]
pub struct Log<T> {
pub term: u64,
pub msg: T,
}
#[derive(Debug)]
pub struct NodeId(Vec<u8>);
#[derive(Debug, SerialEncodable, SerialDecodable)]
struct EncryptedTask {
nonce: Vec<u8>,
payload: Vec<u8>,
}
fn decrypt_task(encrypt_task: &EncryptedTask, secret_key: &SecretKey) -> Option<TaskInfo> {
let public_key = secret_key.public_key();
let msg_box = Box::new(&public_key, secret_key);
let nonce = encrypt_task.nonce.as_slice();
let decrypted_task = match msg_box.decrypt(nonce.into(), &encrypt_task.payload[..]) {
Ok(m) => m,
Err(_) => return None,
};
deserialize(&decrypted_task).ok()
}
type PrivmsgId = u32;
#[derive(Debug, SerialEncodable, SerialDecodable)]
struct Privmsg {
id: PrivmsgId,
nickname: String,
channel: String,
message: String,
}
fn extract_taud() -> Result<String> {
let db_path = expand_path(&"~/.config/darkfi/tau/tau.db").unwrap();
let datastore = DataStore::<EncryptedTask>::new(&db_path.to_str().unwrap())?;
let sk_path = expand_path(&"~/.config/darkfi/tau/secret_key").unwrap();
let sk = {
let loaded_key = load::<String>(&sk_path);
if loaded_key.is_err() {
log::error!(
"Could not load secret key from file, \
please run \"taud --help\" for more information"
);
return Ok("Load secret_key error".into())
}
let sk_bytes = hex::decode(loaded_key.unwrap())?;
let sk_bytes: [u8; KEY_SIZE] = sk_bytes.as_slice().try_into()?;
SecretKey::try_from(sk_bytes)?
};
println!("Extracting db from: {:?}", db_path);
// Retrieve all data trees
let sled_logs = datastore.logs.get_all()?;
let sled_commits = datastore.commits.get_all()?;
let sled_voted_for = datastore.voted_for.get_all()?;
let sled_terms = datastore.current_term.get_all()?;
// Parse retrieved data trees
println!("Data extracted, parsing to viewable form...");
// Logs
let mut logs = vec![];
for log in sled_logs {
let encrypt_task: EncryptedTask = deserialize(&log.msg)?;
let task_info = decrypt_task(&encrypt_task, &sk).unwrap();
logs.push(Log { term: log.term, msg: task_info });
}
// Commits
let mut commits = vec![];
for commit in &sled_commits {
let task_info = decrypt_task(
&EncryptedTask { nonce: commit.nonce.clone(), payload: commit.payload.clone() },
&sk,
)
.unwrap();
commits.push(task_info);
}
// Voted for
let mut voted_for = vec![];
for vote in sled_voted_for {
match vote {
Some(v) => voted_for.push(Some(NodeId(v.0.clone()))),
None => voted_for.push(None),
}
}
// Terms
let mut terms = vec![];
for term in sled_terms {
terms.push(term);
}
let info = Info::<TaskInfo> { logs, commits, voted_for, terms };
let info_string = format!("{:#?}", info);
Ok(info_string)
}
fn extract_ircd() -> Result<String> {
let db_path = expand_path(&"~/.config/darkfi/ircd/ircd.db").unwrap();
let datastore = DataStore::<Privmsg>::new(&db_path.to_str().unwrap())?;
println!("Extracting db from: {:?}", db_path);
// Retrieve all data trees
let sled_logs = datastore.logs.get_all()?;
let sled_commits = datastore.commits.get_all()?;
let sled_voted_for = datastore.voted_for.get_all()?;
let sled_terms = datastore.current_term.get_all()?;
// Parse retrieved data trees
println!("Data extracted, parsing to viewable form...");
// Logs
let mut logs = vec![];
for log in sled_logs {
logs.push(Log { term: log.term, msg: deserialize(&log.msg)? });
}
// Commits
let mut commits = vec![];
for commit in &sled_commits {
commits.push(Privmsg {
id: commit.id,
nickname: commit.nickname.clone(),
channel: commit.channel.clone(),
message: commit.message.clone(),
});
}
// Voted for
let mut voted_for = vec![];
for vote in sled_voted_for {
match vote {
Some(v) => voted_for.push(Some(NodeId(v.0.clone()))),
None => voted_for.push(None),
}
}
// Terms
let mut terms = vec![];
for term in sled_terms {
terms.push(term);
}
let info = Info::<Privmsg> { logs, commits, voted_for, terms };
let info_string = format!("{:#?}", info);
Ok(info_string)
}
#[async_std::main]
async fn main() -> Result<()> {
let info_string = extract_taud()?;
// Generating file
let file_path = "raft_db";
println!("Data parsed, writing to file {:?}", file_path);
let mut file = File::create(file_path)?;
file.write(info_string.as_bytes())?;
println!("File created!");
Ok(())
}

View File

@@ -1,213 +0,0 @@
use std::{
fs, io,
path::{Path, PathBuf},
};
use chrono::{TimeZone, Utc};
use log::debug;
use serde::{Deserialize, Serialize};
use darkfi::util::Timestamp;
use crate::{
error::{TaudError, TaudResult},
task_info::TaskInfo,
util::{load, save},
};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct MonthTasks {
created_at: Timestamp,
task_tks: Vec<String>,
}
impl MonthTasks {
pub fn new(task_tks: &[String]) -> Self {
Self { created_at: Timestamp::current_time(), task_tks: task_tks.to_owned() }
}
pub fn add(&mut self, ref_id: &str) {
debug!(target: "tau", "MonthTasks::add()");
if !self.task_tks.contains(&ref_id.into()) {
self.task_tks.push(ref_id.into());
}
}
pub fn objects(&self, dataset_path: &Path) -> TaudResult<Vec<TaskInfo>> {
debug!(target: "tau", "MonthTasks::objects()");
let mut tks: Vec<TaskInfo> = vec![];
for ref_id in self.task_tks.iter() {
tks.push(TaskInfo::load(ref_id, dataset_path)?);
}
Ok(tks)
}
pub fn remove(&mut self, ref_id: &str) {
debug!(target: "tau", "MonthTasks::remove()");
if let Some(index) = self.task_tks.iter().position(|t| *t == ref_id) {
self.task_tks.remove(index);
}
}
pub fn set_date(&mut self, date: &Timestamp) {
debug!(target: "tau", "MonthTasks::set_date()");
self.created_at = *date;
}
fn get_path(date: &Timestamp, dataset_path: &Path) -> PathBuf {
debug!(target: "tau", "MonthTasks::get_path()");
dataset_path.join("month").join(Utc.timestamp(date.0, 0).format("%m%y").to_string())
}
pub fn save(&self, dataset_path: &Path) -> TaudResult<()> {
debug!(target: "tau", "MonthTasks::save()");
save::<Self>(&Self::get_path(&self.created_at, dataset_path), self)
.map_err(TaudError::Darkfi)
}
fn get_all(dataset_path: &Path) -> io::Result<Vec<PathBuf>> {
debug!(target: "tau", "MonthTasks::get_all()");
let mut entries = fs::read_dir(dataset_path.join("month"))?
.map(|res| res.map(|e| e.path()))
.collect::<Result<Vec<_>, io::Error>>()?;
entries.sort();
Ok(entries)
}
fn create(date: &Timestamp, dataset_path: &Path) -> TaudResult<Self> {
debug!(target: "tau", "MonthTasks::create()");
let mut mt = Self::new(&[]);
mt.set_date(date);
mt.save(dataset_path)?;
Ok(mt)
}
pub fn load_or_create(date: Option<&Timestamp>, dataset_path: &Path) -> TaudResult<Self> {
debug!(target: "tau", "MonthTasks::load_or_create()");
// if a date is given we load that date's month tasks
// if not, we load tasks from all months
match date {
Some(date) => match load::<Self>(&Self::get_path(date, dataset_path)) {
Ok(mt) => Ok(mt),
Err(_) => Self::create(date, dataset_path),
},
None => {
let path_all = match Self::get_all(dataset_path) {
Ok(t) => t,
Err(_) => vec![],
};
let mut loaded_mt = Self::new(&[]);
for path in path_all {
let mt = load::<Self>(&path)?;
loaded_mt.created_at = mt.created_at;
for tks in mt.task_tks {
if !loaded_mt.task_tks.contains(&tks) {
loaded_mt.task_tks.push(tks)
}
}
}
Ok(loaded_mt)
}
}
}
pub fn load_current_open_tasks(dataset_path: &Path) -> TaudResult<Vec<TaskInfo>> {
let mt = Self::load_or_create(None, dataset_path)?;
Ok(mt.objects(dataset_path)?.into_iter().filter(|t| t.get_state() != "stop").collect())
}
}
#[cfg(test)]
mod tests {
use std::{
fs::{create_dir_all, remove_dir_all},
path::PathBuf,
};
use super::*;
use darkfi::Result;
const TEST_DATA_PATH: &str = "/tmp/test_tau_data";
fn get_path() -> Result<PathBuf> {
remove_dir_all(TEST_DATA_PATH).ok();
let path = PathBuf::from(TEST_DATA_PATH);
// mkdir dataset_path if not exists
create_dir_all(path.join("month"))?;
create_dir_all(path.join("task"))?;
Ok(path)
}
#[test]
fn load_and_save_tasks() -> TaudResult<()> {
let dataset_path = get_path()?;
// load and save TaskInfo
///////////////////////
let mut task =
TaskInfo::new("test_title", "test_desc", "NICKNAME", None, 0.0, &dataset_path)?;
task.save(&dataset_path)?;
let t_load = TaskInfo::load(&task.ref_id, &dataset_path)?;
assert_eq!(task, t_load);
task.set_title("test_title_2");
task.save(&dataset_path)?;
let t_load = TaskInfo::load(&task.ref_id, &dataset_path)?;
assert_eq!(task, t_load);
// load and save MonthTasks
///////////////////////
let task_tks = vec![];
let mut mt = MonthTasks::new(&task_tks);
mt.save(&dataset_path)?;
let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
assert_eq!(mt, mt_load);
mt.add(&task.ref_id);
mt.save(&dataset_path)?;
let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
assert_eq!(mt, mt_load);
// activate task
///////////////////////
let task =
TaskInfo::new("test_title_3", "test_desc", "NICKNAME", None, 0.0, &dataset_path)?;
task.save(&dataset_path)?;
let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
assert!(mt_load.task_tks.contains(&task.ref_id));
remove_dir_all(TEST_DATA_PATH).ok();
Ok(())
}
}

View File

@@ -1,274 +0,0 @@
use std::{
io,
path::{Path, PathBuf},
};
use log::debug;
use serde::{Deserialize, Serialize};
use darkfi::util::{
serial::{Decodable, Encodable, SerialDecodable, SerialEncodable, VarInt},
Timestamp,
};
use crate::{
error::{TaudError, TaudResult},
month_tasks::MonthTasks,
util::{find_free_id, load, random_ref_id, save},
};
#[derive(Clone, Debug, Serialize, Deserialize, SerialEncodable, SerialDecodable, PartialEq)]
struct TaskEvent {
action: String,
timestamp: Timestamp,
}
impl TaskEvent {
fn new(action: String) -> Self {
Self { action, timestamp: Timestamp::current_time() }
}
}
#[derive(Clone, Debug, Serialize, Deserialize, SerialDecodable, SerialEncodable, PartialEq)]
pub struct Comment {
content: String,
author: String,
timestamp: Timestamp,
}
impl Comment {
pub fn new(content: &str, author: &str) -> Self {
Self {
content: content.into(),
author: author.into(),
timestamp: Timestamp::current_time(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TaskEvents(Vec<TaskEvent>);
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TaskComments(Vec<Comment>);
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TaskProjects(Vec<String>);
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TaskAssigns(Vec<String>);
#[derive(Clone, Debug, Serialize, Deserialize, SerialEncodable, SerialDecodable, PartialEq)]
pub struct TaskInfo {
pub(crate) ref_id: String,
id: u32,
title: String,
desc: String,
owner: String,
assign: TaskAssigns,
project: TaskProjects,
due: Option<Timestamp>,
rank: f32,
created_at: Timestamp,
events: TaskEvents,
comments: TaskComments,
}
impl TaskInfo {
pub fn new(
title: &str,
desc: &str,
owner: &str,
due: Option<Timestamp>,
rank: f32,
dataset_path: &Path,
) -> TaudResult<Self> {
// generate ref_id
let ref_id = random_ref_id();
let created_at = Timestamp::current_time();
let task_ids: Vec<u32> =
MonthTasks::load_current_open_tasks(dataset_path)?.into_iter().map(|t| t.id).collect();
let id: u32 = find_free_id(&task_ids);
if let Some(d) = &due {
if *d < Timestamp::current_time() {
return Err(TaudError::InvalidDueTime)
}
}
Ok(Self {
ref_id,
id,
title: title.into(),
desc: desc.into(),
owner: owner.into(),
assign: TaskAssigns(vec![]),
project: TaskProjects(vec![]),
due,
rank,
created_at,
comments: TaskComments(vec![]),
events: TaskEvents(vec![]),
})
}
pub fn load(ref_id: &str, dataset_path: &Path) -> TaudResult<Self> {
debug!(target: "tau", "TaskInfo::load()");
let task = load::<Self>(&Self::get_path(ref_id, dataset_path))?;
Ok(task)
}
pub fn save(&self, dataset_path: &Path) -> TaudResult<()> {
debug!(target: "tau", "TaskInfo::save()");
save::<Self>(&Self::get_path(&self.ref_id, dataset_path), self)
.map_err(TaudError::Darkfi)?;
if self.get_state() == "stop" {
self.deactivate(dataset_path)?;
} else {
self.activate(dataset_path)?;
}
Ok(())
}
pub fn activate(&self, path: &Path) -> TaudResult<()> {
debug!(target: "tau", "TaskInfo::activate()");
let mut mt = MonthTasks::load_or_create(Some(&self.created_at), path)?;
mt.add(&self.ref_id);
mt.save(path)
}
pub fn deactivate(&self, path: &Path) -> TaudResult<()> {
debug!(target: "tau", "TaskInfo::deactivate()");
let mut mt = MonthTasks::load_or_create(Some(&self.created_at), path)?;
mt.remove(&self.ref_id);
mt.save(path)
}
pub fn get_state(&self) -> String {
debug!(target: "tau", "TaskInfo::get_state()");
if let Some(ev) = self.events.0.last() {
ev.action.clone()
} else {
"open".into()
}
}
fn get_path(ref_id: &str, dataset_path: &Path) -> PathBuf {
debug!(target: "tau", "TaskInfo::get_path()");
dataset_path.join("task").join(ref_id)
}
pub fn get_id(&self) -> u32 {
debug!(target: "tau", "TaskInfo::get_id()");
self.id
}
pub fn set_title(&mut self, title: &str) {
debug!(target: "tau", "TaskInfo::set_title()");
self.title = title.into();
}
pub fn set_desc(&mut self, desc: &str) {
debug!(target: "tau", "TaskInfo::set_desc()");
self.desc = desc.into();
}
pub fn set_assign(&mut self, assign: &[String]) {
debug!(target: "tau", "TaskInfo::set_assign()");
self.assign = TaskAssigns(assign.to_owned());
}
pub fn set_project(&mut self, project: &[String]) {
debug!(target: "tau", "TaskInfo::set_project()");
self.project = TaskProjects(project.to_owned());
}
pub fn set_comment(&mut self, c: Comment) {
debug!(target: "tau", "TaskInfo::set_comment()");
self.comments.0.push(c);
}
pub fn set_rank(&mut self, r: f32) {
debug!(target: "tau", "TaskInfo::set_rank()");
self.rank = r;
}
pub fn set_due(&mut self, d: Option<Timestamp>) {
debug!(target: "tau", "TaskInfo::set_due()");
self.due = d;
}
pub fn set_state(&mut self, action: &str) {
debug!(target: "tau", "TaskInfo::set_state()");
if self.get_state() == action {
return
}
self.events.0.push(TaskEvent::new(action.into()));
}
}
impl Encodable for TaskEvents {
fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
encode_vec(&self.0, s)
}
}
impl Decodable for TaskEvents {
fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
Ok(Self(decode_vec(d)?))
}
}
impl Encodable for TaskComments {
fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
encode_vec(&self.0, s)
}
}
impl Decodable for TaskComments {
fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
Ok(Self(decode_vec(d)?))
}
}
impl Encodable for TaskProjects {
fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
encode_vec(&self.0, s)
}
}
impl Decodable for TaskProjects {
fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
Ok(Self(decode_vec(d)?))
}
}
impl Encodable for TaskAssigns {
fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
encode_vec(&self.0, s)
}
}
impl Decodable for TaskAssigns {
fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
Ok(Self(decode_vec(d)?))
}
}
fn encode_vec<T: Encodable, S: io::Write>(vec: &[T], mut s: S) -> darkfi::Result<usize> {
let mut len = 0;
len += VarInt(vec.len() as u64).encode(&mut s)?;
for c in vec.iter() {
len += c.encode(&mut s)?;
}
Ok(len)
}
fn decode_vec<T: Decodable, D: io::Read>(mut d: D) -> darkfi::Result<Vec<T>> {
let len = VarInt::decode(&mut d)?.0;
let mut ret = Vec::with_capacity(len as usize);
for _ in 0..len {
ret.push(Decodable::decode(&mut d)?);
}
Ok(ret)
}

View File

@@ -1,59 +0,0 @@
use std::{fs::File, io::BufReader, path::Path};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use serde::{de::DeserializeOwned, Serialize};
use darkfi::Result;
pub fn random_ref_id() -> String {
thread_rng().sample_iter(&Alphanumeric).take(30).map(char::from).collect()
}
pub fn find_free_id(task_ids: &[u32]) -> u32 {
for i in 1.. {
if !task_ids.contains(&i) {
return i
}
}
1
}
pub fn load<T: DeserializeOwned>(path: &Path) -> Result<T> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let value: T = serde_json::from_reader(reader)?;
Ok(value)
}
pub fn save<T: Serialize>(path: &Path, value: &T) -> Result<()> {
let file = File::create(path)?;
serde_json::to_writer_pretty(file, value)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn find_free_id_test() -> Result<()> {
let mut ids: Vec<u32> = vec![1, 3, 8, 9, 10, 3];
let ids_empty: Vec<u32> = vec![];
let ids_duplicate: Vec<u32> = vec![1; 100];
let find_id = find_free_id(&ids);
assert_eq!(find_id, 2);
ids.push(find_id);
assert_eq!(find_free_id(&ids), 4);
assert_eq!(find_free_id(&ids_empty), 1);
assert_eq!(find_free_id(&ids_duplicate), 2);
Ok(())
}
}