Skip to content

Commit

Permalink
feat: find class schedule command
Browse files Browse the repository at this point in the history
  • Loading branch information
albugowy15 committed Feb 11, 2024
1 parent 33f0619 commit 5127976
Show file tree
Hide file tree
Showing 14 changed files with 310 additions and 122 deletions.
14 changes: 4 additions & 10 deletions src/commands/clean.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
use tokio::try_join;

use crate::db::{
repository::{many_to_many::ManyToManyRepository, Repository},
Database,
use crate::{
commands::create_db_connection,
db::repository::{many_to_many::ManyToManyRepository, Repository},
};

pub async fn clean_handler() {
let pool = match Database::create_connection().await {
Ok(pool) => pool,
Err(e) => {
log::error!("Failed to create a db connection: {}", e);
return;
}
};
let pool = create_db_connection().await.unwrap();
log::info!("Clean up invalid foreign key");
let many_to_many_repo = ManyToManyRepository::new(&pool);

Expand Down
115 changes: 76 additions & 39 deletions src/commands/compare.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::path::PathBuf;
use std::{collections::HashMap, path::PathBuf, sync::Arc};

use anyhow::{Error, Result};
use sqlx::MySqlPool;

use crate::{
db::{
repository::{
class::{ClassFromSchedule, ClassRepository},
prepare_data, Repository,
},
Database,
commands::create_db_connection,
db::repository::{
class::{ClassFromSchedule, ClassRepository},
prepare_data, LecturerSubjectSessionMap, Repository,
},
utils::{
excel::{Excel, ScheduleParser},
Expand Down Expand Up @@ -36,28 +37,80 @@ fn compare_class(db_class: &ClassFromSchedule, excel_class: &ClassFromSchedule)
&& cmp_lecs
}

type SpawnGetSchedule =
tokio::task::JoinHandle<Result<HashMap<(String, String), ClassFromSchedule>, Error>>;
fn spawn_get_schedule(pool: &Arc<MySqlPool>) -> SpawnGetSchedule {
let cloned_pool = pool.clone();
tokio::task::spawn(async move { ClassRepository::new(&cloned_pool).get_schedule().await })
}

type SpawnPrepareData = tokio::task::JoinHandle<Result<LecturerSubjectSessionMap, Error>>;
fn spawn_prepare_data(pool: &Arc<MySqlPool>) -> SpawnPrepareData {
let cloned_pool = pool.clone();
tokio::task::spawn(async move { prepare_data(&cloned_pool).await })
}

fn compare_schedule(
excel_classes: Vec<ClassFromSchedule>,
mut db_classes: HashMap<(String, String), ClassFromSchedule>,
) -> (
Vec<ClassFromSchedule>,
Vec<ClassFromSchedule>,
Vec<(ClassFromSchedule, ClassFromSchedule)>,
) {
let (mut added, mut deleted, mut changed) = (Vec::new(), Vec::new(), Vec::new());

for excel_class in excel_classes.into_iter() {
let key = (
excel_class.subject_name.clone(),
excel_class.class_code.clone(),
);
if let Some(db_class) = db_classes.remove(&key) {
let is_same_class = compare_class(&db_class, &excel_class);
if !is_same_class {
changed.push((db_class, excel_class));
}
} else {
added.push(excel_class);
}
}
deleted.extend(db_classes.into_values());

(added, deleted, changed)
}

pub async fn compare_handler(file: &PathBuf, sheet: &str, outdir: &PathBuf) {
let pool = match Database::create_connection().await {
Ok(pool) => pool,
let pool = Arc::new(create_db_connection().await.unwrap());
log::info!("Get existing schedule from DB");

let class_repo_schedule_task = spawn_get_schedule(&pool);
let prepare_data_task = spawn_prepare_data(&pool);
let (db_classes_res, repo_data_res) =
tokio::try_join!(class_repo_schedule_task, prepare_data_task)
.map_err(|e| {
log::error!("Thread error: {}", e);
})
.unwrap();

let db_classes = match db_classes_res {
Ok(res) => res,
Err(e) => {
log::error!("Failed to create a db connection: {}", e);
log::error!("Error getting db classes: {}", e);
return;
}
};

let repo_data = match repo_data_res {
Ok(res) => res,
Err(e) => {
log::error!("Error preparing data: {}", e);
return;
}
};
log::info!("Get existing schedule from DB");
let class_repo = ClassRepository::new(&pool);
let (mut db_classes, repo_data_res) =
match tokio::try_join!(class_repo.get_schedule(), prepare_data(&pool)) {
Ok((db_classes_res, repo_data_res)) => (db_classes_res, repo_data_res),
Err(e) => {
log::error!("Error getting schedule: {}", e);
return;
}
};

log::info!("Get latest schedule from Excel");
let excel = match Excel::new(file, sheet, repo_data_res) {
Ok(excel) => excel,
let excel = match Excel::new(file, sheet) {
Ok(excel) => excel.with_repo_data(repo_data),
Err(e) => {
log::error!("Error opening excel file: {}", e);
return;
Expand All @@ -69,24 +122,8 @@ pub async fn compare_handler(file: &PathBuf, sheet: &str, outdir: &PathBuf) {
"Comparing {} classes from Excel with existing schedule",
excel_classes.len()
);
let (mut added, mut deleted, mut changed) = (Vec::new(), Vec::new(), Vec::new());
let (added, deleted, changed) = compare_schedule(excel_classes, db_classes);

for excel_class in excel_classes {
let key = (
excel_class.subject_name.clone(),
excel_class.class_code.clone(),
);
if let Some(db_class) = db_classes.get(&key) {
let is_same_class = compare_class(db_class, &excel_class);
if !is_same_class {
changed.push((db_class.clone(), excel_class.clone()));
}
db_classes.remove(&key).unwrap();
} else {
added.push(excel_class);
}
}
deleted.extend(db_classes.into_values());
log::info!(
"Detected {} changed, {} added, {} deleted class",
changed.len(),
Expand Down
26 changes: 26 additions & 0 deletions src/commands/find.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use std::path::PathBuf;

use crate::utils::excel::{Excel, FindClassSchedule};

pub async fn find_handler(file: &PathBuf, sheet: &str, subject: &str) {
log::info!("Parse class schedule from Excel");
let excel = match Excel::new(file, sheet) {
Ok(excel) => excel,
Err(e) => {
log::error!("Error opening excel file: {}", e);
return;
}
};

let class_schedule = excel.find_schedule_from_class(subject);
log::info!("Found {} schedules for {}", class_schedule.len(), subject);
for schedule in class_schedule.into_iter() {
log::info!(
"class:{}, lec_codes:{:?}, day: {}, session:{}",
schedule.class,
schedule.lecturers_code,
schedule.day,
schedule.session_start
);
}
}
26 changes: 26 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use std::path::PathBuf;

use clap::Subcommand;
use sqlx::MySqlPool;

use crate::db::Database;

pub mod clean;
pub mod compare;
pub mod find;
pub mod sync;
pub mod update;

Expand Down Expand Up @@ -49,9 +53,31 @@ pub enum Commands {
#[command(
long_about = "Removes any invalid foreign keys present in the _ClassToPlan and _ClassToLecturer tables."
)]
#[command(long_about = "Find class schedule from excel")]
Find {
#[arg(short, long, value_name = "Required for excel file path")]
file: PathBuf,

#[arg(short, long, value_name = "Required for excel sheet name")]
sheet: String,

#[arg(short, long, value_name = "Required for class subject name to find")]
course: String,
},
Clean,
#[command(
long_about = "Synchronizes the taken field in the Class table and the totalSks field in the Plan table to reflect their current values."
)]
Sync,
}

async fn create_db_connection() -> anyhow::Result<MySqlPool> {
let pool = Database::create_connection()
.await
.map_err::<anyhow::Error, _>(|e| {
log::error!("Failed to create a db connection: {}", e);
e
})?;

Ok(pool)
}
47 changes: 31 additions & 16 deletions src/commands/sync.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
use crate::db::{
repository::{class::ClassRepository, plan::PlanRepository, Repository},
Database,
use std::sync::Arc;

use sqlx::MySqlPool;

use crate::{
commands::create_db_connection,
db::repository::{class::ClassRepository, plan::PlanRepository, Repository},
};

pub async fn sync_handler() {
let pool = match Database::create_connection().await {
Ok(pool) => pool,
Err(e) => {
log::error!("Failed to create a db connection: {}", e);
return;
}
};
log::info!("Sync taken from Class");
let class_repo = ClassRepository::new(&pool);
fn sync_taken(pool: &Arc<MySqlPool>) -> tokio::task::JoinHandle<()> {
let cloned_pool = pool.clone();
tokio::task::spawn(async move {
log::info!("Sync taken from Class");
ClassRepository::new(&cloned_pool)
.sync_taken()
.await
.unwrap();
})
}

log::info!("Sync totalSks from Plan");
let plan_repo = PlanRepository::new(&pool);
fn sync_total_sks(pool: &Arc<MySqlPool>) -> tokio::task::JoinHandle<()> {
let cloned_pool = pool.clone();
tokio::task::spawn(async move {
log::info!("Sync totalSks from Plan");
PlanRepository::new(&cloned_pool)
.sync_total_sks()
.await
.unwrap();
})
}

pub async fn sync_handler() {
let pool = Arc::new(create_db_connection().await.unwrap());

if let Err(e) = tokio::try_join!(class_repo.sync_taken(), plan_repo.sync_total_sks()) {
if let Err(e) = tokio::try_join!(sync_taken(&pool), sync_total_sks(&pool)) {
log::error!("Error syncing: {}", e);
return;
}
Expand Down
Loading

0 comments on commit 5127976

Please sign in to comment.