|
1 | 1 | //! CLI client for TMC |
2 | 2 |
|
3 | 3 | mod app; |
| 4 | +mod error; |
4 | 5 | mod output; |
5 | 6 |
|
6 | 7 | use anyhow::{Context, Result}; |
7 | 8 | use clap::{ArgMatches, Error, ErrorKind}; |
| 9 | +use error::InvalidTokenError; |
8 | 10 | use output::{CombinedCourseData, DownloadTarget, ErrorData, Kind, Output, OutputResult, Status}; |
9 | 11 | use serde::Serialize; |
10 | 12 | use std::collections::HashMap; |
@@ -62,6 +64,11 @@ fn main() { |
62 | 64 | /// Goes through the error chain and checks for special error types that should be indicated by the Kind. |
63 | 65 | fn solve_error_kind(e: &anyhow::Error) -> Kind { |
64 | 66 | for cause in e.chain() { |
| 67 | + // check for invalid token |
| 68 | + if cause.downcast_ref::<InvalidTokenError>().is_some() { |
| 69 | + return Kind::InvalidToken; |
| 70 | + } |
| 71 | + |
65 | 72 | // check for http errors |
66 | 73 | if let Some(CoreError::HttpError { |
67 | 74 | url: _, |
@@ -181,7 +188,79 @@ fn run() -> Result<()> { |
181 | 188 | }; |
182 | 189 | print_output(&output)? |
183 | 190 | } |
184 | | - ("core", Some(matches)) => run_core(matches)?, |
| 191 | + ("core", Some(matches)) => { |
| 192 | + let client_name = matches.value_of("client-name").unwrap(); |
| 193 | + |
| 194 | + let client_version = matches.value_of("client-version").unwrap(); |
| 195 | + |
| 196 | + let root_url = env::var("TMC_LANGS_ROOT_URL") |
| 197 | + .unwrap_or_else(|_| "https://tmc.mooc.fi".to_string()); |
| 198 | + let mut core = TmcCore::new_in_config( |
| 199 | + root_url, |
| 200 | + client_name.to_string(), |
| 201 | + client_version.to_string(), |
| 202 | + ) |
| 203 | + .context("Failed to create TmcCore")?; |
| 204 | + |
| 205 | + // set token if a credentials.json is found for the client name |
| 206 | + let tmc_dir = format!("tmc-{}", client_name); |
| 207 | + let config_dir = match env::var("TMC_LANGS_CONFIG_DIR") { |
| 208 | + Ok(v) => PathBuf::from(v), |
| 209 | + Err(_) => dirs::config_dir().context("Failed to find config directory")?, |
| 210 | + }; |
| 211 | + let credentials_path = config_dir.join(tmc_dir).join("credentials.json"); |
| 212 | + if let Ok(file) = File::open(&credentials_path) { |
| 213 | + match serde_json::from_reader(file) { |
| 214 | + Ok(token) => core.set_token(token), |
| 215 | + Err(e) => { |
| 216 | + log::error!( |
| 217 | + "Failed to deserialize credentials.json due to \"{}\", deleting", |
| 218 | + e |
| 219 | + ); |
| 220 | + fs::remove_file(&credentials_path).with_context(|| { |
| 221 | + format!( |
| 222 | + "Failed to remove malformed credentials.json file {}", |
| 223 | + credentials_path.display() |
| 224 | + ) |
| 225 | + })?; |
| 226 | + } |
| 227 | + } |
| 228 | + }; |
| 229 | + |
| 230 | + match run_core(core, client_name, &credentials_path, matches) { |
| 231 | + Ok(token) => token, |
| 232 | + Err(error) => { |
| 233 | + for cause in error.chain() { |
| 234 | + // check if the token was rejected and delete it if so |
| 235 | + if let Some(CoreError::HttpError { status, .. }) = |
| 236 | + cause.downcast_ref::<CoreError>() |
| 237 | + { |
| 238 | + if status.as_u16() == 401 { |
| 239 | + // delete token |
| 240 | + log::info!( |
| 241 | + "deleting credentials file at {} due to error {}", |
| 242 | + credentials_path.display(), |
| 243 | + error |
| 244 | + ); |
| 245 | + fs::remove_file(&credentials_path).with_context(|| { |
| 246 | + format!( |
| 247 | + "Failed to remove credentials file at {} while handling error {}", |
| 248 | + credentials_path.display(), |
| 249 | + error |
| 250 | + ) |
| 251 | + })?; |
| 252 | + return Err(InvalidTokenError { |
| 253 | + path: credentials_path, |
| 254 | + source: error, |
| 255 | + } |
| 256 | + .into()); |
| 257 | + } |
| 258 | + } |
| 259 | + } |
| 260 | + return Err(error); |
| 261 | + } |
| 262 | + } |
| 263 | + } |
185 | 264 | ("extract-project", Some(matches)) => { |
186 | 265 | let archive_path = matches.value_of("archive-path").unwrap(); |
187 | 266 | let archive_path = Path::new(archive_path); |
@@ -494,19 +573,12 @@ fn run() -> Result<()> { |
494 | 573 | Ok(()) |
495 | 574 | } |
496 | 575 |
|
497 | | -fn run_core(matches: &ArgMatches) -> Result<PrintToken> { |
498 | | - let client_name = matches.value_of("client-name").unwrap(); |
499 | | - |
500 | | - let client_version = matches.value_of("client-version").unwrap(); |
501 | | - |
502 | | - let root_url = |
503 | | - env::var("TMC_LANGS_ROOT_URL").unwrap_or_else(|_| "https://tmc.mooc.fi".to_string()); |
504 | | - let mut core = TmcCore::new_in_config( |
505 | | - root_url, |
506 | | - client_name.to_string(), |
507 | | - client_version.to_string(), |
508 | | - ) |
509 | | - .context("Failed to create TmcCore")?; |
| 576 | +fn run_core( |
| 577 | + mut core: TmcCore, |
| 578 | + client_name: &str, |
| 579 | + credentials_path: &Path, |
| 580 | + matches: &ArgMatches, |
| 581 | +) -> Result<PrintToken> { |
510 | 582 | // set progress report to print the updates to stdout as JSON |
511 | 583 | core.set_progress_report(|update| { |
512 | 584 | // convert to output |
@@ -553,31 +625,6 @@ fn run_core(matches: &ArgMatches) -> Result<PrintToken> { |
553 | 625 | Ok(()) |
554 | 626 | }); |
555 | 627 |
|
556 | | - // set token if a credentials.json is found for the client name |
557 | | - let tmc_dir = format!("tmc-{}", client_name); |
558 | | - let config_dir = match env::var("TMC_LANGS_CONFIG_DIR") { |
559 | | - Ok(v) => PathBuf::from(v), |
560 | | - Err(_) => dirs::config_dir().context("Failed to find config directory")?, |
561 | | - }; |
562 | | - let credentials_path = config_dir.join(tmc_dir).join("credentials.json"); |
563 | | - if let Ok(file) = File::open(&credentials_path) { |
564 | | - match serde_json::from_reader(file) { |
565 | | - Ok(token) => core.set_token(token), |
566 | | - Err(e) => { |
567 | | - log::error!( |
568 | | - "Failed to deserialize credentials.json due to \"{}\", deleting", |
569 | | - e |
570 | | - ); |
571 | | - fs::remove_file(&credentials_path).with_context(|| { |
572 | | - format!( |
573 | | - "Failed to remove malformed credentials.json file {}", |
574 | | - credentials_path.display() |
575 | | - ) |
576 | | - })?; |
577 | | - } |
578 | | - } |
579 | | - }; |
580 | | - |
581 | 628 | // proof of having printed the output |
582 | 629 | let printed: PrintToken = match matches.subcommand() { |
583 | 630 | ("download-model-solution", Some(matches)) => { |
|
0 commit comments