use crate::types::{PdfEntry, PdfListResult, PdfSearchResult};
use chrono::{DateTime, Utc};
use rusqlite::{params, Connection, Result};
pub struct PdfDatabase {
conn: Connection,
}
impl PdfDatabase {
pub fn new(path: &std::path::Path) -> Result<Self> {
let conn = Connection::open(path)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS pdfs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
path TEXT NOT NULL UNIQUE,
filename TEXT NOT NULL,
size INTEGER NOT NULL,
modified DATETIME NOT NULL,
indexed_at DATETIME NOT NULL
)",
[],
)?;
Ok(PdfDatabase { conn })
}
pub fn insert_pdf(&self, pdf: &PdfEntry) -> Result<()> {
self.conn.execute(
"INSERT OR REPLACE INTO pdfs (path, filename, size, modified, indexed_at)
VALUES (?, ?, ?, ?, ?)",
params![
pdf.path,
pdf.filename,
pdf.size as i64,
pdf.modified.to_rfc3339(),
pdf.indexed_at.unwrap().to_rfc3339(),
],
)?;
Ok(())
}
pub fn open(path: &std::path::Path) -> Result<Self> {
let conn = Connection::open(path)?;
Ok(PdfDatabase { conn })
}
pub fn count_pdfs(&self) -> Result<i64> {
let count = self
.conn
.query_row("SELECT COUNT(*) FROM pdfs", [], |row| row.get(0))?;
Ok(count)
}
pub fn simple_search(&self, query: &str) -> Result<Vec<PdfSearchResult>> {
let mut results = Vec::new();
let sql = "SELECT id, path, filename, size FROM pdfs WHERE LOWER(filename) LIKE LOWER(?)";
let search_pattern = format!("%{}%", query);
let mut stmt = self.conn.prepare(sql)?;
let pdf_iter = stmt.query_map([&search_pattern], |row| {
Ok(PdfSearchResult {
id: row.get(0)?,
path: row.get(1)?,
filename: row.get(2)?,
size: row.get(3)?,
})
})?;
for pdf in pdf_iter {
results.push(pdf?);
}
Ok(results)
}
pub fn get_all_pdfs(&self) -> Result<Vec<PdfListResult>> {
let mut pdfs = Vec::new();
let sql = "SELECT * FROM pdfs";
let mut stmt = self.conn.prepare(sql)?;
let pdf_iter = stmt.query_map([], |row| {
let modified_str: String = row.get(4)?;
let modified = DateTime::parse_from_rfc3339(&modified_str)
.map_err(|_e| {
rusqlite::Error::InvalidColumnType(
4,
"DATETIME".to_string(),
rusqlite::types::Type::Text,
)
})?
.with_timezone(&Utc);
let size: u64 = row.get(3)?;
Ok(PdfListResult {
id: row.get(0)?,
path: row.get(1)?,
filename: row.get(2)?,
size,
size_human: crate::helpers::help::human_readable_size(size),
modified,
})
})?;
for pdf in pdf_iter {
pdfs.push(pdf?);
}
Ok(pdfs)
}
pub fn get_database_path() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
let data_dir = dirs::data_dir().expect("Failed to get data directory");
let pdfx_dir = data_dir.join("pdfx");
std::fs::create_dir_all(&pdfx_dir)?;
let db_path = pdfx_dir.join("db.sqlite");
Ok(db_path)
}
}