diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 41ec0d0..d9c15a2 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -131,7 +131,7 @@ dependencies = [ "chrono", "dotenvy", "isahc", - "once_cell", + "lazy_static", "reqwest", "rocket", "scraper", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 8a65be9..47a5c74 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -9,12 +9,12 @@ edition = "2021" reqwest = { version = "0.11", features = ["json", "cookies"] } rocket = { version = "=0.5.0-rc.3" , features = ["json", "msgpack", "uuid"] } sqlx = { version = "0.7.1", features = [ "runtime-tokio", "tls-rustls", "mysql", "macros", "chrono" ] } -once_cell = "1.18.0" dotenvy = "0.15.7" serde = "1.0.188" chrono = "0.4.27" scraper = "0.17.1" isahc = { version = "1.7.2", features = ["cookies"] } urlencoding = "2.1.3" +lazy_static = "1.4.0" diff --git a/backend/src/online_classroom/session.rs b/backend/src/online_classroom/session.rs index fbd97d7..7d813dd 100644 --- a/backend/src/online_classroom/session.rs +++ b/backend/src/online_classroom/session.rs @@ -1,12 +1,21 @@ +use lazy_static::lazy_static; use std::time::{SystemTime, UNIX_EPOCH}; use isahc::{cookies::CookieJar, prelude::*, Request}; -use once_cell::sync::OnceCell; +use std::sync::RwLock; + +struct CookieHold { + pub jar: CookieJar, +} + + +lazy_static! { + /// Stores a client with a persistent cookie store + static ref SESSION_COOKIE: RwLock = RwLock::new(CookieHold { jar: CookieJar::new()}); + /// Stores the last time a request was made, in seconds since UNIX epoch + static ref SESSION_TIME: RwLock = RwLock::new(0); +} -/// Stores a client with a persistent cookie store -static SESSION_COOKIE: OnceCell = OnceCell::new(); -/// Stores the last time a request was made, in seconds since UNIX epoch -static SESSION_TIME: OnceCell = OnceCell::new(); /// Makes a request to the online classroom, and returns the html string pub async fn request(url: String) -> Result { @@ -15,9 +24,7 @@ pub async fn request(url: String) -> Result { ensure_session().await?; // Get the stored client - let jar = SESSION_COOKIE - .get() - .expect("SESSION_COOKIE was not set, even after calling ensure_session"); + let jar = SESSION_COOKIE.read().unwrap().jar.clone(); let uri = format!("{}{}", classroom_url, url); @@ -41,10 +48,7 @@ pub async fn request(url: String) -> Result { /// Makes sure that the session cookie is set, and that it is valid pub async fn ensure_session() -> Result<(), String> { - let last_usage_time = match SESSION_TIME.get() { - Some(time) => *time, - None => 0, - }; + let last_usage_time = SESSION_TIME.read().unwrap().clone(); // Get current time in seconds let current_time = SystemTime::now() @@ -57,9 +61,9 @@ pub async fn ensure_session() -> Result<(), String> { // Default PHP session timeout is 1440 seconds. Use a 1400 seconds timeout to be safe if time_passed > 1400 { login().await?; - if let Err(err) = SESSION_TIME.set(current_time) { - return Err(format!("Error setting session time: {:?}", err)); - } + + let mut time_ptr = SESSION_TIME.write().unwrap(); + *time_ptr = current_time; } Ok(()) @@ -86,10 +90,10 @@ async fn login() -> Result<(), String> { .send(); match response { - Ok(_) => match SESSION_COOKIE.set(jar.clone()) { - Ok(_) => Ok(()), - Err(error) => Err(format!("Error saving client: {:?}", error)), - }, + Ok(_) => { + SESSION_COOKIE.write().unwrap().jar = jar.clone(); + Ok(()) + } Err(error) => Err(format!("Error connecting to online classroom: {:?}", error)), } } diff --git a/backend/src/online_classroom/users.rs b/backend/src/online_classroom/users.rs index f0de860..b84845d 100644 --- a/backend/src/online_classroom/users.rs +++ b/backend/src/online_classroom/users.rs @@ -53,11 +53,19 @@ fn parse_users(file: &str) -> Result, String> { let fragment = Html::parse_document(file); - // TODO: If no users are found, the form is not rendered, and the html is different - // Handle this case let form_element = match fragment.select(&form_selector).next() { Some(el) => el, - None => return Err("Error selecting form#form_users_id: not found".into()), + // If the form is not found, it may mean that no users were found + None => { + let Ok(secondary_button) = Selector::parse("#img_plus_and_minus") else { + return Err("Error parsing secondary button selector".into()); + }; + + match fragment.select(&secondary_button).next() { + Some(_) => return Ok(Vec::new()), + None => return Err("Error parsing html: no form or empty form found.".into()), + } + } }; let mut result_vec = Vec::new();