Get rid of DB, parse into memory
This commit is contained in:
parent
12a3868a46
commit
1a6ea07ed9
5 changed files with 118 additions and 455 deletions
|
|
@ -15,7 +15,6 @@ path = "src/records.rs"
|
||||||
scraper = "0.23.1"
|
scraper = "0.23.1"
|
||||||
reqwest = "0.12.15"
|
reqwest = "0.12.15"
|
||||||
tokio = { version = "1.44.1", features = ["full"] }
|
tokio = { version = "1.44.1", features = ["full"] }
|
||||||
rusqlite = { version = "0.32.1", features = ["bundled", "backup"] }
|
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
once_cell = "1.20.2"
|
once_cell = "1.20.2"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
SELECT rq, brand, onlyName, year, configuration, time
|
|
||||||
FROM car_track_times t
|
|
||||||
JOIN cars c ON t.car_rid = c.rid
|
|
||||||
WHERE track_id = 'Mountain Twisty Road'
|
|
||||||
AND track_type = 'Wet'
|
|
||||||
AND rq <= 90
|
|
||||||
ORDER BY CAST(t.time AS DECIMAL) ASC
|
|
||||||
LIMIT 1;
|
|
||||||
48
bestCars.sql
48
bestCars.sql
|
|
@ -1,48 +0,0 @@
|
||||||
WITH RECURSIVE rq_range AS (
|
|
||||||
SELECT DISTINCT rq from cars
|
|
||||||
),
|
|
||||||
track_combinations AS (
|
|
||||||
SELECT DISTINCT track_id, track_type
|
|
||||||
FROM car_track_times
|
|
||||||
),
|
|
||||||
ranked_cars AS (
|
|
||||||
SELECT
|
|
||||||
rq_range.rq AS max_rq,
|
|
||||||
t.track_id,
|
|
||||||
t.track_type,
|
|
||||||
c.rq,
|
|
||||||
c.brand,
|
|
||||||
c.onlyName,
|
|
||||||
c.year,
|
|
||||||
t.configuration,
|
|
||||||
t.time,
|
|
||||||
ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY rq_range.rq, t.track_id, t.track_type
|
|
||||||
ORDER BY t.time ASC
|
|
||||||
) AS rank
|
|
||||||
FROM
|
|
||||||
rq_range
|
|
||||||
CROSS JOIN
|
|
||||||
track_combinations tc
|
|
||||||
JOIN
|
|
||||||
car_track_times t ON tc.track_id = t.track_id AND tc.track_type = t.track_type
|
|
||||||
JOIN
|
|
||||||
cars c ON t.car_rid = c.rid
|
|
||||||
WHERE
|
|
||||||
t.time > 0 -- Exclude invalid times
|
|
||||||
AND t.time IS NOT NULL
|
|
||||||
AND c.rq <= rq_range.rq
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
rq,
|
|
||||||
brand,
|
|
||||||
onlyName,
|
|
||||||
year,
|
|
||||||
Count(time)
|
|
||||||
FROM
|
|
||||||
ranked_cars
|
|
||||||
WHERE
|
|
||||||
rank = 1 -- Only the best car per combination
|
|
||||||
GROUP BY brand, onlyName, year
|
|
||||||
ORDER BY
|
|
||||||
rq, brand, onlyName, year;
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
WITH RECURSIVE rq_range AS (
|
|
||||||
SELECT DISTINCT rq from cars
|
|
||||||
),
|
|
||||||
track_combinations AS (
|
|
||||||
SELECT DISTINCT track_id, track_type
|
|
||||||
FROM car_track_times
|
|
||||||
),
|
|
||||||
ranked_cars AS (
|
|
||||||
SELECT
|
|
||||||
rq_range.rq AS max_rq,
|
|
||||||
t.track_id,
|
|
||||||
t.track_type,
|
|
||||||
c.rq,
|
|
||||||
c.brand,
|
|
||||||
c.onlyName,
|
|
||||||
c.year,
|
|
||||||
t.configuration,
|
|
||||||
t.time,
|
|
||||||
ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY rq_range.rq, t.track_id, t.track_type
|
|
||||||
ORDER BY t.time ASC
|
|
||||||
) AS rank
|
|
||||||
FROM
|
|
||||||
rq_range
|
|
||||||
CROSS JOIN
|
|
||||||
track_combinations tc
|
|
||||||
JOIN
|
|
||||||
car_track_times t ON tc.track_id = t.track_id AND tc.track_type = t.track_type
|
|
||||||
JOIN
|
|
||||||
cars c ON t.car_rid = c.rid
|
|
||||||
WHERE
|
|
||||||
t.time > 0 -- Exclude invalid times
|
|
||||||
AND t.time IS NOT NULL
|
|
||||||
AND c.rq <= rq_range.rq
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
max_rq,
|
|
||||||
track_id,
|
|
||||||
track_type,
|
|
||||||
rq,
|
|
||||||
brand,
|
|
||||||
onlyName,
|
|
||||||
year,
|
|
||||||
configuration,
|
|
||||||
time AS best_time
|
|
||||||
FROM
|
|
||||||
ranked_cars
|
|
||||||
WHERE
|
|
||||||
rank = 1 -- Only the best car per combination
|
|
||||||
ORDER BY
|
|
||||||
max_rq, track_id, track_type;
|
|
||||||
455
src/records.rs
455
src/records.rs
|
|
@ -1,14 +1,13 @@
|
||||||
use std::collections::HashMap;
|
use futures::future::join_all;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use reqwest::get;
|
use reqwest::get;
|
||||||
use rusqlite::backup::Backup;
|
|
||||||
use rusqlite::{params, CachedStatement, Connection};
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use futures::future::{join_all};
|
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
|
use tokio::task;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Car {
|
struct Car {
|
||||||
|
|
@ -17,20 +16,20 @@ struct Car {
|
||||||
only_name: String,
|
only_name: String,
|
||||||
year: u16,
|
year: u16,
|
||||||
rq: u8,
|
rq: u8,
|
||||||
drive: String,
|
|
||||||
tyres: String,
|
|
||||||
top_speed: u32,
|
|
||||||
acel: f64,
|
|
||||||
hand: u32,
|
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct BestCarRecord {
|
struct BestCarRecord {
|
||||||
car_rid: String,
|
car_rid: String,
|
||||||
configuration: String,
|
brand: String,
|
||||||
|
only_name: String,
|
||||||
|
year: u16,
|
||||||
|
rq: u8,
|
||||||
|
config: String,
|
||||||
time: f64,
|
time: f64,
|
||||||
}
|
}
|
||||||
type TripletKey = (String, u8, String); // (Track ID, RQ, Surface)
|
type TripletKey = (String, u8, String); // (Track ID, RQ, Surface)
|
||||||
static BEST_CARS: Lazy<Mutex<HashMap<TripletKey, BestCarRecord>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static BEST_CARS: Lazy<Mutex<HashMap<TripletKey, BestCarRecord>>> =
|
||||||
|
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
|
@ -43,62 +42,24 @@ async fn main() {
|
||||||
Some(argument) => {
|
Some(argument) => {
|
||||||
let command = argument.as_str();
|
let command = argument.as_str();
|
||||||
match command {
|
match command {
|
||||||
"init" => {
|
|
||||||
let mut connection =
|
|
||||||
Connection::open_in_memory().expect("Issue opening in-memory DB!");
|
|
||||||
|
|
||||||
init_db(&mut connection);
|
|
||||||
parse_cars(&mut connection);
|
|
||||||
|
|
||||||
let _ = fs::remove_file("top_drives.db");
|
|
||||||
let mut file =
|
|
||||||
Connection::open("top_drives.db").expect("Issue opening DB file!");
|
|
||||||
let backup =
|
|
||||||
Backup::new(&connection, &mut file).expect("Could not create backup!");
|
|
||||||
backup.step(-1).expect("Could not create backup!"); // -1 copies all pages at once
|
|
||||||
drop(backup);
|
|
||||||
connection.close().unwrap();
|
|
||||||
file.close().unwrap();
|
|
||||||
}
|
|
||||||
"fetch" => {
|
"fetch" => {
|
||||||
get_cars_file().await;
|
get_cars_file().await;
|
||||||
get_tracks_file().await;
|
get_tracks_file().await;
|
||||||
fetch_times().await;
|
fetch_times().await;
|
||||||
}
|
}
|
||||||
"parse" => {
|
|
||||||
let mut file =
|
|
||||||
Connection::open("top_drives.db").expect("Issue opening DB file!");
|
|
||||||
let mut connection =
|
|
||||||
Connection::open_in_memory().expect("Issue opening in-memory DB!");
|
|
||||||
|
|
||||||
let backup =
|
|
||||||
Backup::new(&file, &mut connection).expect("Could not create backup!");
|
|
||||||
backup.step(-1).expect("Could not create backup!"); // -1 copies all pages at once
|
|
||||||
drop(backup);
|
|
||||||
file.close().unwrap();
|
|
||||||
|
|
||||||
parse_all_times(&mut connection).await;
|
|
||||||
|
|
||||||
let _ = fs::remove_file("top_drives.db");
|
|
||||||
file = Connection::open("top_drives.db").expect("Issue opening DB file!");
|
|
||||||
let backup =
|
|
||||||
Backup::new(&connection, &mut file).expect("Could not create backup!");
|
|
||||||
backup.step(-1).expect("Could not create backup!"); // -1 copies all pages at once
|
|
||||||
drop(backup);
|
|
||||||
|
|
||||||
connection.close().unwrap();
|
|
||||||
file.close().unwrap();
|
|
||||||
}
|
|
||||||
"best" => {
|
"best" => {
|
||||||
list_best_cars().await;
|
parse_all_times().await;
|
||||||
|
print_best_cars();
|
||||||
}
|
}
|
||||||
"details" => {
|
"details" => {
|
||||||
list_details().await;
|
parse_all_times().await;
|
||||||
|
print_best_times();
|
||||||
}
|
}
|
||||||
"track" => {
|
"track" => {
|
||||||
if args.get(4).is_none() {
|
if args.get(4).is_none() {
|
||||||
println!("Track name, condition and RQ limit required.");
|
println!("Track name, condition and RQ limit required.");
|
||||||
} else {
|
} else {
|
||||||
|
parse_all_times().await;
|
||||||
let track = args.get(2).unwrap();
|
let track = args.get(2).unwrap();
|
||||||
let condition = args.get(3).unwrap();
|
let condition = args.get(3).unwrap();
|
||||||
let rq = args.get(4).unwrap();
|
let rq = args.get(4).unwrap();
|
||||||
|
|
@ -112,182 +73,72 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn print_best_times() {
|
||||||
|
let best_cars = BEST_CARS.lock().unwrap();
|
||||||
|
|
||||||
fn print_best_car(track: &String, condition: &String, rq: &String) {
|
// Collect keys and sort them by RQ and Track ID
|
||||||
let connection = Connection::open("top_drives.db").expect("Issue opening DB file!");
|
let mut sorted_keys: Vec<_> = best_cars.keys().collect();
|
||||||
|
sorted_keys.sort_by(|a, b| a.1.cmp(&b.1).then(a.0.cmp(&b.0))); // Sort by RQ, then Track ID
|
||||||
|
|
||||||
let mut stmt = connection
|
for key in sorted_keys {
|
||||||
.prepare(
|
let best_car = best_cars.get(key).unwrap();
|
||||||
r#"
|
|
||||||
SELECT rq, brand, onlyName, year, configuration, time
|
|
||||||
FROM car_track_times t
|
|
||||||
JOIN cars c ON t.car_rid = c.rid
|
|
||||||
WHERE track_id = ?
|
|
||||||
AND track_type = ?
|
|
||||||
AND rq <= ?
|
|
||||||
ORDER BY CAST(t.time AS DECIMAL) ASC
|
|
||||||
LIMIT 1;"#
|
|
||||||
).expect("Could not prepare query!");
|
|
||||||
|
|
||||||
let mut rows = stmt.query(params!(track, condition, rq)).expect("Could not execute query!");
|
|
||||||
while let Ok(Some(row)) = rows.next() {
|
|
||||||
let rq :u8 = row.get(0).expect("Cannot extract RQ");
|
|
||||||
let brand: String = row.get(1).expect("Cannot extract RQ");
|
|
||||||
let only_name: String = row.get(2).expect("Cannot extract RQ");
|
|
||||||
let year: u16 = row.get(3).expect("Cannot extract RQ");
|
|
||||||
let configuration: String = row.get(4).expect("Cannot extract RQ");
|
|
||||||
let time: f32 = row.get(5).expect("Cannot extract RQ");
|
|
||||||
println!("{} {} {} {} ({}): {}", rq, brand, only_name, year, configuration, time );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn list_details() {
|
|
||||||
let file = Connection::open("top_drives.db").expect("Issue opening DB file!");
|
|
||||||
let mut connection = Connection::open_in_memory().expect("Issue opening in-memory DB!");
|
|
||||||
|
|
||||||
let backup = Backup::new(&file, &mut connection).expect("Could not create backup!");
|
|
||||||
backup.step(-1).expect("Could not create backup!"); // -1 copies all pages at once
|
|
||||||
drop(backup);
|
|
||||||
drop(file);
|
|
||||||
|
|
||||||
let stmt = connection.prepare(
|
|
||||||
r#"
|
|
||||||
WITH RECURSIVE rq_range AS (
|
|
||||||
SELECT DISTINCT rq from cars
|
|
||||||
),
|
|
||||||
track_combinations AS (
|
|
||||||
SELECT DISTINCT track_id, track_type
|
|
||||||
FROM car_track_times
|
|
||||||
),
|
|
||||||
ranked_cars AS (
|
|
||||||
SELECT
|
|
||||||
rq_range.rq AS max_rq,
|
|
||||||
t.track_id,
|
|
||||||
t.track_type,
|
|
||||||
c.rq,
|
|
||||||
c.brand,
|
|
||||||
c.onlyName,
|
|
||||||
c.year,
|
|
||||||
t.configuration,
|
|
||||||
t.time,
|
|
||||||
ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY rq_range.rq, t.track_id, t.track_type
|
|
||||||
ORDER BY t.time ASC
|
|
||||||
) AS rank
|
|
||||||
FROM rq_range
|
|
||||||
CROSS JOIN track_combinations tc
|
|
||||||
JOIN car_track_times t ON tc.track_id = t.track_id AND tc.track_type = t.track_type
|
|
||||||
JOIN cars c ON t.car_rid = c.rid
|
|
||||||
WHERE t.time > 0 AND t.time IS NOT NULL AND c.rq <= rq_range.rq
|
|
||||||
)
|
|
||||||
SELECT max_rq, track_id, track_type, rq, brand, onlyName, year, configuration, time AS best_time
|
|
||||||
FROM ranked_cars
|
|
||||||
WHERE rank = 1
|
|
||||||
ORDER BY max_rq, track_id, track_type
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut result = stmt.expect("Query failed!");
|
|
||||||
let rows = result
|
|
||||||
.query_map([], |row| {
|
|
||||||
Ok((
|
|
||||||
row.get::<_, i32>(0)?,
|
|
||||||
row.get::<_, String>(1)?,
|
|
||||||
row.get::<_, String>(2)?,
|
|
||||||
row.get::<_, i32>(3)?,
|
|
||||||
row.get::<_, String>(4)?,
|
|
||||||
row.get::<_, String>(5)?,
|
|
||||||
row.get::<_, i32>(6)?,
|
|
||||||
row.get::<_, String>(7)?,
|
|
||||||
row.get::<_, f64>(8)?,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.expect("Result retrieval failed!");
|
|
||||||
|
|
||||||
for row in rows {
|
|
||||||
let (max_rq, track_id, track_type, rq, brand, only_name, year, configuration, best_time) =
|
|
||||||
row.expect("Row retrieval failed!");
|
|
||||||
println!(
|
println!(
|
||||||
"{},{},{},{},{},{},{},{},{}",
|
"{},{},{},{},{},{},{},{},{}",
|
||||||
max_rq, track_id, track_type, rq, brand, only_name, year, configuration, best_time
|
key.1,
|
||||||
|
key.0,
|
||||||
|
key.2,
|
||||||
|
best_car.rq,
|
||||||
|
best_car.brand,
|
||||||
|
best_car.only_name,
|
||||||
|
best_car.year,
|
||||||
|
best_car.config,
|
||||||
|
best_car.time
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
fn print_best_cars() {
|
||||||
|
let best_cars = BEST_CARS.lock().unwrap();
|
||||||
|
let mut car_count: HashMap<String, (usize, u8, BestCarRecord)> = HashMap::new();
|
||||||
|
|
||||||
drop(result);
|
// Count occurrences and store RQ
|
||||||
connection.close().unwrap();
|
for ((_, rq, _), best_car) in best_cars.iter() {
|
||||||
|
let entry = car_count
|
||||||
|
.entry(best_car.car_rid.clone())
|
||||||
|
.or_insert((0, *rq, best_car.clone()));
|
||||||
|
entry.0 += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_best_cars() {
|
// Convert to vector and sort by RQ first, then frequency
|
||||||
let file = Connection::open("top_drives.db").expect("Issue opening DB file!");
|
let mut car_count_vec: Vec<_> = car_count.iter().collect();
|
||||||
let mut connection = Connection::open_in_memory().expect("Issue opening in-memory DB!");
|
car_count_vec.sort_by(|a, b| a.1 .1.cmp(&b.1 .1).then(a.0.cmp(b.0)));
|
||||||
|
|
||||||
let backup = Backup::new(&file, &mut connection).expect("Could not create backup!");
|
for (_car_rid, (count, rq, car_record)) in car_count_vec {
|
||||||
backup.step(-1).expect("Could not create backup!"); // -1 copies all pages at once
|
println!(
|
||||||
drop(backup);
|
"{},{},{},{},{}",
|
||||||
drop(file);
|
rq, car_record.brand, car_record.only_name, car_record.year, count
|
||||||
|
|
||||||
let stmt = connection.prepare(
|
|
||||||
r#"
|
|
||||||
WITH RECURSIVE rq_range AS (
|
|
||||||
SELECT DISTINCT rq from cars
|
|
||||||
),
|
|
||||||
track_combinations AS (
|
|
||||||
SELECT DISTINCT track_id, track_type
|
|
||||||
FROM car_track_times
|
|
||||||
),
|
|
||||||
ranked_cars AS (
|
|
||||||
SELECT
|
|
||||||
rq_range.rq AS max_rq,
|
|
||||||
t.track_id,
|
|
||||||
t.track_type,
|
|
||||||
c.rq,
|
|
||||||
c.brand,
|
|
||||||
c.onlyName,
|
|
||||||
c.year,
|
|
||||||
t.configuration,
|
|
||||||
t.time,
|
|
||||||
ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY rq_range.rq, t.track_id, t.track_type
|
|
||||||
ORDER BY t.time ASC
|
|
||||||
) AS rank
|
|
||||||
FROM rq_range
|
|
||||||
CROSS JOIN track_combinations tc
|
|
||||||
JOIN car_track_times t ON tc.track_id = t.track_id AND tc.track_type = t.track_type
|
|
||||||
JOIN cars c ON t.car_rid = c.rid
|
|
||||||
WHERE t.time > 0 AND t.time IS NOT NULL AND c.rq <= rq_range.rq
|
|
||||||
)
|
|
||||||
SELECT rq, brand, onlyName, year, COUNT(time) AS occurrences
|
|
||||||
FROM ranked_cars
|
|
||||||
WHERE rank = 1
|
|
||||||
GROUP BY brand, onlyName, year
|
|
||||||
ORDER BY rq, brand, onlyName, year
|
|
||||||
"#,
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn print_best_car(track: &String, condition: &String, rq: &String) {
|
||||||
|
let best_cars = BEST_CARS.lock().unwrap();
|
||||||
|
let key = (track.clone(), rq.parse::<u8>().unwrap_or(115), condition.clone());
|
||||||
|
|
||||||
let mut result = stmt.expect("Query failed!");
|
if let Some(best_car) = best_cars.get(&key) {
|
||||||
let rows = result
|
println!(
|
||||||
.query_map([], |row| {
|
"Best car for Track: {}, Condition: {}, RQ: {} -> {} {} ({}) with time {:.2}",
|
||||||
Ok((
|
track, condition, rq,
|
||||||
row.get::<_, i32>(0).expect("Could not assign value"),
|
best_car.brand, best_car.only_name, best_car.year, best_car.time
|
||||||
row.get::<_, String>(1).expect("Could not assign value"),
|
);
|
||||||
row.get::<_, String>(2).expect("Could not assign value"),
|
} else {
|
||||||
row.get::<_, i32>(3).expect("Could not assign value"),
|
println!(
|
||||||
row.get::<_, i32>(4).expect("Could not assign value"),
|
"No best car found for Track: {}, Condition: {}, RQ: {}",
|
||||||
))
|
track, condition, rq
|
||||||
})
|
);
|
||||||
.expect("Result retrieval failed!");
|
}
|
||||||
|
|
||||||
for row in rows {
|
|
||||||
let (rq, brand, only_name, year, occurrences) = row.expect("Row retrieval failed!");
|
|
||||||
println!("{},{},{},{},{}", rq, brand, only_name, year, occurrences);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(result);
|
async fn parse_car_times(car: Car) {
|
||||||
connection.close().unwrap();
|
let file_name = car.rid.to_owned() + ".json";
|
||||||
}
|
|
||||||
|
|
||||||
async fn parse_car_times(rid: String, statement: &mut CachedStatement<'_>) {
|
|
||||||
let file_name = rid.to_owned() + ".json";
|
|
||||||
let local_file_path = "times/".to_owned() + file_name.as_str();
|
let local_file_path = "times/".to_owned() + file_name.as_str();
|
||||||
let file_content = fs::read_to_string(local_file_path);
|
let file_content = fs::read_to_string(local_file_path);
|
||||||
|
|
||||||
|
|
@ -317,86 +168,53 @@ async fn parse_car_times(rid: String, statement: &mut CachedStatement<'_>) {
|
||||||
|
|
||||||
// Insert into the database
|
// Insert into the database
|
||||||
if time > 1.2 && no_down_votes {
|
if time > 1.2 && no_down_votes {
|
||||||
let _ = statement.execute([
|
update_best_car(
|
||||||
rid.as_str(),
|
|
||||||
translate_track_name(cleaned_track_id),
|
translate_track_name(cleaned_track_id),
|
||||||
|
car.rq,
|
||||||
translate_track_type(track_type),
|
translate_track_type(track_type),
|
||||||
time.to_string().as_str(),
|
BestCarRecord {
|
||||||
config,
|
car_rid: car.rid.clone(),
|
||||||
]);
|
brand: car.brand.clone(),
|
||||||
//TODO: update_best_car(cleaned_track_id, translate_track_type(track_type), None)
|
only_name: car.only_name.clone(),
|
||||||
|
year: car.year,
|
||||||
|
rq: car.rq,
|
||||||
|
config: config.clone(),
|
||||||
|
time,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!("Track times imported for {}", rid);
|
|
||||||
}
|
}
|
||||||
fn update_best_car(track_id: &str, rq: u8, surface: &str, new_record: BestCarRecord) {
|
fn update_best_car(track_id: &str, car_rq: u8, surface: &str, new_record: BestCarRecord) {
|
||||||
let key = (track_id.to_string(), rq, surface.to_string());
|
|
||||||
let mut best_cars = BEST_CARS.lock().unwrap();
|
let mut best_cars = BEST_CARS.lock().unwrap();
|
||||||
|
|
||||||
|
for rq in car_rq..116 {
|
||||||
|
let key = (track_id.to_string(), rq, surface.to_string());
|
||||||
|
|
||||||
if let Some(existing) = best_cars.get(&key) {
|
if let Some(existing) = best_cars.get(&key) {
|
||||||
if new_record.time < existing.time {
|
if new_record.time < existing.time {
|
||||||
best_cars.insert(key, new_record);
|
best_cars.insert(key, new_record.clone());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
best_cars.insert(key, new_record);
|
best_cars.insert(key, new_record.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn parse_all_times(connection: &mut Connection) {
|
}
|
||||||
let mut statement = connection.prepare_cached("INSERT INTO car_track_times (car_rid, track_id, track_type, time, configuration) VALUES (?1, ?2, ?3, ?4, ?5)").expect("Could not prepare statement!");
|
async fn parse_all_times() {
|
||||||
|
let mut handles = vec![];
|
||||||
|
|
||||||
for car in get_cars_from_db() {
|
for car in get_car_list() {
|
||||||
parse_car_times(car, &mut statement).await;
|
let handle = task::spawn(async move {
|
||||||
|
parse_car_times(car).await;
|
||||||
|
});
|
||||||
|
handles.push(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.flush_prepared_statement_cache();
|
join_all(handles).await;
|
||||||
let _ = connection.execute("DELETE FROM car_track_times WHERE time < 1.2;", ());
|
|
||||||
}
|
|
||||||
fn parse_cars(connection: &mut Connection) {
|
|
||||||
println!("Parsing cars into DB...");
|
|
||||||
let cars_file = "cars_repo.json";
|
|
||||||
let cars_data: Vec<Value> =
|
|
||||||
serde_json::from_str(&fs::read_to_string(cars_file).unwrap()).unwrap();
|
|
||||||
|
|
||||||
for car in cars_data {
|
|
||||||
if let Err(e) = connection.execute("INSERT OR IGNORE INTO cars
|
|
||||||
(class, rq, onlyName, brand, year, abs, tcs, clearance, country, topSpeed, acel, hand,
|
|
||||||
drive, tyres, mra, weight, name, rid, prize, bodyTypes, fuel, seats, engine, tags, guid, brake)
|
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26);",
|
|
||||||
params![
|
|
||||||
car["class"].as_str().unwrap(),
|
|
||||||
car["rq"].as_u64().unwrap(),
|
|
||||||
car["onlyName"].as_str().unwrap(),
|
|
||||||
car["brand"].as_str().unwrap(),
|
|
||||||
car["year"].as_u64().unwrap(),
|
|
||||||
car["abs"].as_bool().unwrap(),
|
|
||||||
car["tcs"].as_bool().unwrap(),
|
|
||||||
car["clearance"].as_str().unwrap(),
|
|
||||||
car["country"].as_str().unwrap(),
|
|
||||||
car["topSpeed"].as_u64().unwrap(),
|
|
||||||
car["acel"].as_f64().unwrap_or_default(),
|
|
||||||
car["hand"].as_u64().unwrap(),
|
|
||||||
car["drive"].as_str().unwrap(),
|
|
||||||
car["tyres"].as_str().unwrap(),
|
|
||||||
car["mra"].as_f64().unwrap_or_default(),
|
|
||||||
car["weight"].as_u64().unwrap(),
|
|
||||||
car["name"].as_str().unwrap(),
|
|
||||||
car["rid"].as_str().unwrap(),
|
|
||||||
car["prize"].as_bool().unwrap(),
|
|
||||||
Some(serde_json::to_string(&car["bodyTypes"]).unwrap().as_str()).unwrap_or_default(),
|
|
||||||
car["fuel"].as_str().unwrap(),
|
|
||||||
car["seats"].as_u64().unwrap_or_default(),
|
|
||||||
car["engine"].as_str().unwrap(),
|
|
||||||
Some(serde_json::to_string(&car["tags"]).unwrap().as_str()).unwrap_or_default(),
|
|
||||||
car["guid"].as_str().unwrap(),
|
|
||||||
car["brake"].as_str().unwrap(),
|
|
||||||
]) {
|
|
||||||
eprintln!("Insertion error: {}", e);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn split_prefix_postfix<'a>(input: &'a str, delimiter: &'a str) -> Option<(&'a str, &'a str)> {
|
fn split_prefix_postfix<'a>(input: &'a str, delimiter: &'a str) -> Option<(&'a str, &'a str)> {
|
||||||
input.find(delimiter).map(|index| {
|
input.find(delimiter).map(|index| {
|
||||||
|
|
@ -406,13 +224,13 @@ fn split_prefix_postfix<'a>(input: &'a str, delimiter: &'a str) -> Option<(&'a s
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
async fn fetch_times() {
|
async fn fetch_times() {
|
||||||
let semaphore = std::sync::Arc::new(Semaphore::new(20)); // Adjust the number of concurrent requests
|
let semaphore = std::sync::Arc::new(Semaphore::new(20));
|
||||||
let mut handles = vec![];
|
let mut handles = vec![];
|
||||||
|
|
||||||
for car in get_cars_from_db() {
|
for car in get_car_list() {
|
||||||
let permit = semaphore.clone().acquire_owned().await.unwrap();
|
let permit = semaphore.clone().acquire_owned().await.unwrap();
|
||||||
handles.push(tokio::spawn(async move {
|
handles.push(tokio::spawn(async move {
|
||||||
fetch_car(car).await;
|
fetch_car(car.rid).await;
|
||||||
drop(permit);
|
drop(permit);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -426,80 +244,34 @@ async fn fetch_car(rid: String) {
|
||||||
println!("Fetching {}.", url);
|
println!("Fetching {}.", url);
|
||||||
get_file(url.as_str(), local_file_path.as_str()).await;
|
get_file(url.as_str(), local_file_path.as_str()).await;
|
||||||
}
|
}
|
||||||
fn get_cars_from_db() -> Vec<String> {
|
fn get_car_list() -> Vec<Car> {
|
||||||
let mut car_list = Vec::new();
|
let mut car_list: Vec<Car> = Vec::new();
|
||||||
|
|
||||||
let connection = Connection::open("top_drives.db").expect("Issue opening DB file!");
|
let cars_file = "cars_repo.json";
|
||||||
|
let cars_data: Vec<Value> =
|
||||||
|
serde_json::from_str(&fs::read_to_string(cars_file).unwrap()).unwrap();
|
||||||
|
|
||||||
let mut stmt = connection
|
for car in cars_data {
|
||||||
.prepare("SELECT rid FROM cars")
|
let new_car = Car {
|
||||||
.expect("Could not prepare query!");
|
rid: String::from(car["rid"].as_str().unwrap_or("No RID")),
|
||||||
let mut rows = stmt.query([]).expect("Could not execute query!");
|
brand: String::from(car["brand"].as_str().unwrap_or("No Brand")),
|
||||||
|
only_name: String::from(car["onlyName"].as_str().unwrap_or("No Name")),
|
||||||
|
year: car["year"].as_u64().unwrap_or(1900) as u16,
|
||||||
|
rq: car["rq"].as_u64().unwrap_or(200) as u8,
|
||||||
|
};
|
||||||
|
|
||||||
while let Ok(Some(row)) = rows.next() {
|
car_list.push(new_car);
|
||||||
car_list.push(row.get::<usize, String>(0).expect("Could not get column!"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
car_list
|
car_list
|
||||||
}
|
}
|
||||||
fn init_db(connection: &mut Connection) {
|
|
||||||
println!("Initializing DB...");
|
|
||||||
|
|
||||||
let _ = connection.execute(
|
|
||||||
"CREATE TABLE IF NOT EXISTS cars (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
class TEXT,
|
|
||||||
rq INTEGER,
|
|
||||||
onlyName TEXT,
|
|
||||||
brand TEXT,
|
|
||||||
year INTEGER,
|
|
||||||
abs BOOLEAN,
|
|
||||||
tcs BOOLEAN,
|
|
||||||
clearance TEXT,
|
|
||||||
country TEXT,
|
|
||||||
topSpeed INTEGER,
|
|
||||||
acel REAL,
|
|
||||||
hand INTEGER,
|
|
||||||
drive TEXT,
|
|
||||||
tyres TEXT,
|
|
||||||
mra REAL,
|
|
||||||
weight INTEGER,
|
|
||||||
name TEXT,
|
|
||||||
rid TEXT UNIQUE,
|
|
||||||
prize BOOLEAN,
|
|
||||||
bodyTypes TEXT,
|
|
||||||
fuel TEXT,
|
|
||||||
seats TEXT,
|
|
||||||
engine TEXT,
|
|
||||||
tags TEXT,
|
|
||||||
guid TEXT UNIQUE,
|
|
||||||
brake TEXT,
|
|
||||||
owned BOOLEAN
|
|
||||||
);",
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = connection.execute(
|
|
||||||
"CREATE TABLE IF NOT EXISTS car_track_times (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
car_rid TEXT,
|
|
||||||
configuration TEXT,
|
|
||||||
track_id TEXT,
|
|
||||||
track_type INTEGER,
|
|
||||||
time DECIMAL,
|
|
||||||
FOREIGN KEY (car_rid) REFERENCES cars(rid),
|
|
||||||
UNIQUE(car_rid, track_id, track_type, configuration)
|
|
||||||
);",
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
async fn get_tracks_file() {
|
async fn get_tracks_file() {
|
||||||
println!("Fetching tracks file...");
|
println!("Fetching tracks file...");
|
||||||
get_file("https://github.com/TiagoXavi/topdrives-records/raw/refs/heads/main/src/database/tracks_repo.json","tracks_repo.json").await;
|
get_file("https://github.com/TiagoXavi/topdrives-records/raw/refs/heads/main/src/database/tracks_repo.json","tracks_repo.json").await;
|
||||||
}
|
}
|
||||||
async fn get_cars_file() {
|
async fn get_cars_file() {
|
||||||
println!("Fetching cars file...");
|
println!("Fetching cars file...");
|
||||||
get_file("https://github.com/TiagoXavi/topdrives-records/raw/refs/heads/main/src/database/cars_final_PL25-fixed.json", "cars_repo.json").await;
|
get_file("https://github.com/TiagoXavi/topdrives-records/raw/refs/heads/main/src/database/cars_final.json", "cars_repo.json").await;
|
||||||
}
|
}
|
||||||
async fn get_file(file_url: &str, local_file_path: &str) {
|
async fn get_file(file_url: &str, local_file_path: &str) {
|
||||||
let _ = fs::remove_file(local_file_path);
|
let _ = fs::remove_file(local_file_path);
|
||||||
|
|
@ -627,7 +399,6 @@ fn translate_track_name(track_code: &str) -> &str {
|
||||||
"miBeachDrag" => "Miami Beach Drag",
|
"miBeachDrag" => "Miami Beach Drag",
|
||||||
"mnGforce" => "Monaco G-Force Test",
|
"mnGforce" => "Monaco G-Force Test",
|
||||||
"mnHairpin" => "Monaco Hairpin",
|
"mnHairpin" => "Monaco Hairpin",
|
||||||
// "mnNarrowStreets" => "Monaco Narrow Streets",
|
|
||||||
"mnCityNarrow" => "Monaco Narrow Streets",
|
"mnCityNarrow" => "Monaco Narrow Streets",
|
||||||
"mnCity" => "Monaco Streets",
|
"mnCity" => "Monaco Streets",
|
||||||
"mnCityLong" => "Monaco Streets Long",
|
"mnCityLong" => "Monaco Streets Long",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue