use std::{collections::HashMap, sync::Arc, time::Duration};
use futures::future::join_all;
use tokio::{sync::Mutex, time};
mod utils;
pub use utils::{Error, XnodeControllerError};
use crate::utils::{XnodeControllerErrorInner, add_user_config, outside};
pub trait XnodeController: Send + Sync {
fn get_session(&self) -> &xnode_manager_sdk::utils::Session;
fn check_controller(&self) -> impl Future<Output = Option<String>> + Send;
fn controller_config(&self, controller: String) -> String;
fn xnode_identifier(&self) -> String {
self.get_session().base_url.clone()
}
fn set_controller(
&self,
controller: Option<String>,
) -> impl Future<Output = Result<xnode_manager_sdk::os::SetOutput, Error>> + Send {
async {
let session = self.get_session();
let xnode_id = self.xnode_identifier();
let current_os =
xnode_manager_sdk::os::get(xnode_manager_sdk::os::GetInput::new(session))
.await
.map_err(Error::XnodeManagerSDKError)?;
let new_controller_config = if let Some(controller) = controller {
self.controller_config(controller)
} else {
"".to_string()
};
let os_block_start = format!("# START XNODE CONTROLLER {}", xnode_id);
let os_block_end = format!("# END XNODE CONTROLLER {}", xnode_id);
let new_os_config = match outside(¤t_os.flake, &os_block_start, &os_block_end) {
Some((before, after)) => [before, &new_controller_config, after].join("\n"),
None => add_user_config(
¤t_os.flake,
"# START USER CONFIG",
&[
"",
&os_block_start,
&new_controller_config,
&os_block_end,
"",
]
.join("\n"),
)
.ok_or(Error::XnodeControllerError(XnodeControllerError::new(
XnodeControllerErrorInner::NoUserConfig,
)))?,
};
let os_update =
xnode_manager_sdk::os::set(xnode_manager_sdk::os::SetInput::new_with_data(
session,
xnode_manager_sdk::os::OSChange {
acme_email: None,
domain: None,
flake: Some(new_os_config),
update_inputs: None,
user_passwd: None,
xnode_owner: None,
},
))
.await
.map_err(Error::XnodeManagerSDKError)?;
Ok(os_update)
}
}
}
struct XnodeControllerData {
last_controller: Option<String>,
}
pub async fn update_controllers<Xnode: XnodeController + 'static>(
xnodes: Arc<Mutex<Vec<Xnode>>>,
update_interval: Duration,
) {
let mut interval = time::interval(update_interval);
let data = Arc::new(Mutex::new(HashMap::<String, XnodeControllerData>::new()));
loop {
interval.tick().await;
join_all(xnodes.lock().await.iter().map(async |xnode| {
let xnode_id = xnode.xnode_identifier();
let new_controller = xnode.check_controller().await;
if let Some(data) = data.lock().await.get(&xnode_id) {
if new_controller == data.last_controller {
return;
}
}
log::info!("Setting controller on {xnode_id} to {new_controller:?}");
if let Err(e) = xnode.set_controller(new_controller.clone()).await {
log::warn!("Error setting controller on {xnode_id} to {new_controller:?}: {e:?}");
}
data.lock()
.await
.entry(xnode_id)
.and_modify(|data| {
data.last_controller = new_controller.clone();
})
.or_insert(XnodeControllerData {
last_controller: new_controller,
});
}))
.await;
}
}