diff options
Diffstat (limited to 'atuin-client/src/import/resh.rs')
-rw-r--r-- | atuin-client/src/import/resh.rs | 135 |
1 files changed, 100 insertions, 35 deletions
diff --git a/atuin-client/src/import/resh.rs b/atuin-client/src/import/resh.rs index 55c9da7f..a0378c36 100644 --- a/atuin-client/src/import/resh.rs +++ b/atuin-client/src/import/resh.rs @@ -1,91 +1,156 @@ +use std::{ + fs::File, + io::{BufRead, BufReader}, + path::{Path, PathBuf}, +}; + +use atuin_common::utils::uuid_v4; +use chrono::{TimeZone, Utc}; +use directories::UserDirs; +use eyre::{eyre, Result}; use serde::Deserialize; +use super::{count_lines, Importer}; +use crate::history::History; + #[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] pub struct ReshEntry { - #[serde(rename = "cmdLine")] pub cmd_line: String, - #[serde(rename = "exitCode")] pub exit_code: i64, pub shell: String, pub uname: String, - #[serde(rename = "sessionId")] pub session_id: String, pub home: String, pub lang: String, - #[serde(rename = "lcAll")] pub lc_all: String, pub login: String, pub pwd: String, - #[serde(rename = "pwdAfter")] pub pwd_after: String, - #[serde(rename = "shellEnv")] pub shell_env: String, pub term: String, - #[serde(rename = "realPwd")] pub real_pwd: String, - #[serde(rename = "realPwdAfter")] pub real_pwd_after: String, pub pid: i64, - #[serde(rename = "sessionPid")] pub session_pid: i64, pub host: String, pub hosttype: String, pub ostype: String, pub machtype: String, pub shlvl: i64, - #[serde(rename = "timezoneBefore")] pub timezone_before: String, - #[serde(rename = "timezoneAfter")] pub timezone_after: String, - #[serde(rename = "realtimeBefore")] pub realtime_before: f64, - #[serde(rename = "realtimeAfter")] pub realtime_after: f64, - #[serde(rename = "realtimeBeforeLocal")] pub realtime_before_local: f64, - #[serde(rename = "realtimeAfterLocal")] pub realtime_after_local: f64, - #[serde(rename = "realtimeDuration")] pub realtime_duration: f64, - #[serde(rename = "realtimeSinceSessionStart")] pub realtime_since_session_start: f64, - #[serde(rename = "realtimeSinceBoot")] pub realtime_since_boot: f64, - #[serde(rename = "gitDir")] pub git_dir: String, - #[serde(rename = "gitRealDir")] pub git_real_dir: String, - #[serde(rename = "gitOriginRemote")] pub git_origin_remote: String, - #[serde(rename = "gitDirAfter")] pub git_dir_after: String, - #[serde(rename = "gitRealDirAfter")] pub git_real_dir_after: String, - #[serde(rename = "gitOriginRemoteAfter")] pub git_origin_remote_after: String, - #[serde(rename = "machineId")] pub machine_id: String, - #[serde(rename = "osReleaseId")] pub os_release_id: String, - #[serde(rename = "osReleaseVersionId")] pub os_release_version_id: String, - #[serde(rename = "osReleaseIdLike")] pub os_release_id_like: String, - #[serde(rename = "osReleaseName")] pub os_release_name: String, - #[serde(rename = "osReleasePrettyName")] pub os_release_pretty_name: String, - #[serde(rename = "reshUuid")] pub resh_uuid: String, - #[serde(rename = "reshVersion")] pub resh_version: String, - #[serde(rename = "reshRevision")] pub resh_revision: String, - #[serde(rename = "partsMerged")] pub parts_merged: bool, pub recalled: bool, - #[serde(rename = "recallLastCmdLine")] pub recall_last_cmd_line: String, pub cols: String, pub lines: String, } + +#[derive(Debug)] +pub struct Resh { + file: BufReader<File>, + strbuf: String, + loc: usize, + counter: i64, +} + +impl Importer for Resh { + const NAME: &'static str = "resh"; + + fn histpath() -> Result<PathBuf> { + let user_dirs = UserDirs::new().unwrap(); + let home_dir = user_dirs.home_dir(); + + Ok(home_dir.join(".resh_history.json")) + } + + fn parse(path: impl AsRef<Path>) -> Result<Self> { + let file = File::open(path)?; + let mut buf = BufReader::new(file); + let loc = count_lines(&mut buf)?; + + Ok(Self { + file: buf, + strbuf: String::new(), + loc, + counter: 0, + }) + } +} + +impl Iterator for Resh { + type Item = Result<History>; + + fn next(&mut self) -> Option<Self::Item> { + self.strbuf.clear(); + match self.file.read_line(&mut self.strbuf) { + Ok(0) => return None, + Ok(_) => (), + Err(e) => return Some(Err(eyre!("failed to read line: {}", e))), // we can skip past things like invalid utf8 + } + + let entry = match serde_json::from_str::<ReshEntry>(&self.strbuf) { + Ok(e) => e, + Err(e) => { + return Some(Err(eyre!( + "Invalid entry found in resh_history file: {}", + e + ))) + } + }; + + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss)] + let timestamp = { + let secs = entry.realtime_before.floor() as i64; + let nanosecs = (entry.realtime_before.fract() * 1_000_000_000_f64).round() as u32; + Utc.timestamp(secs, nanosecs) + }; + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss)] + let duration = { + let secs = entry.realtime_after.floor() as i64; + let nanosecs = (entry.realtime_after.fract() * 1_000_000_000_f64).round() as u32; + let difference = Utc.timestamp(secs, nanosecs) - timestamp; + difference.num_nanoseconds().unwrap_or(0) + }; + + Some(Ok(History { + id: uuid_v4(), + timestamp, + duration, + exit: entry.exit_code, + command: entry.cmd_line, + cwd: entry.pwd, + session: uuid_v4(), + hostname: entry.host, + })) + } + + fn size_hint(&self) -> (usize, Option<usize>) { + (self.loc, Some(self.loc)) + } +} |