use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileActivity {
pub path: PathBuf,
pub first_touched: DateTime<Utc>,
pub last_touched: DateTime<Utc>,
pub touch_count: u32,
}
impl FileActivity {
#[must_use]
pub fn new(path: PathBuf) -> Self {
let now = Utc::now();
Self {
path,
first_touched: now,
last_touched: now,
touch_count: 1,
}
}
pub fn touch(&mut self) {
self.last_touched = Utc::now();
self.touch_count += 1;
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionState {
pub session_id: Uuid,
pub repo_path: PathBuf,
pub branch: String,
pub started_at: DateTime<Utc>,
pub last_activity: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_commit_at: Option<DateTime<Utc>>,
pub files_touched: HashMap<PathBuf, FileActivity>,
pub commits_made: Vec<String>,
}
impl SessionState {
#[must_use]
pub fn new(repo_path: PathBuf, branch: String) -> Self {
let now = Utc::now();
Self {
session_id: Uuid::new_v4(),
repo_path,
branch,
started_at: now,
last_activity: now,
last_commit_at: None,
files_touched: HashMap::new(),
commits_made: Vec::new(),
}
}
pub fn touch_file(&mut self, path: PathBuf) {
self.last_activity = Utc::now();
let normalized_path = self.normalize_path(path);
self.files_touched
.entry(normalized_path.clone())
.and_modify(FileActivity::touch)
.or_insert_with(|| FileActivity::new(normalized_path));
}
pub fn record_commit(&mut self, hash: String) {
let now = Utc::now();
self.last_activity = now;
self.last_commit_at = Some(now);
self.commits_made.push(hash);
}
#[must_use]
pub fn duration(&self) -> chrono::Duration {
Utc::now() - self.started_at
}
#[must_use]
pub fn files_count(&self) -> usize {
self.files_touched.len()
}
#[must_use]
pub fn recent_files(&self) -> Vec<&FileActivity> {
let mut files: Vec<_> = self.files_touched.values().collect();
files.sort_by_key(|f| std::cmp::Reverse(f.last_touched));
files
}
#[must_use]
pub fn time_since_last_commit(&self) -> Option<chrono::Duration> {
self.last_commit_at
.map(|last_commit_at| Utc::now() - last_commit_at)
}
pub fn set_branch(&mut self, branch: String) {
self.session_id = Uuid::new_v4();
self.started_at = Utc::now();
self.last_activity = self.started_at;
self.last_commit_at = None;
self.files_touched.clear();
self.commits_made.clear();
self.branch = branch;
}
fn normalize_path(&self, path: PathBuf) -> PathBuf {
if path.is_absolute()
&& let Ok(relative) = path.strip_prefix(&self.repo_path)
{
return relative.to_path_buf();
}
path
}
}