mod mattermost;
use std::path::Path;
use anyhow::anyhow;
use tokio::signal::unix;
use tokio::{self};
use tracing::{debug, warn};
struct Vav {
db_connection: Option<sqlite::ConnectionWithFullMutex>,
}
impl Vav {
fn new<T: AsRef<Path>>(path: Option<T>) -> Self {
let ret = Self {
db_connection: path
.and_then(|path| sqlite::Connection::open_with_full_mutex(path).ok()),
};
if let Some(connection) = &ret.db_connection {
let create_table_res = connection.execute(
"CREATE TABLE keyval (key text NOT NULL PRIMARY KEY, value text NOT NULL)",
);
if let Err(err) = create_table_res {
warn!("Error while creating db {err}");
}
}
ret
}
}
const INSERT_STATEMENT: &str = "INSERT INTO keyval (key,value) VALUES (?,?)";
const SELECT_ONE: &str = "SELECT key,value FROM keyval where key=?";
const SELECT_ALL: &str = "SELECT key,value FROM keyval";
#[async_trait::async_trait]
impl mattermost::Handler for Vav {
async fn handle(
&self,
update: mattermost::model::WebsocketUpdate,
client: &mattermost::Client,
) -> Result<(), anyhow::Error> {
debug!("as json: {update:?}");
let posted = match update.update {
mattermost::model::Update::Posted(posted) => posted,
_ => return Ok(()),
};
let message = &posted.post.message;
if !message.starts_with('!') {
return Ok(());
}
let message = message.strip_prefix('!').unwrap();
let (message, rest) = message.split_once(' ').unwrap_or((message, ""));
match message {
"store" => {
let message = rest;
let (name, value) = message
.split_once(' ')
.ok_or(anyhow::anyhow!("missing value in command store"))?;
if let Some(connection) = &self.db_connection {
let mut statement = connection.prepare(INSERT_STATEMENT)?;
statement.bind((1, name))?;
statement.bind((2, value))?;
if let Err(err) = statement.next() {
warn!("Error while writing to db {err}");
}
}
}
"lookup" => {
let name = rest;
let response = if let Some(connection) = &self.db_connection {
let mut statement = connection.prepare(SELECT_ONE)?;
statement.bind((1, name))?;
match statement.next() {
Ok(sqlite::State::Done) => "no entry under that name".to_owned(),
Ok(sqlite::State::Row) => statement.read::<String, _>(1)?,
Err(err) => {
warn!("Error while writing to db {err}");
return Err(err.into());
}
}
} else {
"uggh, no db".to_owned()
};
client.reply_to(posted.post, response).await?;
}
"list" => {
let response = if let Some(connection) = &self.db_connection {
let mut statement = connection.prepare(SELECT_ALL)?;
let mut res = vec!["Stored keys:".to_owned()];
while let Ok(result) = statement.next() {
if result == sqlite::State::Done {
break;
}
res.push(statement.read::<String, _>(0)?)
}
res.join("\n")
} else {
"uggh, no db".to_owned()
};
client.reply_to(posted.post, response).await?;
}
_ => return Err(anyhow!("Unrecognized command {message}")),
}
Ok(())
}
}
#[tokio::main(worker_threads = 2)]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let login = std::env::var("USER_MAIL")?;
let password = std::env::var("USER_PASSWORD")?;
let db = std::env::var("SQLITE_PATH").ok();
let auth = mattermost::AuthData { login, password };
let mut client = mattermost::Client::new(auth, "https://mattermost.continuum.ii.uni.wroc.pl");
client.update_bearer_token().await?;
{
let (shutdown_send, shutdown_recv) = tokio::sync::oneshot::channel();
let (on_task_finish_send, on_task_finish_recv) = tokio::sync::oneshot::channel();
let websocket_task = tokio::spawn(async move {
let result = client
.handle_websocket_stream(Vav::new(db), shutdown_recv)
.await;
on_task_finish_send.send(result).unwrap();
});
{
let mut sigterm = unix::signal(unix::SignalKind::terminate())?;
let mut sigint = unix::signal(unix::SignalKind::interrupt())?;
let mut sigquit = unix::signal(unix::SignalKind::quit())?;
tokio::select! {
_ = sigterm.recv() => {},
_ = sigint.recv() => {},
_ = sigquit.recv() => {},
task_result = on_task_finish_recv => {
warn!("Task closed with result {:?}",task_result);
},
};
}
_ = shutdown_send.send(());
websocket_task.await?;
}
Ok(())
}