use crate::FileFormat;
use crate::home::discover::{Entry, EntryKind};
use crate::table::DataTableState;
use polars::prelude::{DataFrame, DataType};
use std::collections::{HashMap, VecDeque};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
pub const PREVIEW_ROWS: usize = 8;
const PREVIEW_COLUMNS: usize = 24;
const KEPT: usize = 32;
const CELL_MAX: usize = 40;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stamp {
pub len: Option<u64>,
pub modified: Option<SystemTime>,
}
impl Stamp {
pub fn of_entry(entry: &Entry) -> Self {
Self {
len: entry.size,
modified: entry.modified,
}
}
pub fn of_file(path: &Path) -> Option<Self> {
let meta = std::fs::metadata(path).ok()?;
Some(Self {
len: Some(meta.len()),
modified: meta.modified().ok(),
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PreviewRows {
pub columns: Vec<(String, DataType)>,
pub rows: Vec<Vec<Option<String>>>,
pub total_columns: usize,
}
impl PreviewRows {
pub fn from_frame(df: &DataFrame) -> Self {
let columns: Vec<_> = df
.columns()
.iter()
.filter(|c| c.name().as_str() != crate::formats::schema_union::DRIFT_COLUMN)
.collect();
let total_columns = columns.len();
let kept: Vec<_> = columns.into_iter().take(PREVIEW_COLUMNS).collect();
let height = df.height().min(PREVIEW_ROWS);
let rows = (0..height)
.map(|i| {
kept.iter()
.map(|c| match c.get(i) {
Ok(polars::prelude::AnyValue::Null) | Err(_) => None,
Ok(value) => {
let text = crate::exact::str_value(&value);
let line: String = text
.chars()
.map(|ch| if ch.is_control() { ' ' } else { ch })
.take(CELL_MAX)
.collect();
Some(line)
}
})
.collect()
})
.collect();
Self {
columns: kept
.iter()
.map(|c| (c.name().to_string(), c.dtype().clone()))
.collect(),
rows,
total_columns,
}
}
}
pub struct Prepared {
pub(crate) state: Box<DataTableState>,
pub(crate) options: crate::OpenOptions,
pub(crate) debug_label: Option<String>,
pub(crate) progress: Arc<crate::formats::schema_union::FooterProgress>,
}
pub type Handoff = Arc<Mutex<Option<Box<Prepared>>>>;
#[derive(Default)]
pub struct Previews {
rows: HashMap<PathBuf, (Stamp, Option<Arc<PreviewRows>>)>,
order: VecDeque<PathBuf>,
prepared: Option<(PathBuf, Stamp, Box<Prepared>)>,
pub(crate) inflight: Option<PathBuf>,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct ReadCounts {
pub previews: usize,
pub scans: usize,
pub pages: usize,
}
impl Previews {
pub fn rows(&self, path: &Path, stamp: Stamp) -> Option<Option<Arc<PreviewRows>>> {
self.rows
.get(path)
.filter(|(at, _)| *at == stamp)
.map(|(_, rows)| rows.clone())
}
pub fn reading(&self, path: &Path) -> bool {
self.inflight.as_deref() == Some(path)
}
pub(crate) fn landed(
&mut self,
path: PathBuf,
stamp: Stamp,
read_at: Stamp,
rows: Option<Arc<PreviewRows>>,
prepared: Option<Box<Prepared>>,
) {
if self.inflight.as_ref() == Some(&path) {
self.inflight = None;
}
if let Some(prepared) = prepared {
self.prepared = Some((path.clone(), read_at, prepared));
}
self.order.retain(|p| p != &path);
self.order.push_back(path.clone());
self.rows.insert(path, (stamp, rows));
while self.order.len() > KEPT {
if let Some(old) = self.order.pop_front() {
self.rows.remove(&old);
}
}
}
pub(crate) fn take_prepared(&mut self, path: &Path) -> Option<Box<Prepared>> {
if self.prepared.as_ref()?.0 != path {
return None;
}
let now = Stamp::of_file(path);
let (_, stamp, prepared) = self.prepared.take()?;
(now == Some(stamp)).then_some(prepared)
}
pub(crate) fn drop_prepared(&mut self) {
self.prepared = None;
}
}
pub fn previewable(entry: &Entry, max_bytes: u64) -> bool {
if max_bytes == 0
|| entry.kind != EntryKind::File
|| entry.opens_whole_directory
|| entry.table.is_some()
|| entry.format_spec.is_some()
|| crate::home::is_remote_path(&entry.path)
{
return false;
}
let Some(size) = entry.size else {
return false;
};
let preview =
FileFormat::from_path(&entry.path).and_then(|f| crate::formats::readers::of(f).preview);
match preview {
Some(crate::formats::readers::Preview::RowGroup) => {
let groups = entry.cost.row_groups.unwrap_or(1).max(1) as u64;
size / groups <= max_bytes
}
Some(crate::formats::readers::Preview::Scan) => size <= max_bytes,
None => false,
}
}