Skip to content

Commit 01c5d50

Browse files
committed
save download results after each successful download
1 parent a75ff12 commit 01c5d50

File tree

3 files changed

+79
-95
lines changed

3 files changed

+79
-95
lines changed

tmc-langs/src/config/projects_config.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@
22
33
use crate::LangsError;
44
use serde::{Deserialize, Serialize};
5-
use std::collections::BTreeMap;
5+
use std::collections::{BTreeMap, HashMap};
66
use std::path::{Path, PathBuf};
77
use tmc_langs_util::{file_util, FileError};
88
use walkdir::WalkDir;
99

1010
#[derive(Debug)]
1111
pub struct ProjectsConfig {
1212
// BTreeMap used so the exercises in the config file are ordered by key
13-
pub courses: BTreeMap<String, CourseConfig>,
13+
pub courses: HashMap<String, CourseConfig>,
1414
}
1515

1616
impl ProjectsConfig {
1717
pub fn load(projects_dir: &Path) -> Result<ProjectsConfig, LangsError> {
1818
file_util::lock!(projects_dir);
1919

20-
let mut course_configs = BTreeMap::new();
20+
let mut course_configs = HashMap::new();
2121
for file in WalkDir::new(projects_dir).min_depth(1).max_depth(1) {
2222
let file = file?;
2323
let course_config_path = file.path().join("course_config.toml");
@@ -99,6 +99,16 @@ impl ProjectsConfig {
9999
.flatten()
100100
.map(|e| e.1)
101101
}
102+
103+
/// Note: does not save the config on initialization.
104+
pub fn get_or_init_course_config(&mut self, course_name: String) -> &mut CourseConfig {
105+
self.courses
106+
.entry(course_name.clone())
107+
.or_insert(CourseConfig {
108+
course: course_name,
109+
exercises: BTreeMap::new(),
110+
})
111+
}
102112
}
103113

104114
/// A course configuration file. Contains information of all of the exercises of this course in the projects directory.
@@ -121,6 +131,11 @@ impl CourseConfig {
121131
file_util::write_to_file(s.as_bytes(), &target)?;
122132
Ok(())
123133
}
134+
135+
pub fn add_exercise(&mut self, exercise_name: String, id: usize, checksum: String) {
136+
let exercise = ProjectsDirExercise { id, checksum };
137+
self.exercises.insert(exercise_name, exercise);
138+
}
124139
}
125140

126141
/// An exercise in the projects directory.

tmc-langs/src/data.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -164,16 +164,15 @@ pub enum DownloadResult {
164164
},
165165
}
166166

