freechat/chan-like/src/main.rs
shockrah dd61d7e6dd -Removed default value for max_days option
+Adding note about query performance
2021-02-17 15:24:07 -08:00

164 lines
4.9 KiB
Rust

use tokio;
use mysql_async::{Pool, params};
use mysql_async::prelude::*;
use chrono::{Utc, Duration};
use std::env::var;
use clap::{App, Arg};
const MAX_DAYS_HELP: &'static str = "Purge messages older than <D> days old
If the value is 0 then messages are not deleted by their age
";
const MSG_LIMIT_HELP: &'static str = "Sets the max number of messages per channel.
NOTE: Deletes based on time so oldest messages are removed first until the max number of messages has been reached.
Example: #channel-a contains 1500 messages. Limit is set to 1000. Oldest 500 messages are removed
";
async fn remove_old(pool: &Pool, age_limit: i64) -> Result<(), mysql_async::Error> {
// really stupid guard but whatever
if age_limit == 0 {
Ok(())
} else {
// Straight removal of messages by age
let now = Utc::now();
let oldest = (now - Duration::days(age_limit)).timestamp();
let query = "DELETE FROM messages WHERE time < :age";
let p = params!{ "age" => oldest };
let mut conn = pool.get_conn().await?;
conn.exec_drop(query, p).await?;
Ok(())
}
}
async fn remove_maxed(pool: &Pool, max_msg: u64) -> Result<(), mysql_async::Error> {
// For N channels we're going to do n+1 queries making this really expensive
// TODO: reduce this to a single query for reasons below: tags[network, sql, slow]
/*
* Multiple network hits blow(moreso if the the db is located away from the
* main server binary but i digress.
* The first hit is no the expensive part if I'm being honest,
* It's everything after that leverages the channel id's as the messages
* table is likely to be filled with a ton of shit.
*
* The trick is going to be in some really nasty group by/limit thing but
* frankly this works for now so into the backlog it goes
*/
let mut conn = pool.get_conn().await?;
let channel_ids: Vec<u64> = conn.exec_map(
"SELECT id FROM channels", (),
| row | { row }
).await?;
// reverse the order and apply the limit to get the N newest items
for cid in channel_ids {
let query = "DELETE msg FROM messages msg join
(SELECT id FROM messages imsg WHERE imsg.channel_id = :cid
ORDER BY id DESC LIMIT 999 OFFSET :lim
) imsg on imsg.id = msg.id;";
let param = params!{"cid" => cid, "lim" => max_msg};
if let Err(e) = conn.exec_drop(query , param).await {
eprintln!("Couldn't remove messages from channel: {}", cid);
eprintln!("Mysql error: {}", e);
break;
}
}
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), String>{
let args = App::new("chan-chron")
.version("1.0")
.author("shockrah - with <3")
.about("Removes \"old\" messages periodically")
.arg(Arg::with_name("max_days")
.short("d")
.long("max-days")
.value_name("D")
.takes_value(true)
.long_help(MAX_DAYS_HELP))
.arg(Arg::with_name("config_file")
.short("f")
.long("file")
.required(true)
.takes_value(true))
.arg(Arg::with_name("msg_limit")
.short("l")
.long("msg-limit")
.required(false)
.takes_value(true)
.help(MSG_LIMIT_HELP))
.get_matches();
//if let Some(db_url) = args.value_of("db-url") {
let db_url = if let Some(config) = args.value_of("config_file") {
match dotenv::from_filename(config) {
// Not bothering with fucked up configs thats an admin problem not mine
Ok(_) => var("DATABASE_URL").unwrap(),
_ => panic!("Config not found!")
}
} else {
panic!("No config file provided!");
};
// could fail but again, thats a problem with configs sucking
let pool = Pool::from_url(db_url).unwrap();
let mut exec = 0;
if let Some(max_days) = args.value_of("max_days") {
let max: i64 = match max_days.parse() {
Ok(val) => val,
_ => panic!("Couldn't parse max_days value")
};
// Panicing in hopes that we avoid running later on
if let Err(e) = remove_old(&pool, max).await {
panic!("{}", e);
} else {
exec = 1;
}
}
if let Some(limit) = args.value_of("msg_limit") {
let limit: u64 = match limit.parse() {
Ok(val) => val,
_ => panic!("couldn't parse msg_limit value")
};
if let Err(e) = remove_maxed(&pool, limit).await {
panic!("{}", e);
} else {
exec |= 2;
}
}
if exec == 0 {
println!("Nothing todo");
} else {
match exec {
1 => println!("Age Based Purge succeed"),
2 => println!("Max message based purge succeed"),
3 => println!("All succeed"),
_ => {}
};
}
Ok(())
}