atuin_client/import/
nu_histdb.rs1use 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
54async 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 const NAME: &'static str = "nu_histdb";
92
93 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}