shell
This commit is contained in:
@@ -3,16 +3,45 @@
|
||||
//! `std::fs::read_dir` y leyendo archivos a `Vec<u8>` via `tokio::io`.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use shipote_discern::{DiscernPipeline, Hint};
|
||||
use std::fs;
|
||||
use std::io::Cursor;
|
||||
use std::io::{Cursor, Read};
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use yahweh_core::{DataProvider, DisplayType, EntityNode};
|
||||
|
||||
pub const PROVIDER_ID: &str = "local_fs";
|
||||
|
||||
pub struct FileDataProvider;
|
||||
/// Bytes que samplea el discerner por archivo. 4 KiB cubre headers de
|
||||
/// formatos comunes (PNG, ELF, JSON/TOML hasta una clave de profundidad
|
||||
/// razonable) sin saturar I/O al expandir un directorio.
|
||||
const DISCERN_SAMPLE_BYTES: usize = 4096;
|
||||
|
||||
/// Tamaño máximo de archivo que sampleamos. Archivos más grandes se
|
||||
/// discernen igual via los primeros 4 KiB: el `seek/read` siempre lee
|
||||
/// head, y el costo es O(SAMPLE) sin importar el size total.
|
||||
/// Mantenemos esta constante por documentación; no se usa para skipear.
|
||||
const _DISCERN_SAMPLE_DOC: () = ();
|
||||
|
||||
pub struct FileDataProvider {
|
||||
discerner: Arc<DiscernPipeline>,
|
||||
}
|
||||
|
||||
impl FileDataProvider {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
discerner: Arc::new(DiscernPipeline::default_pipeline()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FileDataProvider {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DataProvider for FileDataProvider {
|
||||
@@ -32,17 +61,21 @@ impl DataProvider for FileDataProvider {
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
let display_type = if path.is_dir() {
|
||||
DisplayType::Folder
|
||||
let is_dir = path.is_dir();
|
||||
let display_type = if is_dir { DisplayType::Folder } else { DisplayType::File };
|
||||
|
||||
// Discernimos sólo archivos. Folders no tienen MIME útil.
|
||||
let mime_type = if is_dir {
|
||||
None
|
||||
} else {
|
||||
DisplayType::File
|
||||
discern_head(&path, &self.discerner)
|
||||
};
|
||||
|
||||
children.push(EntityNode {
|
||||
id: path.to_string_lossy().into_owned(),
|
||||
name,
|
||||
display_type,
|
||||
mime_type: None,
|
||||
mime_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -65,3 +98,22 @@ impl DataProvider for FileDataProvider {
|
||||
Err("Escritura en streaming no implementada para FS".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Lee el head del archivo y lo pasa por el DiscernPipeline. Devuelve el
|
||||
/// MIME detectado (si alguno) o `None` si no hubo match.
|
||||
///
|
||||
/// Sync intencional: estamos dentro del runtime que ya es async, pero la
|
||||
/// lectura es de tamaño fijo (4 KiB) y va a page cache; el costo de
|
||||
/// `tokio::fs` no compensaría para esto.
|
||||
fn discern_head(path: &Path, discerner: &DiscernPipeline) -> Option<String> {
|
||||
let mut buf = vec![0u8; DISCERN_SAMPLE_BYTES];
|
||||
let mut f = fs::File::open(path).ok()?;
|
||||
let n = f.read(&mut buf).ok()?;
|
||||
buf.truncate(n);
|
||||
let path_str = path.to_str();
|
||||
let hint = Hint {
|
||||
path: path_str,
|
||||
size_total: None,
|
||||
};
|
||||
discerner.discern(&buf, &hint).and_then(|d| d.mime)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user