From 9fee445ef9afdc934b80b6777f8d0a451ece0e90 Mon Sep 17 00:00:00 2001 From: Araozu Date: Tue, 21 Nov 2023 12:56:11 -0500 Subject: [PATCH] [BE][Scans] Differentiate between QR not found & errors. Convert scans without QR into PDF --- backend/src/controller/scans/mod.rs | 121 +++++++++++++++------------- frontend/src/Scans/ScansList.tsx | 49 ++++++----- frontend/src/Scans/index.tsx | 8 +- 3 files changed, 95 insertions(+), 83 deletions(-) diff --git a/backend/src/controller/scans/mod.rs b/backend/src/controller/scans/mod.rs index a1959f0..05fd1ad 100644 --- a/backend/src/controller/scans/mod.rs +++ b/backend/src/controller/scans/mod.rs @@ -1,3 +1,4 @@ +use crate::model::register::ScanData; use crate::{json_result::JsonResult, model::register::Register}; use rocket::{http::Status, serde::json::Json}; use serde::{Deserialize, Serialize}; @@ -13,20 +14,26 @@ const SCAN_PATH: &str = "/srv/srv/shares/eegsac/ESCANEOS/"; /// Represents the result of parsing an eegsac URL #[derive(Serialize, Deserialize)] pub enum ScanInfo { - /// The url has both DNI & id + /// The url has both DNI & id. + /// The fields are: DNI, id, timestamp Full(String, i32, String), /// The url only has a DNI + /// The fields are: DNI, timestamp Partial(String, String), - /// Te url is invalid + /// There was an error getting the QR code. + /// The field is: timestamp + Empty(String), + /// The url is invalid + /// The field is: reason Error(String), } #[get("/scans/detect")] -pub async fn detect_scans() -> (Status, Json>>) { +pub async fn detect_scans() -> (Status, Json>>) { let files = match get_valid_files() { Ok(f) => f, Err(err) => { - eprintln!("Error getting valid files: {:?}", err); + log::error!("Error getting valid files: {:?}", err); return (Status::InternalServerError, JsonResult::err(err)); } }; @@ -42,19 +49,8 @@ pub fn convert_scans_options() -> Status { } #[post("/scans/convert", format = "json", data = "")] -pub async fn convert_scans( - data: Json>>, -) -> (Status, Json>) { +pub async fn convert_scans(data: Json>) -> (Status, Json>) { let data = data.into_inner(); - let data = match data { - JsonResult::Ok(v) => v, - JsonResult::Error(err) => { - return ( - Status::BadRequest, - JsonResult::err("Se recibiĆ³ un error.".into()), - ) - } - }; match convert_scans_from_data(&data).await { Ok(_) => (Status::Ok, JsonResult::ok(())), @@ -68,32 +64,18 @@ fn get_valid_files() -> Result, String> { let paths = match fs::read_dir(SCAN_PATH) { Ok(paths) => paths, Err(err) => { - eprintln!("Error reading dir: {:?}", err); + log::error!("Error reading dir: {:?}", err); return Err("Error leyendo directorio".into()); } }; let mut result = Vec::::new(); - /* - TODO: The system will detect info from an image. - If successful, that image will be renamed to: - eeg_.jpg - and the timestamp will be sent to the frontend - along with the detected info. - - Then the frontend will send back the list of timestamps & info - and the backend will do the conversion. - - This way, we can ensure that only the images sent to & from the FE - can be converted. - */ - for p in paths { let p = match p { Ok(p) => p, Err(err) => { - eprintln!("Error reading path: {:?}", err); + log::error!("Error reading path: {:?}", err); return Err("Error recuperando archivo de lista de archivos".into()); } }; @@ -107,7 +89,7 @@ fn get_valid_files() -> Result, String> { let filename = match file_path.file_name() { Some(filename) => filename, None => { - eprintln!("Error getting file name: {:?}", file_path); + log::error!("Error getting file name: {:?}", file_path); return Err("Error leyendo archivo en directorio".into()); } }; @@ -115,12 +97,12 @@ fn get_valid_files() -> Result, String> { let filename = match filename.to_str() { Some(p) => p, None => { - eprintln!("Error getting file as string: {:?}", p); + log::error!("Error getting file as string: {:?}", p); return Err("Error leyendo nombre de archivo de escaneo".into()); } }; - if filename.ends_with(".jpg") && filename.starts_with("eeg") { + if filename.starts_with("eeg") && filename.ends_with(".jpg") { result.push(file_path); }; } @@ -129,11 +111,8 @@ fn get_valid_files() -> Result, String> { } /// Detects the QR code data from every file -fn get_details_from_paths(paths: Vec) -> Vec<(PathBuf, ScanInfo)> { - paths - .into_iter() - .map(|path| (path.clone(), get_image_info(path))) - .collect() +fn get_details_from_paths(paths: Vec) -> Vec { + paths.into_iter().map(|path| get_image_info(path)).collect() } fn get_image_info(path: PathBuf) -> ScanInfo { @@ -144,7 +123,7 @@ fn get_image_info(path: PathBuf) -> ScanInfo { let current_time = match current_time { Ok(t) => t, Err(err) => { - eprintln!("Error obteniendo hora actual: {:?}", err); + log::error!("Error obteniendo hora actual: {:?}", err); return ScanInfo::Error("Error obteniendo hora actual: TIME WENT BACKWARDS.".into()); } }; @@ -161,21 +140,35 @@ fn get_image_info(path: PathBuf) -> ScanInfo { // crop image let cropped_img = img.crop_imm(0, third_point_height, mid_point_width, remaining_height); - // TODO: threshold image before getting qr + // + // TODO: threshold cropped image before getting qr + // // get qr from cropped image let results = bardecoder::default_decoder().decode(&cropped_img); if results.is_empty() { - eprintln!("QR not found"); - return ScanInfo::Error("QR no encontrado.".into()); + log::info!("QR not found"); + + // Rename file + let mut new_path = path.clone(); + new_path.set_file_name(format!("eeg_{}.jpg", current_ms)); + + return match fs::rename(path, new_path) { + Ok(_) => ScanInfo::Empty(current_ms.to_string()), + Err(err) => { + log::error!("Error renombrando archivo: {:?}", err); + + ScanInfo::Error("Error renombrando archivo.".into()) + } + } } let url = match &results[0] { Ok(url) => url, Err(reason) => { - eprintln!("Error decoding qr: {:?}", reason); - return ScanInfo::Error("QR no encontrado.".into()); + log::error!("Error decoding qr: {:?}", reason); + return ScanInfo::Error("Error recuperando QR.".into()); } }; @@ -206,7 +199,7 @@ fn get_image_info(path: PathBuf) -> ScanInfo { match fs::rename(path, new_path) { Ok(_) => ScanInfo::Full(dni, v, current_ms.to_string()), Err(err) => { - eprintln!("Error renombrando archivo: {:?}", err); + log::error!("Error renombrando archivo: {:?}", err); ScanInfo::Error("Error renombrando archivo.".into()) } @@ -223,7 +216,7 @@ fn get_image_info(path: PathBuf) -> ScanInfo { match fs::rename(path, new_path) { Ok(_) => ScanInfo::Partial(url.chars().skip(31).collect(), current_ms.to_string()), Err(err) => { - eprintln!("Error renombrando archivo: {:?}", err); + log::error!("Error renombrando archivo: {:?}", err); ScanInfo::Error("Error renombrando archivo.".into()) } @@ -233,10 +226,10 @@ fn get_image_info(path: PathBuf) -> ScanInfo { } // TODO: this should return a list of files that succeeded & failed -async fn convert_scans_from_data(data: &Vec<(PathBuf, ScanInfo)>) -> Result<(), String> { +async fn convert_scans_from_data(data: &Vec) -> Result<(), String> { let (ids, dnis) = data.iter().fold( (vec!["-1".to_string()], vec!["''".to_string()]), - |mut acc, (_, next)| { + |mut acc, next| { match next { ScanInfo::Full(_, id, _) => acc.0.push(id.to_string()), ScanInfo::Partial(dni, _) => acc.1.push(format!("'{}'", dni)), @@ -258,22 +251,21 @@ async fn convert_scans_from_data(data: &Vec<(PathBuf, ScanInfo)>) -> Result<(), let persons_info = match persons_info { Ok(v) => v, Err(err) => { - eprintln!("Error getting persons info: {:?}", err); + log::error!("Error getting persons info: {:?}", err); return Err("Error obteniendo informaciĆ³n de personas.".into()); } }; // Match the results from DB with the data from the frontend, // & convert the files to PDFs - - for (_, info) in data { + for info in data { let (person_info, timestamp) = match info { ScanInfo::Full(dni, id, timestamp) => { let person_info = persons_info.iter().find(|e| e.register_id == *id); match person_info { - Some(p) => (p, timestamp), + Some(p) => (p.clone(), timestamp), None => { - log::error!("Register not found in data from DB: {} {}", id, dni); + log::error!("Register not found in data from DB: id {}, dni {}", id, dni); continue; } } @@ -283,13 +275,26 @@ async fn convert_scans_from_data(data: &Vec<(PathBuf, ScanInfo)>) -> Result<(), .iter() .find(|e| e.person_dni == *dni && e.register_id == 0); match person_info { - Some(p) => (p, timestamp), + Some(p) => (p.clone(), timestamp), None => { - log::error!("Register not found in data from DB: {}", dni); + log::error!("Register not found in data from DB: dni {}", dni); continue; } } } + ScanInfo::Empty(timestamp) => { + // This is dummy data, to convert files without QR + let scan_data = ScanData { + course_name: "pendiente".into(), + person_names: "EEG".into(), + person_paternal_surname: "".into(), + person_maternal_surname: "".into(), + person_dni: "".into(), + register_id: 0, + }; + + (scan_data, timestamp) + } ScanInfo::Error(reason) => { log::info!("Found an error while matching data with DB: {}", reason); continue; @@ -323,7 +328,7 @@ async fn convert_scans_from_data(data: &Vec<(PathBuf, ScanInfo)>) -> Result<(), Ok(_) => { log::info!("Converted file: {}", filename); // Delete image - match fs::remove_file(image_path) { + match fs::remove_file(image_path) { Ok(_) => log::info!("Deleted image for: {}", filename), Err(err) => { log::error!("Error deleting image for {}: {:?}", filename, err); diff --git a/frontend/src/Scans/ScansList.tsx b/frontend/src/Scans/ScansList.tsx index d006645..6901c18 100644 --- a/frontend/src/Scans/ScansList.tsx +++ b/frontend/src/Scans/ScansList.tsx @@ -1,5 +1,5 @@ -import { For, Show, createMemo } from "solid-js"; -import { ScanData, ScanResult } from "."; +import { Show, createMemo } from "solid-js"; +import { ScanResult } from "."; import { FilledButton } from "../components/FilledButton"; import { FilledCard } from "../components/FilledCard"; import { LoadingIcon } from "../icons/LoadingIcon"; @@ -7,33 +7,35 @@ import { PDFIcon } from "../icons/PDFIcon"; import { LoadingStatus, useLoading } from "../utils/functions"; import { QuestionIcon } from "../icons/QuestionIcon"; -export function ScansList(props: {scanData: ScanData | null}) { +export function ScansList(props: {scanData: Array | null}) { const {status, setStatus, error, setError} = useLoading(); const loading = createMemo(() => status() === LoadingStatus.Loading); - const empty = createMemo(() => props.scanData?.Ok.length === 0); + const empty = createMemo(() => props.scanData?.length === 0); const entriesCount = createMemo(() => { if (props.scanData === null) return [0, 0]; let fullCount = 0; let partialCount = 0; - let invalidCount = 0; + let emptyCount = 0; - for (const [, result] of props.scanData.Ok) { - if ("Full" in result) { + for (const entry of props.scanData) { + if ("Full" in entry) { fullCount += 1; - } else if ("Partial" in result) { + } else if ("Partial" in entry) { partialCount += 1; - } else { - invalidCount += 1; + } else if ("Empty" in entry) { + emptyCount += 1; } } - return [fullCount, partialCount, invalidCount]; + return [fullCount, partialCount, emptyCount]; }); + const erroredEntries = createMemo(() => (props.scanData?.filter((entry) => "Error" in entry) ?? []) as Array<{Error: string}>); + const convertScans = () => { setStatus(LoadingStatus.Loading); @@ -43,7 +45,6 @@ export function ScansList(props: {scanData: ScanData | null}) { return; } - fetch( `${import.meta.env.VITE_BACKEND_URL}/api/scans/convert`, { @@ -100,6 +101,20 @@ export function ScansList(props: {scanData: ScanData | null}) {

