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

bin/tau: update code and rpc methods

Dastan-glitch 2 лет назад
Родитель
Сommit
b115638e98

+ 40 - 67
bin/tau/tau-cli/src/main.rs

@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
  */
 
 
-use std::{collections::HashMap, process::exit, sync::Arc};
+use std::{process::exit, sync::Arc};
 
 
 use clap::{Parser, Subcommand};
 use clap::{Parser, Subcommand};
 use log::{error, info};
 use log::{error, info};
@@ -41,7 +41,7 @@ use drawdown::{drawdown, to_naivedate};
 use filter::{apply_filter, get_ids, no_filter_warn};
 use filter::{apply_filter, get_ids, no_filter_warn};
 use primitives::{task_from_cli, State, TaskEvent};
 use primitives::{task_from_cli, State, TaskEvent};
 use util::{due_as_timestamp, prompt_text};
 use util::{due_as_timestamp, prompt_text};
-use view::{find_free_id, print_task_info, print_task_list};
+use view::{print_task_info, print_task_list};
 
 
 use taud::task_info::TaskInfo;
 use taud::task_info::TaskInfo;
 
 
@@ -181,43 +181,28 @@ fn main() -> Result<()> {
         // If not provided we use get_ids() to get them from the daemon.
         // If not provided we use get_ids() to get them from the daemon.
         let ids = get_ids(&mut filters)?;
         let ids = get_ids(&mut filters)?;
         let ids_clone = ids.clone();
         let ids_clone = ids.clone();
-        let mut tasks_local_id = HashMap::new();
+        let task_ids = if ids.is_empty() { tau.get_ids().await? } else { ids };
 
 
-        let task_ref_ids = tau.get_ref_ids().await?;
-
-        let tasks = if filters.contains(&"state:stop".to_string()) ||
+        let mut tasks = if filters.contains(&"state:stop".to_string()) ||
             filters.contains(&"all".to_string())
             filters.contains(&"all".to_string())
         {
         {
             tau.get_stop_tasks(None).await?
             tau.get_stop_tasks(None).await?
         } else {
         } else {
             vec![]
             vec![]
         };
         };
-
-        let mut store_ids = vec![];
-
-        for task in tasks.clone() {
-            let task_id = find_free_id(&store_ids);
-            tasks_local_id.insert(task_id as usize, task);
-            store_ids.push(task_id);
-        }
-
-        for refid in task_ref_ids {
-            let task_id = find_free_id(&store_ids);
-            let element = tau.get_task_by_ref_id(&refid).await?;
-            tasks_local_id.insert(task_id as usize, element);
-            store_ids.push(task_id);
+        for id in task_ids {
+            tasks.push(tau.get_task_by_id(id).await?);
         }
         }
 
 
         if ids_clone.len() == 1 && args.command.is_none() {
         if ids_clone.len() == 1 && args.command.is_none() {
-            let id_itself = ids_clone[0] as usize;
-            let tsk = tasks_local_id.get(&id_itself).unwrap();
-            print_task_info(id_itself, tsk.clone())?;
+            let tsk = tasks[0].clone();
+            print_task_info(tsk)?;
 
 
             return Ok(())
             return Ok(())
         }
         }
 
 
         for filter in filters {
         for filter in filters {
-            apply_filter(&mut tasks_local_id.clone().into_values().collect(), &filter);
+            apply_filter(&mut tasks, &filter);
         }
         }
 
 
         // Parse subcommands
         // Parse subcommands
@@ -241,8 +226,9 @@ fn main() -> Result<()> {
 
 
                     let title = task.clone().title;
                     let title = task.clone().title;
 
 
-                    if tau.add(task).await? {
-                        println!("Created task \"{}\"", title);
+                    let task_id = tau.add(task).await?;
+                    if task_id > 0 {
+                        println!("Created task {} \"{}\"", task_id, title);
                     }
                     }
                     Ok(())
                     Ok(())
                 }
                 }
@@ -251,14 +237,12 @@ fn main() -> Result<()> {
                     if args.filters.is_empty() {
                     if args.filters.is_empty() {
                         no_filter_warn()
                         no_filter_warn()
                     }
                     }
-
                     let base_task = task_from_cli(values)?;
                     let base_task = task_from_cli(values)?;
-                    for id in ids_clone {
-                        let task = tasks_local_id.get(&(id as usize)).unwrap();
-                        let res = tau.modify(&task.ref_id, base_task.clone()).await?;
+                    for task in tasks.clone() {
+                        let res = tau.update(task.id, base_task.clone()).await?;
                         if res {
                         if res {
-                            let tsk = tau.get_task_by_ref_id(&task.ref_id).await?;
-                            print_task_info(id as usize, tsk)?;
+                            let tsk = tau.get_task_by_id(task.id).await?;
+                            print_task_info(tsk)?;
                         }
                         }
                     }
                     }
 
 
@@ -269,12 +253,10 @@ fn main() -> Result<()> {
                     if args.filters.is_empty() {
                     if args.filters.is_empty() {
                         no_filter_warn()
                         no_filter_warn()
                     }
                     }
-
                     let state = State::Start;
                     let state = State::Start;
-                    for id in ids_clone {
-                        let task = tasks_local_id.get(&(id as usize)).unwrap();
-                        if tau.set_state(&task.ref_id, &state).await? {
-                            println!("Started task: {} with refid: {}", id, task.ref_id);
+                    for task in tasks {
+                        if tau.set_state(task.id, &state).await? {
+                            println!("Started task: {:?}", task.id);
                         }
                         }
                     }
                     }
 
 
@@ -285,12 +267,10 @@ fn main() -> Result<()> {
                     if args.filters.is_empty() {
                     if args.filters.is_empty() {
                         no_filter_warn()
                         no_filter_warn()
                     }
                     }
-
                     let state = State::Open;
                     let state = State::Open;
-                    for id in ids_clone {
-                        let task = tasks_local_id.get(&(id as usize)).unwrap();
-                        if tau.set_state(&task.ref_id, &state).await? {
-                            println!("Opened task: {} with refid: {}", id, task.ref_id);
+                    for task in tasks {
+                        if tau.set_state(task.id, &state).await? {
+                            println!("Opened task: {:?}", task.id);
                         }
                         }
                     }
                     }
 
 
@@ -301,12 +281,10 @@ fn main() -> Result<()> {
                     if args.filters.is_empty() {
                     if args.filters.is_empty() {
                         no_filter_warn()
                         no_filter_warn()
                     }
                     }
-
                     let state = State::Pause;
                     let state = State::Pause;
-                    for id in ids_clone {
-                        let task = tasks_local_id.get(&(id as usize)).unwrap();
-                        if tau.set_state(&task.ref_id, &state).await? {
-                            println!("Paused task: {} with refid: {}", id, task.ref_id);
+                    for task in tasks {
+                        if tau.set_state(task.id, &state).await? {
+                            println!("Paused task: {:?}", task.id);
                         }
                         }
                     }
                     }
 
 
@@ -317,12 +295,10 @@ fn main() -> Result<()> {
                     if args.filters.is_empty() {
                     if args.filters.is_empty() {
                         no_filter_warn()
                         no_filter_warn()
                     }
                     }
-
                     let state = State::Stop;
                     let state = State::Stop;
-                    for id in ids_clone {
-                        let task = tasks_local_id.get(&(id as usize)).unwrap();
-                        if tau.set_state(&task.ref_id, &state).await? {
-                            println!("Stopped task: {} with refid: {}", id, task.ref_id);
+                    for task in tasks {
+                        if tau.set_state(task.id, &state).await? {
+                            println!("Stopped task: {}", task.id);
                         }
                         }
                     }
                     }
 
 
@@ -333,9 +309,7 @@ fn main() -> Result<()> {
                     if args.filters.is_empty() {
                     if args.filters.is_empty() {
                         no_filter_warn()
                         no_filter_warn()
                     }
                     }
-
-                    for id in ids_clone {
-                        let task = tasks_local_id.get(&(id as usize)).unwrap();
+                    for task in tasks {
                         let comment = if content.is_empty() {
                         let comment = if content.is_empty() {
                             prompt_text(task.clone(), "comment")?
                             prompt_text(task.clone(), "comment")?
                         } else {
                         } else {
@@ -347,20 +321,19 @@ fn main() -> Result<()> {
                             exit(1)
                             exit(1)
                         }
                         }
 
 
-                        let res = tau.set_comment(&task.ref_id, comment.unwrap().trim()).await?;
+                        let res = tau.set_comment(task.id, comment.unwrap().trim()).await?;
                         if res {
                         if res {
-                            let tsk = tau.get_task_by_ref_id(&task.ref_id).await?;
-                            print_task_info(id as usize, tsk)?;
+                            let tsk = tau.get_task_by_id(task.id).await?;
+                            print_task_info(tsk)?;
                         }
                         }
                     }
                     }
                     Ok(())
                     Ok(())
                 }
                 }
 
 
                 TauSubcommand::Info => {
                 TauSubcommand::Info => {
-                    for id in ids_clone {
-                        let task = tasks_local_id.get(&(id as usize)).unwrap();
-                        let task = tau.get_task_by_ref_id(&task.ref_id).await?;
-                        print_task_info(id as usize, task)?;
+                    for task in tasks {
+                        let task = tau.get_task_by_id(task.id).await?;
+                        print_task_info(task)?;
                     }
                     }
                     Ok(())
                     Ok(())
                 }
                 }
@@ -412,9 +385,9 @@ fn main() -> Result<()> {
                             drawdown(date, tasks, assignee)?;
                             drawdown(date, tasks, assignee)?;
                         }
                         }
                         None => {
                         None => {
-                            let _ws = tau.get_ws().await?;
-                            let _tasks = tau.get_stop_tasks(None).await?;
-                            // print_task_list(tasks, ws)?;
+                            let ws = tau.get_ws().await?;
+                            let tasks = tau.get_stop_tasks(None).await?;
+                            print_task_list(tasks, ws)?;
                         }
                         }
                     }
                     }
 
 
@@ -423,12 +396,12 @@ fn main() -> Result<()> {
 
 
                 TauSubcommand::List => {
                 TauSubcommand::List => {
                     let ws = tau.get_ws().await?;
                     let ws = tau.get_ws().await?;
-                    print_task_list(tasks_local_id, ws)
+                    print_task_list(tasks, ws)
                 }
                 }
             },
             },
             None => {
             None => {
                 let ws = tau.get_ws().await?;
                 let ws = tau.get_ws().await?;
-                print_task_list(tasks_local_id, ws)
+                print_task_list(tasks, ws)
             }
             }
         }?;
         }?;
 
 

