use crate::codetime::discover_xbp_projects as discover_codetime_xbp_projects;
use crate::config::global_xbp_paths;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectInfo {
pub path: PathBuf,
pub name: String,
pub last_accessed: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Profile {
pub last_project_path: Option<PathBuf>,
#[serde(default)]
pub recent_projects: Vec<ProjectInfo>,
}
impl Profile {
pub fn get_profile_path() -> Result<PathBuf, String> {
Ok(global_xbp_paths()?.root_dir.join("profile.yaml"))
}
pub fn load() -> Result<Self, String> {
let profile_path = Self::get_profile_path()?;
if !profile_path.exists() {
#[cfg(target_os = "windows")]
if let Some(legacy_path) = legacy_windows_profile_path() {
if legacy_path.exists() {
let content = fs::read_to_string(&legacy_path)
.map_err(|e| format!("Failed to read legacy profile: {}", e))?;
let profile: Profile = serde_yaml::from_str(&content)
.map_err(|e| format!("Failed to parse legacy profile: {}", e))?;
let _ = profile.save();
return Ok(profile);
}
}
return Ok(Profile::default());
}
let content = fs::read_to_string(&profile_path)
.map_err(|e| format!("Failed to read profile: {}", e))?;
serde_yaml::from_str(&content).map_err(|e| format!("Failed to parse profile: {}", e))
}
pub fn save(&self) -> Result<(), String> {
let profile_path = Self::get_profile_path()?;
let yaml = serde_yaml::to_string(self)
.map_err(|e| format!("Failed to serialize profile: {}", e))?;
fs::write(&profile_path, yaml).map_err(|e| format!("Failed to write profile: {}", e))
}
pub fn update_last_project(&mut self, path: PathBuf, name: String) {
self.last_project_path = Some(path.clone());
let now = Utc::now();
if let Some(existing) = self.recent_projects.iter_mut().find(|p| p.path == path) {
existing.last_accessed = now;
existing.name = name;
} else {
self.recent_projects.push(ProjectInfo {
path,
name,
last_accessed: now,
});
}
self.recent_projects
.sort_by_key(|project| std::cmp::Reverse(project.last_accessed));
if self.recent_projects.len() > 10 {
self.recent_projects.truncate(10);
}
}
}
#[cfg(target_os = "windows")]
fn legacy_windows_profile_path() -> Option<PathBuf> {
dirs::config_dir().map(|config_dir| config_dir.join("xbp").join("profile.yaml"))
}
pub fn find_all_xbp_projects() -> Vec<ProjectInfo> {
discover_codetime_xbp_projects(std::env::current_dir().ok().as_deref())
.into_iter()
.map(|project| ProjectInfo {
path: PathBuf::from(project.root),
name: project.name,
last_accessed: Utc::now(),
})
.collect()
}
pub fn rank_projects_by_proximity(
mut projects: Vec<ProjectInfo>,
current_dir: PathBuf,
) -> Vec<ProjectInfo> {
projects.sort_by_key(|project| {
let common_components = current_dir
.components()
.zip(project.path.components())
.take_while(|(a, b)| a == b)
.count();
std::cmp::Reverse(common_components)
});
projects
}