[BE] Search ESCANEOS directory for scans & get info from qr codes
This commit is contained in:
parent
5939995477
commit
645ad1f387
17934
backend/sql/educa7ls_plataforma_backup_2023-11-10-16:15.sql
Normal file
17934
backend/sql/educa7ls_plataforma_backup_2023-11-10-16:15.sql
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
||||
use crate::json_result::JsonResult;
|
||||
use printpdf::{Mm, PdfDocument};
|
||||
use rocket::{http::Status, serde::json::Json};
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::BufWriter,
|
||||
@ -9,31 +10,52 @@ use std::{
|
||||
|
||||
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")]
|
||||
pub async fn detect_scans() -> (Status, Json<JsonResult<Vec<String>>>) {
|
||||
// TODO: Move to ENV variable?
|
||||
pub async fn detect_scans() -> (Status, Json<JsonResult<Vec<(PathBuf, ScanInfo)>>>) {
|
||||
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) {
|
||||
Ok(paths) => paths,
|
||||
Err(err) => {
|
||||
eprintln!("Error reading dir: {:?}", err);
|
||||
return (
|
||||
Status::InternalServerError,
|
||||
JsonResult::err("Error leyendo directorio".into()),
|
||||
);
|
||||
return Err("Error leyendo directorio".into());
|
||||
}
|
||||
};
|
||||
|
||||
let mut result = Vec::<String>::new();
|
||||
let mut result = Vec::<PathBuf>::new();
|
||||
|
||||
for p in paths {
|
||||
let p = match p {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
eprintln!("Error reading path: {:?}", err);
|
||||
return (
|
||||
Status::InternalServerError,
|
||||
JsonResult::err("Error leyendo directorio".into()),
|
||||
);
|
||||
return Err("Error recuperando archivo de lista de archivos".into());
|
||||
}
|
||||
};
|
||||
|
||||
@ -47,10 +69,7 @@ pub async fn detect_scans() -> (Status, Json<JsonResult<Vec<String>>>) {
|
||||
Some(filename) => filename,
|
||||
None => {
|
||||
eprintln!("Error getting file name: {:?}", file_path);
|
||||
return (
|
||||
Status::InternalServerError,
|
||||
JsonResult::err("Error leyendo archivo en directorio".into()),
|
||||
);
|
||||
return Err("Error leyendo archivo en directorio".into());
|
||||
}
|
||||
};
|
||||
|
||||
@ -58,28 +77,28 @@ pub async fn detect_scans() -> (Status, Json<JsonResult<Vec<String>>>) {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
eprintln!("Error getting file as string: {:?}", p);
|
||||
return (
|
||||
Status::InternalServerError,
|
||||
JsonResult::err("Error leyendo directorio".into()),
|
||||
);
|
||||
return Err("Error leyendo nombre de archivo de escaneo".into());
|
||||
}
|
||||
};
|
||||
|
||||
if filename.ends_with(".jpg") && filename.starts_with("eeg") {
|
||||
result.push(filename.to_string());
|
||||
result.push(file_path);
|
||||
}
|
||||
}
|
||||
|
||||
for filename in result.iter() {
|
||||
let path = PathBuf::from(format!("{}{}", SCAN_PATH, filename));
|
||||
|
||||
examine_scan(&path);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
(Status::Ok, JsonResult::ok(result))
|
||||
|
||||
/// 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 examine_scan(path: &PathBuf) {
|
||||
fn get_image_info(path: PathBuf) -> ScanInfo {
|
||||
let img = image::open(path).unwrap();
|
||||
|
||||
// Get width & height
|
||||
@ -95,31 +114,42 @@ fn examine_scan(path: &PathBuf) {
|
||||
|
||||
// get qr from cropped image
|
||||
let results = bardecoder::default_decoder().decode(&cropped_img);
|
||||
// TODO: get first qr, parse, query db, get info, determine filename
|
||||
for result in results {
|
||||
println!("{}", result.unwrap());
|
||||
|
||||
let url = match &results[0] {
|
||||
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>`
|
||||
|
||||
//
|
||||
doc.save(&mut BufWriter::new(
|
||||
File::create(format!("{}output.pdf", SCAN_PATH)).unwrap(),
|
||||
))
|
||||
.unwrap();
|
||||
match url.find('?') {
|
||||
Some(p) => {
|
||||
let dni_length = p - 31;
|
||||
let equals_pos = match url.find('=') {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { FilledButton } from "../components/FilledButton";
|
||||
import { FilledCard } from "../components/FilledCard";
|
||||
import { MagnifyingGlassIcon } from "../icons/MagnifyingGlassIcon";
|
||||
|
||||
export function Scans() {
|
||||
const detectScans = () => {
|
||||
@ -11,25 +12,6 @@ export function Scans() {
|
||||
return (
|
||||
<div class="grid grid-cols-[25rem_25rem_auto]">
|
||||
<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>
|
||||
<h1 class="py-2 px-4 font-medium text-xl mb-2">
|
||||
Parámetros de detección
|
||||
@ -88,17 +70,38 @@ export function Scans() {
|
||||
Detectar escaneos
|
||||
</h1>
|
||||
<div class="px-4 pb-4">
|
||||
El siguiente botón detectará los escaneos en la carpeta
|
||||
que cumplen con los parámetros de detección, y los mostrará
|
||||
en una lista.
|
||||
<br />
|
||||
<p>
|
||||
El siguiente botón detectará los archivos
|
||||
que cumplen con los parámetros de detección
|
||||
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
|
||||
class="mt-2"
|
||||
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
|
||||
</span>
|
||||
</FilledButton>
|
||||
</div>
|
||||
</div>
|
||||
</FilledCard>
|
||||
<FilledCard class="border border-c-outline overflow-hidden">
|
||||
<h1 class="p-3 font-medium text-xl">
|
||||
|
Loading…
Reference in New Issue
Block a user