From cd98f0cd26d53a9e97c0200d36403c243fc801bc Mon Sep 17 00:00:00 2001 From: Kiana Sheibani Date: Sat, 30 Sep 2023 01:43:33 -0400 Subject: [PATCH] Implement basic logic for syncing --- src/datasets.rs | 59 +++++++++++++++++++++++++++++++++++++++++-------- src/main.rs | 51 +++++++++++++++++++++++++++++++++++++++--- src/queries.rs | 6 ++--- src/state.rs | 6 ----- 4 files changed, 101 insertions(+), 21 deletions(-) delete mode 100644 src/state.rs diff --git a/src/datasets.rs b/src/datasets.rs index 4d28104..ae2447c 100644 --- a/src/datasets.rs +++ b/src/datasets.rs @@ -4,8 +4,8 @@ use std::fs::{self, OpenOptions}; use std::io; use std::path::{Path, PathBuf}; -/// Return the default path to the datasets file. -fn default_datasets_path(config_dir: &Path) -> io::Result { +/// Return the path to the datasets file. +fn datasets_path(config_dir: &Path) -> io::Result { let mut path = config_dir.to_owned(); path.push("ggelo"); @@ -21,11 +21,12 @@ fn default_datasets_path(config_dir: &Path) -> io::Result { } pub fn open_datasets(config_dir: &Path) -> sqlite::Result { - let path = default_datasets_path(config_dir).unwrap(); + let path = datasets_path(config_dir).unwrap(); let query = " CREATE TABLE IF NOT EXISTS datasets ( - name TEXT UNIQUE NOT NULL + name TEXT UNIQUE NOT NULL, + last_sync INTEGER NOT NULL DEFAULT 1 ) STRICT;"; let connection = sqlite::open(path)?; @@ -71,6 +72,29 @@ pub fn new_dataset(connection: &Connection, dataset: &str) -> sqlite::Result<()> connection.execute(query) } +pub fn get_last_sync(connection: &Connection, dataset: &str) -> sqlite::Result> { + let query = "SELECT last_sync FROM datasets WHERE name = ?"; + + Ok(connection + .prepare(query)? + .into_iter() + .bind((1, dataset))? + .map(|x| x.map(|r| r.read::("last_sync").to_owned() as u64)) + .next() + .and_then(Result::ok)) +} + +pub fn update_last_sync(connection: &Connection, dataset: &str, sync: u64) -> sqlite::Result<()> { + let query = "UPDATE datasets SET last_sync = :sync WHERE name = :dataset"; + + connection + .prepare(query)? + .into_iter() + .bind((":sync", sync as i64))? + .bind((":dataset", dataset))? + .try_for_each(|x| x.map(|_| ())) +} + // Score calculation /// Calculate the collective expected score for each team. @@ -163,11 +187,7 @@ pub fn update_ratings( }) } -pub fn update_from_set( - connection: &Connection, - dataset: &str, - results: SetData, -) -> sqlite::Result<()> { +fn update_from_set(connection: &Connection, dataset: &str, results: SetData) -> sqlite::Result<()> { let players_data = results.teams; add_players(connection, dataset, &players_data)?; @@ -180,3 +200,24 @@ pub fn update_from_set( ); update_ratings(connection, dataset, elos) } + +fn update_from_tournament( + connection: &Connection, + dataset: &str, + results: TournamentData, +) -> sqlite::Result<()> { + results + .sets + .into_iter() + .try_for_each(|set| update_from_set(connection, dataset, set)) +} + +pub fn update_from_tournaments( + connection: &Connection, + dataset: &str, + results: Vec, +) -> sqlite::Result<()> { + results + .into_iter() + .try_for_each(|tour| update_from_tournament(connection, dataset, tour)) +} diff --git a/src/main.rs b/src/main.rs index 15be420..1d60a9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,12 +2,11 @@ use clap::{Parser, Subcommand}; use std::io::{self, Write}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; +use std::time::SystemTime; mod queries; use queries::*; -mod state; -use state::*; mod datasets; use datasets::*; @@ -35,6 +34,12 @@ enum Subcommands { #[command(subcommand)] subcommand: DatasetSC, }, + Sync { + #[arg(group = "datasets")] + names: Vec, + #[arg(short, long, group = "datasets")] + all: bool, + }, } #[derive(Subcommand)] @@ -57,6 +62,8 @@ fn main() { Subcommands::Dataset { subcommand: DatasetSC::Delete { name }, } => dataset_delete(name), + + Subcommands::Sync { names, all } => sync(names, all, cli.auth_token), } } @@ -102,3 +109,41 @@ fn dataset_delete(name: Option) { let connection = open_datasets(&config_dir).unwrap(); delete_dataset(&connection, &name).unwrap(); } + +fn sync(names: Vec, all: bool, auth_token: Option) { + let config_dir = dirs::config_dir().unwrap(); + + let auth = auth_token.or_else(|| get_auth_token(&config_dir)).unwrap(); + + let connection = open_datasets(&config_dir).unwrap(); + + let names = if all { + list_datasets(&connection).unwrap() + } else { + names + }; + + for name in names { + let last_sync = get_last_sync(&connection, &name).unwrap().unwrap(); + + let results = run_query::( + TournamentSetsVars { + last_query: Timestamp(last_sync), + game_id: VideogameId(1), + country: None, + state: None, + }, + &auth, + ) + .unwrap(); + + update_from_tournaments(&connection, &name, results).unwrap(); + + let current_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + + update_last_sync(&connection, &name, current_time).unwrap(); + } +} diff --git a/src/queries.rs b/src/queries.rs index 9fb58fe..c6592d4 100644 --- a/src/queries.rs +++ b/src/queries.rs @@ -14,7 +14,7 @@ use schema::schema; // Auth key -pub fn get_auth_key(config_dir: &Path) -> Option { +pub fn get_auth_token(config_dir: &Path) -> Option { use std::env::{var, VarError}; use std::fs::read_to_string; @@ -66,7 +66,7 @@ pub trait QueryUnwrap: 'static + QueryBuilder { } // Generic function for running start.gg queries -pub fn run_query(vars: Vars, state: &AppState) -> Option +pub fn run_query(vars: Vars, auth_token: &str) -> Option where Builder: QueryUnwrap, Vars: Serialize, @@ -78,7 +78,7 @@ where let response = reqwest::blocking::Client::new() .post("https://api.start.gg/gql/alpha") - .header("Authorization", String::from("Bearer ") + &state.auth_token) + .header("Authorization", String::from("Bearer ") + auth_token) .run_graphql(query); Builder::unwrap_response(response.unwrap()) diff --git a/src/state.rs b/src/state.rs deleted file mode 100644 index bb7be58..0000000 --- a/src/state.rs +++ /dev/null @@ -1,6 +0,0 @@ -use std::path::PathBuf; - -pub struct AppState { - pub config_dir: PathBuf, - pub auth_token: String, -}