Skip to main content

binocular/preview/sqlite/
mod.rs

1use 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}