Skip to content

Commit f5f7c35

Browse files
committed
added migrate, do maintenance on course configs on load
1 parent 9fb3fc6 commit f5f7c35

File tree

3 files changed

+189
-83
lines changed

3 files changed

+189
-83
lines changed

tmc-langs-cli/src/app.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,38 @@ fn create_settings_app() -> App<'static, 'static> {
703703
.subcommand(
704704
SubCommand::with_name("list").about("Prints every key=value pair in the settings file"),
705705
)
706+
.subcommand(
707+
SubCommand::with_name("migrate")
708+
.about("Migrates an exercise on disk into the langs project directory")
709+
.arg(
710+
Arg::with_name("exercise-path")
711+
.help("Path to the directory where the project resides.")
712+
.long("exercise-path")
713+
.required(true)
714+
.takes_value(true),
715+
)
716+
.arg(
717+
Arg::with_name("course-slug")
718+
.help("The course slug, e.g. mooc-java-programming-i.")
719+
.long("course-slug")
720+
.required(true)
721+
.takes_value(true),
722+
)
723+
.arg(
724+
Arg::with_name("exercise-slug")
725+
.help("The exercise slug, e.g. part01-Part01_01.Sandbox.")
726+
.long("exercise-slug")
727+
.required(true)
728+
.takes_value(true),
729+
)
730+
.arg(
731+
Arg::with_name("exercise-checksum")
732+
.help("The checksum of the exercise from the TMC server.")
733+
.long("exercise-checksum")
734+
.required(true)
735+
.takes_value(true),
736+
),
737+
)
706738
.subcommand(
707739
SubCommand::with_name("move-projects-dir")
708740
.about(

tmc-langs-cli/src/config/projects_config.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ pub struct ProjectsConfig {
1111
}
1212

1313
impl ProjectsConfig {
14-
pub fn load(path: &Path) -> Result<ProjectsConfig> {
14+
pub fn load(projects_dir: &Path) -> Result<ProjectsConfig> {
1515
let mut course_configs = BTreeMap::new();
16-
for file in fs::read_dir(path)
17-
.with_context(|| format!("Failed to read directory at {}", path.display()))?
16+
for file in fs::read_dir(projects_dir)
17+
.with_context(|| format!("Failed to read directory at {}", projects_dir.display()))?
1818
{
1919
let file =
20-
file.with_context(|| format!("Failed to read file in {}", path.display()))?;
20+
file.with_context(|| format!("Failed to read file in {}", projects_dir.display()))?;
2121
let course_config_path = file.path().join("course_config.toml");
2222
if course_config_path.exists() {
2323
let file_name = file.file_name();
@@ -36,10 +36,33 @@ impl ProjectsConfig {
3636
log::warn!(
3737
"File or directory {} with no config file found while loading projects from {}",
3838
file.path().display(),
39-
path.display()
39+
projects_dir.display()
4040
);
4141
}
4242
}
43+
44+
// maintenance: check that the exercises in the config actually exist on disk
45+
// if any are found that do not, update the course config file accordingly
46+
for (_, course_config) in course_configs.iter_mut() {
47+
let mut deleted_exercises = vec![];
48+
for exercise_name in course_config.exercises.keys() {
49+
let expected_dir = Self::get_exercise_download_target(
50+
projects_dir,
51+
&course_config.course,
52+
&exercise_name,
53+
);
54+
if !expected_dir.exists() {
55+
deleted_exercises.push(exercise_name.clone());
56+
}
57+
}
58+
for deleted_exercise in &deleted_exercises {
59+
course_config.exercises.remove(deleted_exercise).unwrap(); // cannot fail
60+
}
61+
if !deleted_exercises.is_empty() {
62+
// if any exercises were deleted, save the course config
63+
course_config.save_to_projects_dir(projects_dir)?;
64+
}
65+
}
4366
Ok(Self {
4467
courses: course_configs,
4568
})

tmc-langs-cli/src/main.rs

Lines changed: 129 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -842,9 +842,7 @@ fn run_core(
842842
.collect::<Result<_>>()?;
843843
let exercises_details = client.get_exercises_details(exercises)?;
844844

845-
let tmc_config = TmcConfig::load(client_name)?;
846-
847-
let projects_dir = tmc_config.projects_dir;
845+
let projects_dir = TmcConfig::load(client_name)?.projects_dir;
848846
let mut projects_config = ProjectsConfig::load(&projects_dir)?;
849847

850848
let mut course_data = HashMap::<String, Vec<(String, String)>>::new();
@@ -1519,6 +1517,53 @@ fn run_settings(
15191517
});
15201518
print_output(&output, pretty, warnings)
15211519
}
1520+
("migrate", Some(matches)) => {
1521+
let exercise_path = matches.value_of("exercise-path").unwrap();
1522+
let exercise_path = Path::new(exercise_path);
1523+
1524+
let course_slug = matches.value_of("course-slug").unwrap();
1525+
1526+
let exercise_slug = matches.value_of("exercise-slug").unwrap();
1527+
1528+
let exercise_checksum = matches.value_of("exercise-checksum").unwrap();
1529+
1530+
let mut projects_config = ProjectsConfig::load(&tmc_config.projects_dir)?;
1531+
let course_config = projects_config
1532+
.courses
1533+
.entry(course_slug.to_string())
1534+
.or_insert(CourseConfig {
1535+
course: course_slug.to_string(),
1536+
exercises: BTreeMap::new(),
1537+
});
1538+
1539+
let target_dir = ProjectsConfig::get_exercise_download_target(
1540+
&tmc_config.projects_dir,
1541+
course_slug,
1542+
exercise_slug,
1543+
);
1544+
if target_dir.exists() {
1545+
anyhow::bail!("Tried to migrate exercise to {}; however, something already exists at that path.", target_dir.display());
1546+
}
1547+
1548+
course_config.exercises.insert(
1549+
exercise_slug.to_string(),
1550+
Exercise {
1551+
checksum: exercise_checksum.to_string(),
1552+
},
1553+
);
1554+
1555+
move_dir(exercise_path, &target_dir, pretty)?;
1556+
course_config.save_to_projects_dir(&tmc_config.projects_dir)?;
1557+
1558+
let output = Output::<()>::OutputData(OutputData {
1559+
status: Status::Finished,
1560+
result: OutputResult::ExecutedCommand,
1561+
message: Some("Migrated exercise".to_string()),
1562+
percent_done: 1.0,
1563+
data: None,
1564+
});
1565+
print_output(&output, pretty, warnings)
1566+
}
15221567
("move-projects-dir", Some(matches)) => {
15231568
let dir = matches.value_of("dir").unwrap();
15241569
let target = PathBuf::from(dir);
@@ -1547,84 +1592,10 @@ fn run_settings(
15471592
)
15481593
}
15491594

1550-
let reporter = ProgressReporter::new(move |update| {
1551-
let output = Output::StatusUpdate::<()>(update);
1552-
print_output(&output, pretty, &[])?;
1553-
Ok(())
1554-
});
1555-
1556-
reporter
1557-
.progress("Moving projects-dir", 0.0, None)
1558-
.map_err(|e| anyhow::anyhow!(e))?;
1559-
15601595
let old_projects_dir = tmc_config.set_projects_dir(target.clone())?;
1561-
let mut file_count_copied = 0;
1562-
let mut file_count_total = 0;
1563-
for entry in WalkDir::new(&old_projects_dir) {
1564-
let entry = entry.with_context(|| {
1565-
format!("Failed to read file inside {}", old_projects_dir.display())
1566-
})?;
1567-
if entry.path().is_file() {
1568-
file_count_total += 1;
1569-
}
1570-
}
1571-
for entry in WalkDir::new(&old_projects_dir).contents_first(true) {
1572-
let entry = entry.with_context(|| {
1573-
format!("Failed to read file inside {}", old_projects_dir.display())
1574-
})?;
1575-
let entry_path = entry.path();
1576-
1577-
if entry_path.is_file() {
1578-
let relative = entry_path.strip_prefix(&old_projects_dir).unwrap();
1579-
let target_path = target.join(relative);
1580-
log::debug!(
1581-
"Moving {} -> {}",
1582-
entry_path.display(),
1583-
target_path.display()
1584-
);
1585-
1586-
// create parent dir for target and copy it, remove source file after
1587-
if let Some(parent) = target_path.parent() {
1588-
fs::create_dir_all(parent).with_context(|| {
1589-
format!("Failed to create directory at {}", parent.display())
1590-
})?;
1591-
}
1592-
fs::copy(entry_path, &target_path).with_context(|| {
1593-
format!(
1594-
"Failed to copy file from {} to {}",
1595-
entry_path.display(),
1596-
target_path.display()
1597-
)
1598-
})?;
1599-
fs::remove_file(entry_path).with_context(|| {
1600-
format!(
1601-
"Failed to remove file at {} after copying it",
1602-
entry_path.display()
1603-
)
1604-
})?;
1605-
1606-
file_count_copied += 1;
1607-
reporter
1608-
.progress(
1609-
format!("Moved file {} / {}", file_count_copied, file_count_total),
1610-
file_count_copied as f64 / file_count_total as f64,
1611-
None,
1612-
)
1613-
.map_err(|e| anyhow::anyhow!(e))?;
1614-
} else if entry_path.is_dir() {
1615-
log::debug!("Deleting {}", entry_path.display());
1616-
fs::remove_dir(entry_path).with_context(|| {
1617-
format!("Failed to remove directory at {}", entry_path.display())
1618-
})?;
1619-
}
1620-
}
1621-
1596+
move_dir(&old_projects_dir, &target, pretty)?;
16221597
tmc_config.save(client_name)?;
16231598

1624-
reporter
1625-
.finish_step("Finished moving project directory", None)
1626-
.map_err(|e| anyhow::anyhow!(e))?;
1627-
16281599
let output = Output::<()>::OutputData(OutputData {
16291600
status: Status::Finished,
16301601
result: OutputResult::ExecutedCommand,
@@ -1855,4 +1826,84 @@ fn json_to_toml(json: JsonValue) -> Result<TomlValue> {
18551826
}
18561827
}
18571828

1829+
fn move_dir(source: &Path, target: &Path, pretty: bool) -> anyhow::Result<()> {
1830+
let reporter = ProgressReporter::new(move |update| {
1831+
let output = Output::StatusUpdate::<()>(update);
1832+
print_output(&output, pretty, &[])?;
1833+
Ok(())
1834+
});
1835+
1836+
reporter
1837+
.progress(
1838+
format!("Moving dir {} -> {}", source.display(), target.display()),
1839+
0.0,
1840+
None,
1841+
)
1842+
.map_err(|e| anyhow::anyhow!(e))?;
1843+
1844+
let mut file_count_copied = 0;
1845+
let mut file_count_total = 0;
1846+
for entry in WalkDir::new(source) {
1847+
let entry =
1848+
entry.with_context(|| format!("Failed to read file inside {}", source.display()))?;
1849+
if entry.path().is_file() {
1850+
file_count_total += 1;
1851+
}
1852+
}
1853+
for entry in WalkDir::new(source).contents_first(true) {
1854+
let entry =
1855+
entry.with_context(|| format!("Failed to read file inside {}", source.display()))?;
1856+
let entry_path = entry.path();
1857+
1858+
if entry_path.is_file() {
1859+
let relative = entry_path.strip_prefix(source).unwrap();
1860+
let target_path = target.join(relative);
1861+
log::debug!(
1862+
"Moving {} -> {}",
1863+
entry_path.display(),
1864+
target_path.display()
1865+
);
1866+
1867+
// create parent dir for target and copy it, remove source file after
1868+
if let Some(parent) = target_path.parent() {
1869+
fs::create_dir_all(parent).with_context(|| {
1870+
format!("Failed to create directory at {}", parent.display())
1871+
})?;
1872+
}
1873+
fs::copy(entry_path, &target_path).with_context(|| {
1874+
format!(
1875+
"Failed to copy file from {} to {}",
1876+
entry_path.display(),
1877+
target_path.display()
1878+
)
1879+
})?;
1880+
fs::remove_file(entry_path).with_context(|| {
1881+
format!(
1882+
"Failed to remove file at {} after copying it",
1883+
entry_path.display()
1884+
)
1885+
})?;
1886+
1887+
file_count_copied += 1;
1888+
reporter
1889+
.progress(
1890+
format!("Moved file {} / {}", file_count_copied, file_count_total),
1891+
file_count_copied as f64 / file_count_total as f64,
1892+
None,
1893+
)
1894+
.map_err(|e| anyhow::anyhow!(e))?;
1895+
} else if entry_path.is_dir() {
1896+
log::debug!("Deleting {}", entry_path.display());
1897+
fs::remove_dir(entry_path).with_context(|| {
1898+
format!("Failed to remove directory at {}", entry_path.display())
1899+
})?;
1900+
}
1901+
}
1902+
1903+
reporter
1904+
.finish_step("Finished moving project directory", None)
1905+
.map_err(|e| anyhow::anyhow!(e))?;
1906+
Ok(())
1907+
}
1908+
18581909
struct PrintToken;

0 commit comments

Comments
 (0)