[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 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 {
|
|
||||||
println!("{}", result.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate image 90 degrees clockwise
|
let url = match &results[0] {
|
||||||
let img = img.rotate270();
|
Ok(url) => url,
|
||||||
|
Err(reason) => {
|
||||||
// Generate PDF
|
eprintln!("Error decoding qr: {:?}", reason);
|
||||||
let (doc, page1, layer1) =
|
return ScanInfo::Error("QR no encontrado.".into());
|
||||||
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,16 +70,37 @@ 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.
|
||||||
<FilledButton
|
</p>
|
||||||
class="mt-2"
|
<p class="my-2">
|
||||||
onClick={detectScans}
|
La detección demora aprox. 1 segundo por cada archivo que
|
||||||
>
|
cumple con los parámetros.
|
||||||
Detectar escaneos
|
</p>
|
||||||
</FilledButton>
|
<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>
|
</div>
|
||||||
</FilledCard>
|
</FilledCard>
|
||||||
<FilledCard class="border border-c-outline overflow-hidden">
|
<FilledCard class="border border-c-outline overflow-hidden">
|
||||||
|
Loading…
Reference in New Issue
Block a user