1use std::fs::File;
11use std::io::{BufReader, Read};
12use std::path::Path;
13use std::sync::Arc;
14use std::sync::atomic::{AtomicU64, Ordering};
15
16use color_eyre::Result;
17use color_eyre::eyre::eyre;
18use polars::prelude::{DataFrame, LazyFrame, PolarsResult};
19
20use crate::cloud::download::TempDownload;
21use crate::formats::model_files::MetaValue;
22use crate::formats::readers::{ConvertIn, ConvertOut};
23use crate::formats::segments::{Converted, Segments};
24use crate::loading::unfinished::Writer;
25use crate::notes::Note;
26use crate::numfmt::group_chrome;
27use crate::{CompressionFormat, OpenOptions};
28
29const CHUNK: usize = 1 << 16;
31
32#[derive(Debug, Clone, Default, PartialEq)]
36pub struct Detail {
37 pub tab: &'static str,
39 pub lines: Vec<String>,
41 pub warnings: Vec<String>,
43 pub list_title: &'static str,
45 pub list: Vec<(String, MetaValue)>,
47 pub first: bool,
50 pub own_columns: bool,
53 pub tables: Vec<String>,
56 pub table: Option<String>,
58}
59
60pub const fn tab(format: crate::FileFormat) -> &'static str {
63 match format.descriptor().summary {
64 crate::Summary::Tab(tab) => tab,
65 crate::Summary::None(_) => format.descriptor().title,
67 }
68}
69
70pub trait BatchReader {
73 fn push(&mut self, piece: &[u8]) -> Result<()>;
74 fn take_batch(&mut self) -> PolarsResult<Option<DataFrame>>;
76 fn finish(&mut self) -> Result<DataFrame>;
78}
79
80struct Counted<'a, R> {
82 inner: R,
83 read: &'a AtomicU64,
84}
85
86impl<R: Read> Read for Counted<'_, R> {
87 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
88 let n = self.inner.read(buf)?;
89 self.read.fetch_add(n as u64, Ordering::Relaxed);
90 Ok(n)
91 }
92}
93
94pub(crate) fn open_reader<'a>(
97 file: &Path,
98 options: &OpenOptions,
99 read: &'a AtomicU64,
100) -> Result<Box<dyn Read + 'a>> {
101 let f = BufReader::new(Counted {
102 inner: File::open(file)?,
103 read,
104 });
105 Ok(
106 match options
107 .compression
108 .or_else(|| CompressionFormat::from_extension(file))
109 {
110 None => Box::new(f),
111 Some(CompressionFormat::Gzip) => Box::new(flate2::read::MultiGzDecoder::new(f)),
112 Some(CompressionFormat::Zstd) => Box::new(zstd::Decoder::new(f)?),
113 Some(CompressionFormat::Bzip2) => Box::new(bzip2::read::BzDecoder::new(f)),
114 Some(CompressionFormat::Xz) => Box::new(xz2::read::XzDecoder::new(f)),
115 },
116 )
117}
118
119pub(crate) fn read_through<R: BatchReader>(
123 file: &Path,
124 options: &OpenOptions,
125 writer: &Writer,
126 read: &AtomicU64,
127 reader: &mut R,
128 mut each: impl FnMut(&DataFrame),
129) -> Result<(LazyFrame, Vec<TempDownload>)> {
130 let mut source = open_reader(file, options, read)?;
131 let mut segments = Segments::new(options, writer);
132 let mut chunk = vec![0u8; CHUNK];
133 loop {
134 if writer.stopped() {
135 return Err(eyre!("Reading was stopped."));
136 }
137 let n = match source.read(&mut chunk) {
138 Ok(0) => break,
139 Ok(n) => n,
140 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
141 Err(e) => return Err(e.into()),
142 };
143 reader.push(&chunk[..n])?;
144 if let Some(df) = reader.take_batch()? {
145 each(&df);
146 segments.write(&df)?;
147 }
148 }
149 let last = reader.finish()?;
150 each(&last);
151 segments.write(&last)?;
152 segments.finish()
153}
154
155pub(crate) fn convert_with<R: BatchReader>(
159 input: &ConvertIn<'_>,
160 mut reader: R,
161 finish: impl FnOnce(&R, LazyFrame) -> Result<(LazyFrame, Vec<Note>, Detail)>,
162) -> ConvertOut {
163 let [file] = input.files else {
164 return Err(eyre!("Open {} files one at a time.", input.format.name()));
165 };
166 let (lf, files) = read_through(
167 file,
168 input.options,
169 input.writer,
170 input.read,
171 &mut reader,
172 |_| {},
173 )?;
174 let (lf, notes, detail) = finish(&reader, lf)?;
175 let converted = Converted {
176 lf,
177 files,
178 notes,
179 other_tables: Vec::new(),
180 };
181 Ok((converted, Some(Arc::new(detail))))
182}
183
184pub(crate) fn note(summary: String, scope: String) -> Note {
185 Note {
186 summary,
187 scope,
188 read_as_text: None,
189 passed_over: None,
190 }
191}
192
193pub(crate) fn count(n: u64, one: &str, many: &str) -> String {
195 let n = usize::try_from(n).unwrap_or(usize::MAX);
196 format!("{} {}", group_chrome(n), if n == 1 { one } else { many })
197}
198
199pub(crate) fn capped_list(
202 rows: impl Iterator<Item = (String, MetaValue)>,
203 total: usize,
204) -> Vec<(String, MetaValue)> {
205 let mut list: Vec<_> = rows.take(crate::limits::get().detail_rows).collect();
206 if total > list.len() {
207 let more = total - list.len();
208 list.push((
209 crate::glyphs::get().ellipsis.to_string(),
210 MetaValue::Text(format!(
211 "{} more {} limits.detail_rows raises it",
212 group_chrome(more),
213 crate::glyphs::get().middot
214 )),
215 ));
216 }
217 list
218}