Skip to content

Commit a10a2e3

Browse files
committed
even more refactoring
1 parent 46132f2 commit a10a2e3

File tree

20 files changed

+519
-439
lines changed

20 files changed

+519
-439
lines changed

tmc-client/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ license = "MIT OR Apache-2.0"
99
tmc-langs-plugins = { path = "../tmc-langs-plugins" }
1010
tmc-langs-util = { path = "../tmc-langs-util" }
1111

12+
chrono = { version = "0.4", features = ["serde"] }
1213
dirs = "3"
1314
http = "0.2"
1415
lazy_static = "1"
@@ -17,7 +18,7 @@ oauth2 = { version = "4.0.0-alpha.3", features = ["reqwest"] }
1718
percent-encoding = "2"
1819
regex = "1"
1920
reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls", "multipart"] }
20-
schemars = "0.8"
21+
schemars = { version = "0.8", features = ["chrono"] }
2122
serde = { version = "1", features = ["derive"] }
2223
serde_json = "1"
2324
tempfile = "3"

tmc-client/src/error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type TokenError = oauth2::RequestTokenError<
1111
oauth2::StandardErrorResponse<oauth2::basic::BasicErrorResponseType>,
1212
>;
1313

14+
/// The main error type for tmc-client.
1415
#[derive(Debug, Error)]
1516
pub enum ClientError {
1617
// Arc

tmc-client/src/response.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Contains types which model the JSON responses from tmc-server
22
3+
use chrono::{DateTime, FixedOffset};
34
use lazy_static::lazy_static;
45
use regex::Regex;
56
use schemars::JsonSchema;
@@ -39,6 +40,7 @@ pub struct User {
3940
pub administrator: bool,
4041
}
4142

43+
/// Organization information.
4244
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
4345
pub struct Organization {
4446
pub name: String,
@@ -48,6 +50,7 @@ pub struct Organization {
4850
pub pinned: bool,
4951
}
5052

53+
/// Information for a course.
5154
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
5255
pub struct Course {
5356
pub id: usize,
@@ -61,6 +64,7 @@ pub struct Course {
6164
pub spyware_urls: Vec<String>,
6265
}
6366

67+
/// Data for a course.
6468
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
6569
pub struct CourseData {
6670
pub name: String,
@@ -69,7 +73,7 @@ pub struct CourseData {
6973
pub cache_version: Option<usize>,
7074
pub spreadsheet_key: Option<String>,
7175
pub hidden_if_registered_after: Option<String>,
72-
pub refreshed_at: Option<String>,
76+
pub refreshed_at: Option<DateTime<FixedOffset>>,
7377
pub locked_exercise_points_visible: bool,
7478
pub description: Option<String>,
7579
pub paste_visibility: Option<String>,
@@ -102,6 +106,7 @@ struct CourseDetailsInner {
102106
pub exercises: Vec<Exercise>,
103107
}
104108

109+
/// Details for a course.
105110
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
106111
#[serde(from = "CourseDetailsWrapper")]
107112
pub struct CourseDetails {
@@ -149,6 +154,7 @@ pub struct Exercise {
149154
pub solution_zip_url: Option<String>,
150155
}
151156

157+
/// Exercise for a course.
152158
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
153159
pub struct CourseExercise {
154160
pub id: usize,
@@ -195,9 +201,10 @@ pub struct AwardedPoint {
195201
user_id: usize,
196202
submission_id: usize,
197203
name: String,
198-
created_at: String,
204+
created_at: DateTime<FixedOffset>,
199205
}
200206

207+
/// Details for an exercise.
201208
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
202209
pub struct ExerciseDetails {
203210
pub course_name: String,
@@ -219,22 +226,23 @@ pub struct ExercisesDetails {
219226
pub checksum: String,
220227
}
221228

229+
/// Exercise submission.
222230
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
223231
pub struct Submission {
224232
pub id: usize,
225233
pub user_id: usize,
226234
pub pretest_error: Option<String>,
227-
pub created_at: String,
235+
pub created_at: DateTime<FixedOffset>,
228236
pub exercise_name: String,
229237
pub course_id: usize,
230238
pub processed: bool,
231239
pub all_tests_passed: bool,
232240
pub points: Option<String>,
233-
pub processing_tried_at: Option<String>,
234-
pub processing_began_at: Option<String>,
235-
pub processing_completed_at: Option<String>,
241+
pub processing_tried_at: Option<DateTime<FixedOffset>>,
242+
pub processing_began_at: Option<DateTime<FixedOffset>>,
243+
pub processing_completed_at: Option<DateTime<FixedOffset>>,
236244
pub times_sent_to_sandbox: usize,
237-
pub processing_attempts_started_at: String,
245+
pub processing_attempts_started_at: DateTime<FixedOffset>,
238246
pub params_json: Option<String>,
239247
pub requires_review: bool,
240248
pub requests_review: bool,
@@ -253,7 +261,7 @@ pub struct ExerciseSubmission {
253261
pub id: usize,
254262
pub user_id: usize,
255263
pub course_id: usize,
256-
pub created_at: String,
264+
pub created_at: DateTime<FixedOffset>,
257265
pub all_tests_passed: bool,
258266
pub points: Option<String>,
259267
pub submitted_zip_url: String,
@@ -263,6 +271,7 @@ pub struct ExerciseSubmission {
263271
pub requests_review: bool,
264272
}
265273

274+
/// Exercise submission.
266275
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
267276
pub struct NewSubmission {
268277
pub show_submission_url: String,
@@ -291,6 +300,7 @@ pub enum SandboxStatus {
291300
ProcessingOnSandbox,
292301
}
293302

303+
/// Finished submission.
294304
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
295305
pub struct SubmissionFinished {
296306
pub api_version: usize,
@@ -328,6 +338,7 @@ pub enum SubmissionStatus {
328338
Hidden,
329339
}
330340

341+
/// Response to feedback.
331342
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
332343
pub struct SubmissionFeedbackResponse {
333344
pub api_version: usize,
@@ -419,6 +430,7 @@ impl<'de> Visitor<'de> for SubmissionFeedbackKindVisitor {
419430
}
420431
}
421432

433+
/// Code review.
422434
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
423435
pub struct Review {
424436
pub submission_id: String,
@@ -431,10 +443,11 @@ pub struct Review {
431443
pub points_not_awarded: Vec<String>,
432444
pub url: String,
433445
pub update_url: String,
434-
pub created_at: String,
446+
pub created_at: DateTime<FixedOffset>,
435447
pub updated_at: String,
436448
}
437449

450+
/// Updated exercises.
438451
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
439452
pub struct UpdateResult {
440453
pub created: Vec<Exercise>,

tmc-client/src/tmc_client.rs

Lines changed: 5 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@ use serde::{Deserialize, Serialize};
1414
use std::collections::HashMap;
1515
use std::io::Write;
1616
use std::path::{Path, PathBuf};
17-
use std::sync::{
18-
atomic::{AtomicUsize, Ordering},
19-
Arc, Mutex,
20-
};
17+
use std::sync::Arc;
2118
use std::thread;
2219
use std::time::Duration;
2320
use tempfile::NamedTempFile;
2421
use tmc_langs_util::{file_util, progress_reporter, FileError};
2522
use walkdir::WalkDir;
2623

24+
/// Authentication token.
2725
pub type Token =
2826
oauth2::StandardTokenResponse<oauth2::EmptyExtraTokenFields, oauth2::basic::BasicTokenType>;
2927

@@ -37,6 +35,7 @@ pub enum ClientUpdateData {
3735
}
3836

3937
/// A struct for interacting with the TestMyCode service, including authentication.
38+
#[derive(Clone)]
4039
pub struct TmcClient(Arc<TmcCore>);
4140

4241
struct TmcCore {
@@ -194,164 +193,6 @@ impl TmcClient {
194193
self.organization(organization_slug)
195194
}
196195

197-
/// Downloads the given exercises. Overwrites existing exercises if they exist.
198-
///
199-
/// # Errors
200-
/// Returns an error if there's some problem reaching the API, or if the API returns an error.
201-
/// The method extracts zip archives, which may fail.
202-
///
203-
///
204-
/// # Examples
205-
/// ```rust,no_run
206-
/// use tmc_client::TmcClient;
207-
/// use std::path::PathBuf;
208-
///
209-
/// let client = TmcClient::new_in_config("https://tmc.mooc.fi".to_string(), "some_client".to_string(), "some_version".to_string()).unwrap();
210-
/// // authenticate
211-
/// client.download_or_update_exercises(vec![
212-
/// (1234, PathBuf::from("./exercises/1234")),
213-
/// (2345, PathBuf::from("./exercises/2345")),
214-
/// ]);
215-
/// ```
216-
pub fn download_or_update_exercises(
217-
&self,
218-
exercises: Vec<(usize, PathBuf)>,
219-
) -> Result<(), ClientError> {
220-
// todo: bit of a mess, refactor
221-
let exercises_len = exercises.len();
222-
start_stage(
223-
exercises_len * 2 + 1, // each download progresses at 2 points, plus the final finishing step
224-
format!("Downloading {} exercises", exercises_len),
225-
None,
226-
);
227-
228-
// for each exercise, check if there's already something on disk
229-
// if yes, check if it needs updating
230-
// if not, check if there's a previous submission
231-
// if yes, download it
232-
// if not, download the exercise template
233-
234-
let thread_count = exercises_len.min(4); // max 4 threads
235-
let mut handles = vec![];
236-
let exercises = Arc::new(Mutex::new(exercises));
237-
let starting_download_counter = Arc::new(AtomicUsize::new(1));
238-
let downloaded_counter = Arc::new(AtomicUsize::new(1));
239-
240-
// spawn threads
241-
for _thread_id in 0..thread_count {
242-
let client = Arc::clone(&self.0);
243-
let exercises = Arc::clone(&exercises);
244-
let starting_download_counter = Arc::clone(&starting_download_counter);
245-
let downloaded_counter = Arc::clone(&downloaded_counter);
246-
247-
// each thread returns either a list of successful downloads, or a tuple of successful downloads and errors
248-
type ThreadErr = (Vec<usize>, Vec<(usize, Box<ClientError>)>);
249-
let handle = std::thread::spawn(move || -> Result<Vec<usize>, ThreadErr> {
250-
let client_clone = TmcClient(client);
251-
let mut downloaded = vec![];
252-
let mut errors = vec![];
253-
254-
// repeat until out of exercises
255-
loop {
256-
// acquiring mutex
257-
let mut exercises = exercises.lock().expect("the threads should never panic");
258-
let (exercise_id, target) = if let Some((id, path)) = exercises.pop() {
259-
(id, path)
260-
} else {
261-
// no exercises left, break loop and exit thread
262-
break;
263-
};
264-
drop(exercises);
265-
// dropped mutex
266-
267-
let exercise_download_result = || -> Result<(), ClientError> {
268-
// TODO: do in memory without zip_file?
269-
let starting_download_count =
270-
starting_download_counter.fetch_add(1, Ordering::SeqCst);
271-
let zip_file = NamedTempFile::new().map_err(ClientError::TempFile)?;
272-
273-
progress_stage(
274-
format!(
275-
"Downloading exercise {} to '{}'. ({} out of {})",
276-
exercise_id,
277-
target.display(),
278-
starting_download_count,
279-
exercises_len
280-
),
281-
ClientUpdateData::ExerciseDownload {
282-
id: exercise_id,
283-
path: target.clone(),
284-
},
285-
);
286-
287-
client_clone.download_exercise(exercise_id, zip_file.path())?;
288-
let downloaded_count = downloaded_counter.fetch_add(1, Ordering::SeqCst);
289-
tmc_langs_plugins::extract_project(zip_file, &target, true)?;
290-
progress_stage(
291-
format!(
292-
"Downloaded exercise {} to '{}'. ({} out of {})",
293-
exercise_id,
294-
target.display(),
295-
downloaded_count,
296-
exercises_len
297-
),
298-
ClientUpdateData::ExerciseDownload {
299-
id: exercise_id,
300-
path: target,
301-
},
302-
);
303-
Ok(())
304-
}();
305-
306-
// return underlying error with exercise id if download failed
307-
match exercise_download_result {
308-
Ok(()) => downloaded.push(exercise_id),
309-
Err(e) => {
310-
log::error!("Failed to download exercise {}", exercise_id);
311-
errors.push((exercise_id, Box::new(e)));
312-
}
313-
}
314-
}
315-
316-
if errors.is_empty() {
317-
Ok(downloaded)
318-
} else {
319-
Err((downloaded, errors))
320-
}
321-
});
322-
handles.push(handle);
323-
}
324-
325-
let mut successful = vec![];
326-
let mut failed = vec![];
327-
for handle in handles {
328-
match handle.join().expect("the threads should never panic") {
329-
Ok(s) => successful.extend(s),
330-
Err((s, f)) => {
331-
successful.extend(s);
332-
failed.extend(f);
333-
}
334-
}
335-
}
336-
337-
finish_stage(
338-
format!(
339-
"Successfully downloaded {} out of {} exercises.",
340-
successful.len(),
341-
exercises_len
342-
),
343-
None,
344-
);
345-
if !failed.is_empty() {
346-
Err(ClientError::IncompleteDownloadResult {
347-
downloaded: successful,
348-
failed,
349-
})
350-
} else {
351-
Ok(())
352-
}
353-
}
354-
355196
/// Fetches the course's information.
356197
///
357198
/// # Errors
@@ -534,7 +375,7 @@ impl TmcClient {
534375
}
535376
}
536377
}
537-
self.download_or_update_exercises(vec![(exercise_id, exercise_path)])
378+
self.download_exercise(exercise_id, &exercise_path)
538379
}
539380

540381
pub fn download_old_submission(
@@ -862,7 +703,7 @@ mod test {
862703
let target = temp_dir.path().join("temp");
863704
assert!(!target.exists());
864705
let exercises = vec![(1234, target.clone())];
865-
client.download_or_update_exercises(exercises).unwrap();
706+
//client.download_or_update_exercises(exercises).unwrap();
866707
assert!(target.join("src/main/java/Hiekkalaatikko.java").exists());
867708
}
868709

tmc-client/src/tmc_client/api.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -639,11 +639,7 @@ impl TmcClient {
639639
todo!("needs admin?");
640640
}
641641

642-
pub(super) fn download_exercise(
643-
&self,
644-
exercise_id: usize,
645-
target: &Path,
646-
) -> Result<(), ClientError> {
642+
pub fn download_exercise(&self, exercise_id: usize, target: &Path) -> Result<(), ClientError> {
647643
let url_tail = format!("core/exercises/{}/download", exercise_id);
648644
self.download(&url_tail, target)
649645
}

0 commit comments

Comments
 (0)