use crate::models::NewUserProgram; use crate::schema::user_programs::columns; use crate::schema::user_programs::columns::discord_user_id; use crate::schema::user_programs::dsl::user_programs; use bigdecimal::{BigDecimal, FromPrimitive}; use diesel::{BoolExpressionMethods, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl}; use serenity::model::id::UserId; use std::collections::HashMap; pub(crate) struct Program { pub(crate) code: HashMap<u32, String>, } impl Program { pub fn new() -> Self { Self { code: HashMap::new(), } } pub fn list_programs_by_user(conn: &PgConnection, user_id: UserId) -> Option<Vec<String>> { user_programs .filter(columns::discord_user_id.eq(BigDecimal::from_u64(*user_id.as_u64()).unwrap())) .select(columns::name) .get_results(conn) .ok() } pub fn list_published_programs(conn: &PgConnection) -> Option<Vec<(i32, String)>> { user_programs .filter(columns::published.eq(1)) .select((columns::id, columns::name)) .load(conn) .ok() } pub fn load_published_program(&mut self, conn: &PgConnection, id: i32) -> Option<String> { let program: Vec<(String, String)> = user_programs .filter(columns::id.eq(id).and(columns::published.eq(1))) .limit(1) .select((columns::name, columns::code)) .get_results(conn) .ok()?; if program.is_empty() { return None; } let name = &program[0].0; let code = &program[0].1; self.parse_string(code)?; Some(name.to_string()) } pub fn set_program_published( conn: &PgConnection, name: &str, user_id: UserId, published: bool, ) -> Option<()> { if diesel::update( user_programs.filter( columns::discord_user_id .eq(BigDecimal::from_u64(*user_id.as_u64()).unwrap()) .and(columns::name.eq(name)), ), ) .set(columns::published.eq(if published { 1 } else { 0 })) .execute(conn) .ok()? == 1 { Some(()) } else { None } } pub fn stringify(&self) -> String { let mut code: Vec<(u32, String)> = self.code.iter().map(|(a, b)| (*a, b.to_owned())).collect(); code.sort_by(|a, b| a.0.cmp(&b.0)); code.into_iter() .map(|a| a.1) .collect::<Vec<String>>() .join("\n") } pub fn stringy_line_nums(&self) -> String { let mut code: Vec<(u32, String)> = self.code.iter().map(|(a, b)| (*a, b.to_owned())).collect(); code.sort_by(|a, b| a.0.cmp(&b.0)); code.into_iter() .map(|a| format!("{}\t{}", a.0, a.1)) .collect::<Vec<String>>() .join("\n") } pub fn save_program(&self, conn: &PgConnection, user_id: UserId, name: &str) -> Option<()> { let code = self.stringy_line_nums(); let new_program = NewUserProgram { discord_user_id: BigDecimal::from_u64(*user_id.as_u64()).unwrap(), name, code: code.as_str(), }; diesel::insert_into(user_programs) .values(&new_program) .on_conflict((discord_user_id, columns::name)) .do_update() .set(columns::code.eq(&code)) .execute(conn) .ok()?; Some(()) } pub fn load_program(&mut self, conn: &PgConnection, user_id: UserId, name: &str) -> Option<()> { let code: Vec<String> = user_programs .filter( columns::discord_user_id .eq(BigDecimal::from_u64(*user_id.as_u64()).unwrap()) .and(columns::name.eq(name)), ) .limit(1) .select(columns::code) .get_results(conn) .ok()?; if code.is_empty() { return None; } let code = &code[0]; self.parse_string(code)?; Some(()) } fn parse_string(&mut self, code: &str) -> Option<()> { let mut valid = true; let code: HashMap<u32, String> = code .split('\n') .map(|line| { let mut iter = line.splitn(2, &[' ', '\t'][..]); // This unwrap_or_else thing is pretty ugly // Is there a better way? let line_num: u32 = iter .next() .unwrap_or_else(|| { valid = false; "0" }) .parse() .unwrap_or_else(|_| { valid = false; 0 }); let line_code = iter .next() .unwrap_or_else(|| { valid = false; "" }) .to_owned(); (line_num, line_code) }) .collect(); if valid { self.code = code; Some(()) } else { None } } }