git_iris/companion/
session.rs1use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9use uuid::Uuid;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct FileActivity {
14 pub path: PathBuf,
16 pub first_touched: DateTime<Utc>,
18 pub last_touched: DateTime<Utc>,
20 pub touch_count: u32,
22}
23
24impl FileActivity {
25 #[must_use]
27 pub fn new(path: PathBuf) -> Self {
28 let now = Utc::now();
29 Self {
30 path,
31 first_touched: now,
32 last_touched: now,
33 touch_count: 1,
34 }
35 }
36
37 pub fn touch(&mut self) {
39 self.last_touched = Utc::now();
40 self.touch_count += 1;
41 }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct SessionState {
47 pub session_id: Uuid,
49 pub repo_path: PathBuf,
51 pub branch: String,
53 pub started_at: DateTime<Utc>,
55 pub last_activity: DateTime<Utc>,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub last_commit_at: Option<DateTime<Utc>>,
60 pub files_touched: HashMap<PathBuf, FileActivity>,
62 pub commits_made: Vec<String>,
64}
65
66impl SessionState {
67 #[must_use]
69 pub fn new(repo_path: PathBuf, branch: String) -> Self {
70 let now = Utc::now();
71 Self {
72 session_id: Uuid::new_v4(),
73 repo_path,
74 branch,
75 started_at: now,
76 last_activity: now,
77 last_commit_at: None,
78 files_touched: HashMap::new(),
79 commits_made: Vec::new(),
80 }
81 }
82
83 pub fn touch_file(&mut self, path: PathBuf) {
85 self.last_activity = Utc::now();
86 let normalized_path = self.normalize_path(path);
87 self.files_touched
88 .entry(normalized_path.clone())
89 .and_modify(FileActivity::touch)
90 .or_insert_with(|| FileActivity::new(normalized_path));
91 }
92
93 pub fn record_commit(&mut self, hash: String) {
95 let now = Utc::now();
96 self.last_activity = now;
97 self.last_commit_at = Some(now);
98 self.commits_made.push(hash);
99 }
100
101 #[must_use]
103 pub fn duration(&self) -> chrono::Duration {
104 Utc::now() - self.started_at
105 }
106
107 #[must_use]
109 pub fn files_count(&self) -> usize {
110 self.files_touched.len()
111 }
112
113 #[must_use]
115 pub fn recent_files(&self) -> Vec<&FileActivity> {
116 let mut files: Vec<_> = self.files_touched.values().collect();
117 files.sort_by_key(|f| std::cmp::Reverse(f.last_touched));
118 files
119 }
120
121 #[must_use]
123 pub fn time_since_last_commit(&self) -> Option<chrono::Duration> {
124 self.last_commit_at
125 .map(|last_commit_at| Utc::now() - last_commit_at)
126 }
127
128 pub fn set_branch(&mut self, branch: String) {
130 self.session_id = Uuid::new_v4();
131 self.started_at = Utc::now();
132 self.last_activity = self.started_at;
133 self.last_commit_at = None;
134 self.files_touched.clear();
135 self.commits_made.clear();
136 self.branch = branch;
137 }
138
139 fn normalize_path(&self, path: PathBuf) -> PathBuf {
141 if path.is_absolute()
142 && let Ok(relative) = path.strip_prefix(&self.repo_path)
143 {
144 return relative.to_path_buf();
145 }
146
147 path
148 }
149}