2020-06-15 22:03:40 +02:00
|
|
|
|
use crate::models::{Entry, Keyword, NewEntry, NewKeyword};
|
|
|
|
|
use diesel::pg::PgConnection;
|
|
|
|
|
use diesel::prelude::*;
|
2020-08-02 01:26:01 +02:00
|
|
|
|
use failure::format_err;
|
2020-08-02 02:31:02 +02:00
|
|
|
|
use failure::Error;
|
2020-06-15 22:03:40 +02:00
|
|
|
|
use std::borrow::Cow;
|
|
|
|
|
|
2020-07-02 23:32:25 +02:00
|
|
|
|
/// Maximum number of times we'll follow a `see: ` pointer.
|
|
|
|
|
const RECURSION_LIMIT: usize = 5;
|
|
|
|
|
|
2020-06-15 22:03:40 +02:00
|
|
|
|
pub struct KeywordDetails {
|
|
|
|
|
pub keyword: Keyword,
|
|
|
|
|
pub entries: Vec<Entry>,
|
|
|
|
|
}
|
2020-07-01 18:56:08 +02:00
|
|
|
|
|
2020-06-15 22:03:40 +02:00
|
|
|
|
impl KeywordDetails {
|
|
|
|
|
pub fn learn(&mut self, nick: &str, text: &str, dbc: &PgConnection) -> Result<usize, Error> {
|
|
|
|
|
let now = ::chrono::Utc::now().naive_utc();
|
|
|
|
|
let ins = NewEntry {
|
|
|
|
|
keyword_id: self.keyword.id,
|
|
|
|
|
idx: (self.entries.len() + 1) as _,
|
|
|
|
|
text,
|
|
|
|
|
creation_ts: now,
|
|
|
|
|
created_by: nick,
|
|
|
|
|
};
|
|
|
|
|
let new = {
|
|
|
|
|
use crate::schema::entries;
|
|
|
|
|
::diesel::insert_into(entries::table)
|
|
|
|
|
.values(ins)
|
|
|
|
|
.get_result(dbc)?
|
|
|
|
|
};
|
|
|
|
|
self.entries.push(new);
|
|
|
|
|
Ok(self.entries.len())
|
|
|
|
|
}
|
2020-07-01 18:56:08 +02:00
|
|
|
|
|
2020-06-15 22:03:40 +02:00
|
|
|
|
pub fn process_moves(&mut self, moves: &[(i32, i32)], dbc: &PgConnection) -> Result<(), Error> {
|
|
|
|
|
for (oid, new_idx) in moves {
|
|
|
|
|
{
|
|
|
|
|
use crate::schema::entries::dsl::*;
|
|
|
|
|
::diesel::update(entries.filter(id.eq(oid)))
|
|
|
|
|
.set(idx.eq(new_idx))
|
|
|
|
|
.execute(dbc)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.entries = Self::get_entries(self.keyword.id, dbc)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2020-07-01 18:56:08 +02:00
|
|
|
|
|
2020-06-15 22:03:40 +02:00
|
|
|
|
pub fn swap(&mut self, idx_a: usize, idx_b: usize, dbc: &PgConnection) -> Result<(), Error> {
|
|
|
|
|
let mut moves = vec![];
|
|
|
|
|
for ent in self.entries.iter() {
|
|
|
|
|
if ent.idx == idx_a as i32 {
|
|
|
|
|
moves.push((ent.id, idx_b as i32));
|
|
|
|
|
}
|
|
|
|
|
if ent.idx == idx_b as i32 {
|
|
|
|
|
moves.push((ent.id, idx_a as i32));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if moves.len() != 2 {
|
|
|
|
|
Err(format_err!("Invalid swap operation."))?;
|
|
|
|
|
}
|
|
|
|
|
self.process_moves(&moves, dbc)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2020-07-01 18:56:08 +02:00
|
|
|
|
|
2020-06-15 22:03:40 +02:00
|
|
|
|
pub fn update(&mut self, idx: usize, val: &str, dbc: &PgConnection) -> Result<(), Error> {
|
|
|
|
|
let ent = self
|
|
|
|
|
.entries
|
|
|
|
|
.get_mut(idx.saturating_sub(1))
|
|
|
|
|
.ok_or(format_err!("No such element to update."))?;
|
|
|
|
|
{
|
|
|
|
|
use crate::schema::entries::dsl::*;
|
|
|
|
|
::diesel::update(entries.filter(id.eq(ent.id)))
|
|
|
|
|
.set(text.eq(val))
|
|
|
|
|
.execute(dbc)?;
|
|
|
|
|
}
|
|
|
|
|
ent.text = val.to_string();
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2020-07-01 18:56:08 +02:00
|
|
|
|
|
2020-06-15 22:03:40 +02:00
|
|
|
|
pub fn delete(&mut self, idx: usize, dbc: &PgConnection) -> Result<(), Error> {
|
|
|
|
|
// step 1: delete the element
|
|
|
|
|
{
|
|
|
|
|
let ent = self
|
|
|
|
|
.entries
|
|
|
|
|
.get(idx.saturating_sub(1))
|
|
|
|
|
.ok_or(format_err!("No such element to delete."))?;
|
|
|
|
|
{
|
|
|
|
|
use crate::schema::entries::dsl::*;
|
|
|
|
|
::diesel::delete(entries.filter(id.eq(ent.id))).execute(dbc)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// step 2: move all the elements in front of it back one
|
|
|
|
|
let mut moves = vec![];
|
|
|
|
|
for ent in self.entries.iter() {
|
|
|
|
|
if idx > ent.idx as _ {
|
|
|
|
|
moves.push((ent.id, ent.idx.saturating_sub(1)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.process_moves(&moves, dbc)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2020-07-01 18:56:08 +02:00
|
|
|
|
|
2020-06-15 22:03:40 +02:00
|
|
|
|
pub fn add_zwsp_to_name(name: &str) -> Option<String> {
|
|
|
|
|
let second_index = name.char_indices().nth(1).map(|(i, _)| i)?;
|
|
|
|
|
let (start, end) = name.split_at(second_index);
|
|
|
|
|
Some(format!("{}{}", start, end))
|
|
|
|
|
}
|
2020-07-01 18:56:08 +02:00
|
|
|
|
|
2020-06-15 22:03:40 +02:00
|
|
|
|
pub fn format_entry(&self, idx: usize) -> Option<String> {
|
|
|
|
|
if let Some(ent) = self.entries.get(idx.saturating_sub(1)) {
|
|
|
|
|
let gen_clr = if self.keyword.chan == "*" {
|
|
|
|
|
"\x0307"
|
|
|
|
|
} else {
|
|
|
|
|
""
|
|
|
|
|
};
|
|
|
|
|
let zwsp_name = Self::add_zwsp_to_name(&self.keyword.name)
|
|
|
|
|
.unwrap_or_else(|| self.keyword.name.clone());
|
|
|
|
|
Some(format!(
|
|
|
|
|
"\x02{}{}\x0f\x0315[{}/{}]\x0f: {} \x0f\x0314[{}]\x0f",
|
|
|
|
|
gen_clr,
|
|
|
|
|
zwsp_name,
|
|
|
|
|
idx,
|
|
|
|
|
self.entries.len(),
|
|
|
|
|
ent.text,
|
|
|
|
|
ent.creation_ts.date()
|
|
|
|
|
))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-01 18:56:08 +02:00
|
|
|
|
|
2020-06-15 22:03:40 +02:00
|
|
|
|
pub fn get_or_create(word: &str, c: &str, dbc: &PgConnection) -> Result<Self, Error> {
|
|
|
|
|
if let Some(ret) = Self::get(word, c, dbc)? {
|
|
|
|
|
Ok(ret)
|
|
|
|
|
} else {
|
|
|
|
|
Ok(Self::create(word, c, dbc)?)
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-01 18:56:08 +02:00
|
|
|
|
|
2020-06-15 22:03:40 +02:00
|
|
|
|
pub fn create(word: &str, c: &str, dbc: &PgConnection) -> Result<Self, Error> {
|
|
|
|
|
let val = NewKeyword {
|
|
|
|
|
name: word,
|
|
|
|
|
chan: c,
|
|
|
|
|
};
|
|
|
|
|
let ret: Keyword = {
|
|
|
|
|
use crate::schema::keywords;
|
|
|
|
|
::diesel::insert_into(keywords::table)
|
|
|
|
|
.values(val)
|
|
|
|
|
.get_result(dbc)?
|
|
|
|
|
};
|
|
|
|
|
Ok(KeywordDetails {
|
|
|
|
|
keyword: ret,
|
|
|
|
|
entries: vec![],
|
|
|
|
|
})
|
|
|
|
|
}
|
2020-07-01 18:56:08 +02:00
|
|
|
|
|
2020-06-15 22:03:40 +02:00
|
|
|
|
fn get_entries(kid: i32, dbc: &PgConnection) -> Result<Vec<Entry>, Error> {
|
|
|
|
|
let entries: Vec<Entry> = {
|
|
|
|
|
use crate::schema::entries::dsl::*;
|
|
|
|
|
entries
|
|
|
|
|
.filter(keyword_id.eq(kid))
|
|
|
|
|
.order_by(idx.asc())
|
|
|
|
|
.load(dbc)?
|
|
|
|
|
};
|
|
|
|
|
Ok(entries)
|
|
|
|
|
}
|
2020-07-01 18:56:08 +02:00
|
|
|
|
|
2020-07-02 23:32:25 +02:00
|
|
|
|
fn get_inner<'a, T: Into<Cow<'a, str>>>(
|
2020-06-15 22:03:40 +02:00
|
|
|
|
word: T,
|
|
|
|
|
c: &str,
|
|
|
|
|
dbc: &PgConnection,
|
2020-07-02 23:32:25 +02:00
|
|
|
|
recursion_count: usize,
|
2020-06-15 22:03:40 +02:00
|
|
|
|
) -> Result<Option<Self>, Error> {
|
|
|
|
|
let word = word.into();
|
|
|
|
|
let keyword: Option<Keyword> = {
|
|
|
|
|
use crate::schema::keywords::dsl::*;
|
|
|
|
|
keywords
|
|
|
|
|
.filter(name.ilike(word).and(chan.eq(c).or(chan.eq("*"))))
|
|
|
|
|
.first(dbc)
|
|
|
|
|
.optional()?
|
|
|
|
|
};
|
|
|
|
|
if let Some(k) = keyword {
|
|
|
|
|
let entries = Self::get_entries(k.id, dbc)?;
|
|
|
|
|
if let Some(e0) = entries.get(0) {
|
|
|
|
|
if e0.text.starts_with("see: ") {
|
2020-07-02 23:32:25 +02:00
|
|
|
|
if recursion_count > RECURSION_LIMIT {
|
|
|
|
|
// Oh dear.
|
|
|
|
|
Err(format_err!("Halt. You're having a bit too much fun."))?
|
|
|
|
|
}
|
|
|
|
|
let new_word = e0.text.replace("see: ", "");
|
|
|
|
|
return Self::get_inner(new_word, c, dbc, recursion_count + 1);
|
2020-06-15 22:03:40 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(Some(KeywordDetails {
|
|
|
|
|
keyword: k,
|
|
|
|
|
entries,
|
|
|
|
|
}))
|
|
|
|
|
} else {
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-02 23:32:25 +02:00
|
|
|
|
|
|
|
|
|
pub fn get<'a, T: Into<Cow<'a, str>>>(
|
|
|
|
|
word: T,
|
|
|
|
|
c: &str,
|
|
|
|
|
dbc: &PgConnection,
|
|
|
|
|
) -> Result<Option<Self>, Error> {
|
|
|
|
|
Self::get_inner(word, c, dbc, 0)
|
|
|
|
|
}
|
2020-06-15 22:03:40 +02:00
|
|
|
|
}
|