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);
});
};