jsonrpc.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{
  19. collections::{HashMap, HashSet},
  20. fs::create_dir_all,
  21. path::PathBuf,
  22. sync::Arc,
  23. };
  24. use async_trait::async_trait;
  25. use crypto_box::ChaChaBox;
  26. use log::{debug, warn};
  27. use smol::lock::{Mutex, MutexGuard};
  28. use tinyjson::JsonValue;
  29. use darkfi::{
  30. net,
  31. rpc::{
  32. jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResult, JsonSubscriber},
  33. p2p_method::HandlerP2p,
  34. server::RequestHandler,
  35. },
  36. system::StoppableTaskPtr,
  37. util::{path::expand_path, time::Timestamp},
  38. Error,
  39. };
  40. use taud::{
  41. error::{to_json_result, TaudError, TaudResult},
  42. month_tasks::MonthTasks,
  43. task_info::{Comment, TaskInfo},
  44. util::set_event,
  45. };
  46. pub struct JsonRpcInterface {
  47. dataset_path: PathBuf,
  48. notify_queue_sender: smol::channel::Sender<TaskInfo>,
  49. nickname: String,
  50. workspace: Mutex<String>,
  51. workspaces: Arc<HashMap<String, ChaChaBox>>,
  52. p2p: net::P2pPtr,
  53. dnet_sub: JsonSubscriber,
  54. rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
  55. }
  56. #[async_trait]
  57. impl RequestHandler for JsonRpcInterface {
  58. async fn handle_request(&self, req: JsonRequest) -> JsonResult {
  59. let rep = match req.method.as_str() {
  60. "add" => self.add(req.params).await,
  61. "get_ref_ids" => self.get_ref_ids(req.params).await,
  62. "get_archive_ref_ids" => self.get_archive_ref_ids(req.params).await,
  63. "modify" => self.modify(req.params).await,
  64. "set_state" => self.set_state(req.params).await,
  65. "set_comment" => self.set_comment(req.params).await,
  66. "get_task_by_ref_id" => self.get_task_by_ref_id(req.params).await,
  67. "switch_ws" => self.switch_ws(req.params).await,
  68. "get_ws" => self.get_ws(req.params).await,
  69. "export" => self.export_to(req.params).await,
  70. "import" => self.import_from(req.params).await,
  71. "fetch_deactive_tasks" => self.fetch_deactive_tasks(req.params).await,
  72. "fetch_archive_task" => self.fetch_archive_task(req.params).await,
  73. "ping" => return self.pong(req.id, req.params).await,
  74. "dnet.subscribe_events" => return self.dnet_subscribe_events(req.id, req.params).await,
  75. "dnet.switch" => self.dnet_switch(req.params).await,
  76. // TODO: make this optional
  77. "p2p.get_info" => return self.p2p_get_info(req.id, req.params).await,
  78. _ => return JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
  79. };
  80. to_json_result(rep, req.id)
  81. }
  82. async fn connections_mut(&self) -> MutexGuard<'_, HashSet<StoppableTaskPtr>> {
  83. self.rpc_connections.lock().await
  84. }
  85. }
  86. impl HandlerP2p for JsonRpcInterface {
  87. fn p2p(&self) -> net::P2pPtr {
  88. self.p2p.clone()
  89. }
  90. }
  91. impl JsonRpcInterface {
  92. pub fn new(
  93. dataset_path: PathBuf,
  94. notify_queue_sender: smol::channel::Sender<TaskInfo>,
  95. nickname: String,
  96. workspaces: Arc<HashMap<String, ChaChaBox>>,
  97. p2p: net::P2pPtr,
  98. dnet_sub: JsonSubscriber,
  99. ) -> Self {
  100. let workspace = Mutex::new(workspaces.iter().last().unwrap().0.clone());
  101. Self {
  102. dataset_path,
  103. nickname,
  104. workspace,
  105. workspaces,
  106. notify_queue_sender,
  107. p2p,
  108. rpc_connections: Mutex::new(HashSet::new()),
  109. dnet_sub,
  110. }
  111. }
  112. // RPCAPI:
  113. // Activate or deactivate dnet in the P2P stack.
  114. // By sending `true`, dnet will be activated, and by sending `false` dnet will
  115. // be deactivated. Returns `true` on success.
  116. //
  117. // --> {"jsonrpc": "2.0", "method": "dnet_switch", "params": [true], "id": 42}
  118. // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
  119. async fn dnet_switch(&self, params: JsonValue) -> TaudResult<JsonValue> {
  120. let params = params.get::<Vec<JsonValue>>().unwrap();
  121. if params.len() != 1 || !params[0].is_bool() {
  122. return Err(TaudError::InvalidData("Invalid parameters".into()))
  123. }
  124. let switch = params[0].get::<bool>().unwrap();
  125. if *switch {
  126. self.p2p.dnet_enable().await;
  127. } else {
  128. self.p2p.dnet_disable().await;
  129. }
  130. Ok(JsonValue::Boolean(true))
  131. }
  132. // RPCAPI:
  133. // Initializes a subscription to p2p dnet events.
  134. // Once a subscription is established, `darkirc` will send JSON-RPC notifications of
  135. // new network events to the subscriber.
  136. //
  137. // --> {"jsonrpc": "2.0", "method": "dnet.subscribe_events", "params": [], "id": 1}
  138. // <-- {"jsonrpc": "2.0", "method": "dnet.subscribe_events", "params": [`event`]}
  139. pub async fn dnet_subscribe_events(&self, id: u16, params: JsonValue) -> JsonResult {
  140. let params = params.get::<Vec<JsonValue>>().unwrap();
  141. if !params.is_empty() {
  142. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  143. }
  144. self.dnet_sub.clone().into()
  145. }
  146. // RPCAPI:
  147. // Add new task and returns `true` upon success.
  148. // --> {"jsonrpc": "2.0", "method": "add",
  149. // "params":
  150. // [{
  151. // "title": "..",
  152. // "desc": "..",
  153. // assign: [..],
  154. // project: [..],
  155. // "due": ..,
  156. // "rank": ..
  157. // }],
  158. // "id": 1
  159. // }
  160. // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
  161. async fn add(&self, params: JsonValue) -> TaudResult<JsonValue> {
  162. let params = params.get::<Vec<JsonValue>>().unwrap();
  163. debug!(target: "tau", "JsonRpc::add() params {:?}", params);
  164. if !params[0].is_object() {
  165. return Err(TaudError::InvalidData("Invalid parameters".to_string()))
  166. }
  167. let params = params[0].get::<HashMap<String, JsonValue>>().unwrap();
  168. if params.len() != 9 {
  169. return Err(TaudError::InvalidData("Invalid parameters".to_string()))
  170. }
  171. let due = match params["due"] {
  172. JsonValue::Null => None,
  173. JsonValue::Number(numba) => Some(Timestamp(numba as u64)),
  174. _ => return Err(TaudError::InvalidData("Invalid parameter \"due\"".to_string())),
  175. };
  176. let rank = match params["rank"] {
  177. JsonValue::Null => None,
  178. JsonValue::Number(numba) => Some(numba as f32),
  179. _ => return Err(TaudError::InvalidData("Invalid parameter \"rank\"".to_string())),
  180. };
  181. let tags = {
  182. let mut tags = vec![];
  183. for val in params["tags"].get::<Vec<JsonValue>>().unwrap().iter() {
  184. if let Some(tag) = val.get::<String>() {
  185. tags.push(tag.clone());
  186. } else {
  187. return Err(TaudError::InvalidData("Invalid parameter \"tags\"".to_string()))
  188. }
  189. }
  190. tags
  191. };
  192. let assigns = {
  193. let mut assigns = vec![];
  194. for val in params["assign"].get::<Vec<JsonValue>>().unwrap().iter() {
  195. if let Some(assign) = val.get::<String>() {
  196. assigns.push(assign.clone());
  197. } else {
  198. return Err(TaudError::InvalidData("Invalid parameter \"assign\"".to_string()))
  199. }
  200. }
  201. assigns
  202. };
  203. let projects = {
  204. let mut projects = vec![];
  205. for val in params["project"].get::<Vec<JsonValue>>().unwrap().iter() {
  206. if let Some(project) = val.get::<String>() {
  207. projects.push(project.clone());
  208. } else {
  209. return Err(TaudError::InvalidData("Invalid parameter \"project\"".to_string()))
  210. }
  211. }
  212. projects
  213. };
  214. let created_at = match params["created_at"] {
  215. JsonValue::Number(numba) => Some(numba as u64),
  216. _ => return Err(TaudError::InvalidData("Invalid parameter \"created_at\"".to_string())),
  217. };
  218. let mut new_task: TaskInfo = TaskInfo::new(
  219. self.workspace.lock().await.clone(),
  220. params["title"].get::<String>().unwrap(),
  221. params["desc"].get::<String>().unwrap(),
  222. &self.nickname,
  223. due,
  224. rank,
  225. Timestamp(created_at.unwrap()),
  226. )?;
  227. new_task.set_project(&projects);
  228. new_task.set_assign(&assigns);
  229. new_task.set_tags(&tags);
  230. self.notify_queue_sender.send(new_task.clone()).await.map_err(Error::from)?;
  231. Ok(JsonValue::Boolean(true))
  232. }
  233. // RPCAPI:
  234. // List tasks
  235. // --> {"jsonrpc": "2.0", "method": "get_ids", "params": [], "id": 1}
  236. // <-- {"jsonrpc": "2.0", "result": [task_id, ...], "id": 1}
  237. async fn get_ref_ids(&self, params: JsonValue) -> TaudResult<JsonValue> {
  238. let params = params.get::<Vec<JsonValue>>().unwrap();
  239. debug!(target: "tau", "JsonRpc::get_ids() params {:?}", params);
  240. let ws = self.workspace.lock().await.clone();
  241. let tasks = MonthTasks::load_current_tasks(&self.dataset_path, ws, false)?;
  242. let task_ref_ids: Vec<JsonValue> =
  243. tasks.iter().map(|task| JsonValue::String(task.get_ref_id())).collect();
  244. Ok(JsonValue::Array(task_ref_ids))
  245. }
  246. // RPCAPI:
  247. // List tasks
  248. // --> {"jsonrpc": "2.0", "method": "get_ids", "params": [], "id": 1}
  249. // <-- {"jsonrpc": "2.0", "result": [task_id, ...], "id": 1}
  250. async fn get_archive_ref_ids(&self, params: JsonValue) -> TaudResult<JsonValue> {
  251. let params = params.get::<Vec<JsonValue>>().unwrap();
  252. debug!(target: "tau", "JsonRpc::get_archive_ref_ids() params {:?}", params);
  253. let month = match params[0].get::<String>() {
  254. Some(u64_str) => match u64_str.parse::<u64>() {
  255. Ok(v) => Some(Timestamp(v)),
  256. //Err(e) => return Err(TaudError::InvalidData(e.to_string())),
  257. Err(_) => None,
  258. },
  259. None => None,
  260. };
  261. let ws = self.workspace.lock().await.clone();
  262. let tasks = MonthTasks::load_stop_tasks(&self.dataset_path, ws, month.as_ref())?;
  263. let task_ref_ids: Vec<JsonValue> =
  264. tasks.iter().map(|task| JsonValue::String(task.get_ref_id())).collect();
  265. Ok(JsonValue::Array(task_ref_ids))
  266. }
  267. // RPCAPI:
  268. // Modify task and returns `true` upon success.
  269. // --> {"jsonrpc": "2.0", "method": "modify", "params": [task_id, {"title": "new title"} ], "id": 1}
  270. // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
  271. async fn modify(&self, params: JsonValue) -> TaudResult<JsonValue> {
  272. let params = params.get::<Vec<JsonValue>>().unwrap();
  273. debug!(target: "tau", "JsonRpc::modify() params {:?}", params);
  274. if params.len() != 2 || !params[0].is_string() || !params[1].is_object() {
  275. return Err(TaudError::InvalidData("len of params should be 2".into()))
  276. }
  277. let ws = self.workspace.lock().await.clone();
  278. let task = self.check_params_for_modify(
  279. params[0].get::<String>().unwrap(),
  280. params[1].get::<HashMap<String, JsonValue>>().unwrap(),
  281. ws,
  282. )?;
  283. self.notify_queue_sender.send(task).await.map_err(Error::from)?;
  284. Ok(JsonValue::Boolean(true))
  285. }
  286. // RPCAPI:
  287. // Set state for a task and returns `true` upon success.
  288. // --> {"jsonrpc": "2.0", "method": "set_state", "params": [task_id, state], "id": 1}
  289. // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
  290. async fn set_state(&self, params: JsonValue) -> TaudResult<JsonValue> {
  291. // Allowed states for a task
  292. let states = ["stop", "start", "open", "pause"];
  293. let params = params.get::<Vec<JsonValue>>().unwrap();
  294. debug!(target: "tau", "JsonRpc::set_state() params {:?}", params);
  295. if params.len() != 2 || !params[0].is_string() || !params[1].is_string() {
  296. return Err(TaudError::InvalidData("len of params should be 2".into()))
  297. }
  298. let state = params[1].get::<String>().unwrap();
  299. let ws = self.workspace.lock().await.clone();
  300. let mut task: TaskInfo =
  301. self.load_task_by_ref_id(params[0].get::<String>().unwrap(), ws)?;
  302. if states.contains(&state.as_str()) {
  303. task.set_state(state);
  304. set_event(&mut task, "state", &self.nickname, state);
  305. }
  306. self.notify_queue_sender.send(task).await.map_err(Error::from)?;
  307. Ok(JsonValue::Boolean(true))
  308. }
  309. // RPCAPI:
  310. // Set comment for a task and returns `true` upon success.
  311. // --> {"jsonrpc": "2.0", "method": "set_comment", "params": [task_id, comment_content], "id": 1}
  312. // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
  313. async fn set_comment(&self, params: JsonValue) -> TaudResult<JsonValue> {
  314. let params = params.get::<Vec<JsonValue>>().unwrap();
  315. debug!(target: "tau", "JsonRpc::set_comment() params {:?}", params);
  316. if params.len() != 2 || !params[0].is_string() || !params[1].is_string() {
  317. return Err(TaudError::InvalidData("len of params should be 2".into()))
  318. }
  319. let ref_id = params[0].get::<String>().unwrap();
  320. let comment_content = params[1].get::<String>().unwrap();
  321. let ws = self.workspace.lock().await.clone();
  322. let mut task: TaskInfo = self.load_task_by_ref_id(ref_id, ws)?;
  323. task.set_comment(Comment::new(comment_content, &self.nickname));
  324. set_event(&mut task, "comment", &self.nickname, comment_content);
  325. self.notify_queue_sender.send(task).await.map_err(Error::from)?;
  326. Ok(JsonValue::Boolean(true))
  327. }
  328. // RPCAPI:
  329. // Get a task by id.
  330. // --> {"jsonrpc": "2.0", "method": "get_task_by_id", "params": [task_id], "id": 1}
  331. // <-- {"jsonrpc": "2.0", "result": "task", "id": 1}
  332. async fn get_task_by_ref_id(&self, params: JsonValue) -> TaudResult<JsonValue> {
  333. let params = params.get::<Vec<JsonValue>>().unwrap();
  334. debug!(target: "tau", "JsonRpc::get_task_by_ref_id() params {:?}", params);
  335. if params.len() != 1 || !params[0].is_string() {
  336. return Err(TaudError::InvalidData("len of params should be 1".into()))
  337. }
  338. let ws = self.workspace.lock().await.clone();
  339. let task: TaskInfo = self.load_task_by_ref_id(params[0].get::<String>().unwrap(), ws)?;
  340. let task: JsonValue = (&task).into();
  341. Ok(task)
  342. }
  343. // RPCAPI:
  344. // Get all tasks.
  345. // --> {"jsonrpc": "2.0", "method": "fetch_deactive_tasks", "params": [task_id], "id": 1}
  346. // <-- {"jsonrpc": "2.0", "result": "task", "id": 1}
  347. async fn fetch_deactive_tasks(&self, params: JsonValue) -> TaudResult<JsonValue> {
  348. let params = params.get::<Vec<JsonValue>>().unwrap();
  349. debug!(target: "tau", "JsonRpc::fetch_deactive_tasks() params {:?}", params);
  350. if params.len() != 1 || !params[0].is_string() {
  351. return Err(TaudError::InvalidData("len of params should be 1".into()))
  352. }
  353. let month = match params[0].get::<String>() {
  354. Some(u64_str) => match u64_str.parse::<u64>() {
  355. Ok(v) => Some(Timestamp(v)),
  356. //Err(e) => return Err(TaudError::InvalidData(e.to_string())),
  357. Err(_) => None,
  358. },
  359. None => None,
  360. };
  361. let ws = self.workspace.lock().await.clone();
  362. let tasks = MonthTasks::load_stop_tasks(&self.dataset_path, ws, month.as_ref())?;
  363. let tasks: Vec<JsonValue> = tasks.iter().map(|x| x.into()).collect();
  364. Ok(JsonValue::Array(tasks))
  365. }
  366. async fn fetch_archive_task(&self, params: JsonValue) -> TaudResult<JsonValue> {
  367. let params = params.get::<Vec<JsonValue>>().unwrap();
  368. debug!(target: "tau", "JsonRpc::fetch_archive_task() params {:?}", params);
  369. if params.len() != 2 || !params[0].is_string() || !params[1].is_string() {
  370. return Err(TaudError::InvalidData("len of params should be 2".into()))
  371. }
  372. let ref_id = params[0].get::<String>().unwrap();
  373. let month = match params[1].get::<String>() {
  374. Some(u64_str) => match u64_str.parse::<u64>() {
  375. Ok(v) => Some(Timestamp(v)),
  376. //Err(e) => return Err(TaudError::InvalidData(e.to_string())),
  377. Err(_) => None,
  378. },
  379. None => None,
  380. };
  381. let ws = self.workspace.lock().await.clone();
  382. let mut tasks = MonthTasks::load_stop_tasks(&self.dataset_path, ws, month.as_ref())?;
  383. tasks.retain(|x| x.ref_id == *ref_id);
  384. if tasks.len() != 1 {
  385. return Err(TaudError::InvalidData("Must return a single value".into()))
  386. }
  387. let task: JsonValue = (&tasks[0]).into();
  388. Ok(task)
  389. }
  390. // RPCAPI:
  391. // Switch tasks workspace.
  392. // --> {"jsonrpc": "2.0", "method": "switch_ws", "params": [workspace], "id": 1}
  393. // <-- {"jsonrpc": "2.0", "result": "true", "id": 1}
  394. async fn switch_ws(&self, params: JsonValue) -> TaudResult<JsonValue> {
  395. let params = params.get::<Vec<JsonValue>>().unwrap();
  396. debug!(target: "tau", "JsonRpc::switch_ws() params {:?}", params);
  397. if params.len() != 1 {
  398. return Err(TaudError::InvalidData("len of params should be 1".into()))
  399. }
  400. if !params[0].is_string() {
  401. return Err(TaudError::InvalidData("Invalid workspace".into()))
  402. }
  403. let ws = params[0].get::<String>().unwrap();
  404. let mut s = self.workspace.lock().await;
  405. if self.workspaces.contains_key(ws) {
  406. *s = ws.to_string()
  407. } else {
  408. warn!("Workspace \"{}\" is not configured", ws);
  409. return Ok(JsonValue::Boolean(false))
  410. }
  411. Ok(JsonValue::Boolean(true))
  412. }
  413. // RPCAPI:
  414. // Get workspace.
  415. // --> {"jsonrpc": "2.0", "method": "get_ws", "params": [], "id": 1}
  416. // <-- {"jsonrpc": "2.0", "result": "workspace", "id": 1}
  417. async fn get_ws(&self, params: JsonValue) -> TaudResult<JsonValue> {
  418. let params = params.get::<Vec<JsonValue>>().unwrap();
  419. debug!(target: "tau", "JsonRpc::get_ws() params {:?}", params);
  420. let ws = self.workspace.lock().await.clone();
  421. Ok(JsonValue::String(ws))
  422. }
  423. // RPCAPI:
  424. // Export tasks.
  425. // --> {"jsonrpc": "2.0", "method": "export_to", "params": [path], "id": 1}
  426. // <-- {"jsonrpc": "2.0", "result": "true", "id": 1}
  427. async fn export_to(&self, params: JsonValue) -> TaudResult<JsonValue> {
  428. let params = params.get::<Vec<JsonValue>>().unwrap();
  429. debug!(target: "tau", "JsonRpc::export_to() params {:?}", params);
  430. if params.len() != 1 {
  431. return Err(TaudError::InvalidData("len of params should be 1".into()))
  432. }
  433. if !params[0].is_string() {
  434. return Err(TaudError::InvalidData("Invalid path".into()))
  435. }
  436. // mkdir datastore_path if not exists
  437. let path = params[0].get::<String>().unwrap();
  438. let path = expand_path(path)?.join("exported_tasks");
  439. create_dir_all(path.join("month")).map_err(Error::from)?;
  440. create_dir_all(path.join("task")).map_err(Error::from)?;
  441. let ws = self.workspace.lock().await.clone();
  442. let tasks = MonthTasks::load_current_tasks(&self.dataset_path, ws, true)?;
  443. for task in tasks {
  444. task.save(&path)?;
  445. }
  446. Ok(JsonValue::Boolean(true))
  447. }
  448. // RPCAPI:
  449. // Import tasks.
  450. // --> {"jsonrpc": "2.0", "method": "import_from", "params": [path], "id": 1}
  451. // <-- {"jsonrpc": "2.0", "result": "true", "id": 1}
  452. async fn import_from(&self, params: JsonValue) -> TaudResult<JsonValue> {
  453. let params = params.get::<Vec<JsonValue>>().unwrap();
  454. debug!(target: "tau", "JsonRpc::import_from() params {:?}", params);
  455. if params.len() != 1 {
  456. return Err(TaudError::InvalidData("len of params should be 1".into()))
  457. }
  458. if !params[0].is_string() {
  459. return Err(TaudError::InvalidData("Invalid path".into()))
  460. }
  461. let path = params[0].get::<String>().unwrap();
  462. let path = expand_path(path)?.join("exported_tasks");
  463. let ws = self.workspace.lock().await.clone();
  464. let imported_tasks = MonthTasks::load_current_tasks(&path, ws.clone(), true)?;
  465. for task in imported_tasks {
  466. if MonthTasks::load_current_tasks(&self.dataset_path, ws.clone(), false)?
  467. .into_iter()
  468. .map(|t| t.ref_id)
  469. .any(|x| x == task.ref_id)
  470. {
  471. continue
  472. }
  473. self.notify_queue_sender.send(task).await.map_err(Error::from)?;
  474. }
  475. Ok(JsonValue::Boolean(true))
  476. }
  477. fn load_task_by_ref_id(&self, task_ref_id: &str, ws: String) -> TaudResult<TaskInfo> {
  478. let tasks = MonthTasks::load_current_tasks(&self.dataset_path, ws, false)?;
  479. let task = tasks.into_iter().find(|t| (t.get_ref_id()) == task_ref_id);
  480. task.ok_or(TaudError::InvalidId)
  481. }
  482. fn check_params_for_modify(
  483. &self,
  484. task_ref_id: &str,
  485. fields: &HashMap<String, JsonValue>,
  486. ws: String,
  487. ) -> TaudResult<TaskInfo> {
  488. let mut task: TaskInfo = self.load_task_by_ref_id(task_ref_id, ws)?;
  489. if fields.contains_key("title") {
  490. let title = fields["title"].get::<String>().unwrap();
  491. if !title.is_empty() {
  492. task.set_title(title);
  493. set_event(&mut task, "title", &self.nickname, title);
  494. }
  495. }
  496. if fields.contains_key("desc") {
  497. let desc = fields["desc"].get::<String>().unwrap();
  498. if !desc.is_empty() {
  499. task.set_desc(desc);
  500. set_event(&mut task, "desc", &self.nickname, desc);
  501. }
  502. }
  503. if fields.contains_key("rank") {
  504. match fields["rank"] {
  505. JsonValue::Null => set_event(&mut task, "rank", &self.nickname, "None"),
  506. JsonValue::Number(rank) => {
  507. task.set_rank(Some(rank as f32));
  508. set_event(&mut task, "rank", &self.nickname, &rank.to_string())
  509. }
  510. _ => unreachable!(),
  511. }
  512. }
  513. if fields.contains_key("due") {
  514. match &fields["due"] {
  515. JsonValue::Null => set_event(&mut task, "due", &self.nickname, "None"),
  516. JsonValue::Number(ts_num) => {
  517. task.set_due(Some(Timestamp(*ts_num as u64)));
  518. set_event(&mut task, "due", &self.nickname, &ts_num.to_string())
  519. }
  520. _ => unreachable!(),
  521. }
  522. }
  523. if fields.contains_key("assign") {
  524. let assign: Vec<String> = fields["assign"]
  525. .get::<Vec<JsonValue>>()
  526. .unwrap()
  527. .iter()
  528. .map(|x| x.get::<String>().unwrap().clone())
  529. .collect();
  530. if !assign.is_empty() {
  531. task.set_assign(&assign);
  532. set_event(&mut task, "assign", &self.nickname, &assign.join(", "));
  533. }
  534. }
  535. if fields.contains_key("project") {
  536. let project: Vec<String> = fields["project"]
  537. .get::<Vec<JsonValue>>()
  538. .unwrap()
  539. .iter()
  540. .map(|x| x.get::<String>().unwrap().clone())
  541. .collect();
  542. if !project.is_empty() {
  543. task.set_project(&project);
  544. set_event(&mut task, "project", &self.nickname, &project.join(", "));
  545. }
  546. }
  547. if fields.contains_key("tags") {
  548. let tags: Vec<String> = fields["tags"]
  549. .get::<Vec<JsonValue>>()
  550. .unwrap()
  551. .iter()
  552. .map(|x| x.get::<String>().unwrap().clone())
  553. .collect();
  554. if !tags.is_empty() {
  555. task.set_tags(&tags);
  556. set_event(&mut task, "tags", &self.nickname, &tags.join(", "));
  557. }
  558. }
  559. Ok(task)
  560. }
  561. }