datui_lib/home/
home_preview.rs1use crate::FileFormat;
10use crate::home::discover::{Entry, EntryKind};
11use crate::table::DataTableState;
12use polars::prelude::{DataFrame, DataType};
13use std::collections::{HashMap, VecDeque};
14use std::path::{Path, PathBuf};
15use std::sync::{Arc, Mutex};
16use std::time::SystemTime;
17
18pub const PREVIEW_ROWS: usize = 8;
20const PREVIEW_COLUMNS: usize = 24;
22const KEPT: usize = 32;
24const CELL_MAX: usize = 40;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct Stamp {
31 pub len: Option<u64>,
32 pub modified: Option<SystemTime>,
33}
34
35impl Stamp {
36 pub fn of_entry(entry: &Entry) -> Self {
38 Self {
39 len: entry.size,
40 modified: entry.modified,
41 }
42 }
43
44 pub fn of_file(path: &Path) -> Option<Self> {
46 let meta = std::fs::metadata(path).ok()?;
47 Some(Self {
48 len: Some(meta.len()),
49 modified: meta.modified().ok(),
50 })
51 }
52}
53
54#[derive(Debug, Clone, PartialEq)]
57pub struct PreviewRows {
58 pub columns: Vec<(String, DataType)>,
60 pub rows: Vec<Vec<Option<String>>>,
62 pub total_columns: usize,
64}
65
66impl PreviewRows {
67 pub fn from_frame(df: &DataFrame) -> Self {
69 let columns: Vec<_> = df
70 .columns()
71 .iter()
72 .filter(|c| c.name().as_str() != crate::formats::schema_union::DRIFT_COLUMN)
73 .collect();
74 let total_columns = columns.len();
75 let kept: Vec<_> = columns.into_iter().take(PREVIEW_COLUMNS).collect();
76 let height = df.height().min(PREVIEW_ROWS);
77 let rows = (0..height)
78 .map(|i| {
79 kept.iter()
80 .map(|c| match c.get(i) {
81 Ok(polars::prelude::AnyValue::Null) | Err(_) => None,
82 Ok(value) => {
83 let text = crate::exact::str_value(&value);
84 let line: String = text
86 .chars()
87 .map(|ch| if ch.is_control() { ' ' } else { ch })
88 .take(CELL_MAX)
89 .collect();
90 Some(line)
91 }
92 })
93 .collect()
94 })
95 .collect();
96 Self {
97 columns: kept
98 .iter()
99 .map(|c| (c.name().to_string(), c.dtype().clone()))
100 .collect(),
101 rows,
102 total_columns,
103 }
104 }
105}
106
107pub struct Prepared {
109 pub(crate) state: Box<DataTableState>,
110 pub(crate) options: crate::OpenOptions,
111 pub(crate) debug_label: Option<String>,
112 pub(crate) progress: Arc<crate::formats::schema_union::FooterProgress>,
113}
114
115pub type Handoff = Arc<Mutex<Option<Box<Prepared>>>>;
117
118#[derive(Default)]
120pub struct Previews {
121 rows: HashMap<PathBuf, (Stamp, Option<Arc<PreviewRows>>)>,
124 order: VecDeque<PathBuf>,
125 prepared: Option<(PathBuf, Stamp, Box<Prepared>)>,
128 pub(crate) inflight: Option<PathBuf>,
131}
132
133#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
136pub struct ReadCounts {
137 pub previews: usize,
139 pub scans: usize,
141 pub pages: usize,
143 pub schemas: usize,
145}
146
147impl Previews {
148 pub fn rows(&self, path: &Path, stamp: Stamp) -> Option<Option<Arc<PreviewRows>>> {
150 self.rows
151 .get(path)
152 .filter(|(at, _)| *at == stamp)
153 .map(|(_, rows)| rows.clone())
154 }
155
156 pub fn reading(&self, path: &Path) -> bool {
158 self.inflight.as_deref() == Some(path)
159 }
160
161 pub(crate) fn landed(
165 &mut self,
166 path: PathBuf,
167 stamp: Stamp,
168 read_at: Stamp,
169 rows: Option<Arc<PreviewRows>>,
170 prepared: Option<Box<Prepared>>,
171 ) {
172 if self.inflight.as_ref() == Some(&path) {
173 self.inflight = None;
174 }
175 if let Some(prepared) = prepared {
176 self.prepared = Some((path.clone(), read_at, prepared));
177 }
178 self.order.retain(|p| p != &path);
179 self.order.push_back(path.clone());
180 self.rows.insert(path, (stamp, rows));
181 while self.order.len() > KEPT {
182 if let Some(old) = self.order.pop_front() {
183 self.rows.remove(&old);
184 }
185 }
186 }
187
188 pub(crate) fn take_prepared(&mut self, path: &Path) -> Option<Box<Prepared>> {
190 if self.prepared.as_ref()?.0 != path {
191 return None;
192 }
193 let now = Stamp::of_file(path);
194 let (_, stamp, prepared) = self.prepared.take()?;
195 (now == Some(stamp)).then_some(prepared)
196 }
197
198 pub(crate) fn drop_prepared(&mut self) {
200 self.prepared = None;
201 }
202}
203
204pub fn previewable(entry: &Entry, max_bytes: u64) -> bool {
208 if max_bytes == 0
209 || entry.kind != EntryKind::File
210 || entry.opens_whole_directory
211 || entry.table.is_some()
212 || entry.format_spec.is_some()
213 || crate::home::is_remote_path(&entry.path)
214 {
215 return false;
216 }
217 let Some(size) = entry.size else {
218 return false;
219 };
220 let preview =
221 FileFormat::from_path(&entry.path).and_then(|f| crate::formats::readers::of(f).preview);
222 match preview {
223 Some(crate::formats::readers::Preview::RowGroup) => {
225 let groups = entry.cost.row_groups.unwrap_or(1).max(1) as u64;
226 size / groups <= max_bytes
227 }
228 Some(crate::formats::readers::Preview::Scan) => size <= max_bytes,
229 None => false,
230 }
231}