atuin_client/import/
nu_histdb.rs

1// import old shell history!
2// automatically hoover up all that we can find
3
4use std::path::PathBuf;
5
6use async_trait::async_trait;
7use directories::BaseDirs;
8use eyre::{eyre, Result};
9use sqlx::{sqlite::SqlitePool, Pool};
10use time::{Duration, OffsetDateTime};
11
12use super::Importer;
13use crate::history::History;
14use crate::import::Loader;
15
16#[derive(sqlx::FromRow, Debug)]
17pub struct HistDbEntry {
18    pub id: i64,
19    pub command_line: Vec<u8>,
20    pub start_timestamp: i64,
21    pub session_id: i64,
22    pub hostname: Vec<u8>,
23    pub cwd: Vec<u8>,
24    pub duration_ms: i64,
25    pub exit_status: i64,
26    pub more_info: Vec<u8>,
27}
28
29impl From<HistDbEntry> for History {
30    fn from(histdb_item: HistDbEntry) -> Self {
31        let ts_secs = histdb_item.start_timestamp / 1000;
32        let ts_ns = (histdb_item.start_timestamp % 1000) * 1_000_000;
33        let imported = History::import()
34            .timestamp(
35                OffsetDateTime::from_unix_timestamp(ts_secs).unwrap()
36                    + Duration::nanoseconds(ts_ns),
37            )
38            .command(String::from_utf8(histdb_item.command_line).unwrap())
39            .cwd(String::from_utf8(histdb_item.cwd).unwrap())
40            .exit(histdb_item.exit_status)
41            .duration(histdb_item.duration_ms)
42            .session(format!("{:x}", histdb_item.session_id))
43            .hostname(String::from_utf8(histdb_item.hostname).unwrap());
44
45        imported.build().into()
46    }
47}
48
49#[derive(Debug)]
50pub struct NuHistDb {
51    histdb: Vec<HistDbEntry>,
52}
53
54/// Read db at given file, return vector of entries.
55async fn hist_from_db(dbpath: PathBuf) -> Result<Vec<HistDbEntry>> {
56    let pool = SqlitePool::connect(dbpath.to_str().unwrap()).await?;
57    hist_from_db_conn(pool).await
58}
59
60async fn hist_from_db_conn(pool: Pool<sqlx::Sqlite>) -> Result<Vec<HistDbEntry>> {
61    let query = r#"
62        SELECT
63            id, command_line, start_timestamp, session_id, hostname, cwd, duration_ms, exit_status,
64            more_info
65        FROM history
66        ORDER BY start_timestamp
67    "#;
68    let histdb_vec: Vec<HistDbEntry> = sqlx::query_as::<_, HistDbEntry>(query)
69        .fetch_all(&pool)
70        .await?;
71    Ok(histdb_vec)
72}
73
74impl NuHistDb {
75    pub fn histpath() -> Result<PathBuf> {
76        let base = BaseDirs::new().ok_or_else(|| eyre!("could not determine data directory"))?;
77        let config_dir = base.config_dir().join("nushell");
78
79        let histdb_path = config_dir.join("history.sqlite3");
80        if histdb_path.exists() {
81            Ok(histdb_path)
82        } else {
83            Err(eyre!("Could not find history file."))
84        }
85    }
86}
87
88#[async_trait]
89impl Importer for NuHistDb {
90    // Not sure how this is used
91    const NAME: &'static str = "nu_histdb";
92
93    /// Creates a new NuHistDb and populates the history based on the pre-populated data
94    /// structure.
95    async fn new() -> Result<Self> {
96        let dbpath = NuHistDb::histpath()?;
97        let histdb_entry_vec = hist_from_db(dbpath).await?;
98        Ok(Self {
99            histdb: histdb_entry_vec,
100        })
101    }
102
103    async fn entries(&mut self) -> Result<usize> {
104        Ok(self.histdb.len())
105    }
106
107    async fn load(self, h: &mut impl Loader) -> Result<()> {
108        for i in self.histdb {
109            h.push(i.into()).await?;
110        }
111        Ok(())
112    }
113}