167-
pub enum DownloadTarget {
168-
Template {
169-
target: ExerciseDownload,
170-
checksum: String,
171-
},
172-
Submission {
173-
target: ExerciseDownload,
174-
submission_id: usize,
175-
checksum: String,
176-
},
167+
pub struct DownloadTarget {
168+
pub target: ExerciseDownload,
169+
pub checksum: String,
170+
pub kind: DownloadTargetKind,
171+
}
172+
173+
pub enum DownloadTargetKind {
174+
Template,
175+
Submission { submission_id: usize },
177176
}
178177

179178
#[derive(Debug, Clone, Serialize, JsonSchema)]

tmc-langs/src/lib.rs

Lines changed: 52 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub use crate::data::{
2121
pub use crate::error::{LangsError, ParamError};
2222
pub use crate::submission_packaging::prepare_submission;
2323
pub use crate::submission_processing::prepare_solution;
24+
use data::DownloadTargetKind;
2425
use hmac::{Hmac, NewMac};
2526
use serde::Serialize;
2627
use sha2::Sha256;
@@ -176,7 +177,7 @@ pub fn download_or_update_course_exercises(
176177
);
177178

178179
let exercises_details = client.get_exercises_details(exercises)?;
179-
let mut projects_config = ProjectsConfig::load(projects_dir)?;
180+
let projects_config = ProjectsConfig::load(projects_dir)?;
180181

181182
// separate exercises into downloads and skipped
182183
let mut to_be_downloaded = vec![];
@@ -220,30 +221,33 @@ pub fn download_or_update_course_exercises(
220221
.max_by_key(|s| s.created_at)
221222
{
222223
// previous submission found
223-
to_be_downloaded.push(DownloadTarget::Submission {
224+
to_be_downloaded.push(DownloadTarget {
224225
target: ExerciseDownload {
225226
id: exercise_detail.id,
226227
course_slug: exercise_detail.course_name,
227228
exercise_slug: exercise_detail.exercise_name,
228229
path: target,
229230
},
230-
submission_id: latest_submission.id,
231231
checksum: exercise_detail.checksum,
232+
kind: DownloadTargetKind::Submission {
233+
submission_id: latest_submission.id,
234+
},
232235
});
233236
continue;
234237
}
235238
}
236239
}
237240

238241
// not skipped, either not on disk or no previous submissions, downloading template
239-
to_be_downloaded.push(DownloadTarget::Template {
242+
to_be_downloaded.push(DownloadTarget {
240243
target: ExerciseDownload {
241244
id: exercise_detail.id,
242245
course_slug: exercise_detail.course_name.clone(),
243246
exercise_slug: exercise_detail.exercise_name.clone(),
244247
path: target,
245248
},
246249
checksum: exercise_detail.checksum,
250+
kind: DownloadTargetKind::Template,
247251
});
248252
}
249253

@@ -259,9 +263,12 @@ pub fn download_or_update_course_exercises(
259263
let thread_count = to_be_downloaded.len().min(4); // max 4 threads
260264
let mut handles = vec![];
261265
let exercises = Arc::new(Mutex::new(to_be_downloaded));
266+
let projects_config = Arc::new(Mutex::new(projects_config));
262267
for _thread_id in 0..thread_count {
263268
let client = client.clone();
264269
let exercises = Arc::clone(&exercises);
270+
let projects_config = Arc::clone(&projects_config);
271+
let projects_dir = projects_dir.to_path_buf();
265272

266273
// each thread returns either a list of successful downloads, or a tuple of successful downloads and errors
267274
type ThreadErr = (Vec<DownloadTarget>, Vec<(DownloadTarget, LangsError)>);
@@ -284,41 +291,35 @@ pub fn download_or_update_course_exercises(
284291
let exercise_download_result = || -> Result<(), LangsError> {
285292
let zip_file = file_util::named_temp_file()?;
286293

287-
let target_exercise = match download_target {
288-
DownloadTarget::Template { ref target, .. } => target,
289-
DownloadTarget::Submission { ref target, .. } => target,
290-
};
291294
progress_reporter::progress_stage::<ClientUpdateData>(
292295
format!(
293296
"Downloading exercise {} to '{}'",
294-
target_exercise.id,
295-
target_exercise.path.display(),
297+
download_target.target.id,
298+
download_target.target.path.display(),
296299
),
297300
Some(ClientUpdateData::ExerciseDownload {
298-
id: target_exercise.id,
299-
path: target_exercise.path.clone(),
301+
id: download_target.target.id,
302+
path: download_target.target.path.clone(),
300303
}),
301304
);
302305

303-
match &download_target {
304-
DownloadTarget::Template { target, .. } => {
305-
client.download_exercise(target.id, zip_file.path())?;
306-
extract_project(zip_file, &target.path, false)?;
306+
// execute download based on type
307+
match &download_target.kind {
308+
DownloadTargetKind::Template => {
309+
client.download_exercise(download_target.target.id, zip_file.path())?;
310+
extract_project(zip_file, &download_target.target.path, false)?;
307311
}
308-
DownloadTarget::Submission {
309-
target,
310-
submission_id,
311-
..
312-
} => {
313-
client.download_exercise(target.id, zip_file.path())?;
314-
extract_project(&zip_file, &target.path, false)?;
315-
316-
let plugin = get_language_plugin(&target.path)?;
317-
let tmc_project_yml = TmcProjectYml::load_or_default(&target.path)?;
312+
DownloadTargetKind::Submission { submission_id } => {
313+
client.download_exercise(download_target.target.id, zip_file.path())?;
314+
extract_project(&zip_file, &download_target.target.path, false)?;
315+
316+
let plugin = get_language_plugin(&download_target.target.path)?;
317+
let tmc_project_yml =
318+
TmcProjectYml::load_or_default(&download_target.target.path)?;
318319
let config =
319320
plugin.get_exercise_packaging_configuration(tmc_project_yml)?;
320321
for student_file in config.student_file_paths {
321-
let student_file = target.path.join(&student_file);
322+
let student_file = download_target.target.path.join(&student_file);
322323
log::debug!("student file {}", student_file.display());
323324
if student_file.is_file() {
324325
file_util::remove_file(&student_file)?;
@@ -328,19 +329,32 @@ pub fn download_or_update_course_exercises(
328329
}
329330

330331
client.download_old_submission(*submission_id, zip_file.path())?;
331-
plugin.extract_student_files(&zip_file, &target.path)?;
332+
plugin
333+
.extract_student_files(&zip_file, &download_target.target.path)?;
332334
}
333335
}
336+
// download successful, save to course config
337+
let mut projects_config =
338+
projects_config.lock().map_err(|_| LangsError::MutexError)?; // lock mutex
339+
let course_config = projects_config
340+
.get_or_init_course_config(download_target.target.course_slug.clone());
341+
course_config.add_exercise(
342+
download_target.target.exercise_slug.clone(),
343+
download_target.target.id,
344+
download_target.checksum.clone(),
345+
);
346+
course_config.save_to_projects_dir(&projects_dir)?;
347+
drop(projects_config); // drop mutex
334348

335349
progress_reporter::progress_stage::<ClientUpdateData>(
336350
format!(
337351
"Downloaded exercise {} to '{}'",
338-
target_exercise.id,
339-
target_exercise.path.display(),
352+
download_target.target.id,
353+
download_target.target.path.display(),
340354
),
341355
Some(ClientUpdateData::ExerciseDownload {
342-
id: target_exercise.id,
343-
path: target_exercise.path.clone(),
356+
id: download_target.target.id,
357+
path: download_target.target.path.clone(),
344358
}),
345359
);
346360

@@ -365,6 +379,7 @@ pub fn download_or_update_course_exercises(
365379
handles.push(handle);
366380
}
367381

382+
// gather results from each thread
368383
let mut successful = vec![];
369384
let mut failed = vec![];
370385
for handle in handles {
@@ -377,42 +392,9 @@ pub fn download_or_update_course_exercises(
377392
}
378393
}
379394

380-
log::debug!("save updated information to the course config");
381-
// turn the downloaded exercises into a hashmap with the course as key
382-
let mut course_data = HashMap::<String, Vec<(String, String, usize)>>::new();
383-
for download_target in &successful {
384-
let (target, checksum) = match download_target {
385-
DownloadTarget::Submission {
386-
target, checksum, ..
387-
}
388-
| DownloadTarget::Template {
389-
target, checksum, ..
390-
} => (target, checksum),
391-
};
392-
let entry = course_data.entry(target.course_slug.clone());
393-
let course_exercises = entry.or_default();
394-
course_exercises.push((target.exercise_slug.clone(), checksum.clone(), target.id));
395-
}
396-
// update/create the course configs that contain downloaded or updated exercises
397-
for (course_name, exercise_names) in course_data {
398-
let exercises = exercise_names
399-
.into_iter()
400-
.map(|(name, checksum, id)| (name, ProjectsDirExercise { id, checksum }))
401-
.collect();
402-
if let Some(course_config) = projects_config.courses.get_mut(&course_name) {
403-
course_config.exercises.extend(exercises);
404-
course_config.save_to_projects_dir(projects_dir)?;
405-
} else {
406-
let course_config = CourseConfig {
407-
course: course_name,
408-
exercises,
409-
};
410-
course_config.save_to_projects_dir(projects_dir)?;
411-
};
412-
}
413-
395+
// report
414396
let finish_message = if failed.is_empty() {
415-
if successful.len() == 0 && exercises_len == 0 {
397+
if successful.is_empty() && exercises_len == 0 {
416398
"Exercises are already up-to-date!".to_string()
417399
} else {
418400
format!(
@@ -429,18 +411,10 @@ pub fn download_or_update_course_exercises(
429411
failed.len(),
430412
)
431413
};
432-
433414
progress_reporter::finish_stage::<ClientUpdateData>(finish_message, None);
434415

435-
let downloaded = successful
436-
.into_iter()
437-
.map(|t| match t {
438-
DownloadTarget::Submission { target, .. } => target,
439-
DownloadTarget::Template { target, .. } => target,
440-
})
441-
.collect();
442-
443-
// return an error if any downloads failed
416+
// return information about the downloads
417+
let downloaded = successful.into_iter().map(|t| t.target).collect();
444418
if !failed.is_empty() {
445419
// add an error trace to each failed download
446420
let failed = failed
@@ -452,11 +426,7 @@ pub fn download_or_update_course_exercises(
452426
chain.push(source.to_string());
453427
error = source;
454428
}
455-
let target = match target {
456-
DownloadTarget::Submission { target, .. }
457-
| DownloadTarget::Template { target, .. } => target,
458-
};
459-
(target, chain)
429+
(target.target, chain)
460430
})
461431
.collect();
462432
return Ok(DownloadResult::Failure {

0 commit comments

Comments
 (0)