[BE] Search ESCANEOS directory for scans & get info from qr codes

master
Araozu 2023-11-13 16:59:37 -05:00
parent 5939995477
commit 645ad1f387
3 changed files with 18047 additions and 80 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
use crate::json_result::JsonResult; use crate::json_result::JsonResult;
use printpdf::{Mm, PdfDocument}; use printpdf::{Mm, PdfDocument};
use rocket::{http::Status, serde::json::Json}; use rocket::{http::Status, serde::json::Json};
use serde::Serialize;
use std::{ use std::{
fs::{self, File}, fs::{self, File},
io::BufWriter, io::BufWriter,
@ -9,31 +10,52 @@ use std::{
const SCAN_PATH: &str = "/srv/srv/shares/eegsac/ESCANEOS/"; const SCAN_PATH: &str = "/srv/srv/shares/eegsac/ESCANEOS/";
/// Represents the result of parsing an eegsac URL
#[derive(Serialize)]
enum ScanInfo {
/// The url has both DNI & id
Full(String, i32),
/// The url only has a DNI
Partial(String),
/// Te url is invalid
Error(String),
}
#[get("/scans/detect")] #[get("/scans/detect")]
pub async fn detect_scans() -> (Status, Json<JsonResult<Vec<String>>>) { pub async fn detect_scans() -> (Status, Json<JsonResult<Vec<(PathBuf, ScanInfo)>>>) {
// TODO: Move to ENV variable? let files = match get_valid_files() {
Ok(f) => f,
Err(err) => {
eprintln!("Error getting valid files: {:?}", err);
return (Status::InternalServerError, JsonResult::err(err));
}
};
let file_details = get_details_from_paths(files);
(Status::Ok, JsonResult::ok(file_details))
}
/// Reads the SCAN_PATH directory and returns a list of file paths
/// that are valid for processing.
fn get_valid_files() -> Result<Vec<PathBuf>, String> {
let paths = match fs::read_dir(SCAN_PATH) { let paths = match fs::read_dir(SCAN_PATH) {
Ok(paths) => paths, Ok(paths) => paths,
Err(err) => { Err(err) => {
eprintln!("Error reading dir: {:?}", err); eprintln!("Error reading dir: {:?}", err);
return ( return Err("Error leyendo directorio".into());
Status::InternalServerError,
JsonResult::err("Error leyendo directorio".into()),
);
} }
}; };
let mut result = Vec::<String>::new(); let mut result = Vec::<PathBuf>::new();
for p in paths { for p in paths {
let p = match p { let p = match p {
Ok(p) => p, Ok(p) => p,
Err(err) => { Err(err) => {
eprintln!("Error reading path: {:?}", err); eprintln!("Error reading path: {:?}", err);
return ( return Err("Error recuperando archivo de lista de archivos".into());
Status::InternalServerError,
JsonResult::err("Error leyendo directorio".into()),
);
} }
}; };
@ -47,10 +69,7 @@ pub async fn detect_scans() -> (Status, Json<JsonResult<Vec<String>>>) {
Some(filename) => filename, Some(filename) => filename,
None => { None => {
eprintln!("Error getting file name: {:?}", file_path); eprintln!("Error getting file name: {:?}", file_path);
return ( return Err("Error leyendo archivo en directorio".into());
Status::InternalServerError,
JsonResult::err("Error leyendo archivo en directorio".into()),
);
} }
}; };
@ -58,28 +77,28 @@ pub async fn detect_scans() -> (Status, Json<JsonResult<Vec<String>>>) {
Some(p) => p, Some(p) => p,
None => { None => {
eprintln!("Error getting file as string: {:?}", p); eprintln!("Error getting file as string: {:?}", p);
return ( return Err("Error leyendo nombre de archivo de escaneo".into());
Status::InternalServerError,
JsonResult::err("Error leyendo directorio".into()),
);
} }
}; };
if filename.ends_with(".jpg") && filename.starts_with("eeg") { if filename.ends_with(".jpg") && filename.starts_with("eeg") {
result.push(filename.to_string()); result.push(file_path);
} }
} }
for filename in result.iter() { Ok(result)
let path = PathBuf::from(format!("{}{}", SCAN_PATH, filename));
examine_scan(&path);
}
(Status::Ok, JsonResult::ok(result))
} }
fn examine_scan(path: &PathBuf) {
/// Detects the QR code data from every file
fn get_details_from_paths(paths: Vec<PathBuf>) -> Vec<(PathBuf, ScanInfo)> {
paths
.into_iter()
.map(|path| (path.clone(), get_image_info(path)))
.collect()
}
fn get_image_info(path: PathBuf) -> ScanInfo {
let img = image::open(path).unwrap(); let img = image::open(path).unwrap();
// Get width & height // Get width & height
@ -95,31 +114,42 @@ fn examine_scan(path: &PathBuf) {
// get qr from cropped image // get qr from cropped image
let results = bardecoder::default_decoder().decode(&cropped_img); let results = bardecoder::default_decoder().decode(&cropped_img);
// TODO: get first qr, parse, query db, get info, determine filename
for result in results { let url = match &results[0] {
println!("{}", result.unwrap()); Ok(url) => url,
Err(reason) => {
eprintln!("Error decoding qr: {:?}", reason);
return ScanInfo::Error("QR no encontrado.".into());
} }
// Rotate image 90 degrees clockwise
let img = img.rotate270();
// Generate PDF
let (doc, page1, layer1) =
PdfDocument::new("PDF_Document_title", Mm(297.0), Mm(210.0), "Layer 1");
let current_layer = doc.get_page(page1).get_layer(layer1);
let pdf_image = printpdf::image::Image::from_dynamic_image(&img);
let img_transform = printpdf::ImageTransform::default();
let img_transform = printpdf::ImageTransform {
dpi: Some(200.0),
..img_transform
}; };
pdf_image.add_to_layer(current_layer, img_transform); // URL must have format `https://eegsac.com/certificado/<dni>?iid=<id>`
// or `https://eegsac.com/certificado/<dni>`
// match url.find('?') {
doc.save(&mut BufWriter::new( Some(p) => {
File::create(format!("{}output.pdf", SCAN_PATH)).unwrap(), let dni_length = p - 31;
)) let equals_pos = match url.find('=') {
.unwrap(); Some(p) => p,
None => {
return ScanInfo::Error(
"QR invalido: Tiene signo de interrogacion (?) pero no igual (=).".into(),
)
}
};
let dni = url.chars().skip(31).take(dni_length).collect::<String>();
let iid = url.chars().skip(equals_pos + 1).collect::<String>();
match iid.parse() {
Ok(v) => ScanInfo::Full(dni, v),
Err(_) => ScanInfo::Error(
"QR invalido: El iid no es un número.".into(),
),
}
}
None => {
return ScanInfo::Partial(url.chars().skip(31).collect());
}
}
} }

