diff --git a/src/apps/binary_upload.rs b/src/apps/binary_upload.rs index 34b248b..b478376 100644 --- a/src/apps/binary_upload.rs +++ b/src/apps/binary_upload.rs @@ -1,13 +1,12 @@ +use std::borrow::BorrowMut; + use crate::helpers::{ - refresh, - submission::{Submission, SubmissionPromise, SubmissionResult}, + fetchers::Requestor, + submission::{Submission, SubmissionResult}, Challenges, Languages, }; -use gloo_net::http; -use poll_promise::Promise; use std::future::Future; use std::sync::mpsc::{channel, Receiver, Sender}; -use web_sys::RequestCredentials; struct Binary { filename: String, @@ -17,8 +16,6 @@ struct Binary { #[derive(serde::Deserialize, serde::Serialize)] #[serde(default)] pub struct BinaryUpload { - #[serde(skip)] - promise: SubmissionPromise, #[serde(skip)] last_result: SubmissionResult, url: String, @@ -27,15 +24,12 @@ pub struct BinaryUpload { #[serde(skip)] binary_channel: (Sender, Receiver), #[serde(skip)] - submit: bool, - #[serde(skip)] - token_refresh_promise: refresh::RefreshPromise, + submitter: Option, } impl Default for BinaryUpload { fn default() -> Self { Self { - promise: Default::default(), url: option_env!("BACKEND_URL") .unwrap_or("http://123.4.5.6:3000/") .to_string(), @@ -44,59 +38,17 @@ impl Default for BinaryUpload { ..Default::default() }, binary_channel: channel(), - submit: false, - token_refresh_promise: None, + submitter: None, last_result: SubmissionResult::NotStarted, } } } impl BinaryUpload { - fn submit(&mut self, ctx: &egui::Context) { - if !self.submit { - return; - } - self.submit = false; + fn submit(&mut self) { let submission = self.run.clone(); - - let url = format!("{}api/game/binary", self.url); - log::debug!("Sending to {}", url); - let ctx = ctx.clone(); - - let promise = Promise::spawn_local(async move { - let formdata = submission.to_formdata(); - - let response = http::Request::post(&url) - .credentials(RequestCredentials::Include) - .body(formdata) - .unwrap() - .send() - .await - .unwrap(); - - let result: SubmissionResult = match response.status() { - 200 => response.json().await.unwrap(), - 401 => { - let text = response.text().await; - let text = text.map(|text| text.to_owned()); - let text = match text { - Ok(text) => text, - Err(e) => e.to_string(), - }; - log::warn!("Auth Error: {:?}", text); - SubmissionResult::NotAuthorized - } - _ => { - return Err(format!("Failed to submit code: {:?}", response)); - } - }; - - ctx.request_repaint(); // wake up UI thread - log::info!("Result: {:?}", result); - Ok(result) - }); - - self.promise = Some(promise); + let url = format!("{}api/game/submit", self.url); + self.submitter = submission.sender(&url); } } @@ -116,27 +68,19 @@ impl super::App for BinaryUpload { self.run.filename = f.filename; self.run.binary = Some(f.bytes); } - self.submit(ctx); - match refresh::check_refresh_promise(&mut self.token_refresh_promise) { - refresh::RefreshStatus::InProgress => {} - refresh::RefreshStatus::Success => { - self.submit = true; - } - refresh::RefreshStatus::Failed(_) => {} - _ => (), - } - let submission = Submission::check_submit_promise(&mut self.promise); + let submission = Submission::check_sender(&mut self.submitter); match submission { SubmissionResult::NotStarted => {} - SubmissionResult::NotAuthorized => { - self.token_refresh_promise = refresh::submit_refresh(); - self.last_result = submission; - } _ => { self.last_result = submission; } } + if let Some(fetcher) = self.submitter.borrow_mut() { + if fetcher.refresh_context() { + ctx.request_repaint(); + } + } } } @@ -191,7 +135,7 @@ impl super::View for BinaryUpload { if ui.button("Submit").clicked() { match self.run.validate() { Ok(_) => { - self.submit = true; + self.submit(); } Err(e) => { self.last_result = SubmissionResult::Failure { message: e }; diff --git a/src/apps/challenge_info.rs b/src/apps/challenge_info.rs index 587a70b..1e37994 100644 --- a/src/apps/challenge_info.rs +++ b/src/apps/challenge_info.rs @@ -1,4 +1,4 @@ -use crate::helpers::{fetchers::Getter, Challenges}; +use crate::helpers::{fetchers::Requestor, Challenges}; use egui_commonmark::*; use std::borrow::BorrowMut; @@ -15,7 +15,7 @@ pub struct ChallengeInfoApp { #[serde(skip)] active_challenge: Challenges, #[serde(skip)] - info_fetcher: Option, + info_fetcher: Option, instructions: String, } diff --git a/src/apps/code_editor.rs b/src/apps/code_editor.rs index dd71dc4..4653cf2 100644 --- a/src/apps/code_editor.rs +++ b/src/apps/code_editor.rs @@ -1,17 +1,14 @@ +use std::borrow::BorrowMut; + use crate::helpers::{ - refresh, - submission::{Submission, SubmissionPromise, SubmissionResult}, + fetchers::Requestor, + submission::{Submission, SubmissionResult}, Challenges, Languages, }; -use gloo_net::http; -use poll_promise::Promise; -use web_sys::RequestCredentials; #[derive(serde::Deserialize, serde::Serialize)] #[serde(default)] pub struct CodeEditor { - #[serde(skip)] - promise: SubmissionPromise, #[serde(skip)] url: String, #[serde(skip)] @@ -19,11 +16,9 @@ pub struct CodeEditor { #[serde(skip)] last_result: SubmissionResult, #[serde(skip)] - submit: bool, - #[serde(skip)] code: String, #[serde(skip)] - token_refresh_promise: refresh::RefreshPromise, + submitter: Option, } impl Default for CodeEditor { @@ -34,62 +29,22 @@ impl Default for CodeEditor { }; run.language = Languages::Python; Self { - promise: Default::default(), url: option_env!("BACKEND_URL") .unwrap_or("http://123.4.5.6:3000/") .to_string(), run, code: "#A very simple example\nprint(\"Hello world!\")".into(), - submit: false, - token_refresh_promise: None, last_result: SubmissionResult::NotStarted, + submitter: None, } } } impl CodeEditor { - fn submit(&mut self, ctx: &egui::Context) { - if !self.submit { - return; - } - self.submit = false; + fn submit(&mut self) { let submission = self.run.clone(); - let url = format!("{}api/game/submit", self.url); - log::debug!("Sending to {}", url); - let ctx = ctx.clone(); - - let promise = Promise::spawn_local(async move { - let response = http::Request::post(&url) - .credentials(RequestCredentials::Include) - .json(&submission) - .unwrap() - .send() - .await - .unwrap(); - - let result: SubmissionResult = match response.status() { - 200 => response.json().await.unwrap(), - 401 => { - let text = response.text().await; - let text = text.map(|text| text.to_owned()); - let text = match text { - Ok(text) => text, - Err(e) => e.to_string(), - }; - log::warn!("Auth Error: {:?}", text); - SubmissionResult::NotAuthorized - } - _ => { - return Err(format!("Failed to submit code: {:?}", response)); - } - }; - - ctx.request_repaint(); // wake up UI thread - Ok(result) - }); - - self.promise = Some(promise); + self.submitter = submission.sender(&url); } fn as_test_submission(&mut self) { @@ -109,33 +64,24 @@ impl super::App for CodeEditor { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { - self.submit(ctx); use super::View as _; egui::Window::new(self.name()) .open(open) .default_height(500.0) .show(ctx, |ui| self.ui(ui)); - match refresh::check_refresh_promise(&mut self.token_refresh_promise) { - refresh::RefreshStatus::InProgress => {} - refresh::RefreshStatus::Success => { - self.submit = true; - } - refresh::RefreshStatus::Failed(_) => {} - _ => (), - } - - let submission = Submission::check_submit_promise(&mut self.promise); + let submission = Submission::check_sender(&mut self.submitter); match submission { SubmissionResult::NotStarted => {} - SubmissionResult::NotAuthorized => { - self.token_refresh_promise = refresh::submit_refresh(); - self.last_result = submission; - } _ => { self.last_result = submission; } } + if let Some(fetcher) = self.submitter.borrow_mut() { + if fetcher.refresh_context() { + ctx.request_repaint(); + } + } } } @@ -211,7 +157,7 @@ impl super::View for CodeEditor { self.as_submission(); match self.run.validate() { Ok(_) => { - self.submit = true; + self.submit(); } Err(e) => { self.last_result = SubmissionResult::Failure { message: e }; @@ -223,7 +169,7 @@ impl super::View for CodeEditor { self.as_test_submission(); match self.run.validate() { Ok(_) => { - self.submit = true; + self.submit(); } Err(e) => { self.last_result = SubmissionResult::Failure { message: e }; diff --git a/src/apps/scoreboard_app.rs b/src/apps/scoreboard_app.rs index 754102e..6f37d34 100644 --- a/src/apps/scoreboard_app.rs +++ b/src/apps/scoreboard_app.rs @@ -1,5 +1,5 @@ use crate::helpers::{ - fetchers::{GetStatus, Getter}, + fetchers::{GetStatus, Requestor}, Challenges, }; use scoreboard_db::Builder as FilterBuilder; @@ -36,7 +36,7 @@ pub struct ScoreBoardApp { url: String, #[serde(skip)] - score_fetcher: Option, + score_fetcher: Option, } impl Default for ScoreBoardApp { @@ -65,8 +65,8 @@ impl ScoreBoardApp { let url = format!("{}api/game/scores/{}", self.url, self.challenge); log::debug!("Fetching challenge info"); - let mut getter = Getter::new(&url, true); - getter.get(); + let mut getter = Requestor::new_get(&url, true); + getter.send(); self.score_fetcher = Some(getter); } diff --git a/src/code_editor/editor.rs b/src/code_editor/editor.rs index e34670e..e31b238 100644 --- a/src/code_editor/editor.rs +++ b/src/code_editor/editor.rs @@ -1,16 +1,12 @@ use crate::helpers::{ - fetchers::{GetStatus, Getter}, - refresh, - submission::{Submission, SubmissionPromise, SubmissionResult}, + fetchers::{GetStatus, Requestor}, + submission::{Submission, SubmissionResult}, Challenges, Languages, }; use egui::*; use egui_commonmark::*; use egui_notify::Toasts; -use gloo_net::http; -use poll_promise::Promise; use std::{borrow::BorrowMut, time::Duration}; -use web_sys::RequestCredentials; #[derive(serde::Deserialize, serde::Serialize)] pub struct CodeEditor { @@ -25,22 +21,18 @@ pub struct CodeEditor { #[serde(skip)] url: String, #[serde(skip)] - promise: SubmissionPromise, - #[serde(skip)] last_result: SubmissionResult, #[serde(skip)] - submit: bool, - #[serde(skip)] toasts: Toasts, #[serde(skip)] - token_refresh_promise: refresh::RefreshPromise, - #[serde(skip)] active_challenge: Challenges, #[serde(skip)] selected_challenge: Challenges, #[serde(skip)] - info_fetcher: Option, + info_fetcher: Option, + #[serde(skip)] + submitter: Option, } impl Default for CodeEditor { @@ -53,14 +45,12 @@ impl Default for CodeEditor { instructions: "No Challenge Loaded".into(), label: "Code Editor".into(), - promise: Default::default(), url: option_env!("BACKEND_URL") .unwrap_or("http://123.4.5.6:3000/") .to_string(), - submit: false, last_result: SubmissionResult::NotStarted, toasts: Toasts::default(), - token_refresh_promise: None, + submitter: None, info_fetcher: None, active_challenge: Challenges::None, selected_challenge: Challenges::default(), @@ -69,48 +59,10 @@ impl Default for CodeEditor { } impl CodeEditor { - fn submit(&mut self, ctx: &egui::Context) { - if !self.submit { - return; - } - self.submit = false; + fn submit(&mut self) { let submission = self.run.clone(); - let url = format!("{}api/game/submit", self.url); - log::debug!("Sending to {}", url); - let ctx = ctx.clone(); - - let promise = Promise::spawn_local(async move { - let response = http::Request::post(&url) - .credentials(RequestCredentials::Include) - .json(&submission) - .unwrap() - .send() - .await - .unwrap(); - - let result: SubmissionResult = match response.status() { - 200 => response.json().await.unwrap(), - 401 => { - let text = response.text().await; - let text = text.map(|text| text.to_owned()); - let text = match text { - Ok(text) => text, - Err(e) => e.to_string(), - }; - log::warn!("Auth Error: {:?}", text); - SubmissionResult::NotAuthorized - } - _ => { - return Err(format!("Failed to submit code: {:?}", response)); - } - }; - - ctx.request_repaint(); // wake up UI thread - Ok(result) - }); - - self.promise = Some(promise); + self.submitter = submission.sender(&url); } fn fetch(&mut self) { @@ -146,29 +98,12 @@ impl CodeEditor { impl CodeEditor { pub fn panels(&mut self, ctx: &egui::Context) { self.fetch(); - if let Some(fetcher) = self.info_fetcher.borrow_mut() { - if fetcher.refresh_context() { - ctx.request_repaint(); - } - } + self.check_info_promise(); - match refresh::check_refresh_promise(&mut self.token_refresh_promise) { - refresh::RefreshStatus::InProgress => {} - refresh::RefreshStatus::Success => { - self.submit = true; - } - refresh::RefreshStatus::Failed(_) => {} - _ => (), - } - self.submit(ctx); - let submission = Submission::check_submit_promise(&mut self.promise); + let submission = Submission::check_sender(&mut self.submitter); match submission { SubmissionResult::NotStarted => {} - SubmissionResult::NotAuthorized => { - self.token_refresh_promise = refresh::submit_refresh(); - self.last_result = submission; - } SubmissionResult::Success { score: _, message } => { self.toasts .info(format!("Result: {}", message)) @@ -178,6 +113,12 @@ impl CodeEditor { self.last_result = submission; } } + + if let Some(fetcher) = self.info_fetcher.borrow_mut() { + if fetcher.refresh_context() { + ctx.request_repaint(); + } + } self.toasts.show(ctx); egui::TopBottomPanel::bottom("code_editor_bottom").show(ctx, |_ui| { @@ -249,7 +190,7 @@ impl CodeEditor { self.run.challenge = self.selected_challenge; match self.run.validate() { Ok(_) => { - self.submit = true; + self.submit(); } Err(e) => { self.toasts @@ -266,7 +207,7 @@ impl CodeEditor { match self.run.validate() { Ok(_) => { log::debug!("Testing code"); - self.submit = true; + self.submit(); } Err(e) => { self.toasts diff --git a/src/helpers/challenges.rs b/src/helpers/challenges.rs index 5af7268..ac296a4 100644 --- a/src/helpers/challenges.rs +++ b/src/helpers/challenges.rs @@ -1,4 +1,4 @@ -use super::fetchers::Getter; +use super::fetchers::Requestor; use std::fmt::{self, Display, Formatter}; #[derive( @@ -36,12 +36,12 @@ impl Challenges { } } - pub fn fetcher(&self) -> Option { + pub fn fetcher(&self) -> Option { match self { Challenges::None => None, _ => { - let mut getter = Getter::new(&self.get_info_url(), false); - getter.get(); + let mut getter = Requestor::new_get(&self.get_info_url(), false); + getter.send(); Some(getter) } } diff --git a/src/helpers/fetchers.rs b/src/helpers/fetchers.rs index d1b74c3..323f1bb 100644 --- a/src/helpers/fetchers.rs +++ b/src/helpers/fetchers.rs @@ -1,7 +1,7 @@ use crate::helpers::refresh; use gloo_net::http; use poll_promise::Promise; -use web_sys::RequestCredentials; +use web_sys::{FormData, RequestCredentials}; #[derive(Clone, Debug)] pub enum GetStatus { @@ -11,6 +11,11 @@ pub enum GetStatus { Failed(String), } +enum Method { + Get, + Post, +} + impl std::fmt::Display for GetStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -28,17 +33,37 @@ enum FetchResponse { FailAuth, } -pub struct Getter { +pub struct Requestor { promise: Option>>, with_credentials: bool, url: String, retry_count: usize, state_has_changed: bool, token_refresh_promise: refresh::RefreshPromise, + post_data: Option, + form_data: Option, + method: Method, } -impl Getter { - pub fn new(url: &str, with_credentials: bool) -> Self { +impl Requestor { + pub fn new_get(url: &str, with_credentials: bool) -> Self { + Self::new(url, with_credentials, None, None, Method::Get) + } + + pub fn new_post(url: &str, with_credentials: bool, data: Option) -> Self { + Self::new(url, with_credentials, data, None, Method::Post) + } + pub fn new_form_post(url: &str, with_credentials: bool, data: Option) -> Self { + Self::new(url, with_credentials, None, data, Method::Post) + } + + fn new( + url: &str, + with_credentials: bool, + data: Option, + form: Option, + method: Method, + ) -> Self { Self { url: url.to_string(), promise: None, @@ -49,6 +74,9 @@ impl Getter { }, state_has_changed: false, token_refresh_promise: None, + post_data: data, + form_data: form, + method, } } @@ -105,8 +133,15 @@ impl Getter { } } -impl Getter { - pub fn get(&mut self) { +impl Requestor { + pub fn send(&mut self) { + match self.method { + Method::Get => self.get(), + Method::Post => self.post(), + } + } + + fn get(&mut self) { let url = self.url.clone(); let with_credentials = self.with_credentials; let promise = Promise::spawn_local(async move { @@ -133,4 +168,48 @@ impl Getter { }); self.promise = Some(promise); } + + fn post(&mut self) { + let url = self.url.clone(); + let with_credentials = self.with_credentials; + let json_data = self.post_data.clone(); + let form_data = self.form_data.clone(); + + let promise = Promise::spawn_local(async move { + let request = http::Request::post(&url); + let request = match with_credentials { + true => request.credentials(RequestCredentials::Include), + false => request, + }; + let request = { + if let Some(data) = json_data { + request + .header("Content-Type", "application/json") + .body(data) + .map_err(|e| e.to_string())? + } else if let Some(data) = form_data { + request.body(data).map_err(|e| e.to_string())? + } else { + request.build().map_err(|e| e.to_string())? + } + }; + + let response = request.send().await.map_err(|e| e.to_string())?; + let text = response.text().await.map_err(|e| e.to_string())?; + + let result = match response.status() { + 200 => FetchResponse::Success(GetStatus::Success(text)), + 401 => { + log::warn!("Auth Error: {}", text); + FetchResponse::FailAuth + } + _ => { + log::error!("Response: {}", text); + FetchResponse::Failure(text) + } + }; + Ok(result) + }); + self.promise = Some(promise); + } } diff --git a/src/helpers/submission.rs b/src/helpers/submission.rs index c6b4784..ae403ba 100644 --- a/src/helpers/submission.rs +++ b/src/helpers/submission.rs @@ -1,9 +1,11 @@ use std::fmt::Display; -use poll_promise::Promise; use web_sys::FormData; -use super::{Challenges, Languages}; +use super::{ + fetchers::{GetStatus, Requestor}, + Challenges, Languages, +}; #[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct Submission { @@ -17,8 +19,6 @@ pub struct Submission { pub binary: Option>, } -pub type SubmissionPromise = Option>>; - impl Submission { pub fn to_formdata(&self) -> FormData { let form = FormData::new().unwrap(); @@ -46,25 +46,44 @@ impl Submission { form } - pub fn check_submit_promise(promise: &mut SubmissionPromise) -> SubmissionResult { - let mut result = SubmissionResult::NotStarted; - if let Some(p) = &promise { - result = SubmissionResult::Busy; - if let Some(response) = p.ready() { - match response { - Ok(submission_response) => { - result = submission_response.clone(); - } - Err(error) => { - result = SubmissionResult::Failure { + pub fn check_sender(sender: &mut Option) -> SubmissionResult { + if let Some(requestor) = sender { + let result = &requestor.check_promise(); + + match result { + GetStatus::Success(text) => { + *sender = None; + match serde_json::from_str::(text) { + Ok(submission_response) => submission_response.clone(), + Err(error) => SubmissionResult::Failure { message: error.to_string(), - }; + }, } } - *promise = None; + GetStatus::Failed(e) => { + *sender = None; + SubmissionResult::Failure { + message: e.to_string(), + } + } + GetStatus::InProgress => SubmissionResult::Busy, + GetStatus::NotStarted => SubmissionResult::NotStarted, } + } else { + SubmissionResult::NotStarted } - result + } + + pub fn sender(&self, url: &str) -> Option { + let mut submitter = if self.code.is_some() { + let submission = Some(serde_json::to_string(&self).unwrap()); + Requestor::new_post(url, true, submission) + } else { + let submission = Some(self.to_formdata()); + Requestor::new_form_post(url, true, submission) + }; + submitter.send(); + Some(submitter) } pub fn validate(&self) -> Result<(), String> {