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}
144
145impl Previews {
146 pub fn rows(&self, path: &Path, stamp: Stamp) -> Option<Option<Arc<PreviewRows>>> {
148 self.rows
149 .get(path)
150 .filter(|(at, _)| *at == stamp)
151 .map(|(_, rows)| rows.clone())
152 }
153
154 pub fn reading(&self, path: &Path) -> bool {
156 self.inflight.as_deref() == Some(path)
157 }
158
159 pub(crate) fn landed(
163 &mut self,
164 path: PathBuf,
165 stamp: Stamp,
166 read_at: Stamp,
167 rows: Option<Arc<PreviewRows>>,
168 prepared: Option<Box<Prepared>>,
169 ) {
170 if self.inflight.as_ref() == Some(&path) {
171 self.inflight = None;
172 }
173 if let Some(prepared) = prepared {
174 self.prepared = Some((path.clone(), read_at, prepared));
175 }
176 self.order.retain(|p| p != &path);
177 self.order.push_back(path.clone());
178 self.rows.insert(path, (stamp, rows));
179 while self.order.len() > KEPT {
180 if let Some(old) = self.order.pop_front() {
181 self.rows.remove(&old);
182 }
183 }
184 }
185
186 pub(crate) fn take_prepared(&mut self, path: &Path) -> Option<Box<Prepared>> {
188 if self.prepared.as_ref()?.0 != path {
189 return None;
190 }
191 let now = Stamp::of_file(path);
192 let (_, stamp, prepared) = self.prepared.take()?;
193 (now == Some(stamp)).then_some(prepared)
194 }
195
196 pub(crate) fn drop_prepared(&mut self) {
198 self.prepared = None;
199 }
200}
201
202pub fn previewable(entry: &Entry, max_bytes: u64) -> bool {
206 if max_bytes == 0
207 || entry.kind != EntryKind::File
208 || entry.opens_whole_directory
209 || entry.table.is_some()
210 || entry.format_spec.is_some()
211 || crate::home::is_remote_path(&entry.path)
212 {
213 return false;
214 }
215 let Some(size) = entry.size else {
216 return false;
217 };
218 let preview =
219 FileFormat::from_path(&entry.path).and_then(|f| crate::formats::readers::of(f).preview);
220 match preview {
221 Some(crate::formats::readers::Preview::RowGroup) => {
223 let groups = entry.cost.row_groups.unwrap_or(1).max(1) as u64;
224 size / groups <= max_bytes
225 }
226 Some(crate::formats::readers::Preview::Scan) => size <= max_bytes,
227 None => false,
228 }
229}