ada-judge-app 0.2.2

Native desktop app for ada-judge
//! Native desktop app for `ada-judge`

#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![deny(clippy::nursery)]
#![deny(warnings)]

use crate::{
    alert::show_alert,
    app_state::AppState,
    contests::{create_contest, delete_contest, update_contest},
    leaderboard::load_contest_leaderboard,
    login::login,
    problems::{create_problem, load_contest_problems, load_my_problems},
    register::register,
    submissions::load_problem_submissions,
    users::{load_account_data, load_user_data, logout},
};
use ada_judge_public_models::users::AdminLevel;
use contests::load_contests;
use keyring::Entry;
use problems::retest_problem_submissions;
use rfd::AsyncFileDialog;
use slint::{Model, ToSharedString, Weak};
use std::{env, error::Error, sync::Arc};
use submissions::submit;
use tokio::sync::Mutex;

mod alert;
mod app_state;
mod contests;
mod converts;
mod leaderboard;
mod login;
mod problems;
mod register;
mod submissions;
mod users;

slint::include_modules!();

async fn load_all(state: Arc<Mutex<AppState>>, weak: Weak<AJMainWindow>) {
    load_account_data(state.clone(), weak.clone()).await;
    load_contests(state.clone(), weak.clone()).await;
    let user = state.lock().await.user.clone();
    slint::invoke_from_event_loop(move || {
        if let Some(ui) = weak.upgrade() {
            let contest_index = ui.global::<AJContestState>().get_index();
            let problem_index = ui.global::<AJContestState>().get_current_problem_index() as usize;
            let all_submissions = ui.global::<AJContestState>().get_all_submissions();
            if contest_index != -1 {
                let contest_id = ui
                    .global::<AJAppState>()
                    .get_contests()
                    .iter()
                    .collect::<Vec<AJContest>>()[contest_index as usize]
                    .id as i64;
                {
                    let state = state.clone();
                    let weak = weak.clone();

                    tokio::spawn(async move {
                        if let Err(e) =
                            load_contest_problems(contest_id, state.clone(), weak.clone()).await
                        {
                            show_alert(weak.clone(), e, AJAlertType::Error);
                        }
                        let problem_id =
                            state.lock().await.problems.clone()[problem_index].id as i64;
                        if let Err(e) = load_problem_submissions(
                            problem_id,
                            all_submissions,
                            state.clone(),
                            weak.clone(),
                        )
                        .await
                        {
                            show_alert(weak.clone(), e, AJAlertType::Error);
                        }
                        if let Err(e) =
                            load_contest_leaderboard(contest_id, state.clone(), weak.clone()).await
                        {
                            show_alert(weak.clone(), e, AJAlertType::Error);
                        }
                    });
                }
            }
            if let Some(user) = user {
                ui.global::<AJAppState>().set_can_create_contests(
                    user.admin_level >= ada_judge_public_models::users::AdminLevel::Admin,
                );
                let weak = weak.clone();
                tokio::spawn(async move {
                    if user.admin_level >= AdminLevel::Admin {
                        if let Err(e) = load_my_problems(state.clone(), weak.clone()).await {
                            show_alert(weak.clone(), e, AJAlertType::Error);
                        }
                    }
                });
            }
        }
        show_alert(weak.clone(), "успешно", AJAlertType::Success);
    })
    .unwrap();
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let ui = AJMainWindow::new()?;
    let state = Arc::new(Mutex::new(AppState::default()));
    state.lock().await.base_url =
        env::var("BASE_URL").unwrap_or("https://contest.oneprog.org".into());
    let weak = ui.as_weak();
    match Entry::new("com.oneprog.ada-judge", "jwt") {
        Ok(entry) => match entry.get_password() {
            Ok(token) => {
                state.lock().await.token = token.trim_matches('"').to_string();
                load_all(state.clone(), weak.clone()).await;
                if state.lock().await.user.is_some() {
                    ui.global::<AJAppState>().set_page(3);
                }
            }
            Err(_) => {}
        },
        Err(_) => {}
    }
    ui.global::<AJAppState>()
        .set_version(env!("CARGO_PKG_VERSION").to_shared_string());
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJWelcomeCallbacks>()
            .on_login(move |login_str, password_str| {
                login(login_str, password_str, weak.clone(), state.clone());
            });
    }
    {
        let weak = weak.clone();
        let state = state.clone();
        ui.global::<AJWelcomeCallbacks>().on_register(
            move |login_str, password_str, password_confirmation_str, master_password_str| {
                register(
                    login_str,
                    password_str,
                    password_confirmation_str,
                    master_password_str,
                    state.clone(),
                    weak.clone(),
                );
            },
        );
    }
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJHomeCallbacks>()
            .on_enter_contest(move |index| {
                let state_async = state.clone();
                let weak_async = weak.clone();

                tokio::spawn(async move {
                    load_all(state_async.clone(), weak_async.clone()).await;
                });

                if let Some(ui) = weak.upgrade() {
                    ui.global::<AJAppState>().set_page(5);
                    ui.global::<AJContestState>().set_index(index);
                }
            });
    }
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJContestCallbacks>()
            .on_pick_solution_file(move || {
                let state = state.clone();
                let weak = weak.clone();

                tokio::spawn(async move {
                    match AsyncFileDialog::new().set_directory("/").pick_file().await {
                        Some(handle) => {
                            state.lock().await.solution_file = handle.path().to_path_buf();
                            slint::invoke_from_event_loop(move || {
                                if let Some(ui) = weak.upgrade() {
                                    ui.global::<AJContestState>().set_solution_picked(true);
                                }
                            })
                            .unwrap();
                        }
                        _ => (),
                    }
                });
            });
    }
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJAppCallbacks>().on_reload(move || {
            let state = state.clone();
            let weak = weak.clone();

            tokio::spawn(async move {
                load_all(state.clone(), weak.clone()).await;
            });
        });
    }
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJContestCallbacks>()
            .on_submit(move |language, contest_id, problem_id| {
                let language = language.into();

                submit(
                    contest_id as i64,
                    problem_id as i64,
                    language,
                    state.clone(),
                    weak.clone(),
                )
            })
    };
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJContestCallbacks>()
            .on_retest_problem(move |problem_id| {
                retest_problem_submissions(problem_id as i64, state.clone(), weak.clone())
            });
    }
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJHomeCallbacks>().on_create_contest(
            move |name, starts_at, ends_at, statements_url| {
                create_contest(
                    name.to_string(),
                    starts_at,
                    ends_at,
                    statements_url.to_string(),
                    state.clone(),
                    weak.clone(),
                )
            },
        );
    }
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJHomeCallbacks>().on_update_contest(
            move |id, name, starts_at, ends_at, statements_url| {
                update_contest(
                    id,
                    name.to_string(),
                    starts_at,
                    ends_at,
                    statements_url.to_string(),
                    state.clone(),
                    weak.clone(),
                )
            },
        );
    }
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJHomeCallbacks>().on_delete_contest(
            move |id, login, password, password_confirmation, deletion_confirmation| {
                delete_contest(
                    id,
                    login.to_string(),
                    password.to_string(),
                    password_confirmation.to_string(),
                    deletion_confirmation,
                    state.clone(),
                    weak.clone(),
                );
                if let Some(ui) = weak.upgrade() {
                    if id
                        == ui
                            .global::<AJAppState>()
                            .get_contests()
                            .iter()
                            .collect::<Vec<AJContest>>()
                            [ui.global::<AJContestState>().get_index() as usize]
                            .id
                    {
                        ui.global::<AJContestState>().set_index(-1);
                    }
                }
            },
        );
    }
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJProblemsCallbacks>()
            .on_pick_problem_archive(move || {
                let state = state.clone();
                let weak = weak.clone();

                tokio::spawn(async move {
                    match AsyncFileDialog::new()
                        .set_directory("/")
                        .add_filter("архив", &["zip"])
                        .pick_file()
                        .await
                    {
                        Some(handle) => {
                            state.lock().await.problem_archive = handle.path().to_path_buf();
                            slint::invoke_from_event_loop(move || {
                                if let Some(ui) = weak.upgrade() {
                                    ui.global::<AJProblemsState>().set_problem_picked(true);
                                }
                            })
                            .unwrap();
                        }
                        _ => (),
                    }
                });
            });
    }
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJProblemsCallbacks>()
            .on_create_problem(move || {
                create_problem(state.clone(), weak.clone());
            });
    }
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJHomeCallbacks>().on_logout(move || {
            logout(state.clone(), weak.clone());
        });
    }
    {
        let state = state.clone();
        let weak = weak.clone();
        ui.global::<AJContestCallbacks>()
            .on_open_profile(move |id| {
                load_user_data(id as i64, state.clone(), weak.clone());
            });
    }
    ui.run()?;
    Ok(())
}