Skip to content

Commit 30704a4

Browse files
committed
block on submit by default, progress updates
1 parent 082148e commit 30704a4

File tree

7 files changed

+155
-43
lines changed

7 files changed

+155
-43
lines changed

tmc-langs-cli/src/app.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,11 @@ pub fn create_app() -> App<'static, 'static> {
188188
.long("client-name")
189189
.required(true)
190190
.takes_value(true))
191+
.arg(Arg::with_name("client-version")
192+
.help("Client version")
193+
.long("client-version")
194+
.required(true)
195+
.takes_value(true))
191196

192197
.subcommand(SubCommand::with_name("login")
193198
.about("Login and store OAuth2 token in config. You can login either by email and password or an access token.")
@@ -301,7 +306,7 @@ pub fn create_app() -> App<'static, 'static> {
301306
.multiple(true)))
302307

303308
.subcommand(SubCommand::with_name("submit")
304-
.about("Submit exercise.")
309+
.about("Submit exercise. Will block until the submission results are returned.")
305310
.arg(Arg::with_name("submission-url")
306311
.help("URL where the submission should be posted.")
307312
.long("submission-url")
@@ -315,7 +320,10 @@ pub fn create_app() -> App<'static, 'static> {
315320
.arg(Arg::with_name("locale")
316321
.help("Language as a three letter ISO 639-3 code, e.g. 'eng' or 'fin'.")
317322
.long("locale")
318-
.takes_value(true)))
323+
.takes_value(true))
324+
.arg(Arg::with_name("dont-block")
325+
.help("Set to avoid blocking.")
326+
.long("dont-block")))
319327

