From 336845709666b52bcf37c4eea373a0af72abd86e Mon Sep 17 00:00:00 2001 From: Kiana Sheibani Date: Tue, 2 Jan 2024 01:38:28 -0500 Subject: [PATCH] Add dataset intervals --- Cargo.lock | 142 +++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 2 +- src/database.rs | 40 ++++++++++---- src/main.rs | 122 +++++++++++++++++++++++++++++++++++++---- src/sync.rs | 10 ++-- 5 files changed, 272 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3377d53..6852564 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.5.0" @@ -155,6 +170,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.48.5", +] + [[package]] name = "clap" version = "4.4.5" @@ -594,6 +623,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1154,6 +1206,7 @@ dependencies = [ name = "startrnr" version = "0.2.0" dependencies = [ + "chrono", "clap", "cynic", "cynic-codegen", @@ -1163,7 +1216,6 @@ dependencies = [ "schema", "serde", "sqlite", - "time-format", ] [[package]] @@ -1233,12 +1285,6 @@ dependencies = [ "syn 2.0.29", ] -[[package]] -name = "time-format" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42baec394ad2773a90e037d7b6dfc7518d1f121b9dbaeebad42e19568c39f196" - [[package]] name = "tinyvec" version = "1.6.0" @@ -1504,13 +1550,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1519,13 +1574,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -1534,42 +1604,84 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winreg" version = "0.50.0" diff --git a/Cargo.toml b/Cargo.toml index 87dbb6a..678e541 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ path = "src/main.rs" [dependencies] # CLI clap = { version = "4.4", features = ["derive"] } -time-format = "1.1" +chrono = "0.4" # GraphQL schema schema.path = "schema" diff --git a/src/database.rs b/src/database.rs index 30c199d..5497837 100644 --- a/src/database.rs +++ b/src/database.rs @@ -4,6 +4,8 @@ use std::fs::{self, OpenOptions}; use std::path::{Path, PathBuf}; pub struct DatasetMetadata { + pub start: Timestamp, + pub end: Option, pub last_sync: Timestamp, pub game_id: VideogameId, @@ -41,6 +43,8 @@ pub fn open_datasets(config_dir: &Path) -> sqlite::Result { let query = " CREATE TABLE IF NOT EXISTS datasets ( name TEXT UNIQUE NOT NULL, + start INTEGER NOT NULL, + end INTEGER, last_sync INTEGER NOT NULL, game_id INTEGER NOT NULL, game_name TEXT NOT NULL, @@ -100,6 +104,10 @@ pub fn list_datasets(connection: &Connection) -> sqlite::Result("name").to_owned(), DatasetMetadata { + start: Timestamp(r_.read::("start") as u64), + end: r_ + .read::, _>("end") + .map(|x| Timestamp(x as u64)), last_sync: Timestamp(r_.read::("last_sync") as u64), game_id: VideogameId(r_.read::("game_id") as u64), game_name: r_.read::<&str, _>("game_name").to_owned(), @@ -150,7 +158,7 @@ pub fn new_dataset( dataset: &str, metadata: DatasetMetadata, ) -> sqlite::Result<()> { - let query1 = r#"INSERT INTO datasets VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"#; + let query1 = r#"INSERT INTO datasets VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"#; let query2 = format!( r#"CREATE TABLE "{0}_players" ( id INTEGER PRIMARY KEY REFERENCES players, @@ -193,17 +201,19 @@ CREATE INDEX "{0}_network_B" ON "{0}_network" (player_B);"#, .prepare(query1)? .into_iter() .bind((1, dataset))? - .bind((2, metadata.last_sync.0 as i64))? - .bind((3, metadata.game_id.0 as i64))? - .bind((4, &metadata.game_name[..]))? - .bind((5, &metadata.game_slug[..]))? - .bind((6, metadata.country.as_deref()))? - .bind((7, metadata.state.as_deref()))? - .bind((8, metadata.set_limit as i64))? - .bind((9, metadata.decay_rate))? - .bind((10, metadata.adj_decay_rate))? - .bind((11, metadata.period))? - .bind((12, metadata.tau))? + .bind((2, metadata.start.0 as i64))? + .bind((3, metadata.end.map(|x| x.0 as i64)))? + .bind((4, metadata.last_sync.0 as i64))? + .bind((5, metadata.game_id.0 as i64))? + .bind((6, &metadata.game_name[..]))? + .bind((7, &metadata.game_slug[..]))? + .bind((8, metadata.country.as_deref()))? + .bind((9, metadata.state.as_deref()))? + .bind((10, metadata.set_limit as i64))? + .bind((11, metadata.decay_rate))? + .bind((12, metadata.adj_decay_rate))? + .bind((13, metadata.period))? + .bind((14, metadata.tau))? .try_for_each(|x| x.map(|_| ()))?; connection.execute(query2) @@ -223,6 +233,10 @@ pub fn get_metadata( .map(|r| { let r_ = r?; Ok(DatasetMetadata { + start: Timestamp(r_.read::("start") as u64), + end: r_ + .read::, _>("end") + .map(|x| Timestamp(x as u64)), last_sync: Timestamp(r_.read::("last_sync") as u64), game_id: VideogameId(r_.read::("game_id") as u64), game_name: r_.read::<&str, _>("game_name").to_owned(), @@ -748,6 +762,8 @@ CREATE TABLE IF NOT EXISTS sets ( pub fn metadata() -> DatasetMetadata { DatasetMetadata { + start: Timestamp(1), + end: None, last_sync: Timestamp(1), game_id: VideogameId(0), game_name: String::from("Test Game"), diff --git a/src/main.rs b/src/main.rs index 002118a..28f8afa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ #![feature(iterator_try_collect)] #![feature(extend_one)] +use chrono::{Local, TimeZone, Utc}; use clap::{Parser, Subcommand}; use sqlite::*; -use std::path::PathBuf; -use time_format::strftime_utc; +use std::{cmp::min, path::PathBuf}; mod queries; use queries::*; @@ -117,7 +117,6 @@ fn main() { let connection = open_datasets(&config_dir).unwrap_or_else(|_| error("Could not open datasets file", 2)); - #[allow(unreachable_patterns)] match cli.subcommand { Subcommands::Dataset { subcommand: DatasetSC::List, @@ -171,15 +170,42 @@ fn dataset_list(connection: &Connection) { println!("(Global)"); } - if metadata.last_sync.0 == 1 { + let start = if metadata.start.0 != 1 { + Some( + Utc.timestamp_opt(metadata.start.0 as i64, 0) + .unwrap() + .format("%m/%d/%Y"), + ) + } else { + None + }; + let end = metadata + .end + .map(|x| Utc.timestamp_opt(x.0 as i64, 0).unwrap().format("%m/%d/%Y")); + + match (start, end) { + (None, None) => (), + (Some(s), None) => println!("after {}", s), + (None, Some(e)) => println!("until {}", e), + (Some(s), Some(e)) => println!("{} - {}", s, e), + } + + if metadata.last_sync == metadata.start { print!("\x1b[1m\x1b[91mUnsynced\x1b[0m"); + } else if Some(metadata.last_sync) == metadata.end { + print!("\x1b[1m\x1b[92mComplete\x1b[0m"); } else { print!( "\x1b[1mLast synced:\x1b[0m {}", - strftime_utc("%b %e, %Y %I:%M %p", metadata.last_sync.0 as i64).unwrap() + Local + .timestamp_opt(metadata.last_sync.0 as i64, 0) + .unwrap() + .format("%b %e, %Y %r") ); } - if current_time().0 - metadata.last_sync.0 > SECS_IN_WEEK { + if current_time().0 - metadata.last_sync.0 > SECS_IN_WEEK + && Some(metadata.last_sync) != metadata.end + { if name == "default" { print!(" - \x1b[33mRun 'startrnr sync' to update!\x1b[0m"); } else { @@ -300,6 +326,70 @@ State/province to track ratings for (leave empty for none): " None }; + // Interval + + print!( + " +\x1b[1mStart Date\x1b[0m +The rating system will process tournaments starting at this date. If only a year +is entered, the date will be the start of that year. + +Start date (year, m/y, or m/d/y): " + ); + let start = { + let string = read_string(); + if string.is_empty() { + Timestamp(1) + } else if string.chars().all(|c| c.is_ascii_digit() || c == '/') { + if let Some((y, m, d)) = match string.split('/').collect::>()[..] { + [] => None, + [y] => Some((y.parse().unwrap(), 1, 1)), + [m, y] => Some((y.parse().unwrap(), m.parse().unwrap(), 1)), + [m, d, y] => Some((y.parse().unwrap(), m.parse().unwrap(), d.parse().unwrap())), + _ => error("Input is not a date", 1), + } { + Timestamp(Utc.with_ymd_and_hms(y, m, d, 0, 1, 1).unwrap().timestamp() as u64) + } else { + Timestamp(1) + } + } else { + error("Input is not a date", 1); + } + }; + + print!( + " +\x1b[1mEnd Date\x1b[0m +The rating system will stop processing tournaments when it reaches this date. If +only a year is entered, the date will be the end of that year. + +End date (year, m/y, or m/d/y): " + ); + let end = { + let string = read_string(); + if string.is_empty() { + None + } else if string.chars().all(|c| c.is_ascii_digit() || c == '/') { + if let Some((y, m, d)) = match string.split('/').collect::>()[..] { + [] => None, + [y] => Some((y.parse().unwrap(), 12, 31)), + [m, y] => Some((y.parse().unwrap(), m.parse().unwrap(), 30)), + [m, d, y] => Some((y.parse().unwrap(), m.parse().unwrap(), d.parse().unwrap())), + _ => error("Input is not a date", 1), + } { + Some(Timestamp( + Utc.with_ymd_and_hms(y, m, d, 11, 59, 59) + .unwrap() + .timestamp() as u64, + )) + } else { + None + } + } else { + error("Input is not a date", 1); + } + }; + // Set Limit let mut set_limit = 0; @@ -426,7 +516,9 @@ Tau constant (default 0.4): " connection, &name, DatasetMetadata { - last_sync: Timestamp(1), + start, + end, + last_sync: start, game_id, game_name, game_slug, @@ -663,14 +755,22 @@ fn sync(connection: &Connection, auth: String, datasets: Vec, all: bool) let current_time = current_time(); for dataset in datasets { - let dataset_config = get_metadata(connection, &dataset) + let dataset_metadata = get_metadata(connection, &dataset) .expect("Error communicating with SQLite") .unwrap_or_else(|| error(&format!("Dataset {} does not exist!", dataset), 1)); - sync_dataset(connection, &dataset, dataset_config, current_time, &auth) + let before = dataset_metadata + .end + .map(|end| min(end, current_time)) + .unwrap_or(current_time); + + sync_dataset(connection, &dataset, dataset_metadata, before, &auth) .expect("Error communicating with SQLite"); - update_last_sync(connection, &dataset, current_time) - .expect("Error communicating with SQLite"); + update_last_sync(connection, &dataset, before).expect("Error communicating with SQLite"); } } + +fn ranking_create(connection: &Connection, dataset: Option) { + let dataset = dataset.unwrap_or_else(|| String::from("default")); +} diff --git a/src/sync.rs b/src/sync.rs index f5d8e1e..4d2db87 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -125,7 +125,7 @@ fn get_event_sets(event: EventId, auth: &str) -> Option> { fn get_tournament_events( metadata: &DatasetMetadata, - current_time: Timestamp, + before: Timestamp, auth: &str, ) -> Option> { println!("Accessing tournaments..."); @@ -135,7 +135,7 @@ fn get_tournament_events( let tour_response = run_query::( TournamentEventsVars { after_date: after, - before_date: current_time, + before_date: before, game_id: metadata.game_id, country: metadata.country.as_deref(), state: metadata.state.as_deref(), @@ -160,7 +160,7 @@ fn get_tournament_events( let next_response = run_query::( TournamentEventsVars { after_date: after, - before_date: current_time, + before_date: before, game_id: metadata.game_id, country: metadata.country.as_deref(), state: metadata.state.as_deref(), @@ -304,10 +304,10 @@ pub fn sync_dataset( connection: &Connection, dataset: &str, metadata: DatasetMetadata, - current_time: Timestamp, + before: Timestamp, auth: &str, ) -> sqlite::Result<()> { - let events = get_tournament_events(&metadata, current_time, auth) + let events = get_tournament_events(&metadata, before, auth) .unwrap_or_else(|| error("Could not access start.gg", 1)); connection.execute("BEGIN;")?;