main.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. use std::{fs::create_dir_all, path::PathBuf, sync::Arc};
  2. use async_executor::Executor;
  3. use async_trait::async_trait;
  4. use clap::{IntoApp, Parser};
  5. use log::debug;
  6. use serde_json::{json, Value};
  7. use simplelog::{ColorChoice, TermLogger, TerminalMode};
  8. use darkfi::{
  9. rpc::{
  10. jsonrpc::{error as jsonerr, response as jsonresp, ErrorCode::*, JsonRequest, JsonResult},
  11. rpcserver::{listen_and_serve, RequestHandler, RpcServerConfig},
  12. },
  13. util::{
  14. cli::{log_config, spawn_config, Config},
  15. expand_path, join_config_path,
  16. },
  17. Error, Result,
  18. };
  19. mod month_tasks;
  20. mod task_info;
  21. mod util;
  22. use crate::{
  23. month_tasks::MonthTasks,
  24. task_info::TaskInfo,
  25. util::{get_current_time, CliTaud, Settings, TauConfig, Timestamp, CONFIG_FILE_CONTENTS},
  26. };
  27. struct JsonRpcInterface {
  28. settings: Settings,
  29. }
  30. #[async_trait]
  31. impl RequestHandler for JsonRpcInterface {
  32. async fn handle_request(&self, req: JsonRequest, _executor: Arc<Executor<'_>>) -> JsonResult {
  33. if req.params.as_array().is_none() {
  34. return JsonResult::Err(jsonerr(InvalidParams, None, req.id))
  35. }
  36. debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap());
  37. match req.method.as_str() {
  38. Some("add") => return self.add(req.id, req.params).await,
  39. Some("list") => return self.list(req.id, req.params).await,
  40. Some("update") => return self.update(req.id, req.params).await,
  41. Some(_) | None => return JsonResult::Err(jsonerr(MethodNotFound, None, req.id)),
  42. }
  43. }
  44. }
  45. impl JsonRpcInterface {
  46. // RPCAPI:
  47. // Add new task and returns `true` upon success.
  48. // --> {"jsonrpc": "2.0", "method": "add", "params": ["title", "desc", ["assign"], ["project"], "due", "rank"], "id": 1}
  49. // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
  50. async fn add(&self, id: Value, params: Value) -> JsonResult {
  51. let args = params.as_array().unwrap();
  52. if args.len() != 6 {
  53. return JsonResult::Err(jsonerr(InvalidParams, None, id))
  54. }
  55. let mut task: TaskInfo;
  56. match (args[0].as_str(), args[1].as_str(), args[5].as_u64()) {
  57. (Some(title), Some(desc), Some(rank)) => {
  58. let due: Option<Timestamp> = if args[4].is_i64() {
  59. let timestamp = args[4].as_i64().unwrap();
  60. let timestamp = Timestamp(timestamp);
  61. if timestamp < get_current_time() {
  62. return JsonResult::Err(jsonerr(
  63. InvalidParams,
  64. Some("invalid due date".into()),
  65. id,
  66. ))
  67. }
  68. Some(timestamp)
  69. } else {
  70. None
  71. };
  72. match TaskInfo::new(title, desc, due, rank as u32, &self.settings) {
  73. Ok(t) => task = t,
  74. Err(e) => {
  75. return JsonResult::Err(jsonerr(InternalError, Some(e.to_string()), id))
  76. }
  77. }
  78. }
  79. (None, _, _) => {
  80. return JsonResult::Err(jsonerr(InvalidParams, Some("invalid title".into()), id))
  81. }
  82. (_, None, _) => {
  83. return JsonResult::Err(jsonerr(InvalidParams, Some("invalid desc".into()), id))
  84. }
  85. (_, _, None) => {
  86. return JsonResult::Err(jsonerr(InvalidParams, Some("invalid rank".into()), id))
  87. }
  88. }
  89. let assign = args[2].as_array();
  90. if assign.is_some() && assign.unwrap().len() > 0 {
  91. task.set_assign(
  92. &assign
  93. .unwrap()
  94. .into_iter()
  95. .filter(|a| a.as_str().is_some())
  96. .map(|a| a.as_str().unwrap().to_string())
  97. .collect(),
  98. );
  99. }
  100. let project = args[3].as_array();
  101. if project.is_some() && project.unwrap().len() > 0 {
  102. task.set_project(
  103. &project
  104. .unwrap()
  105. .into_iter()
  106. .filter(|p| p.as_str().is_some())
  107. .map(|p| p.as_str().unwrap().to_string())
  108. .collect(),
  109. );
  110. }
  111. let result = || -> Result<()> {
  112. task.save()?;
  113. task.activate()?;
  114. Ok(())
  115. };
  116. match result() {
  117. Ok(()) => JsonResult::Resp(jsonresp(json!(true), id)),
  118. Err(e) => JsonResult::Err(jsonerr(ServerError(-32603), Some(e.to_string()), id)),
  119. }
  120. }
  121. // RPCAPI:
  122. // List tasks
  123. // --> {"jsonrpc": "2.0", "method": "list", "params": [month_date], "id": 1}
  124. // <-- {"jsonrpc": "2.0", "result": [task, ...], "id": 1}
  125. async fn list(&self, id: Value, params: Value) -> JsonResult {
  126. let args = params.as_array().unwrap();
  127. if args.len() != 1 {
  128. return JsonResult::Err(jsonerr(InvalidParams, None, id))
  129. }
  130. let result = || -> Result<Vec<TaskInfo>> {
  131. let tasks: Vec<TaskInfo>;
  132. if args[0].is_i64() {
  133. tasks = MonthTasks::load_or_create(
  134. &Timestamp(args[0].as_i64().unwrap()),
  135. &self.settings,
  136. )?
  137. .objects()?;
  138. } else {
  139. tasks = MonthTasks::load_current_open_tasks(&self.settings)?;
  140. }
  141. Ok(tasks)
  142. };
  143. match result() {
  144. Ok(tks) => JsonResult::Resp(jsonresp(json!(tks), id)),
  145. Err(e) => JsonResult::Err(jsonerr(ServerError(-32603), Some(e.to_string()), id)),
  146. }
  147. }
  148. // RPCAPI:
  149. // Update task returns `true` upon success.
  150. // --> {"jsonrpc": "2.0", "method": "id", "params": [task_id, {"title": "new title"} ], "id": 1}
  151. // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
  152. async fn update(&self, id: Value, params: Value) -> JsonResult {
  153. let args = params.as_array().unwrap();
  154. if args.len() != 2 {
  155. return JsonResult::Err(jsonerr(InvalidParams, None, id))
  156. }
  157. let tasks: Vec<TaskInfo>;
  158. if !args[0].is_u64() {
  159. return JsonResult::Err(jsonerr(InvalidParams, Some("invalid id".into()), id))
  160. }
  161. if !args[1].is_object() {
  162. return JsonResult::Err(jsonerr(InvalidParams, Some("invalid update data".into()), id))
  163. }
  164. let task_id = args[0].as_u64().unwrap();
  165. let data = args[0].as_object().unwrap();
  166. match MonthTasks::load_current_open_tasks(&self.settings) {
  167. Ok(tks) => tasks = tks,
  168. Err(_) => return JsonResult::Err(jsonerr(InvalidParams, None, id)),
  169. }
  170. let task = tasks.into_iter().find(|t| (t.get_id() as u64) == task_id);
  171. if task.is_none() {
  172. return JsonResult::Err(jsonerr(
  173. InvalidRequest,
  174. Some("Didn't find a task with the provided id".into()),
  175. id,
  176. ))
  177. }
  178. let mut task = task.unwrap();
  179. let mut result = || -> std::result::Result<(), &str> {
  180. if data.contains_key("title") {
  181. let title = data
  182. .get("title")
  183. .ok_or("error parsing title")?
  184. .as_str()
  185. .ok_or("invalid value for title")?;
  186. task.set_title(title);
  187. }
  188. if data.contains_key("description") {
  189. let description = data
  190. .get("description")
  191. .ok_or("error parsing description")?
  192. .as_str()
  193. .ok_or("invalid value for description")?;
  194. task.set_desc(description);
  195. }
  196. if data.contains_key("rank") {
  197. let rank = data
  198. .get("rank")
  199. .ok_or("error parsing rank")?
  200. .as_u64()
  201. .ok_or("invalid value for rank")?;
  202. task.set_rank(rank as u32);
  203. }
  204. if data.contains_key("due") {
  205. let due = Some(Timestamp(
  206. data.get("due")
  207. .ok_or("error parsing rank")?
  208. .as_i64()
  209. .ok_or("invalid value for rank")?,
  210. ));
  211. task.set_due(due);
  212. }
  213. if data.contains_key("assign") {
  214. task.set_assign(
  215. &data
  216. .get("assign")
  217. .ok_or("error parsing assign")?
  218. .as_array()
  219. .ok_or("invalid value for assign")?
  220. .into_iter()
  221. .filter(|a| a.as_str().is_some())
  222. .map(|a| a.as_str().unwrap().to_string())
  223. .collect(),
  224. );
  225. }
  226. if data.contains_key("project") {
  227. task.set_project(
  228. &data
  229. .get("project")
  230. .ok_or("error parsing project")?
  231. .as_array()
  232. .ok_or("invalid value for project")?
  233. .into_iter()
  234. .filter(|p| p.as_str().is_some())
  235. .map(|p| p.as_str().unwrap().to_string())
  236. .collect(),
  237. );
  238. }
  239. Ok(())
  240. };
  241. match result() {
  242. Ok(()) => JsonResult::Resp(jsonresp(json!(true), id)),
  243. Err(e) => JsonResult::Err(jsonerr(InvalidParams, Some(e.to_string()), id)),
  244. }
  245. }
  246. }
  247. async fn start(config: TauConfig, executor: Arc<Executor<'_>>) -> Result<()> {
  248. if config.dataset_path.is_empty() {
  249. return Err(Error::ParseFailed("Failed to parse dataset_path"))
  250. }
  251. let dataset_path = expand_path(&config.dataset_path)?;
  252. // mkdir dataset_path if not exists
  253. create_dir_all(dataset_path.join("month"))?;
  254. create_dir_all(dataset_path.join("task"))?;
  255. let settings = Settings { dataset_path };
  256. let server_config = RpcServerConfig {
  257. socket_addr: config.rpc_listener_url.url.parse()?,
  258. use_tls: false,
  259. // this is all random filler that is meaningless bc tls is disabled
  260. identity_path: Default::default(),
  261. identity_pass: Default::default(),
  262. };
  263. let rpc_interface = Arc::new(JsonRpcInterface { settings });
  264. listen_and_serve(server_config, rpc_interface, executor).await
  265. }
  266. #[async_std::main]
  267. async fn main() -> Result<()> {
  268. let args = CliTaud::parse();
  269. let matches = CliTaud::into_app().get_matches();
  270. let config_path = if args.config.is_some() {
  271. expand_path(&args.config.unwrap())?
  272. } else {
  273. join_config_path(&PathBuf::from("taud_config.toml"))?
  274. };
  275. // Spawn config file if it's not in place already.
  276. spawn_config(&config_path, CONFIG_FILE_CONTENTS)?;
  277. let verbosity_level = matches.occurrences_of("verbose");
  278. let (lvl, conf) = log_config(verbosity_level)?;
  279. TermLogger::init(lvl, conf, TerminalMode::Mixed, ColorChoice::Auto)?;
  280. let config: TauConfig = Config::<TauConfig>::load(config_path.to_path_buf())?;
  281. let ex = Arc::new(Executor::new());
  282. smol::block_on(ex.run(start(config, ex.clone())))
  283. }
  284. #[cfg(test)]
  285. mod tests {
  286. use std::fs::create_dir_all;
  287. use crate::{month_tasks::MonthTasks, task_info::TaskInfo};
  288. use super::*;
  289. fn get_path() -> Result<PathBuf> {
  290. let path = PathBuf::from("/tmp/test_tau_data");
  291. // mkdir dataset_path if not exists
  292. create_dir_all(path.join("month"))?;
  293. create_dir_all(path.join("task"))?;
  294. Ok(path)
  295. }
  296. #[test]
  297. fn load_and_save_tasks() -> Result<()> {
  298. let settings = Settings { dataset_path: get_path()?.clone() };
  299. // load and save TaskInfo
  300. ///////////////////////
  301. let mut task = TaskInfo::new("test_title", "test_desc", None, 0, &settings)?;
  302. task.save()?;
  303. let t_load = TaskInfo::load(&task.get_ref_id(), &settings)?;
  304. assert_eq!(task, t_load);
  305. task.set_title("test_title_2");
  306. task.save()?;
  307. let t_load = TaskInfo::load(&task.get_ref_id(), &settings)?;
  308. assert_eq!(task, t_load);
  309. // load and save MonthTasks
  310. ///////////////////////
  311. let task_tks = vec![];
  312. let mut mt = MonthTasks::new(&task_tks, &settings);
  313. mt.save()?;
  314. let mt_load = MonthTasks::load_or_create(&get_current_time(), &settings)?;
  315. assert_eq!(mt, mt_load);
  316. mt.add(&task.get_ref_id());
  317. mt.save()?;
  318. let mt_load = MonthTasks::load_or_create(&get_current_time(), &settings)?;
  319. assert_eq!(mt, mt_load);
  320. Ok(())
  321. }
  322. #[test]
  323. fn test_activate_task() -> Result<()> {
  324. let settings = Settings { dataset_path: get_path()?.clone() };
  325. // activate task
  326. ///////////////////////
  327. let task = TaskInfo::new("test_title_3", "test_desc", None, 0, &settings)?;
  328. task.save()?;
  329. let mt_load = MonthTasks::load_or_create(&get_current_time(), &settings)?;
  330. assert!(!mt_load.get_task_tks().contains(&task.get_ref_id()));
  331. task.activate()?;
  332. let mt_load = MonthTasks::load_or_create(&get_current_time(), &settings)?;
  333. assert!(mt_load.get_task_tks().contains(&task.get_ref_id()));
  334. Ok(())
  335. }
  336. }