2021-04-13 12:14:07 -06:00
|
|
|
use std::fs::{create_dir_all, File};
|
|
|
|
use std::io::prelude::*;
|
|
|
|
use std::path::{Path, PathBuf};
|
2021-03-10 14:24:08 -07:00
|
|
|
|
2021-04-13 12:14:07 -06:00
|
|
|
use chrono::prelude::*;
|
|
|
|
use chrono::Utc;
|
2022-03-17 15:26:57 -06:00
|
|
|
use config::{Config, Environment, File as ConfigFile, FileFormat};
|
2021-11-17 04:50:34 -07:00
|
|
|
use eyre::{eyre, Context, Result};
|
2021-04-13 12:14:07 -06:00
|
|
|
use parse_duration::parse;
|
2021-03-10 14:24:08 -07:00
|
|
|
|
2021-04-13 12:14:07 -06:00
|
|
|
pub const HISTORY_PAGE_SIZE: i64 = 100;
|
|
|
|
|
2021-05-09 01:33:56 -06:00
|
|
|
#[derive(Clone, Debug, Deserialize, Copy)]
|
|
|
|
pub enum SearchMode {
|
|
|
|
#[serde(rename = "prefix")]
|
|
|
|
Prefix,
|
|
|
|
|
|
|
|
#[serde(rename = "fulltext")]
|
|
|
|
FullText,
|
2021-06-01 01:38:19 -06:00
|
|
|
|
|
|
|
#[serde(rename = "fuzzy")]
|
|
|
|
Fuzzy,
|
2021-05-09 01:33:56 -06:00
|
|
|
}
|
|
|
|
|
2021-05-09 04:39:39 -06:00
|
|
|
// FIXME: Can use upstream Dialect enum if https://github.com/stevedonovan/chrono-english/pull/16 is merged
|
|
|
|
#[derive(Clone, Debug, Deserialize, Copy)]
|
|
|
|
pub enum Dialect {
|
|
|
|
#[serde(rename = "us")]
|
|
|
|
Us,
|
|
|
|
|
|
|
|
#[serde(rename = "uk")]
|
|
|
|
Uk,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Dialect> for chrono_english::Dialect {
|
|
|
|
fn from(d: Dialect) -> chrono_english::Dialect {
|
|
|
|
match d {
|
|
|
|
Dialect::Uk => chrono_english::Dialect::Uk,
|
|
|
|
Dialect::Us => chrono_english::Dialect::Us,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-13 12:14:07 -06:00
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
2021-04-20 14:53:07 -06:00
|
|
|
pub struct Settings {
|
2021-05-09 04:39:39 -06:00
|
|
|
pub dialect: Dialect,
|
2021-04-13 12:14:07 -06:00
|
|
|
pub auto_sync: bool,
|
2021-04-09 05:40:21 -06:00
|
|
|
pub sync_address: String,
|
|
|
|
pub sync_frequency: String,
|
|
|
|
pub db_path: String,
|
2021-04-13 12:14:07 -06:00
|
|
|
pub key_path: String,
|
|
|
|
pub session_path: String,
|
2021-05-09 01:33:56 -06:00
|
|
|
pub search_mode: SearchMode,
|
2021-04-13 12:14:07 -06:00
|
|
|
// This is automatically loaded when settings is created. Do not set in
|
|
|
|
// config! Keep secrets and settings apart.
|
|
|
|
pub session_token: String,
|
2021-03-10 14:24:08 -07:00
|
|
|
}
|
|
|
|
|
2021-04-20 14:53:07 -06:00
|
|
|
impl Settings {
|
2021-04-13 12:14:07 -06:00
|
|
|
pub fn save_sync_time() -> Result<()> {
|
2021-04-25 11:21:52 -06:00
|
|
|
let data_dir = atuin_common::utils::data_dir();
|
|
|
|
let data_dir = data_dir.as_path();
|
|
|
|
|
|
|
|
let sync_time_path = data_dir.join("last_sync_time");
|
2021-04-13 12:14:07 -06:00
|
|
|
|
|
|
|
std::fs::write(sync_time_path, Utc::now().to_rfc3339())?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn last_sync() -> Result<chrono::DateTime<Utc>> {
|
2021-04-25 11:21:52 -06:00
|
|
|
let data_dir = atuin_common::utils::data_dir();
|
|
|
|
let data_dir = data_dir.as_path();
|
2021-04-13 12:14:07 -06:00
|
|
|
|
2021-04-25 11:21:52 -06:00
|
|
|
let sync_time_path = data_dir.join("last_sync_time");
|
2021-04-13 12:14:07 -06:00
|
|
|
|
|
|
|
if !sync_time_path.exists() {
|
|
|
|
return Ok(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
let time = std::fs::read_to_string(sync_time_path)?;
|
|
|
|
let time = chrono::DateTime::parse_from_rfc3339(time.as_str())?;
|
|
|
|
|
|
|
|
Ok(time.with_timezone(&Utc))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn should_sync(&self) -> Result<bool> {
|
2021-05-09 13:11:17 -06:00
|
|
|
let session_path = atuin_common::utils::data_dir().join("session");
|
|
|
|
|
|
|
|
if !self.auto_sync || !session_path.exists() {
|
2021-04-13 12:14:07 -06:00
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
match parse(self.sync_frequency.as_str()) {
|
|
|
|
Ok(d) => {
|
|
|
|
let d = chrono::Duration::from_std(d).unwrap();
|
2021-04-20 14:53:07 -06:00
|
|
|
Ok(Utc::now() - Settings::last_sync()? >= d)
|
2021-04-13 12:14:07 -06:00
|
|
|
}
|
|
|
|
Err(e) => Err(eyre!("failed to check sync: {}", e)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-10 14:24:08 -07:00
|
|
|
pub fn new() -> Result<Self> {
|
2021-04-25 11:21:52 -06:00
|
|
|
let config_dir = atuin_common::utils::config_dir();
|
|
|
|
|
|
|
|
let data_dir = atuin_common::utils::data_dir();
|
2021-03-10 14:24:08 -07:00
|
|
|
|
2021-11-17 04:50:34 -07:00
|
|
|
create_dir_all(&config_dir)
|
|
|
|
.wrap_err_with(|| format!("could not create dir {:?}", config_dir))?;
|
|
|
|
create_dir_all(&data_dir)
|
|
|
|
.wrap_err_with(|| format!("could not create dir {:?}", data_dir))?;
|
2021-03-10 14:24:08 -07:00
|
|
|
|
2021-04-21 11:13:51 -06:00
|
|
|
let mut config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG_DIR") {
|
2021-04-13 15:31:41 -06:00
|
|
|
PathBuf::from(p)
|
|
|
|
} else {
|
|
|
|
let mut config_file = PathBuf::new();
|
|
|
|
config_file.push(config_dir);
|
|
|
|
config_file
|
|
|
|
};
|
2021-03-10 14:24:08 -07:00
|
|
|
|
2021-04-21 11:13:51 -06:00
|
|
|
config_file.push("config.toml");
|
|
|
|
|
2021-04-25 11:21:52 -06:00
|
|
|
let db_path = data_dir.join("history.db");
|
|
|
|
let key_path = data_dir.join("key");
|
|
|
|
let session_path = data_dir.join("session");
|
2021-04-13 12:14:07 -06:00
|
|
|
|
2022-03-17 15:26:57 -06:00
|
|
|
let mut config_builder = Config::builder()
|
|
|
|
.set_default("db_path", db_path.to_str())?
|
|
|
|
.set_default("key_path", key_path.to_str())?
|
|
|
|
.set_default("session_path", session_path.to_str())?
|
|
|
|
.set_default("dialect", "us")?
|
|
|
|
.set_default("auto_sync", true)?
|
|
|
|
.set_default("sync_frequency", "1h")?
|
|
|
|
.set_default("sync_address", "https://api.atuin.sh")?
|
|
|
|
.set_default("search_mode", "prefix")?
|
|
|
|
.set_default("session_token", "")?
|
|
|
|
.add_source(Environment::with_prefix("atuin").separator("_"));
|
|
|
|
|
|
|
|
config_builder = if config_file.exists() {
|
|
|
|
config_builder.add_source(ConfigFile::new(
|
|
|
|
config_file.to_str().unwrap(),
|
|
|
|
FileFormat::Toml,
|
|
|
|
))
|
2021-04-13 12:14:07 -06:00
|
|
|
} else {
|
|
|
|
let example_config = include_bytes!("../config.toml");
|
2021-11-17 04:50:34 -07:00
|
|
|
let mut file = File::create(config_file).wrap_err("could not create config file")?;
|
|
|
|
file.write_all(example_config)
|
|
|
|
.wrap_err("could not write default config file")?;
|
2021-03-10 14:24:08 -07:00
|
|
|
|
2022-03-17 15:26:57 -06:00
|
|
|
config_builder
|
|
|
|
};
|
|
|
|
|
|
|
|
let config = config_builder.build()?;
|
|
|
|
let mut settings: Settings = config
|
|
|
|
.try_deserialize()
|
|
|
|
.map_err(|e| eyre!("failed to deserialize: {}", e))?;
|
2021-04-13 15:31:41 -06:00
|
|
|
|
2021-03-10 14:24:08 -07:00
|
|
|
// all paths should be expanded
|
2022-03-17 15:26:57 -06:00
|
|
|
let db_path = settings.db_path;
|
|
|
|
let db_path = shellexpand::full(&db_path)?;
|
|
|
|
settings.db_path = db_path.to_string();
|
2021-04-13 12:14:07 -06:00
|
|
|
|
2022-03-17 15:26:57 -06:00
|
|
|
let key_path = settings.key_path;
|
|
|
|
let key_path = shellexpand::full(&key_path)?;
|
|
|
|
settings.key_path = key_path.to_string();
|
2021-04-13 12:14:07 -06:00
|
|
|
|
2022-03-17 15:26:57 -06:00
|
|
|
let session_path = settings.session_path;
|
|
|
|
let session_path = shellexpand::full(&session_path)?;
|
|
|
|
settings.session_path = session_path.to_string();
|
2021-04-13 12:14:07 -06:00
|
|
|
|
|
|
|
// Finally, set the auth token
|
|
|
|
if Path::new(session_path.to_string().as_str()).exists() {
|
|
|
|
let token = std::fs::read_to_string(session_path.to_string())?;
|
2022-03-17 15:26:57 -06:00
|
|
|
settings.session_token = token.trim().to_string();
|
2021-04-13 12:14:07 -06:00
|
|
|
} else {
|
2022-03-17 15:26:57 -06:00
|
|
|
settings.session_token = String::from("not logged in");
|
2021-04-13 12:14:07 -06:00
|
|
|
}
|
2021-03-10 14:24:08 -07:00
|
|
|
|
2022-03-17 15:26:57 -06:00
|
|
|
Ok(settings)
|
2021-03-10 14:24:08 -07:00
|
|
|
}
|
|
|
|
}
|