320328
.subcommand(SubCommand::with_name("wait-for-submission")
321329
.about("Wait for a submission to finish.")

tmc-langs-cli/src/main.rs

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,14 @@ fn run() -> Result<()> {
295295
if let Some(matches) = matches.subcommand_matches("core") {
296296
let root_url =
297297
env::var("TMC_LANGS_ROOT_URL").unwrap_or_else(|_| "https://tmc.mooc.fi".to_string());
298-
let mut core = TmcCore::new_in_config(root_url).context("Failed to create TmcCore")?;
298+
let client_name = matches.value_of("client-name").unwrap();
299+
let client_version = matches.value_of("client-version").unwrap();
300+
let mut core = TmcCore::new_in_config(
301+
root_url,
302+
client_name.to_string(),
303+
client_version.to_string(),
304+
)
305+
.context("Failed to create TmcCore")?;
299306
// set progress report to print the updates to stdout as JSON
300307
core.set_progress_report(|update| {
301308
// convert to output
@@ -310,7 +317,6 @@ fn run() -> Result<()> {
310317
});
311318

312319
// set token if a credentials.json is found for the client name
313-
let client_name = matches.value_of("client-name").unwrap();
314320
let tmc_dir = format!("tmc-{}", client_name);
315321

316322
let config_dir = match env::var("TMC_LANGS_CONFIG_DIR") {
@@ -621,18 +627,40 @@ fn run() -> Result<()> {
621627
None
622628
};
623629

630+
let dont_block = matches.is_present("dont-block");
631+
624632
let new_submission = core
625633
.submit(submission_url, submission_path, optional_locale)
626634
.context("Failed to submit")?;
627635

628-
let output = Output {
629-
status: Status::Successful,
630-
message: None,
631-
result: OutputResult::SentData,
632-
percent_done: 1.0,
633-
data: Some(new_submission),
634-
};
635-
print_output(&output)?;
636+
if dont_block {
637+
let output = Output {
638+
status: Status::Successful,
639+
message: None,
640+
result: OutputResult::SentData,
641+
percent_done: 1.0,
642+
data: Some(new_submission),
643+
};
644+
645+
print_output(&output)?;
646+
} else {
647+
// same as wait-for-submission
648+
let submission_url = new_submission.submission_url;
649+
let submission_finished = core
650+
.wait_for_submission(&submission_url)
651+
.context("Failed while waiting for submissions")?;
652+
let submission_finished = serde_json::to_string(&submission_finished)
653+
.context("Failed to serialize submission results")?;
654+
655+
let output = Output {
656+
status: Status::Successful,
657+
message: None,
658+
result: OutputResult::RetrievedData,
659+
percent_done: 1.0,
660+
data: Some(submission_finished),
661+
};
662+
print_output(&output)?;
663+
}
636664
} else if let Some(matches) = matches.subcommand_matches("wait-for-submission") {
637665
let submission_url = matches.value_of("submission-url").unwrap();
638666
let submission_finished = core

tmc-langs-core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! ```rust,no_run
44
//! use tmc_langs_core::TmcCore;
55
//!
6-
//! let mut core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string()).unwrap();
6+
//! let mut core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string(), "some_client".to_string(), "some_version".to_string()).unwrap();
77
//! core.authenticate("client_name", "email".to_string(), "password".to_string());
88
//! let organizations = core.get_organizations();
99
//! ```

tmc-langs-core/src/tmc_core.rs

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ pub struct TmcCore {
4747
auth_url: String,
4848
token: Option<Token>,
4949
progress_report: Option<UpdateClosure>,
50+
client_name: String,
51+
client_version: String,
5052
}
5153

5254
// TODO: cache API results?
@@ -61,9 +63,14 @@ impl TmcCore {
6163
/// use tmc_langs_core::TmcCore;
6264
/// use std::path::PathBuf;
6365
///
64-
/// let core = TmcCore::new(PathBuf::from("./config"), "https://tmc.mooc.fi".to_string()).unwrap();
66+
/// let core = TmcCore::new(PathBuf::from("./config"), "https://tmc.mooc.fi".to_string(), "some_client".to_string(), "some_version".to_string()).unwrap();
6567
/// ```
66-
pub fn new(config_dir: PathBuf, root_url: String) -> Result<Self> {
68+
pub fn new(
69+
config_dir: PathBuf,
70+
root_url: String,
71+
client_name: String,
72+
client_version: String,
73+
) -> Result<Self> {
6774
// guarantee a trailing slash, otherwise join will drop the last component
6875
let root_url = if root_url.ends_with('/') {
6976
root_url
@@ -83,6 +90,8 @@ impl TmcCore {
8390
auth_url,
8491
token: None,
8592
progress_report: None,
93+
client_name,
94+
client_version,
8695
})
8796
}
8897

@@ -95,11 +104,15 @@ impl TmcCore {
95104
/// ```rust,no_run
96105
/// use tmc_langs_core::TmcCore;
97106
///
98-
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string()).unwrap();
107+
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string(), "some_client".to_string(), "some_ver".to_string()).unwrap();
99108
/// ```
100-
pub fn new_in_config(root_url: String) -> Result<Self> {
109+
pub fn new_in_config(
110+
root_url: String,
111+
client_name: String,
112+
client_version: String,
113+
) -> Result<Self> {
101114
let config_dir = dirs::cache_dir().ok_or(CoreError::CacheDir)?;
102-
Self::new(config_dir, root_url)
115+
Self::new(config_dir, root_url, client_name, client_version)
103116
}
104117

105118
pub fn set_token(&mut self, token: Token) {
@@ -145,7 +158,7 @@ impl TmcCore {
145158
/// ```rust,no_run
146159
/// use tmc_langs_core::TmcCore;
147160
///
148-
/// let mut core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string()).unwrap();
161+
/// let mut core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string(), "some_client".to_string(), "some_version".to_string()).unwrap();
149162
/// core.authenticate("client", "user".to_string(), "pass".to_string()).unwrap();
150163
/// ```
151164
pub fn authenticate(
@@ -214,7 +227,7 @@ impl TmcCore {
214227
/// use tmc_langs_core::TmcCore;
215228
/// use std::path::Path;
216229
///
217-
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string()).unwrap();
230+
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string(), "some_client".to_string(), "some_version".to_string()).unwrap();
218231
/// // authenticate
219232
/// core.download_or_update_exercises(vec![
220233
/// (1234, Path::new("./exercises/1234")),
@@ -250,7 +263,7 @@ impl TmcCore {
250263
/// ```rust,no_run
251264
/// use tmc_langs_core::TmcCore;
252265
///
253-
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string()).unwrap();
266+
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string(), "some_client".to_string(), "some_version".to_string()).unwrap();
254267
/// // authenticate
255268
/// let course_details = core.get_course_details(600).unwrap();
256269
/// ```
@@ -275,7 +288,7 @@ impl TmcCore {
275288
/// ```rust,no_run
276289
/// use tmc_langs_core::TmcCore;
277290
///
278-
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string()).unwrap();
291+
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string(), "some_client".to_string(), "some_version".to_string()).unwrap();
279292
/// // authenticate
280293
/// let courses = core.list_courses("hy").unwrap();
281294
/// ```
@@ -297,7 +310,7 @@ impl TmcCore {
297310
/// use url::Url;
298311
/// use std::path::Path;
299312
///
300-
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string()).unwrap();
313+
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string(), "some_client".to_string(), "some_version".to_string()).unwrap();
301314
/// // authenticate
302315
/// let course_details = core.get_course_details(600).unwrap();
303316
/// let submission_url = &course_details.exercises[0].return_url;
@@ -335,7 +348,7 @@ impl TmcCore {
335348
/// use tmc_langs_core::{TmcCore, Language};
336349
/// use std::path::Path;
337350
///
338-
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string()).unwrap();
351+
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string(), "some_client".to_string(), "some_version".to_string()).unwrap();
339352
/// // authenticate
340353
/// let validation_result = core.run_checkstyle(Path::new("./exercises/python/123"), Language::Eng).unwrap();
341354
/// match validation_result {
@@ -418,10 +431,30 @@ impl TmcCore {
418431
}
419432

420433
pub fn wait_for_submission(&self, submission_url: &str) -> Result<SubmissionFinished> {
434+
let mut previous_status = None;
421435
loop {
422436
match self.check_submission(submission_url)? {
423-
SubmissionProcessingStatus::Finished(f) => return Ok(*f),
424-
SubmissionProcessingStatus::Processing(_p) => {
437+
SubmissionProcessingStatus::Finished(f) => {
438+
self.report_complete("Submission finished processing!");
439+
return Ok(*f);
440+
}
441+
SubmissionProcessingStatus::Processing(p) => {
442+
match (&mut previous_status, p.sandbox_status) {
443+
(Some(previous), status) if status == *previous => {} // no change, ignore
444+
(_, status) => {
445+
// new status, update progress
446+
match status {
447+
SandboxStatus::Created => self.report_progress("Created", 0.25),
448+
SandboxStatus::SendingToSandbox => {
449+
self.report_progress("Sending to sandbox", 0.5)
450+
}
451+
SandboxStatus::ProcessingOnSandbox => {
452+
self.report_progress("Processing on sandbox", 0.75)
453+
}
454+
}
455+
previous_status = Some(status);
456+
}
457+
}
425458
thread::sleep(Duration::from_secs(1));
426459
}
427460
}
@@ -441,7 +474,7 @@ impl TmcCore {
441474
/// ```rust,no_run
442475
/// use tmc_langs_core::TmcCore;
443476
///
444-
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string()).unwrap();
477+
/// let core = TmcCore::new_in_config("https://tmc.mooc.fi".to_string(), "some_client".to_string(), "some_version".to_string()).unwrap();
445478
/// // authenticate
446479
/// let mut checksums = std::collections::HashMap::new();
447480
/// checksums.insert(1234, "exercisechecksum".to_string());
@@ -573,7 +606,12 @@ mod test {
573606
.create();
574607
let local_server = mockito::server_url();
575608
log::debug!("local {}", local_server);
576-
let mut core = TmcCore::new_in_config(local_server.to_string()).unwrap();
609+
let mut core = TmcCore::new_in_config(
610+
local_server.to_string(),
611+
"some_client".to_string(),
612+
"some_ver".to_string(),
613+
)
614+
.unwrap();
577615
core.authenticate("client_name", "email".to_string(), "password".to_string())
578616
.unwrap();
579617
(core, local_server)
@@ -583,6 +621,8 @@ mod test {
583621
fn gets_organizations() {
584622
let (core, _addr) = init();
585623
let _m = mock("GET", "/api/v8/org.json")
624+
.match_header("client", "some_client")
625+
.match_header("client_version", "some_ver")
586626
.with_body(
587627
serde_json::json!([
588628
{
@@ -604,6 +644,8 @@ mod test {
604644
fn downloads_or_update_exercises() {
605645
let (core, _addr) = init();
606646
let _m = mock("GET", "/api/v8/core/exercises/1234/download")
647+
.match_header("client", "some_client")
648+
.match_header("client_version", "some_ver")
607649
.with_body_from_file(Path::new("tests/data/81842.zip"))
608650
.create();
609651

@@ -619,6 +661,8 @@ mod test {
619661
fn gets_course_details() {
620662
let (core, _addr) = init();
621663
let _m = mock("GET", "/api/v8/core/courses/1234")
664+
.match_header("client", "some_client")
665+
.match_header("client_version", "some_ver")
622666
.with_body(serde_json::json!({
623667
"course": {
624668
"id": 588,
@@ -669,6 +713,8 @@ mod test {
669713
fn lists_courses() {
670714
let (core, _addr) = init();
671715
let _m = mock("GET", "/api/v8/core/org/slug/courses")
716+
.match_header("client", "some_client")
717+
.match_header("client_version", "some_ver")
672718
.with_body(serde_json::json!([
673719
{
674720
"id": 277,
@@ -696,6 +742,8 @@ mod test {
696742
let (core, url) = init();
697743
let submission_url = Url::parse(&format!("{}/submission", url)).unwrap();
698744
let _m = mock("POST", "/submission")
745+
.match_header("client", "some_client")
746+
.match_header("client_version", "some_ver")
699747
.match_body(Matcher::Regex("paste".to_string()))
700748
.match_body(Matcher::Regex("message_for_paste".to_string()))
701749
.match_body(Matcher::Regex("abcdefg".to_string()))
@@ -738,6 +786,8 @@ mod test {
738786
let (core, url) = init();
739787
let feedback_url = Url::parse(&format!("{}/feedback", url)).unwrap();
740788
let _m = mock("POST", "/feedback")
789+
.match_header("client", "some_client")
790+
.match_header("client_version", "some_ver")
741791
.match_body(Matcher::AllOf(vec![
742792
Matcher::Regex(r#"answers\[0\]\[question_id\]"#.to_string()),
743793
Matcher::Regex(r#"answers\[0\]\[answer\]"#.to_string()),
@@ -780,6 +830,8 @@ mod test {
780830
let (core, url) = init();
781831
let submission_url = Url::parse(&format!("{}/submission", url)).unwrap();
782832
let _m = mock("POST", "/submission")
833+
.match_header("client", "some_client")
834+
.match_header("client_version", "some_ver")
783835
.match_body(Matcher::Regex(r#"submission\[file\]"#.to_string()))
784836
.with_body(
785837
serde_json::json!({
@@ -808,6 +860,8 @@ mod test {
808860
fn gets_exercise_updates() {
809861
let (core, _addr) = init();
810862
let _m = mock("GET", "/api/v8/core/courses/1234")
863+
.match_header("client", "some_client")
864+
.match_header("client_version", "some_ver")
811865
.with_body(serde_json::json!({
812866
"course": {
813867
"id": 588,
@@ -924,6 +978,8 @@ mod test {
924978
let (core, addr) = init();
925979
let reviews_url = Url::parse(&format!("{}/reviews", addr)).unwrap();
926980
let _m = mock("GET", "/reviews")
981+
.match_header("client", "some_client")
982+
.match_header("client_version", "some_ver")
927983
.with_body(
928984
serde_json::json!([
929985
{
@@ -955,6 +1011,8 @@ mod test {
9551011
let (core, url) = init();
9561012
let submission_url = Url::parse(&format!("{}/submission", url)).unwrap();
9571013
let _m = mock("POST", "/submission")
1014+
.match_header("client", "some_client")
1015+
.match_header("client_version", "some_ver")
9581016
.match_body(Matcher::Regex("request_review".to_string()))
9591017
.match_body(Matcher::Regex("message_for_reviewer".to_string()))
9601018
.match_body(Matcher::Regex("abcdefg".to_string()))
@@ -987,6 +1045,8 @@ mod test {
9871045
let (core, addr) = init();
9881046
let solution_url = Url::parse(&format!("{}/solution", addr)).unwrap();
9891047
let _m = mock("GET", "/solution")
1048+
.match_header("client", "some_client")
1049+
.match_header("client_version", "some_ver")
9901050
.with_body_from_file(Path::new("tests/data/81842.zip"))
9911051
.create();
9921052

@@ -1001,6 +1061,8 @@ mod test {
10011061
fn checks_submission_processing() {
10021062
let (core, addr) = init();
10031063
let _m = mock("GET", "/submission-url")
1064+
.match_header("client", "some_client")
1065+
.match_header("client_version", "some_ver")
10041066
.with_body(
10051067
serde_json::json!({
10061068
"status": "processing",
@@ -1023,7 +1085,8 @@ mod test {
10231085
#[test]
10241086
fn checks_submission_finished() {
10251087
let (core, addr) = init();
1026-
let _m = mock("GET", "/submission-url").with_body(serde_json::json!({
1088+
let m = mock("GET", "/submission-url")
1089+
.with_body(serde_json::json!({
10271090
"api_version": 7,
10281091
"all_tests_passed": true,
10291092
"user_id": 3232,

0 commit comments

Comments
 (0)