search_hub

at 18c4440 Raw

use crate::importer::open_backup;
use crate::models::Bookmark;
use chrono::DateTime;
use std::path::{Path, PathBuf};

pub fn query_bookmarks(profile_path: &Path) -> anyhow::Result<Vec<Bookmark>> {
    let conn = open_backup(profile_path)?;

    let mut stmt = conn.prepare(
        "SELECT b.id, COALESCE(b.title, p.title, ''), p.url, p.description,
                CAST(b.dateAdded AS INTEGER) / 1000000
         FROM moz_bookmarks b
         JOIN moz_places p ON b.fk = p.id
         WHERE b.type = 1",
    )?;

    let bookmarks = stmt
        .query_map([], |row| {
            let id: i32 = row.get(0)?;
            let title: String = row.get(1)?;
            let url: String = row.get(2)?;
            let description: Option<String> = row.get(3)?;
            let timestamp: i64 = row.get(4)?;

            let created_at = DateTime::from_timestamp(timestamp, 0)
                .unwrap_or(DateTime::from_timestamp(0, 0).unwrap());

            Ok(Bookmark {
                id,
                title,
                url,
                description: description.filter(|d| !d.is_empty()),
                source: "bookmark".into(),
                content: None,
                tags: None,
                created_at,
            })
        })?
        .collect::<Result<Vec<_>, _>>()?;

    Ok(bookmarks)
}

pub fn query_history(profile_path: &Path) -> anyhow::Result<Vec<Bookmark>> {
    let conn = open_backup(profile_path)?;

    let mut stmt = conn.prepare(
        "SELECT DISTINCT p.id, COALESCE(p.title, ''), p.url, p.description,
                CAST(COALESCE(p.last_visit_date, 0) AS INTEGER) / 1000000
         FROM moz_places p
         JOIN moz_historyvisits v ON p.id = v.place_id
         WHERE p.hidden = 0
         ORDER BY p.last_visit_date DESC",
    )?;

    let entries = stmt
        .query_map([], |row| {
            let id: i32 = row.get(0)?;
            let title: String = row.get(1)?;
            let url: String = row.get(2)?;
            let description: Option<String> = row.get(3)?;
            let timestamp: i64 = row.get(4)?;

            let created_at = DateTime::from_timestamp(timestamp, 0)
                .unwrap_or(DateTime::from_timestamp(0, 0).unwrap());

            Ok(Bookmark {
                id,
                title,
                url,
                description: description.filter(|d| !d.is_empty()),
                source: "history".into(),
                content: None,
                tags: None,
                created_at,
            })
        })?
        .collect::<Result<Vec<_>, _>>()?;

    Ok(entries)
}

pub fn discover_profiles(base_dir: &str) -> Vec<PathBuf> {
    let mut profiles = Vec::new();
    let dir = home_dir().map(|p| p.join(base_dir));

    if let Some(dir) = dir {
        if let Ok(entries) = std::fs::read_dir(dir) {
            for entry in entries.flatten() {
                let path = entry.path();
                if path.is_dir() && path.join("places.sqlite").exists() {
                    profiles.push(path);
                }
            }
        }
    }

    profiles
}

fn home_dir() -> Option<PathBuf> {
    std::env::var("HOME")
        .ok()
        .map(PathBuf::from)
        .or_else(|| {
            if cfg!(target_os = "windows") {
                std::env::var("USERPROFILE").ok().map(PathBuf::from)
            } else {
                None
            }
        })
}