binocular/preview/sqlite/
mod.rs1use crate::preview::doc::{format_file_size, PreviewDoc};
2use crate::preview::sqlite::document::render_table_document;
3use crate::preview::sqlite::schema::{list_objects, DbObject};
4use ratatui::style::Color;
5use ratatui::text::Text;
6use std::path::Path;
7
8mod detect;
9mod document;
10mod schema;
11
12const MAX_TABLES_DETAIL: usize = 20;
13
14const SAMPLE_ROWS: usize = 5;
15
16pub fn is_sqlite(path: &Path) -> bool {
17 detect::is_sqlite(path)
18}
19
20pub fn generate_preview(path: &Path) -> Text<'static> {
21 let mut doc = PreviewDoc::new();
22
23 if let Ok(meta) = std::fs::metadata(path) {
24 doc.push_section("File Info");
25 doc.push_field("Size", format_file_size(meta.len()), Color::White);
26 doc.push_blank_line();
27 }
28
29 let conn = match rusqlite::Connection::open_with_flags(
30 path,
31 rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX,
32 ) {
33 Ok(c) => c,
34 Err(e) => {
35 doc.push_section("Error");
36 doc.push_field("Message", e.to_string(), Color::Red);
37 return doc.into_text();
38 }
39 };
40
41 let page_size: i64 = conn
42 .pragma_query_value(None, "page_size", |r| r.get(0))
43 .unwrap_or(0);
44 let page_count: i64 = conn
45 .pragma_query_value(None, "page_count", |r| r.get(0))
46 .unwrap_or(0);
47 let db_size = page_size * page_count;
48
49 doc.push_section("Database Info");
50 doc.push_field("Page size", format!("{} bytes", page_size), Color::White);
51 if db_size > 0 {
52 doc.push_field("DB size", format_file_size(db_size as u64), Color::White);
53 }
54 doc.push_blank_line();
55
56 let objects = match list_objects(&conn) {
57 Ok(o) => o,
58 Err(e) => {
59 doc.push_section("Error");
60 doc.push_field("Message", e.to_string(), Color::Red);
61 return doc.into_text();
62 }
63 };
64
65 let tables: Vec<&DbObject> = objects.iter().filter(|o| o.kind == "table").collect();
66 let views: Vec<&DbObject> = objects.iter().filter(|o| o.kind == "view").collect();
67
68 doc.push_section("Schema");
69 doc.push_field("Tables", tables.len().to_string(), Color::White);
70 doc.push_field("Views", views.len().to_string(), Color::White);
71 doc.push_blank_line();
72
73 for (i, obj) in tables.iter().take(MAX_TABLES_DETAIL).enumerate() {
74 if i > 0 {
75 doc.push_blank_line();
76 }
77 render_table_document(&conn, &mut doc, obj, SAMPLE_ROWS);
78 }
79
80 if tables.len() > MAX_TABLES_DETAIL {
81 doc.push_blank_line();
82 doc.push_muted_italic(format!(
83 " … {} more tables",
84 tables.len() - MAX_TABLES_DETAIL
85 ));
86 }
87
88 if !views.is_empty() {
89 doc.push_blank_line();
90 doc.push_section("Views");
91 for view in &views {
92 doc.push_field(" view", view.name.clone(), Color::Cyan);
93 }
94 }
95
96 doc.into_text()
97}