View File

@ -1,5 +1,6 @@
import { FilledButton } from "../components/FilledButton"; import { FilledButton } from "../components/FilledButton";
import { FilledCard } from "../components/FilledCard"; import { FilledCard } from "../components/FilledCard";
import { MagnifyingGlassIcon } from "../icons/MagnifyingGlassIcon";
export function Scans() { export function Scans() {
const detectScans = () => { const detectScans = () => {
@ -11,25 +12,6 @@ export function Scans() {
return ( return (
<div class="grid grid-cols-[25rem_25rem_auto]"> <div class="grid grid-cols-[25rem_25rem_auto]">
<div> <div>
<FilledCard>
<h1 class="py-2 px-4 font-medium text-xl mb-2">
Ruta de la carpeta "Certificados"
</h1>
<div class="px-2">
<input
class="w-full p-2 border border-c-outline rounded-md font-mono"
type="text"
value="/srv/srv/shares/eegsac/ESCANEOS/"
/>
<p class="py-2 px-2">
Si no hay ningún problema con la ruta, no es necesario cambiarla.
<br />
Esta ruta es relativa al servidor, no al cliente.
</p>
</div>
</FilledCard>
<FilledCard> <FilledCard>
<h1 class="py-2 px-4 font-medium text-xl mb-2"> <h1 class="py-2 px-4 font-medium text-xl mb-2">
Parámetros de detección Parámetros de detección
@ -88,17 +70,38 @@ export function Scans() {
Detectar escaneos Detectar escaneos
</h1> </h1>
<div class="px-4 pb-4"> <div class="px-4 pb-4">
El siguiente botón detectará los escaneos en la carpeta <p>
que cumplen con los parámetros de detección, y los mostrará El siguiente botón detectará los archivos
en una lista. que cumplen con los parámetros de detección
<br /> en la carpeta <code>ESCANEOS</code>, y los mostrará en una lista.
</p>
<p class="my-2">
La detección demora aprox. 1 segundo por cada archivo que
cumple con los parámetros.
</p>
<p class="my-2">
Si la detección demora más de 90 segundos, se cancelará.
</p>
<div class="relative">
<FilledButton <FilledButton
class="mt-2" class="mt-2"
onClick={detectScans} onClick={detectScans}
> >
<span
class="absolute top-[0.9rem] left-2 inline-block"
>
<MagnifyingGlassIcon
fill="var(--c-on-primary)"
/>
</span>
<span class="ml-5">
Detectar escaneos Detectar escaneos
</span>
</FilledButton> </FilledButton>
</div> </div>
</div>
</FilledCard> </FilledCard>
<FilledCard class="border border-c-outline overflow-hidden"> <FilledCard class="border border-c-outline overflow-hidden">
<h1 class="p-3 font-medium text-xl"> <h1 class="p-3 font-medium text-xl">