1mod limits;
2mod runner;
3mod types;
4
5pub use runner::Plugin;
6pub use types::{PushResponse, SyncCreate, SyncDelete, SyncReport, SyncUpdate};
7
8use crate::config::Config;
9use crate::error::Result;
10use crate::store::{task_lock, Store};
11use crate::task::Task;
12use serde_json::Value;
13use std::collections::BTreeMap;
14
15pub fn run_plugin_push(store: &Store, task: &Task) -> Result<BTreeMap<String, PushResponse>> {
18 let cfg = store.load_config()?;
19 let mut results = BTreeMap::new();
20 for (name, entry) in active_plugins(&cfg) {
21 if entry.sync_on_change {
22 let plugin = Plugin::resolve(store, name, entry);
23 if !plugin.auth_check() {
24 continue;
25 }
26 if let Ok(Some(result)) = plugin.push(task) {
27 results.insert(name.clone(), result);
28 }
29 }
30 }
31 Ok(results)
32}
33
34pub fn run_plugin_sync(
36 store: &Store,
37 filter: Option<&str>,
38) -> Result<Vec<(String, SyncReport)>> {
39 let cfg = store.load_config()?;
40 let tasks = store.all_tasks()?;
41 let mut reports = Vec::new();
42 for (name, entry) in active_plugins(&cfg) {
43 let plugin = Plugin::resolve(store, name, entry);
44 if !plugin.auth_check() {
45 continue;
46 }
47 if let Ok(Some(report)) = plugin.sync(&tasks, filter) {
48 reports.push((name.clone(), report));
49 }
50 }
51 Ok(reports)
52}
53
54pub fn apply_push_response(
56 store: &Store,
57 task_id: &str,
58 results: &BTreeMap<String, PushResponse>,
59) -> Result<()> {
60 if results.is_empty() {
61 return Ok(());
62 }
63 let _g = task_lock(store, task_id)?;
64 let mut task = store.load_task(task_id)?;
65 for (plugin_name, response) in results {
66 let ext_value = Value::Object(response.0.clone());
67 task.external.insert(plugin_name.clone(), ext_value);
68 }
69 task.touch();
70 store.save_task(&task)?;
71 store.commit_task(task_id, &format!("balls: update external for {task_id}"))?;
72 Ok(())
73}
74
75fn active_plugins(cfg: &Config) -> impl Iterator<Item = (&String, &crate::config::PluginEntry)> {
76 cfg.plugins.iter().filter(|(_, e)| e.enabled)
77}