Add sessions

This commit is contained in:
Ellie Huxtable 2021-02-13 20:21:49 +00:00
parent 099afe66ec
commit 440c4fc233
9 changed files with 130 additions and 37 deletions

20
Cargo.lock generated
View file

@ -49,12 +49,13 @@ dependencies = [
[[package]]
name = "atuin"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"chrono",
"directories",
"eyre",
"home",
"hostname",
"indicatif",
"log",
"pretty_env_logger",
@ -314,6 +315,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "hostname"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
dependencies = [
"libc",
"match_cfg",
"winapi",
]
[[package]]
name = "humantime"
version = "1.3.0"
@ -373,6 +385,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "match_cfg"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "memchr"
version = "2.3.4"

View file

@ -1,6 +1,6 @@
[package]
name = "atuin"
version = "0.2.0"
version = "0.2.1"
authors = ["Ellie Huxtable <e@elm.sh>"]
edition = "2018"
license = "MIT"
@ -17,6 +17,7 @@ directories = "3.*"
uuid = { version = "0.8", features = ["serde", "v4"] }
home = "0.5.3"
indicatif = "0.15.0"
hostname = "0.3.1"
[dependencies.rusqlite]
version = "0.24.*"

View file

@ -5,4 +5,15 @@
Through the fathomless deeps of space swims the star turtle Great ATuin, bearing on its back the four giant elephants who carry on their shoulders the mass of the Discworld.
</blockquote>
`atuin` manages and synchronizes your shell history!
`atuin` manages and synchronizes your shell history! Instead of storing
everything in a text file (such as ~/.history), `atuin` uses a sqlite database.
This lets us do all kinds of analysis on it!
As well as the expected command, this stores
- duration
- exit code
- working directory
- hostname
- time
- a unique session ID

View file

@ -32,13 +32,21 @@ pub enum HistoryCmd {
}
impl HistoryCmd {
pub fn run(&self, db: SqliteDatabase) -> Result<()> {
pub fn run(&self, db: &mut SqliteDatabase) -> Result<()> {
match self {
HistoryCmd::Start { command: words } => {
let command = words.join(" ");
let cwd = env::current_dir()?.display().to_string();
let h = History::new(chrono::Utc::now().timestamp_nanos(), command, cwd, -1, -1);
let h = History::new(
chrono::Utc::now().timestamp_nanos(),
command,
cwd,
-1,
-1,
None,
None,
);
// print the ID
// we use this as the key for calling end

View file

@ -87,12 +87,23 @@ impl ImportCmd {
}
pub fn run(&self, db: &mut SqliteDatabase) -> Result<()> {
println!(" A'Tuin ");
println!("=====================");
println!(" 🌍 ");
println!(" 🐘🐘🐘🐘 ");
println!(" 🐢 ");
println!("=====================");
println!("Importing history...");
match self {
ImportCmd::Auto => {
let shell = env::var("SHELL").unwrap_or(String::from("NO_SHELL"));
match shell.as_str() {
"/bin/zsh" => self.import_zsh(db),
"/bin/zsh" => {
println!("Detected ZSH");
self.import_zsh(db)
}
_ => {
println!("cannot import {} history", shell);

View file

@ -8,7 +8,7 @@ use rusqlite::{params, Connection};
use crate::History;
pub trait Database {
fn save(&self, h: History) -> Result<()>;
fn save(&mut self, h: History) -> Result<()>;
fn save_bulk(&mut self, h: &Vec<History>) -> Result<()>;
fn load(&self, id: &str) -> Result<History>;
fn list(&self) -> Result<()>;
@ -53,6 +53,8 @@ impl SqliteDatabase {
exit integer not null,
command text not null,
cwd text not null,
session text not null,
hostname text not null,
unique(timestamp, cwd, command)
)",
@ -64,22 +66,11 @@ impl SqliteDatabase {
}
impl Database for SqliteDatabase {
fn save(&self, h: History) -> Result<()> {
fn save(&mut self, h: History) -> Result<()> {
debug!("saving history to sqlite");
let v = vec![h];
self.conn.execute(
"insert or ignore into history (
id,
timestamp,
duration,
exit,
command,
cwd
) values (?1, ?2, ?3, ?4, ?5, ?6)",
params![h.id, h.timestamp, h.duration, h.exit, h.command, h.cwd],
)?;
Ok(())
self.save_bulk(&v)
}
fn save_bulk(&mut self, h: &Vec<History>) -> Result<()> {
@ -95,9 +86,20 @@ impl Database for SqliteDatabase {
duration,
exit,
command,
cwd
) values (?1, ?2, ?3, ?4, ?5, ?6)",
params![i.id, i.timestamp, i.duration, i.exit, i.command, i.cwd],
cwd,
session,
hostname
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
params![
i.id,
i.timestamp,
i.duration,
i.exit,
i.command,
i.cwd,
i.session,
i.hostname
],
)?;
}
@ -110,7 +112,7 @@ impl Database for SqliteDatabase {
debug!("loading history item");
let mut stmt = self.conn.prepare(
"select id, timestamp, duration, exit, command, cwd from history
"select id, timestamp, duration, exit, command, cwd, session, hostname from history
where id = ?1",
)?;
@ -122,6 +124,8 @@ impl Database for SqliteDatabase {
exit: row.get(3)?,
command: row.get(4)?,
cwd: row.get(5)?,
session: row.get(6)?,
hostname: row.get(7)?,
})
})?;
@ -137,9 +141,9 @@ impl Database for SqliteDatabase {
self.conn.execute(
"update history
set timestamp = ?2, duration = ?3, exit = ?4, command = ?5, cwd = ?6
set timestamp = ?2, duration = ?3, exit = ?4, command = ?5, cwd = ?6, session = ?7, hostname = ?8
where id = ?1",
params![h.id, h.timestamp, h.duration, h.exit, h.command, h.cwd],
params![h.id, h.timestamp, h.duration, h.exit, h.command, h.cwd, h.session, h.hostname],
)?;
Ok(())
@ -148,9 +152,9 @@ impl Database for SqliteDatabase {
fn list(&self) -> Result<()> {
debug!("listing history");
let mut stmt = self
.conn
.prepare("SELECT id, timestamp, duration, exit, command, cwd FROM history")?;
let mut stmt = self.conn.prepare(
"SELECT id, timestamp, duration, exit, command, cwd, session, hostname FROM history",
)?;
let history_iter = stmt.query_map(params![], |row| {
Ok(History {
@ -160,6 +164,8 @@ impl Database for SqliteDatabase {
exit: row.get(3)?,
command: row.get(4)?,
cwd: row.get(5)?,
session: row.get(6)?,
hostname: row.get(7)?,
})
})?;
@ -167,8 +173,8 @@ impl Database for SqliteDatabase {
let h = h.unwrap();
println!(
"{} | {} | {} | {} | {}",
h.timestamp, h.cwd, h.duration, h.exit, h.command
"{} | {} | {} | {} | {} | {} | {}",
h.timestamp, h.hostname, h.session, h.cwd, h.duration, h.exit, h.command
);
}

View file

@ -1,4 +1,6 @@
use chrono;
use std::env;
use hostname;
use uuid::Uuid;
#[derive(Debug)]
@ -9,10 +11,32 @@ pub struct History {
pub exit: i64,
pub command: String,
pub cwd: String,
pub session: String,
pub hostname: String,
}
impl History {
pub fn new(timestamp: i64, command: String, cwd: String, exit: i64, duration: i64) -> History {
pub fn new(
timestamp: i64,
command: String,
cwd: String,
exit: i64,
duration: i64,
session: Option<String>,
hostname: Option<String>,
) -> History {
// get the current session or just generate a random string
let env_session =
env::var("ATUIN_SESSION").unwrap_or(Uuid::new_v4().to_simple().to_string());
// best attempt at getting the current hostname, or just unknown
let os_hostname = hostname::get().unwrap();
let os_hostname = os_hostname.to_str().unwrap();
let os_hostname = String::from(os_hostname);
let session = session.unwrap_or(env_session);
let hostname = hostname.unwrap_or(os_hostname);
History {
id: Uuid::new_v4().to_simple().to_string(),
timestamp,
@ -20,6 +44,8 @@ impl History {
cwd,
exit,
duration,
session,
hostname,
}
}
}

View file

@ -70,11 +70,13 @@ fn parse_extended(line: String) -> History {
// use nanos, because why the hell not? we won't display them.
History::new(
Utc.timestamp(time, 0).timestamp_nanos(),
time * 1_000_000_000,
trim_newline(command),
String::from("unknown"),
-1,
duration * 1_000_000_000,
None,
None,
)
}
@ -104,6 +106,8 @@ impl Iterator for ImportZsh {
String::from("unknown"),
-1,
-1,
None,
None,
)))
}
}

View file

@ -3,6 +3,7 @@ use std::path::PathBuf;
use directories::ProjectDirs;
use eyre::{eyre, Result};
use structopt::StructOpt;
use uuid::Uuid;
#[macro_use]
extern crate log;
@ -40,8 +41,11 @@ enum AtuinCmd {
#[structopt(about = "import shell history from file")]
Import(ImportCmd),
#[structopt(about = "start a atuin server")]
#[structopt(about = "start an atuin server")]
Server,
#[structopt(about = "generates a UUID")]
Uuid,
}
impl Atuin {
@ -66,8 +70,12 @@ impl Atuin {
let mut db = SqliteDatabase::new(db_path)?;
match self.atuin {
AtuinCmd::History(history) => history.run(db),
AtuinCmd::History(history) => history.run(&mut db),
AtuinCmd::Import(import) => import.run(&mut db),
AtuinCmd::Uuid => {
println!("{}", Uuid::new_v4().to_simple().to_string());
Ok(())
}
_ => Ok(()),
}
}