use std::env;
use std::fs;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use super::database_structs::{Database, Command, DeletedCommands};
pub fn initialize_database_from_history(db: &mut Database, deleted_commands: &DeletedCommands) -> Result<(), Box<dyn std::error::Error>> {
if !db.command_list.is_empty() {
return Ok(());
}
let history_file = get_history_file_path()?;
if !Path::new(&history_file).exists() {
return Ok(());
}
if history_file.contains("history") || history_file.contains("HIST") {
let metadata = fs::metadata(&history_file)?;
if metadata.is_file() {
let history_content = match fs::read_to_string(&history_file) {
Ok(content) => content,
Err(e) => {
eprintln!("Warning: Could not read history file '{}': {}", history_file, e);
eprintln!("Attempting to read with lossy conversion...");
match fs::read(&history_file) {
Ok(bytes) => String::from_utf8_lossy(&bytes).into_owned(),
Err(e2) => {
eprintln!("Error: Could not read history file at all: {}", e2);
return Ok(());
}
}
}
};
let commands = parse_history_file(&history_content);
if commands.is_empty() {
return Ok(());
}
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let interval_seconds = 120;
for (i, command) in commands.iter().enumerate() {
let timestamp = now - (i as u64 * interval_seconds);
insert_command_with_timestamp(command, timestamp, db, deleted_commands);
}
println!("Initialized database with {} commands from history", commands.len());
}
}
Ok(())
}
fn get_history_file_path() -> Result<String, Box<dyn std::error::Error>> {
if let Ok(histfile) = env::var("HISTFILE") {
if !histfile.is_empty() {
return Ok(histfile);
}
}
let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?;
let possible_paths = vec![
home_dir.join(".local/share/fish/fish_history"), home_dir.join(".zsh_history"), home_dir.join(".bash_history"), home_dir.join(".history"), home_dir.join(".fish_history"), ];
for path in possible_paths {
if path.exists() {
return Ok(path.to_string_lossy().to_string());
}
}
Err("No history file found".into())
}
fn parse_history_file(content: &str) -> Vec<String> {
let mut commands = Vec::new();
let lines: Vec<&str> = content.lines().collect();
for line in lines.iter().rev() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if trimmed.starts_with('#') ||
trimmed.starts_with("HISTTIMEFORMAT") ||
trimmed.starts_with("HISTSIZE") ||
trimmed.starts_with("HISTFILESIZE") {
continue;
}
let command = extract_command_from_history_line(trimmed);
if !command.is_empty() && command.len() > 2 {
commands.push(command);
}
}
commands
}
fn extract_command_from_history_line(line: &str) -> String {
if line.starts_with(": ") {
if let Some(semicolon_pos) = line.find(';') {
let command_part = line[semicolon_pos + 1..].trim();
if !command_part.is_empty() {
return command_part.to_string();
}
}
return String::new();
}
if line.starts_with("- cmd:") {
let command_part = line[6..].trim();
if !command_part.is_empty() {
return command_part.to_string();
}
return String::new();
}
if line.starts_with('#') {
return String::new();
}
line.to_string()
}
fn insert_command_with_timestamp(command: &str, timestamp: u64, db: &mut Database, deleted_commands: &DeletedCommands) {
if command.is_empty() || deleted_commands.deleted_commands.contains(command) {
return;
}
let parts: Vec<&str> = command.split_whitespace().collect();
if parts.len() <= 1 || command.len() <= 5 {
return;
}
let binary_name = std::env::args()
.next()
.and_then(|path| std::path::Path::new(&path).file_name().map(|f| f.to_os_string()))
.and_then(|os_str| os_str.into_string().ok());
if let Some(name) = binary_name {
if parts[0] == name {
return;
}
}
let length: i16 = command.split_whitespace().map(|s| s.len()).sum::<usize>() as i16;
let number_of_words: i8 = parts.len() as i8;
let frequency: i32 = 1;
let mut new_command = Command {
score: 0, last_access_time: timestamp as i64,
frequency,
length,
command_text: command.to_string(),
number_of_words,
};
new_command.score = calculate_score(&new_command);
let mut temp = String::new();
for word in parts.iter() {
if !temp.is_empty() {
temp.push(' ');
}
temp.push_str(word);
if temp.len() > 2 {
let prefix_command = create_prefix_command(&temp, timestamp);
db.add_command_with_existing(prefix_command);
}
}
db.add_command_with_existing(new_command);
}
fn create_prefix_command(command_text: &str, timestamp: u64) -> Command {
let length: i16 = command_text.split_whitespace().map(|s| s.len()).sum::<usize>() as i16;
let number_of_words: i8 = command_text.split_whitespace().count() as i8;
let frequency: i32 = 1;
let mut command = Command {
score: 0,
last_access_time: timestamp as i64,
frequency,
length,
command_text: command_text.to_string(),
number_of_words,
};
command.score = calculate_score(&command);
command
}
fn calculate_score(command: &Command) -> i32 {
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64;
let time_difference: i64 = now - command.last_access_time;
let mult: f64 = if time_difference <= 3600 {
4.0
} else if time_difference <= 86400 {
2.0
} else if time_difference <= 604800 {
0.5
} else {
0.25
};
let length = command.length as f64;
let frequency = command.frequency as f64;
(mult * length.powf(3.0 / 5.0) * frequency) as i32
}