Files
mini_projects/scryfall_deser/src/db.rs

257 lines
8.7 KiB
Rust

use deunicode::deunicode;
use rusqlite;
use std::fmt;
use std::fs;
use std::path::PathBuf;
use super::deser::ScryfallCard;
use super::utils::get_local_cache_folder;
use super::utils::{create_cache_folder, get_local_data_folder, SQLITE_FILENAME};
fn get_local_data_sqlite_file() -> PathBuf {
let mut folder = get_local_data_folder();
folder.push(SQLITE_FILENAME);
folder
}
pub fn get_all_card_names() -> Vec<String> {
let sqlite_file = get_local_data_sqlite_file();
let conn = rusqlite::Connection::open(sqlite_file).unwrap();
let mut stmt = conn.prepare("SELECT name FROM cards;").unwrap();
let mut rows = stmt.query([]).unwrap();
let mut card_names = Vec::new();
while let Some(row) = rows.next().unwrap() {
card_names.push(row.get(0).unwrap());
}
card_names
}
pub fn get_all_lowercase_card_names() -> Vec<String> {
let sqlite_file = get_local_data_sqlite_file();
let conn = rusqlite::Connection::open(sqlite_file).unwrap();
let mut stmt = conn.prepare("SELECT lowercase_name FROM cards;").unwrap();
let mut rows = stmt.query([]).unwrap();
let mut card_names = Vec::new();
while let Some(row) = rows.next().unwrap() {
card_names.push(row.get(0).unwrap());
}
card_names
}
pub fn update_db_with_file(file: PathBuf) -> bool {
let ac = fs::read_to_string(file).unwrap();
let ac: Vec<ScryfallCard> = serde_json::from_str(&ac).unwrap();
let sqlite_file = get_local_data_sqlite_file();
let mut conn = rusqlite::Connection::open(sqlite_file).unwrap();
let tx = conn.transaction().unwrap();
for card in ac {
for word in card.name.split_whitespace() {
let word = deunicode(word);
let res = tx.execute(
"INSERT INTO magic_words (word) VALUES (?1)
ON CONFLICT (word) DO NOTHING;",
[word.replace(",", "")],
);
}
let lowercase_name = deunicode(&card.name.to_lowercase());
let power_toughness = match card.power {
Some(p) => Some(format!("{}/{}", p, card.toughness.unwrap())),
None => None,
};
let oracle_text = match card.oracle_text {
Some(ot) => ot,
None => "<No Oracle Text>".to_string(),
};
let loyalty = match card.loyalty {
Some(loy) => Some(loy),
None => None,
};
let mana_cost = match card.mana_cost {
Some(mc) => Some(mc),
None => None,
};
let res = tx.execute(
"INSERT INTO cards (name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
rusqlite::params![card.name, lowercase_name, card.type_line, oracle_text, power_toughness, loyalty, mana_cost, card.scryfall_uri],
);
}
tx.commit();
true
}
// unsure if this should be in this file...
impl fmt::Display for DbCard {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.mana_cost {
Some(mc) => writeln!(f, "{}\t{}", self.name, mc)?,
None => writeln!(f, "{}", self.name)?,
}
writeln!(f, "{}", self.type_line)?;
match &self.oracle_text {
Some(ot) => writeln!(f, "{}", ot)?,
None => (),
}
match &self.power_toughness {
Some(pt) => writeln!(f, "{}", pt)?,
None => (),
}
match &self.loyalty {
Some(l) => writeln!(f, "Starting Loyalty: {}", l)?,
None => (),
}
writeln!(f, "Scryfall URI: {}", self.scryfall_uri)?;
Ok(())
}
}
#[derive(Debug)]
pub struct DbCard {
pub name: String,
pub lowercase_name: String,
pub type_line: String,
pub oracle_text: Option<String>,
pub power_toughness: Option<String>,
pub loyalty: Option<String>,
pub mana_cost: Option<String>,
pub scryfall_uri: String,
}
pub enum GetNameType {
Name,
LowercaseName,
}
pub fn get_card_by_name(name: &str, name_type: GetNameType) -> Option<DbCard> {
let sqlite_file = get_local_data_sqlite_file();
let conn = rusqlite::Connection::open(sqlite_file).unwrap();
let sql = match name_type {
GetNameType::Name => {
"SELECT name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri
FROM cards WHERE name = (?1)"
}
GetNameType::LowercaseName => {
"SELECT name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri
FROM cards WHERE lowercase_name = (?1)"
}
};
let mut stmt = conn.prepare(sql).unwrap();
let mut rows = stmt.query([name]).unwrap();
match rows.next().unwrap() {
Some(row) => Some(DbCard {
name: row.get(0).unwrap(),
lowercase_name: row.get(1).unwrap(),
type_line: row.get(2).unwrap(),
oracle_text: row.get(3).unwrap(),
power_toughness: row.get(4).unwrap(),
loyalty: row.get(5).unwrap(),
mana_cost: row.get(6).unwrap(),
scryfall_uri: row.get(7).unwrap(),
}),
None => None,
}
}
pub fn find_matching_cards_seperate_words(search_strings: &Vec<String>) -> Vec<DbCard> {
assert!(!search_strings.is_empty());
let sqlite_file = get_local_data_sqlite_file();
let conn = rusqlite::Connection::open(sqlite_file).unwrap();
let mut percentaged_string = Vec::new();
// I know that .clone fixes my problem - I'm not sure why I need to though
for mut search_string in search_strings.clone() {
search_string.push('%');
search_string.insert(0, '%');
percentaged_string.push(search_string);
}
let mut sql: String = "SELECT name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri
FROM cards WHERE".into();
for i in 0..search_strings.len() {
sql.push_str(&format!(" lowercase_name LIKE (?{}) AND", i + 1));
}
sql.pop();
sql.pop();
sql.pop();
sql.pop();
dbg!(&sql);
let mut stmt = conn.prepare(&sql).unwrap();
stmt.query_map(rusqlite::params_from_iter(percentaged_string), |row| {
Ok(DbCard {
name: row.get(0).unwrap(),
lowercase_name: row.get(1).unwrap(),
type_line: row.get(2).unwrap(),
oracle_text: row.get(3).unwrap(),
power_toughness: row.get(4).unwrap(),
loyalty: row.get(5).unwrap(),
mana_cost: row.get(6).unwrap(),
scryfall_uri: row.get(7).unwrap(),
})
})
.unwrap()
.filter_map(|res| res.ok())
.collect()
}
pub fn find_matching_cards(name: &str) -> Vec<DbCard> {
let sqlite_file = get_local_data_sqlite_file();
let conn = rusqlite::Connection::open(sqlite_file).unwrap();
// There must be something better than this - although I don't think it's possible with a str
let mut name = name.to_string();
name.push('%');
name.insert(0, '%');
let mut stmt = conn
.prepare(
"SELECT name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri
FROM cards WHERE lowercase_name LIKE (?1)",
)
.unwrap();
stmt.query_map([name], |row| {
Ok(DbCard {
name: row.get(0).unwrap(),
lowercase_name: row.get(1).unwrap(),
type_line: row.get(2).unwrap(),
oracle_text: row.get(3).unwrap(),
power_toughness: row.get(4).unwrap(),
loyalty: row.get(5).unwrap(),
mana_cost: row.get(6).unwrap(),
scryfall_uri: row.get(7).unwrap(),
})
})
.unwrap()
.filter_map(|res| res.ok())
.collect()
}
const CREATE_CARDS_TABLE_SQL: &str = "
CREATE TABLE cards (
name TEXT NOT NULL UNIQUE,
lowercase_name TEXT NOT NULL UNIQUE,
type_line TEXT,
oracle_text TEXT,
power_toughness TEXT,
loyalty TEXT,
mana_cost TEXT,
scryfall_uri TEXT NOT NULL UNIQUE
)";
const CREATE_MAGIC_WORDS_TABLE_SQL: &str = "
CREATE TABLE magic_words (
word TEXT NOT NULL UNIQUE
)";
// Will delete your current db
pub fn init_db() -> bool {
create_cache_folder();
let sqlite_file = get_local_data_sqlite_file();
println!("sqlite file location: {}", sqlite_file.display());
// TESTING
println!("cache folder: {}", get_local_cache_folder().display());
let _res = fs::remove_file(&sqlite_file);
// TODO actually check result for whether it was a permissions thing or something
let connection = rusqlite::Connection::open(sqlite_file).unwrap();
connection.execute(&CREATE_CARDS_TABLE_SQL, ()).unwrap();
connection
.execute(&CREATE_MAGIC_WORDS_TABLE_SQL, ())
.unwrap();
true
}