use log::{log_enabled, error, info, Level};
use mongodb::bson::{doc, Document};
use pgnparse::parser::*;
use mongodb::{Client};
use mongodb::options::{UpdateOptions};
use futures::stream::StreamExt;
use crate::models::pgnwithdigest::*;
use crate::models::bookmove::*;
use crate::utils::env::*;
use crate::mongo::operations::*;
use crate::models::conv::{get_variant};
pub struct MongoBook {
mongodb_uri: String,
client: Option<Client>,
book_depth: usize,
book_db: String
}
impl MongoBook {
pub fn new() -> MongoBook {
MongoBook {
mongodb_uri: env_string_or("MONGODB_URI", "mongodb://localhost:27017"),
client: None,
book_depth: env_usize_or("BOOK_DEPTH", 40),
book_db: env_string_or("BOOK_DB", "rustbook"),
}
}
pub fn book_depth(mut self, book_depth: usize) -> MongoBook {
self.book_depth = book_depth;
self
}
pub async fn connect(&mut self) {
match connect(&self.mongodb_uri).await {
Ok(client) => self.client = Some(client),
_ => self.client = None
}
}
pub async fn drop_coll<T>(&mut self, coll: T)
where T: core::fmt::Display {
let coll = format!("{}", coll);
if let Some(client) = &self.client {
if log_enabled!(Level::Info) {
info!("dropping {}", coll);
}
match client.database(&self.book_db).collection(&coll).drop(None).await {
Ok(_) => {
if log_enabled!(Level::Info) {
info!("{} dropped ok", coll);
}
},
Err(err) => {
if log_enabled!(Level::Error) {
error!("dropping {} failed {:?}", coll, err);
}
}
}
}
}
pub async fn upsert_one<T>(&mut self, coll: T, filter: Document, doc: Document)
where T: core::fmt::Display {
let coll = format!("{}", coll);
if let Some(client) = &self.client {
let result = client.database(&self.book_db).collection(&coll)
.update_one(filter, doc!{"$set": doc}, UpdateOptions::builder().upsert(true).build()).await;
match result {
Ok(_) => {
if log_enabled!(Level::Info) {
info!("upserted in {} ok", coll);
}
},
Err(err) => {
if log_enabled!(Level::Error) {
error!("upsert in {} failed {:?}", coll, err);
}
}
}
}
}
pub async fn get_moves<T, V>(&mut self, variant: V, epd: T) -> std::collections::HashMap::<String, i32>
where T: core::fmt::Display, V: core::fmt::Display {
let variant = format!("{}", variant);
let epd = format!("{}", epd);
let mut moves = std::collections::HashMap::<String, i32>::new();
if let Some(client) = &self.client {
let result = client.database(&self.book_db).collection("moves")
.find(doc!{
"variant": variant.to_owned(),
"epd": epd.to_owned(),
}, None).await;
match result {
Ok(cursor) => {
let mut cursor = cursor;
if log_enabled!(Level::Info) {
info!("got cursor for epd {}", &epd);
}
while let Some(doc) = cursor.next().await {
match doc {
Ok(doc) => {
let result_wrt = doc.get_i32("result_wrt").unwrap();
let uci = doc.get("uci").unwrap().to_string();
*moves.entry(uci).or_insert(0) += result_wrt;
},
Err(err) => {
if log_enabled!(Level::Error) {
info!("cursor next failed {:?}", err);
}
}
}
}
},
Err(err) => {
if log_enabled!(Level::Error) {
error!("getting cursor for {} failed {:?}", &epd, err);
}
}
}
}
moves
}
pub async fn add_pgn_to_book<T>(&mut self, all_pgn: T)
where T: core::fmt::Display {
let all_pgn = format!("{}", all_pgn);
if let Some(client) = &self.client {
if log_enabled!(Level::Info) {
info!("adding pgn of size {} to book", all_pgn.len());
}
let db = client.database(&self.book_db);
let pgns = db.collection("pgns");
let mut items:Vec<&str> = all_pgn.split("\r\n\r\n\r\n").collect();
let _ = items.pop();
if log_enabled!(Level::Info) {
info!("number of games {}", items.len());
}
for pgn_str in items {
let old_pgn_str = pgn_str.to_owned();
let mut pgn_with_digest:PgnWithDigest = pgn_str.into();
if log_enabled!(Level::Info) {
info!("processing pgn with sha {}", pgn_with_digest.sha256_base64);
}
let result = pgns.find_one(doc!{"_id": pgn_with_digest.sha256_base64.to_owned()}, None).await;
let mut process_from = 0;
let mut processed_depth = 0;
if let Ok(Some(doc)) = result {
let pgn_with_digest_stored:PgnWithDigest = doc.into();
processed_depth = pgn_with_digest_stored.processed_depth as usize;
if log_enabled!(Level::Info) {
info!("pgn already in db {} processed depth {}",
pgn_with_digest_stored.sha256_base64, processed_depth)
}
process_from = processed_depth;
}
let mut moves = parse_pgn_to_rust_struct(old_pgn_str);
let num = moves.moves.len();
if ( num <= processed_depth ) || ( processed_depth >= self.book_depth ) {
if log_enabled!(Level::Info) {
info!("pgn has no moves beyond processed depth, skipping")
}
}else{
let mut process_to = self.book_depth;
if num < process_to {
process_to = num;
}
if log_enabled!(Level::Info) {
let result = match moves.get_header("Result").as_str() {
"1-0" => 2,
"0-1" => 0,
_ => 1
};
info!("pgn of orig variant {} and variant key {} has unprocessed moves from {} to {}\n{} {} - {} {} {}",
moves.get_header("Variant"),
get_variant(moves.get_header("Variant")),
process_from,
process_to,
moves.get_header("White"),
moves.get_header("WhiteElo"),
moves.get_header("Black"),
moves.get_header("BlackElo"),
moves.get_header("Result"),
);
for i in process_from..process_to {
let m = &moves.moves[i];
let mut result_wrt = result;
let parts:Vec<&str> = m.epd_before.split(" ").collect();
if parts[1] == "b" {
result_wrt = 2 - result_wrt;
}
if log_enabled!(Level::Info) {
let sha = pgn_with_digest.sha256_base64.to_owned();
let epd = m.epd_before.to_owned();
let san = m.san.to_owned();
let uci = m.uci.to_owned();
let book_move = BookMove{
variant: get_variant(moves.get_header("Variant")),
sha: sha,
epd: epd,
san: san,
uci: uci,
result_wrt: result_wrt,
};
info!("adding move {}", book_move);
let doc:Document = book_move.into();
let result = db.collection("moves").insert_one(doc, None).await;
match result {
Ok(_) => {
if log_enabled!(Level::Info) {
info!("move inserted ok")
}
},
Err(err) => {
if log_enabled!(Level::Error) {
error!("inserting move failed {:?}", err)
}
}
}
}
}
pgn_with_digest.processed_depth = process_to as i32;
let sha = pgn_with_digest.sha256_base64.to_owned();
let doc:Document = pgn_with_digest.into();
if log_enabled!(Level::Info) {
info!("moves processed ok, updating {:?}", &doc);
}
let filter = doc!{"_id": sha};
self.upsert_one("pgns", filter, doc).await;
}
}
}
}
}
}
impl std::fmt::Display for MongoBook {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("MongoBook\n-> uri = {}\n-> book depth = {}",
self.mongodb_uri, self.book_depth
))
}
}