+ 1 - 0
bin/tau/tau-cli/src/primitives.rs

@@ -37,6 +37,7 @@ impl From<BaseTask> for TaskInfo {
         Self {
         Self {
             ref_id: String::default(),
             ref_id: String::default(),
             workspace: String::default(),
             workspace: String::default(),
+            id: u32::default(),
             title: value.title,
             title: value.title,
             tags: value.tags,
             tags: value.tags,
             desc: String::default(),
             desc: String::default(),

+ 24 - 27
bin/tau/tau-cli/src/rpc.rs

@@ -16,8 +16,6 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
  */
 
 
-use std::collections::HashMap;
-
 use darkfi::{rpc::jsonrpc::JsonRequest, Result};
 use darkfi::{rpc::jsonrpc::JsonRequest, Result};
 use log::debug;
 use log::debug;
 use tinyjson::JsonValue;
 use tinyjson::JsonValue;
@@ -33,7 +31,7 @@ impl Tau {
     }
     }
 
 
     /// Add a new task.
     /// Add a new task.
-    pub async fn add(&self, task: BaseTask) -> Result<bool> {
+    pub async fn add(&self, task: BaseTask) -> Result<u32> {
         let mut params = vec![
         let mut params = vec![
             JsonValue::String(task.title.clone()),
             JsonValue::String(task.title.clone()),
             JsonValue::Array(task.tags.iter().map(|x| JsonValue::String(x.clone())).collect()),
             JsonValue::Array(task.tags.iter().map(|x| JsonValue::String(x.clone())).collect()),
@@ -57,49 +55,48 @@ impl Tau {
         let rep = self.rpc_client.request(req).await?;
         let rep = self.rpc_client.request(req).await?;
 
 
         debug!("Got reply: {:?}", rep);
         debug!("Got reply: {:?}", rep);
-        Ok(*rep.get::<bool>().unwrap())
+        Ok(*rep.get::<f64>().unwrap() as u32)
     }
     }
 
 
     /// Get current open tasks ids.
     /// Get current open tasks ids.
-    pub async fn get_ref_ids(&self) -> Result<Vec<String>> {
-        let req = JsonRequest::new("get_ref_ids", vec![]);
+    pub async fn get_ids(&self) -> Result<Vec<u32>> {
+        let req = JsonRequest::new("get_ids", vec![]);
         let rep = self.rpc_client.request(req).await?;
         let rep = self.rpc_client.request(req).await?;
 
 
         debug!("Got reply: {:?}", rep);
         debug!("Got reply: {:?}", rep);
 
 
         let mut ret = vec![];
         let mut ret = vec![];
         for i in rep.get::<Vec<JsonValue>>().unwrap() {
         for i in rep.get::<Vec<JsonValue>>().unwrap() {
-            ret.push(i.get::<String>().unwrap().to_string())
+            ret.push(*i.get::<f64>().unwrap() as u32)
         }
         }
 
 
         Ok(ret)
         Ok(ret)
     }
     }
 
 
-    /// modify existing task given it's ID and some params.
-    pub async fn modify(&self, ref_id: &str, task: BaseTask) -> Result<bool> {
-        let mut params = HashMap::new();
-
-        let map = |x: &String| JsonValue::String(x.clone().to_owned());
-        params.insert("title".into(), JsonValue::String(task.title.clone()));
-        params.insert("desc".into(), JsonValue::String(task.desc.unwrap_or("".to_string())));
-        params.insert("tags".into(), JsonValue::Array(task.tags.iter().map(map).collect()));
-        params.insert("assign".into(), JsonValue::Array(task.assign.iter().map(map).collect()));
-        params.insert("project".into(), JsonValue::Array(task.project.iter().map(map).collect()));
+    /// Update existing task given it's ID and some params.
+    pub async fn update(&self, id: u32, task: BaseTask) -> Result<bool> {
+        let mut params = vec![
+            JsonValue::String(task.title.clone()),
+            JsonValue::Array(task.tags.iter().map(|x| JsonValue::String(x.clone())).collect()),
+            JsonValue::String(task.desc.unwrap_or("".to_string())),
+            JsonValue::Array(task.assign.iter().map(|x| JsonValue::String(x.clone())).collect()),
+            JsonValue::Array(task.project.iter().map(|x| JsonValue::String(x.clone())).collect()),
+        ];
 
 
         let due = if let Some(num) = task.due {
         let due = if let Some(num) = task.due {
             JsonValue::String(num.to_string())
             JsonValue::String(num.to_string())
         } else {
         } else {
             JsonValue::Null
             JsonValue::Null
         };
         };
-        params.insert("due".into(), due);
+        params.push(due);
 
 
         let rank =
         let rank =
             if let Some(num) = task.rank { JsonValue::Number(num.into()) } else { JsonValue::Null };
             if let Some(num) = task.rank { JsonValue::Number(num.into()) } else { JsonValue::Null };
-        params.insert("rank".into(), rank);
+        params.push(rank);
 
 
         let req = JsonRequest::new(
         let req = JsonRequest::new(
-            "modify",
-            vec![JsonValue::String(ref_id.into()), JsonValue::Object(params)],
+            "update",
+            vec![JsonValue::Number(id.into()), JsonValue::Array(params)],
         );
         );
         let rep = self.rpc_client.request(req).await?;
         let rep = self.rpc_client.request(req).await?;
 
 
@@ -108,10 +105,10 @@ impl Tau {
     }
     }
 
 
     /// Set the state for a task.
     /// Set the state for a task.
-    pub async fn set_state(&self, ref_id: &str, state: &State) -> Result<bool> {
+    pub async fn set_state(&self, id: u32, state: &State) -> Result<bool> {
         let req = JsonRequest::new(
         let req = JsonRequest::new(
             "set_state",
             "set_state",
-            vec![JsonValue::String(ref_id.into()), JsonValue::String(state.to_string())],
+            vec![JsonValue::Number(id.into()), JsonValue::String(state.to_string())],
         );
         );
         let rep = self.rpc_client.request(req).await?;
         let rep = self.rpc_client.request(req).await?;
 
 
@@ -120,10 +117,10 @@ impl Tau {
     }
     }
 
 
     /// Set a comment for a task.
     /// Set a comment for a task.
-    pub async fn set_comment(&self, ref_id: &str, content: &str) -> Result<bool> {
+    pub async fn set_comment(&self, id: u32, content: &str) -> Result<bool> {
         let req = JsonRequest::new(
         let req = JsonRequest::new(
             "set_comment",
             "set_comment",
-            vec![JsonValue::String(ref_id.into()), JsonValue::String(content.to_string())],
+            vec![JsonValue::Number(id.into()), JsonValue::String(content.to_string())],
         );
         );
         let rep = self.rpc_client.request(req).await?;
         let rep = self.rpc_client.request(req).await?;
 
 
@@ -132,8 +129,8 @@ impl Tau {
     }
     }
 
 
     /// Get task data by its ID.
     /// Get task data by its ID.
-    pub async fn get_task_by_ref_id(&self, ref_id: &str) -> Result<TaskInfo> {
-        let req = JsonRequest::new("get_task_by_ref_id", vec![JsonValue::String(ref_id.into())]);
+    pub async fn get_task_by_id(&self, id: u32) -> Result<TaskInfo> {
+        let req = JsonRequest::new("get_task_by_id", vec![JsonValue::Number(id.into())]);
         let rep = self.rpc_client.request(req).await?;
         let rep = self.rpc_client.request(req).await?;
 
 
         debug!("Got reply: {:?}", rep);
         debug!("Got reply: {:?}", rep);

+ 1 - 1
bin/tau/tau-cli/src/util.rs

@@ -75,7 +75,7 @@ pub fn prompt_text(task_info: TaskInfo, what: &str) -> Result<Option<String>> {
     writeln!(file, "\n# ------------------------ >8 ------------------------")?;
     writeln!(file, "\n# ------------------------ >8 ------------------------")?;
     writeln!(file, "# Do not modify or remove the line above.")?;
     writeln!(file, "# Do not modify or remove the line above.")?;
     writeln!(file, "# Everything below it will be ignored.")?;
     writeln!(file, "# Everything below it will be ignored.")?;
-    writeln!(file, "\n{}", taskinfo_table(0, task_info.clone())?)?;
+    writeln!(file, "\n{}", taskinfo_table(task_info.clone())?)?;
     writeln!(file, "{}", events_table(task_info.clone())?)?;
     writeln!(file, "{}", events_table(task_info.clone())?)?;
     writeln!(file, "{}", comments_table(task_info)?)?;
     writeln!(file, "{}", comments_table(task_info)?)?;
 
 

+ 9 - 18
bin/tau/tau-cli/src/view.rs

@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
  */
 
 
-use std::{cmp::Ordering, collections::HashMap, fmt::Write, str::FromStr};
+use std::{cmp::Ordering, fmt::Write, str::FromStr};
 
 
 use prettytable::{
 use prettytable::{
     format::{consts::FORMAT_NO_COLSEP, FormatBuilder, LinePosition, LineSeparator},
     format::{consts::FORMAT_NO_COLSEP, FormatBuilder, LinePosition, LineSeparator},
@@ -34,8 +34,8 @@ use crate::{
     TaskEvent,
     TaskEvent,
 };
 };
 
 
-pub fn print_task_list(tasks_map: HashMap<usize, TaskInfo>, ws: String) -> Result<()> {
-    let mut tasks = tasks_map.clone().into_values().collect::<Vec<TaskInfo>>();
+pub fn print_task_list(tasks: Vec<TaskInfo>, ws: String) -> Result<()> {
+    let mut tasks = tasks;
 
 
     let mut table = Table::new();
     let mut table = Table::new();
     table.set_format(
     table.set_format(
@@ -70,7 +70,7 @@ pub fn print_task_list(tasks_map: HashMap<usize, TaskInfo>, ws: String) -> Resul
         min_rank = last.rank;
         min_rank = last.rank;
     }
     }
 
 
-    for (task_id, task) in tasks_map {
+    for task in tasks {
         let state = State::from_str(&task.state.clone())?;
         let state = State::from_str(&task.state.clone())?;
 
 
         let (max_style, min_style, mid_style, gen_style) = if state.is_start() {
         let (max_style, min_style, mid_style, gen_style) = if state.is_start() {
@@ -96,7 +96,7 @@ pub fn print_task_list(tasks_map: HashMap<usize, TaskInfo>, ws: String) -> Resul
         };
         };
 
 
         table.add_row(Row::new(vec![
         table.add_row(Row::new(vec![
-            Cell::new(&task_id.to_string()).style_spec(gen_style),
+            Cell::new(&task.id.to_string()).style_spec(gen_style),
             Cell::new(&task.title).style_spec(gen_style),
             Cell::new(&task.title).style_spec(gen_style),
             Cell::new(&print_tags.join(", ")).style_spec(gen_style),
             Cell::new(&print_tags.join(", ")).style_spec(gen_style),
             Cell::new(&task.project.join(", ")).style_spec(gen_style),
             Cell::new(&task.project.join(", ")).style_spec(gen_style),
@@ -136,7 +136,7 @@ pub fn print_task_list(tasks_map: HashMap<usize, TaskInfo>, ws: String) -> Resul
     Ok(())
     Ok(())
 }
 }
 
 
-pub fn taskinfo_table(id: usize, taskinfo: TaskInfo) -> Result<Table> {
+pub fn taskinfo_table(taskinfo: TaskInfo) -> Result<Table> {
     let due_ = match taskinfo.due {
     let due_ = match taskinfo.due {
         Some(ts) => ts.0,
         Some(ts) => ts.0,
         None => 0,
         None => 0,
@@ -149,7 +149,7 @@ pub fn taskinfo_table(id: usize, taskinfo: TaskInfo) -> Result<Table> {
     let mut table = table!(
     let mut table = table!(
          [Bd => "ref_id", &taskinfo.ref_id],
          [Bd => "ref_id", &taskinfo.ref_id],
          ["workspace", &taskinfo.workspace],
          ["workspace", &taskinfo.workspace],
-         [Bd =>"id", &id.to_string()],
+         [Bd =>"id", &taskinfo.id.to_string()],
          ["owner", &taskinfo.owner],
          ["owner", &taskinfo.owner],
          [Bd =>"title", &taskinfo.title],
          [Bd =>"title", &taskinfo.title],
          ["tags", &taskinfo.tags.join(", ")],
          ["tags", &taskinfo.tags.join(", ")],
@@ -189,8 +189,8 @@ pub fn comments_table(taskinfo: TaskInfo) -> Result<Table> {
     Ok(comments_table)
     Ok(comments_table)
 }
 }
 
 
-pub fn print_task_info(id: usize, taskinfo: TaskInfo) -> Result<()> {
-    let table = taskinfo_table(id, taskinfo.clone())?;
+pub fn print_task_info(taskinfo: TaskInfo) -> Result<()> {
+    let table = taskinfo_table(taskinfo.clone())?;
     table.printstd();
     table.printstd();
 
 
     let events_table = events_table(taskinfo.clone())?;
     let events_table = events_table(taskinfo.clone())?;
@@ -285,12 +285,3 @@ pub fn comments_as_string(events: Vec<TaskEvent>) -> (String, String) {
     }
     }
     (events_str, timestamps_str)
     (events_str, timestamps_str)
 }
 }
-
-pub fn find_free_id(task_ids: &[u32]) -> u32 {
-    for i in 1.. {
-        if !task_ids.contains(&i) {
-            return i
-        }
-    }
-    1
-}

+ 33 - 26
bin/tau/tau-python/api.py

@@ -4,18 +4,16 @@ import sys
 # import lib.config
 # import lib.config
 from lib.net import Channel
 from lib.net import Channel
 
 
-async def create_channel():
-    # server_name = lib.config.get("server", "localhost")
-    server_name = "localhost"
-    reader, writer = await asyncio.open_connection(server_name, 23341)
+async def create_channel(server_name, port):
+    reader, writer = await asyncio.open_connection(server_name, port)
     channel = Channel(reader, writer)
     channel = Channel(reader, writer)
     return channel
     return channel
 
 
 def random_id():
 def random_id():
     return random.randint(0, 2**32)
     return random.randint(0, 2**32)
 
 
-async def query(method, params):
-    channel = await create_channel()
+async def query(method, params, server_name, port):
+    channel = await create_channel(server_name, port)
     request = {
     request = {
         "id": random_id(),
         "id": random_id(),
         "method": method,
         "method": method,
@@ -38,34 +36,43 @@ async def query(method, params):
 
 
     return response["result"]
     return response["result"]
 
 
-async def get_info():
-    return await query("get_info", [])
+async def get_info(server_name, port):
+    return await query("get_info", [], server_name, int(port))
 
 
-async def add_task(task):
-    return await query("add", [task])
+async def get_workspace(server_name, port):
+    return await query("get_ws", [], server_name, int(port))
 
 
-async def get_ref_ids():
-    return await query("get_ref_ids", [])
+async def add_task(task, server_name, port):
+    return await query("add", [task], server_name, int(port))
 
 
-async def fetch_task(refid):
-    return await query("get_task_by_ref_id", [refid])
+async def get_ref_ids(server_name, port):
+    return await query("get_ref_ids", [], server_name, int(port))
 
 
-async def change_task_status(refid, status):
-    await query("set_state", [refid, status])
+async def get_archive_ref_ids(month_ts, server_name, port):
+    return await query("get_archive_ref_ids", [str(month_ts)], server_name, int(port))
+
+async def fetch_task(refid, server_name, port):
+    return await query("get_task_by_ref_id", [refid], server_name, int(port))
+
+async def change_task_status(refid, status, server_name, port):
+    await query("set_state", [refid, status], server_name, int(port))
     return True
     return True
 
 
-async def modify_task(refid, changes):
-    return await query("modify", [refid, changes])
+async def modify_task(refid, changes, server_name, port):
+    return await query("modify", [refid, changes], server_name, int(port))
+
+async def switch_workspace(workspace, server_name, port):
+    return await query("switch_ws", [workspace], server_name, int(port))
 
 
-async def fetch_active_tasks():
-    return await query("fetch_active_tasks", [])
+async def fetch_active_tasks(server_name, port):
+    return await query("fetch_active_tasks", [], server_name, int(port))
 
 
-async def fetch_deactive_tasks(month):
-    return await query("fetch_deactive_tasks", [month])
+async def fetch_deactive_tasks(month_ts, server_name, port):
+    return await query("fetch_deactive_tasks", [str(month_ts)], server_name, int(port))
 
 
-async def fetch_archive_task(task_refid, month):
-    return await query("fetch_archive_task", [task_refid, month])
+async def fetch_archive_task(task_refid, month_ts, server_name, port):
+    return await query("fetch_archive_task", [task_refid, str(month_ts)], server_name, int(port))
 
 
-async def add_task_comment(refid, comment):
-    await query("set_comment", [refid, comment])
+async def add_task_comment(refid, comment, server_name, port):
+    await query("set_comment", [refid, comment], server_name, int(port))
     return True
     return True

+ 5 - 6
bin/tau/tau-python/lib/util.py

@@ -1,5 +1,5 @@
 import random, time
 import random, time
-from datetime import datetime
+from datetime import UTC, datetime
 
 
 def random_blob_idx():
 def random_blob_idx():
     return "%030x" % random.randrange(16**30)
     return "%030x" % random.randrange(16**30)
@@ -7,12 +7,11 @@ def random_blob_idx():
 def datetime_to_unix(dt):
 def datetime_to_unix(dt):
     return int(time.mktime(dt.timetuple()))
     return int(time.mktime(dt.timetuple()))
 def now():
 def now():
-    return datetime_to_unix(datetime.now())
+    return datetime_to_unix(datetime.now(tz=UTC))
 
 
-# returns MMYY format
-def current_month():
-    today = datetime.today()
-    return today.strftime("%m%y")
+def month_to_unix(month=None):
+    month_year = month if month is not None else datetime.utcnow().strftime("%m%y")
+    return datetime.strptime(month_year,"%m%y").timestamp()
 
 
 def unix_to_datetime(timestamp):
 def unix_to_datetime(timestamp):
     return datetime.utcfromtimestamp(int(timestamp))
     return datetime.utcfromtimestamp(int(timestamp))

+ 168 - 93
bin/tau/tau-python/main.py

@@ -1,16 +1,13 @@
 #!/usr/bin/python3
 #!/usr/bin/python3
-import asyncio, json, os, sys, tempfile
+import asyncio, os, sys, tempfile
 from datetime import datetime
 from datetime import datetime
 import time
 import time
 from tabulate import tabulate
 from tabulate import tabulate
-from colorama import Fore, Back, Style
+from colorama import Fore, Style
 
 
 import api, lib.util
 import api, lib.util
 
 
-# USERNAME = lib.config.get("username", "Anonymous")
-USERNAME = "Anonymous"
-
-async def add_task(task_args):
+async def add_task(task_args, server_name, port):
     task = {
     task = {
         "title": None,
         "title": None,
         "tags": [],
         "tags": [],
@@ -55,8 +52,19 @@ async def add_task(task_args):
     if task["desc"].strip() == '':
     if task["desc"].strip() == '':
         print("Abort adding the task due to empty description.")
         print("Abort adding the task due to empty description.")
         exit(-1)
         exit(-1)
-
-    if await api.add_task(task):
+    
+    if task["rank"] is not None:
+        task["rank"] = round(task["rank"], 4)
+    
+    try:
+        if task["ref_id"].strip() == '':
+            task.pop('ref_id')
+        if task["workspace"].strip() == '':
+            task.pop('workspace')
+    except KeyError:
+        pass
+
+    if await api.add_task(task, server_name, port):
         print(f"Created task '{title}'.")
         print(f"Created task '{title}'.")
 
 
 def prompt_text(comment_lines):
 def prompt_text(comment_lines):
@@ -87,7 +95,7 @@ def prompt_description_text(task):
         "\n# ------------------------ >8 ------------------------",
         "\n# ------------------------ >8 ------------------------",
         "# Do not modify or remove the line above.",
         "# Do not modify or remove the line above.",
         "# Everything below it will be ignored.",
         "# Everything below it will be ignored.",
-        f"\n{tabulate_task(task)}"
+        f"\n{tabulate_task(task, True)}"
     ])
     ])
 
 
 def prompt_comment_text():
 def prompt_comment_text():
@@ -145,18 +153,19 @@ def convert_attr_val(attr, val):
         print(f"error: unhandled attr '{attr}' = {val}")
         print(f"error: unhandled attr '{attr}' = {val}")
         sys.exit(-1)
         sys.exit(-1)
 
 
-async def show_active_tasks():
-    refids = await api.get_ref_ids()
+async def show_active_tasks(workspace, server_name, port):
+    refids = await api.get_ref_ids(server_name, port)
     tasks = []
     tasks = []
     for refid in refids:
     for refid in refids:
-        tasks.append(await api.fetch_task(refid))
-    list_tasks(tasks, [])
+        tasks.append(await api.fetch_task(refid, server_name, port))
+    list_tasks(tasks, workspace, [])
 
 
-async def show_deactive_tasks(month):
-    tasks = await api.fetch_deactive_tasks(month)
-    list_tasks(tasks, [])
+async def show_deactive_tasks(month_ts, workspace, server_name, port):
+    tasks = await api.fetch_deactive_tasks(month_ts, server_name, port)
+    list_tasks(tasks, workspace, [])
 
 
-def list_tasks(tasks, filters):
+def list_tasks(tasks, workspace, filters):
+    print(f"Workspace: {workspace}")
     headers = ["ID", "Title", "Status", "Project",
     headers = ["ID", "Title", "Status", "Project",
                "Tags", "assign", "Rank", "Due"]
                "Tags", "assign", "Rank", "Due"]
     table_rows = []
     table_rows = []
@@ -197,15 +206,24 @@ def list_tasks(tasks, filters):
             assign =    Fore.YELLOW + str(assign)    + Style.RESET_ALL
             assign =    Fore.YELLOW + str(assign)    + Style.RESET_ALL
             rank =      Fore.YELLOW + str(rank)      + Style.RESET_ALL
             rank =      Fore.YELLOW + str(rank)      + Style.RESET_ALL
             due =       Fore.YELLOW + str(due)       + Style.RESET_ALL
             due =       Fore.YELLOW + str(due)       + Style.RESET_ALL
+        elif status == "stop":
+            id =        Fore.RED + str(id)           + Style.RESET_ALL
+            title =     Fore.RED + str(title)        + Style.RESET_ALL
+            status =    Fore.RED + str(status)       + Style.RESET_ALL
+            project =   Fore.RED + str(project)      + Style.RESET_ALL
+            tags =      Fore.RED + str(tags)         + Style.RESET_ALL
+            assign =    Fore.RED + str(assign)       + Style.RESET_ALL
+            rank =      Fore.RED + str(rank)         + Style.RESET_ALL
+            due =       Fore.RED + str(due)          + Style.RESET_ALL
         else:
         else:
-            #id =        Style.DIM  + str(id)        + Style.RESET_ALL
-            #title =     Style.DIM  + str(title)     + Style.RESET_ALL
-            #status =    Style.DIM  + str(status)    + Style.RESET_ALL
-            project =    Style.DIM  + str(project)   + Style.RESET_ALL
-            tags =       Style.DIM  + str(tags)      + Style.RESET_ALL
-            #assign =    Style.DIM  + str(assign)    + Style.RESET_ALL
-            rank =       Style.DIM  + str(rank)      + Style.RESET_ALL
-            due =        Style.DIM  + str(due)       + Style.RESET_ALL
+            #id =       Style.DIM  + str(id)         + Style.RESET_ALL
+            #title =    Style.DIM  + str(title)      + Style.RESET_ALL
+            #status =   Style.DIM  + str(status)     + Style.RESET_ALL
+            project =   Style.DIM  + str(project)    + Style.RESET_ALL
+            tags =      Style.DIM  + str(tags)       + Style.RESET_ALL
+            #assign =   Style.DIM  + str(assign)     + Style.RESET_ALL
+            rank =      Style.DIM  + str(rank)       + Style.RESET_ALL
+            due =       Style.DIM  + str(due)        + Style.RESET_ALL
 
 
         rank_value = task["rank"] if task["rank"] is not None else 0
         rank_value = task["rank"] if task["rank"] is not None else 0
         row = [
         row = [
@@ -224,17 +242,17 @@ def list_tasks(tasks, filters):
              sorted(table_rows, key=lambda item: item[0], reverse=True)]
              sorted(table_rows, key=lambda item: item[0], reverse=True)]
     print(tabulate(table, headers=headers))
     print(tabulate(table, headers=headers))
 
 
-async def show_task(refid):
-    task = await api.fetch_task(refid)
+async def show_task(refid, server_name, port):
+    task = await api.fetch_task(refid, server_name, port)
     task_table(task)
     task_table(task)
     return 0
     return 0
 
 
-async def show_archive_task(id, month):
-    task = await api.fetch_archive_task(id, month)
+async def show_archive_task(ref_id, month_ts, server_name, port):
+    task = await api.fetch_archive_task(ref_id, month_ts, server_name, port)
     task_table(task)
     task_table(task)
     return 0
     return 0
 
 
-def tabulate_task(task):
+def tabulate_task(task, prompt):
     tags = " ".join(f"+{tag}" for tag in task["tags"])
     tags = " ".join(f"+{tag}" for tag in task["tags"])
     assign = " ".join(f"@{assign}" for assign in task["assign"])
     assign = " ".join(f"@{assign}" for assign in task["assign"])
     project = " ".join(f"{project}" for project in task["project"])
     project = " ".join(f"{project}" for project in task["project"])
@@ -249,13 +267,19 @@ def tabulate_task(task):
     dt = lib.util.unix_to_datetime(task["created_at"])
     dt = lib.util.unix_to_datetime(task["created_at"])
     created_at = dt.strftime("%H:%M %d/%m/%y")
     created_at = dt.strftime("%H:%M %d/%m/%y")
 
 
+    if prompt:
+        task["ref_id"] = ''
+        task["workspace"] = ''
+
     table = [
     table = [
+        ["RefID:", task["ref_id"]],
         ["Title:", task["title"]],
         ["Title:", task["title"]],
+        ["Workspace:", task["workspace"]],
         ["Description:", task["desc"]],
         ["Description:", task["desc"]],
         ["Status:", task["state"]],
         ["Status:", task["state"]],
         ["Project:", project],
         ["Project:", project],
         ["Tags:", tags],
         ["Tags:", tags],
-        ["assign:", assign],
+        ["Assign:", assign],
         ["Rank:", rank],
         ["Rank:", rank],
         ["Due:", due],
         ["Due:", due],
         ["Created:", created_at],
         ["Created:", created_at],
@@ -263,7 +287,7 @@ def tabulate_task(task):
     return tabulate(table, headers=["Attribute", "Value"])
     return tabulate(table, headers=["Attribute", "Value"])
 
 
 def task_table(task):
 def task_table(task):
-    print(tabulate_task(task))
+    print(tabulate_task(task, False))
 
 
     table = []
     table = []
     for event in task["events"]:
     for event in task["events"]:
@@ -277,23 +301,13 @@ def task_table(task):
                 "",
                 "",
                 Style.DIM + when + Style.RESET_ALL
                 Style.DIM + when + Style.RESET_ALL
             ])
             ])
-        elif act == "tags":
+        elif act == "tags" or act == "assign":
             val = f"{args}"
             val = f"{args}"
-            tags_event = f"{who} added {val} to {act}"
+            event = f"{who} added {val} to {act}"
             if val[0] == "-":
             if val[0] == "-":
-                tags_event = f"{who} removed {val} from {act}"
+                event = f"{who} removed {val} from {act}"
             table.append([
             table.append([
-                Style.DIM + tags_event + Style.RESET_ALL,
-                "",
-                Style.DIM + when + Style.RESET_ALL
-            ])
-        elif act == "assign":
-            val = f"{args}"
-            assign_event = f"{who} added {val} to {act}"
-            if val[0] == "-":
-                assign_event = f"{who} removed {val} from {act}"
-            table.append([
-                Style.DIM + assign_event + Style.RESET_ALL,
+                Style.DIM + event + Style.RESET_ALL,
                 "",
                 "",
                 Style.DIM + when + Style.RESET_ALL
                 Style.DIM + when + Style.RESET_ALL
             ])
             ])
@@ -315,6 +329,8 @@ def task_table(task):
                 "",
                 "",
                 Style.DIM + when + Style.RESET_ALL
                 Style.DIM + when + Style.RESET_ALL
             ])
             ])
+        elif act == "comment":
+            continue
         else:
         else:
             table.append([
             table.append([
                 Style.DIM + f"{who} changed {act} to {args}" + Style.RESET_ALL,
                 Style.DIM + f"{who} changed {act} to {args}" + Style.RESET_ALL,
@@ -351,7 +367,7 @@ def wrap_comment(comment, width):
         lines.append(comment[line_start:])
         lines.append(comment[line_start:])
     return '\n'.join(lines)
     return '\n'.join(lines)
 
 
-async def modify_task(refid, args):
+async def modify_task(refid, args, server_name, port):
     changes = {}    
     changes = {}    
     for arg in args:
     for arg in args:
         # This must go before the next elif block
         # This must go before the next elif block
@@ -374,15 +390,15 @@ async def modify_task(refid, args):
             changes[str(attr)] = val
             changes[str(attr)] = val
         else:
         else:
             print(f"warning: unknown arg '{arg}'. Skipping...", file=sys.stderr)
             print(f"warning: unknown arg '{arg}'. Skipping...", file=sys.stderr)
-    await api.modify_task(refid, changes)
+    await api.modify_task(refid, changes, server_name, port)
     return 0
     return 0
 
 
-async def change_task_status(refid, status):
-    task = await api.fetch_task(refid)
+async def change_task_status(refid, status, server_name, port):
+    task = await api.fetch_task(refid, server_name, port)
     assert task is not None
     assert task is not None
     title = task["title"]
     title = task["title"]
 
 
-    if not await api.change_task_status(refid, status):
+    if not await api.change_task_status(refid, status, server_name, port):
         return -1
         return -1
 
 
     if status == "start":
     if status == "start":
@@ -396,16 +412,16 @@ async def change_task_status(refid, status):
 
 
     return 0
     return 0
 
 
-async def comment(refid, args):
+async def comment(refid, args, server_name, port):
     if not args:
     if not args:
         comment = prompt_comment_text()
         comment = prompt_comment_text()
     else:
     else:
         comment = " ".join(args)
         comment = " ".join(args)
 
 
-    if not await api.add_task_comment(refid, comment):
+    if not await api.add_task_comment(refid, comment, server_name, port):
         return -1
         return -1
 
 
-    task = await api.fetch_task(refid)
+    task = await api.fetch_task(refid, server_name, port)
     assert task is not None
     assert task is not None
     title = task["title"]
     title = task["title"]
     print(f"Commented on task'{title}'")
     print(f"Commented on task'{title}'")
@@ -437,11 +453,6 @@ def is_filtered(task, filters):
                     sys.exit(-1)
                     sys.exit(-1)
                 if task["state"] != val:
                 if task["state"] != val:
                     return True
                     return True
-            elif attr == "project":
-                if task["project"] is None:
-                    return True
-                if not task["project"].startswith(val):
-                    return True
             else:
             else:
                 val = convert_attr_val(attr, val)
                 val = convert_attr_val(attr, val)
                 if task[attr] != val:
                 if task[attr] != val:
@@ -462,20 +473,33 @@ def map_ids(task_ids, ref_ids):
     return dict(zip(task_ids, ref_ids))
     return dict(zip(task_ids, ref_ids))
 
 
 async def main():
 async def main():
-    refids = await api.get_ref_ids()
+    val = str('127.0.0.1:23330')
+
+    for i in range(1, len(sys.argv)):
+        if sys.argv[i] == "-e":
+            val = sys.argv[i+1]
+            del sys.argv[i]
+            del sys.argv[i]
+            break
+    
+    server_name, port = val.split(':')
+    
+    refids = await api.get_ref_ids(server_name, port)
     free_ids = []
     free_ids = []
     tasks = []
     tasks = []
     for refid in refids:
     for refid in refids:
-        tasks.append(await api.fetch_task(refid))
+        tasks.append(await api.fetch_task(refid, server_name, port))
         free_ids.append(find_free_id(free_ids))
         free_ids.append(find_free_id(free_ids))
 
 
-    data = map_ids(free_ids, refids)    
+    data = map_ids(free_ids, refids)
+
+    workspace = await api.get_workspace(server_name, port)
 
 
     if len(sys.argv) == 1:
     if len(sys.argv) == 1:
-        await show_active_tasks()
+        await show_active_tasks(workspace, server_name, port)
         return 0
         return 0
 
 
-    if sys.argv[1] in ["-h", "--help", "help"]:
+    if any(x in ["-h", "--help", "help"] for x in sys.argv):
         print('''USAGE:
         print('''USAGE:
     tau [OPTIONS] [SUBCOMMAND]
     tau [OPTIONS] [SUBCOMMAND]
 
 
@@ -490,44 +514,106 @@ SUBCOMMANDS:
     pause      Pause task(s).
     pause      Pause task(s).
     start      Start task(s).
     start      Start task(s).
     stop       Stop task(s).
     stop       Stop task(s).
+    switch     Switch between configured workspaces.
     help       Show this help text.
     help       Show this help text.
 
 
-Example:
+Examples:
     tau add task one due:0312 rank:1.022 project:zk +lol @sk desc:desc +abc +def
     tau add task one due:0312 rank:1.022 project:zk +lol @sk desc:desc +abc +def
-    tau add task two  rank:1.044 project:cr +mol @up desc:desc2
+    tau add task two rank:1.044 project:cr +mol @up desc:desc2
     tau add task three due:0512 project:zy +trol @kk desc:desc3 +who
     tau add task three due:0512 project:zy +trol @kk desc:desc3 +who
     tau 1 modify @upgr due:1112 rank:none
     tau 1 modify @upgr due:1112 rank:none
+    tau 1 modify -@up
     tau 1 modify -mol -xx
     tau 1 modify -mol -xx
     tau 2 start
     tau 2 start
     tau 1 comment "this is an awesome comment"
     tau 1 comment "this is an awesome comment"
     tau 2 pause
     tau 2 pause
+    tau switch darkfi
     tau archive         # current month's completed tasks
     tau archive         # current month's completed tasks
     tau archive 1122    # completed tasks in Nov. 2022
     tau archive 1122    # completed tasks in Nov. 2022
-    tau 0 archive 1122  # show info of task completed in Nov. 2022
+    tau archive 1 1122  # show info of task completed in Nov. 2022
 ''')
 ''')
         return 0
         return 0
     elif sys.argv[1] == "add":
     elif sys.argv[1] == "add":
         task_args = sys.argv[2:]
         task_args = sys.argv[2:]
-        await add_task(task_args)
+        await add_task(task_args, server_name, port)
         return 0
         return 0
     elif sys.argv[1] == "archive":
     elif sys.argv[1] == "archive":
-        if len(sys.argv) > 2:
+        if len(sys.argv) == 4:
+            if len(sys.argv[3]) == 4:
+                month = sys.argv[3]
+                month_ts = lib.util.month_to_unix(month)
+            else:
+                print("error: month must be of format MMYY")
+                return -1
+                
+            archive_refids = await api.get_archive_ref_ids(month_ts, server_name, port)
+            afree_ids = []
+            atasks = []
+            for arefid in archive_refids:
+                atasks.append(await api.fetch_archive_task(arefid, month_ts, server_name, port))
+                afree_ids.append(find_free_id(afree_ids))
+
+            adata = map_ids(afree_ids, archive_refids)
+
+            if len(sys.argv[2]) < 4:
+                try:
+                    tid = int(sys.argv[2])
+                    arefid = adata[tid]
+                except (ValueError, KeyError):
+                    print("error: invalid ID", file=sys.stderr)
+                    return -1
+            else:
+                print("error: invalid ID", file=sys.stderr)
+                return -1
+            
+            
+            if (errc := await show_archive_task(arefid, month_ts, server_name, port)) < 0:
+                return errc
+        elif len(sys.argv) == 3:
             if len(sys.argv[2]) == 4:
             if len(sys.argv[2]) == 4:
                 month = sys.argv[2]
                 month = sys.argv[2]
+                month_ts = lib.util.month_to_unix(month)
+                await show_deactive_tasks(month_ts, workspace, server_name, port)
+            elif len(sys.argv[2]) < 4:
+                month_ts = lib.util.month_to_unix()
+                archive_refids = await api.get_archive_ref_ids(month_ts, server_name, port)
+                afree_ids = []
+                atasks = []
+                for arefid in archive_refids:
+                    atasks.append(await api.fetch_archive_task(arefid, month_ts, server_name, port))
+                    afree_ids.append(find_free_id(afree_ids))
+
+                adata = map_ids(afree_ids, archive_refids)
+
+                try:
+                    tid = int(sys.argv[2])
+                    arefid = adata[tid]
+                except (ValueError, KeyError):
+                    print("error: invalid ID", file=sys.stderr)
+                    return -1
+                
+                if (errc := await show_archive_task(arefid, month_ts, server_name, port)) < 0:
+                    return errc
             else:
             else:
-                print("error: month must be of format MMYY")
+                print("error: usage format is: tau archive [ID] [MONTH]")
                 return -1
                 return -1
         else:
         else:
-            month = lib.util.current_month()
-
-        await show_deactive_tasks(month)
+            month_ts = lib.util.month_to_unix()
+            await show_deactive_tasks(month_ts, workspace, server_name, port)
+        
         return 0
         return 0
     elif sys.argv[1] == "show":
     elif sys.argv[1] == "show":
         if len(sys.argv) > 2:
         if len(sys.argv) > 2:
             filters = sys.argv[2:]
             filters = sys.argv[2:]
-            list_tasks(tasks, filters)
+            list_tasks(tasks, workspace, filters)
         else:
         else:
-            await show_active_tasks()
+            await show_active_tasks(workspace, server_name, port)
+        return 0
+    elif sys.argv[1] == "switch":
+        if not await api.switch_workspace(sys.argv[2], server_name, port):
+            print(f"Error: Workspace \"{sys.argv[2]}\" is not configured.")
+        else:
+            print(f"You are now on \"{sys.argv[2]}\" workspace.")
         return 0
         return 0
 
 
     try:
     try:
@@ -540,34 +626,23 @@ Example:
     args = sys.argv[2:]
     args = sys.argv[2:]
 
 
     if not args:
     if not args:
-        return await show_task(refid)
+        return await show_task(refid, server_name, port)
 
 
     subcmd, args = args[0], args[1:]
     subcmd, args = args[0], args[1:]
 
 
     if subcmd == "modify":
     if subcmd == "modify":
-        if (errc := await modify_task(refid, args)) < 0:
+        if (errc := await modify_task(refid, args, server_name, port)) < 0:
             return errc
             return errc
         time.sleep(0.1)
         time.sleep(0.1)
-        return await show_task(refid)
-    elif subcmd in ["start", "pause", "stop", "cancel"]:
+        return await show_task(refid, server_name, port)
+    elif subcmd in ["start", "pause", "stop", "open"]:
         status = subcmd
         status = subcmd
-        if (errc := await change_task_status(refid, status)) < 0:
+        if (errc := await change_task_status(refid, status, server_name, port)) < 0:
             return errc
             return errc
     elif subcmd == "comment":
     elif subcmd == "comment":
-        if (errc := await comment(refid, args)) < 0:
-            return errc
-    elif subcmd == "archive":
-        if len(args) == 1:
-            if len(args[0]) == 4:
-                month = args[0]
-            else:
-                print("Error: month must be of format MMYY")
-                return -1
-        else:
-            month = lib.util.current_month()
-
-        if (errc := await show_archive_task(refid, month)) < 0:
+        if (errc := await comment(refid, args, server_name, port)) < 0:
             return errc
             return errc
+        time.sleep(0.2)
     else:
     else:
         print(f"error: unknown subcommand '{subcmd}'")
         print(f"error: unknown subcommand '{subcmd}'")
         return -1
         return -1

+ 4 - 0
bin/tau/tau-python/requirements.txt

@@ -0,0 +1,4 @@
+tabulate
+pycryptodome
+colorama
+toml

+ 67 - 4
bin/tau/taud/src/jsonrpc.rs

@@ -65,6 +65,7 @@ impl RequestHandler for JsonRpcInterface {
         let rep = match req.method.as_str() {
         let rep = match req.method.as_str() {
             "add" => self.add(req.params).await,
             "add" => self.add(req.params).await,
             "get_ref_ids" => self.get_ref_ids(req.params).await,
             "get_ref_ids" => self.get_ref_ids(req.params).await,
+            "get_archive_ref_ids" => self.get_archive_ref_ids(req.params).await,
             "modify" => self.modify(req.params).await,
             "modify" => self.modify(req.params).await,
             "set_state" => self.set_state(req.params).await,
             "set_state" => self.set_state(req.params).await,
             "set_comment" => self.set_comment(req.params).await,
             "set_comment" => self.set_comment(req.params).await,
@@ -73,7 +74,8 @@ impl RequestHandler for JsonRpcInterface {
             "get_ws" => self.get_ws(req.params).await,
             "get_ws" => self.get_ws(req.params).await,
             "export" => self.export_to(req.params).await,
             "export" => self.export_to(req.params).await,
             "import" => self.import_from(req.params).await,
             "import" => self.import_from(req.params).await,
-            "get_stop_tasks" => self.get_stop_tasks(req.params).await,
+            "fetch_deactive_tasks" => self.fetch_deactive_tasks(req.params).await,
+            "fetch_archive_task" => self.fetch_archive_task(req.params).await,
 
 
             "ping" => return self.pong(req.id, req.params).await,
             "ping" => return self.pong(req.id, req.params).await,
             "dnet.subscribe_events" => return self.dnet_subscribe_events(req.id, req.params).await,
             "dnet.subscribe_events" => return self.dnet_subscribe_events(req.id, req.params).await,
@@ -281,6 +283,33 @@ impl JsonRpcInterface {
         Ok(JsonValue::Array(task_ref_ids))
         Ok(JsonValue::Array(task_ref_ids))
     }
     }
 
 
+    // RPCAPI:
+    // List tasks
+    // --> {"jsonrpc": "2.0", "method": "get_ids", "params": [], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": [task_id, ...], "id": 1}
+    async fn get_archive_ref_ids(&self, params: JsonValue) -> TaudResult<JsonValue> {
+        let params = params.get::<Vec<JsonValue>>().unwrap();
+        debug!(target: "tau", "JsonRpc::get_archive_ref_ids() params {:?}", params);
+
+        let month = match params[0].get::<String>() {
+            Some(u64_str) => match u64_str.parse::<u64>() {
+                Ok(v) => Some(Timestamp(v)),
+                //Err(e) => return Err(TaudError::InvalidData(e.to_string())),
+                Err(_) => None,
+            },
+
+            None => None,
+        };
+
+        let ws = self.workspace.lock().await.clone();
+        let tasks = MonthTasks::load_stop_tasks(&self.dataset_path, ws, month.as_ref())?;
+
+        let task_ref_ids: Vec<JsonValue> =
+            tasks.iter().map(|task| JsonValue::String(task.get_ref_id())).collect();
+
+        Ok(JsonValue::Array(task_ref_ids))
+    }
+
     // RPCAPI:
     // RPCAPI:
     // Modify task and returns `true` upon success.
     // Modify task and returns `true` upon success.
     // --> {"jsonrpc": "2.0", "method": "modify", "params": [task_id, {"title": "new title"} ], "id": 1}
     // --> {"jsonrpc": "2.0", "method": "modify", "params": [task_id, {"title": "new title"} ], "id": 1}
@@ -384,11 +413,11 @@ impl JsonRpcInterface {
 
 
     // RPCAPI:
     // RPCAPI:
     // Get all tasks.
     // Get all tasks.
-    // --> {"jsonrpc": "2.0", "method": "get_stop_tasks", "params": [task_id], "id": 1}
+    // --> {"jsonrpc": "2.0", "method": "fetch_deactive_tasks", "params": [task_id], "id": 1}
     // <-- {"jsonrpc": "2.0", "result": "task", "id": 1}
     // <-- {"jsonrpc": "2.0", "result": "task", "id": 1}
-    async fn get_stop_tasks(&self, params: JsonValue) -> TaudResult<JsonValue> {
+    async fn fetch_deactive_tasks(&self, params: JsonValue) -> TaudResult<JsonValue> {
         let params = params.get::<Vec<JsonValue>>().unwrap();
         let params = params.get::<Vec<JsonValue>>().unwrap();
-        debug!(target: "tau", "JsonRpc::get_stop_tasks() params {:?}", params);
+        debug!(target: "tau", "JsonRpc::fetch_deactive_tasks() params {:?}", params);
 
 
         if params.len() != 1 || !params[0].is_string() {
         if params.len() != 1 || !params[0].is_string() {
             return Err(TaudError::InvalidData("len of params should be 1".into()))
             return Err(TaudError::InvalidData("len of params should be 1".into()))
@@ -412,6 +441,40 @@ impl JsonRpcInterface {
         Ok(JsonValue::Array(tasks))
         Ok(JsonValue::Array(tasks))
     }
     }
 
 
+    async fn fetch_archive_task(&self, params: JsonValue) -> TaudResult<JsonValue> {
+        let params = params.get::<Vec<JsonValue>>().unwrap();
+        debug!(target: "tau", "JsonRpc::fetch_archive_task() params {:?}", params);
+
+        if params.len() != 2 || !params[0].is_string() || !params[1].is_string() {
+            return Err(TaudError::InvalidData("len of params should be 2".into()))
+        }
+
+        let ref_id = params[0].get::<String>().unwrap();
+
+        let month = match params[1].get::<String>() {
+            Some(u64_str) => match u64_str.parse::<u64>() {
+                Ok(v) => Some(Timestamp(v)),
+                //Err(e) => return Err(TaudError::InvalidData(e.to_string())),
+                Err(_) => None,
+            },
+
+            None => None,
+        };
+
+        let ws = self.workspace.lock().await.clone();
+
+        let mut tasks = MonthTasks::load_stop_tasks(&self.dataset_path, ws, month.as_ref())?;
+        tasks.retain(|x| x.ref_id == *ref_id);
+
+        if tasks.len() != 1 {
+            return Err(TaudError::InvalidData("Must return a single value".into()))
+        }
+
+        let task: JsonValue = (&tasks[0]).into();
+
+        Ok(task)
+    }
+
     // RPCAPI:
     // RPCAPI:
     // Switch tasks workspace.
     // Switch tasks workspace.
     // --> {"jsonrpc": "2.0", "method": "switch_ws", "params": [workspace], "id": 1}
     // --> {"jsonrpc": "2.0", "method": "switch_ws", "params": [workspace], "id": 1}

+ 62 - 6
bin/tau/taud/src/main.rs

@@ -53,7 +53,7 @@ use darkfi::{
         jsonrpc::JsonSubscriber,
         jsonrpc::JsonSubscriber,
         server::{listen_and_serve, RequestHandler},
         server::{listen_and_serve, RequestHandler},
     },
     },
-    system::StoppableTask,
+    system::{sleep, StoppableTask},
     util::path::expand_path,
     util::path::expand_path,
     Error, Result,
     Error, Result,
 };
 };
@@ -155,12 +155,12 @@ async fn start_sync_loop(
     datastore_path: std::path::PathBuf,
     datastore_path: std::path::PathBuf,
     piped: bool,
     piped: bool,
     p2p: P2pPtr,
     p2p: P2pPtr,
-    sled: sled::Db,
     last_sent: RwLock<blake3::Hash>,
     last_sent: RwLock<blake3::Hash>,
     seen: OnceLock<sled::Tree>,
     seen: OnceLock<sled::Tree>,
 ) -> TaudResult<()> {
 ) -> TaudResult<()> {
     let incoming = event_graph.event_sub.clone().subscribe().await;
     let incoming = event_graph.event_sub.clone().subscribe().await;
-    seen.set(sled.open_tree("tau_db").unwrap()).unwrap();
+    let seen_events = seen.get().unwrap();
+
     loop {
     loop {
         select! {
         select! {
             task_event = broadcast_rcv.recv().fuse() => {
             task_event = broadcast_rcv.recv().fuse() => {
@@ -200,6 +200,11 @@ async fn start_sync_loop(
                 if *last_sent.read().await == event_id {
                 if *last_sent.read().await == event_id {
                     continue
                     continue
                 }
                 }
+
+                if seen_events.contains_key(event_id.as_bytes()).unwrap() {
+                    continue
+                }
+
                 // Try to deserialize the `Event`'s content into a `Privmsg`
                 // Try to deserialize the `Event`'s content into a `Privmsg`
                 let enc_task: EncryptedTask = match deserialize_async_partial(task_event.content()).await {
                 let enc_task: EncryptedTask = match deserialize_async_partial(task_event.content()).await {
                     Ok((v, _)) => v,
                     Ok((v, _)) => v,
@@ -352,7 +357,7 @@ async fn realmain(settings: Args, executor: Arc<smol::Executor<'static>>) -> Res
     let sled_db = sled::open(datastore)?;
     let sled_db = sled::open(datastore)?;
     let p2p = P2p::new(settings.net.into(), executor.clone()).await;
     let p2p = P2p::new(settings.net.into(), executor.clone()).await;
     let event_graph =
     let event_graph =
-        EventGraph::new(p2p.clone(), sled_db.clone(), "darkirc_dag", 1, executor.clone()).await?;
+        EventGraph::new(p2p.clone(), sled_db.clone(), "taud_dag", 0, executor.clone()).await?;
 
 
     info!("Registering EventGraph P2P protocol");
     info!("Registering EventGraph P2P protocol");
     let event_graph_ = Arc::clone(&event_graph);
     let event_graph_ = Arc::clone(&event_graph);
@@ -369,12 +374,64 @@ async fn realmain(settings: Args, executor: Arc<smol::Executor<'static>>) -> Res
     info!(target: "taud", "Starting P2P network");
     info!(target: "taud", "Starting P2P network");
     p2p.clone().start().await?;
     p2p.clone().start().await?;
 
 
+    info!(target: "taud", "Waiting for some P2P connections...");
+    sleep(5).await;
+
+    // We'll attempt to sync 5 times
+    if !settings.skip_dag_sync {
+        for i in 1..=6 {
+            info!("Syncing event DAG (attempt #{})", i);
+            match event_graph.dag_sync().await {
+                Ok(()) => break,
+                Err(e) => {
+                    if i == 6 {
+                        error!("Failed syncing DAG. Exiting.");
+                        p2p.stop().await;
+                        return Err(Error::DagSyncFailed)
+                    } else {
+                        // TODO: Maybe at this point we should prune or something?
+                        // TODO: Or maybe just tell the user to delete the DAG from FS.
+                        error!("Failed syncing DAG ({}), retrying in 10s...", e);
+                        sleep(10).await;
+                    }
+                }
+            }
+        }
+    }
+
     ////////////////////
     ////////////////////
     // Listner
     // Listner
     ////////////////////
     ////////////////////
     info!(target: "taud", "Starting sync loop task");
     info!(target: "taud", "Starting sync loop task");
     let last_sent = RwLock::new(NULL_ID);
     let last_sent = RwLock::new(NULL_ID);
     let seen = OnceLock::new();
     let seen = OnceLock::new();
+    seen.set(sled_db.open_tree("tau_db").unwrap()).unwrap();
+
+    ////////////////////
+    // get history
+    ////////////////////
+    let dag_events = event_graph.order_events().await;
+    let seen_events = seen.get().unwrap();
+
+    for event_id in dag_events.iter() {
+        // If it was seen, skip
+        if seen_events.contains_key(event_id.as_bytes()).unwrap() {
+            continue
+        }
+
+        // Get the event from the DAG
+        let event = event_graph.dag_get(event_id).await.unwrap().unwrap();
+
+        // Try to deserialize it. (Here we skip errors)
+        let Ok((enc_task, _)) = deserialize_async_partial(event.content()).await else { continue };
+
+        // Potentially decrypt the privmsg
+        on_receive_task(&enc_task, &datastore_path, &workspaces, false).await.unwrap();
+
+        debug!("Marking event {} as seen", event_id);
+        seen_events.insert(event_id.as_bytes(), &[]).unwrap();
+    }
+
     let sync_loop_task = StoppableTask::new();
     let sync_loop_task = StoppableTask::new();
     sync_loop_task.clone().start(
     sync_loop_task.clone().start(
         start_sync_loop(
         start_sync_loop(
@@ -384,9 +441,8 @@ async fn realmain(settings: Args, executor: Arc<smol::Executor<'static>>) -> Res
             datastore_path.clone(),
             datastore_path.clone(),
             settings.piped,
             settings.piped,
             p2p.clone(),
             p2p.clone(),
-            sled_db.clone(),
             last_sent,
             last_sent,
-            seen,
+            seen.clone(),
         ),
         ),
         |res| async {
         |res| async {
             match res {
             match res {

+ 3 - 0
bin/tau/taud/src/settings.rs

@@ -67,6 +67,9 @@ pub struct Args {
     #[structopt(long)]
     #[structopt(long)]
     pub nickname: Option<String>,
     pub nickname: Option<String>,
 
 
+    #[structopt(long)]
+    pub skip_dag_sync: bool,
+
     // Whether to pipe notifications or not
     // Whether to pipe notifications or not
     #[structopt(long)]
     #[structopt(long)]
     pub piped: bool,
     pub piped: bool,

+ 1 - 1
bin/tau/taud/src/task_info.rs

@@ -28,7 +28,6 @@ use log::debug;
 use tinyjson::JsonValue;
 use tinyjson::JsonValue;
 
 
 use darkfi::{
 use darkfi::{
-    event_graph::gen_id,
     util::{
     util::{
         file::{load_json_file, save_json_file},
         file::{load_json_file, save_json_file},
         time::Timestamp,
         time::Timestamp,
@@ -39,6 +38,7 @@ use darkfi::{
 use crate::{
 use crate::{
     error::{TaudError, TaudResult},
     error::{TaudError, TaudResult},
     month_tasks::MonthTasks,
     month_tasks::MonthTasks,
+    util::gen_id,
 };
 };
 
 
 pub enum State {
 pub enum State {

+ 5 - 0
bin/tau/taud/src/util.rs

@@ -25,6 +25,7 @@ use std::{
 use log::debug;
 use log::debug;
 
 
 use darkfi::{Error, Result};
 use darkfi::{Error, Result};
+use rand::{distributions::Alphanumeric, rngs::OsRng, Rng};
 
 
 use crate::task_info::{TaskEvent, TaskInfo};
 use crate::task_info::{TaskEvent, TaskInfo};
 /*
 /*
@@ -54,6 +55,10 @@ pub fn pipe_write<P: AsRef<Path>>(path: P) -> Result<File> {
         .map_err(Error::from)
         .map_err(Error::from)
 }
 }
 
 
+pub fn gen_id(len: usize) -> String {
+    OsRng.sample_iter(&Alphanumeric).take(len).map(char::from).collect()
+}
+
 // #[cfg(test)]
 // #[cfg(test)]
 // mod tests {
 // mod tests {
 //     use super::*;
 //     use super::*;