+ 0}> +
+

+ Errores: +

+
    + {erroredEntries().map((entry) => ( +
  • + {entry.Error} +
  • + ))} +
+
+
@@ -146,13 +161,3 @@ export function ScansList(props: {scanData: ScanData | null}) { ); } - -function dataFromScanResult(result: ScanResult) { - if ("Full" in result) { - return `DNI: ${result.Full[0]}, iid: ${result.Full[1]}`; - } else if ("Partial" in result) { - return `DNI: ${result.Partial[0]}`; - } else if ("Error" in result) { - return `Error: ${result.Error}`; - } -} diff --git a/frontend/src/Scans/index.tsx b/frontend/src/Scans/index.tsx index 8f56939..f0e5e71 100644 --- a/frontend/src/Scans/index.tsx +++ b/frontend/src/Scans/index.tsx @@ -11,19 +11,20 @@ import { ScansList } from "./ScansList"; * Serialization & Deserialization is done by the backend. */ export interface ScanData { - Ok: Array<[string, ScanResult]>, + Ok: Array, } export type ScanResult = | {Full: [string, number, string]} | {Partial: [string, string]} +| {Empty: string} | {Error: string}; export function Scans() { const {status, setStatus, error, setError} = useLoading(); - const [scanData, setScanData] = createSignal(null); + const [scanData, setScanData] = createSignal | null>(null); const loading = createMemo(() => status() === LoadingStatus.Loading); @@ -35,11 +36,12 @@ export function Scans() { .then((res) => res.json()) .then((data) => { setStatus(LoadingStatus.Ok); - setScanData(data); + setScanData(data.Ok); }) .catch((err) => { setStatus(LoadingStatus.Error); console.error(err); + setError(err.Error); }); };