use anyhow::{Context, Result};
use std::path::PathBuf;
use super::utils;
use crate::config;
use crate::cursor::folder_id;
#[derive(Debug, Default)]
pub struct Stats {
pub project_path: PathBuf,
pub chat_sessions: Option<usize>,
pub workspace_size: u64,
pub projects_size: u64,
pub folder_id: String,
pub workspace_hash: Option<String>,
}
pub fn stats(project_path: Option<PathBuf>) -> Result<Stats> {
let project_path = match project_path {
Some(p) => p,
None => std::env::current_dir().context("Failed to get current directory")?,
};
let project_path = project_path
.canonicalize()
.with_context(|| format!("Path does not exist: {}", project_path.display()))?;
let project_path = utils::strip_windows_prefix(&project_path);
let folder_id_str = folder_id::path_to_folder_id(&project_path);
let cursor_projects_dir = config::cursor_projects_dir()?;
let projects_dir = cursor_projects_dir.join(&folder_id_str);
let workspace_dir = utils::find_workspace_dir(&project_path)?;
let projects_size = if projects_dir.exists() {
utils::calculate_dir_size(&projects_dir).unwrap_or(0)
} else {
0
};
let (workspace_size, chat_sessions, workspace_hash) = match &workspace_dir {
Some(dir) => {
let size = utils::calculate_dir_size(dir).unwrap_or(0);
let chats = utils::count_chat_sessions_if_available(dir).with_context(|| {
format!("Failed to discover chat sessions in {}", dir.display())
})?;
let hash = dir.file_name().map(|n| n.to_string_lossy().to_string());
(size, chats, hash)
}
None => (0, Some(0), None),
};
Ok(Stats {
project_path,
chat_sessions,
workspace_size,
projects_size,
folder_id: folder_id_str,
workspace_hash,
})
}
pub fn format_stats(stats: &Stats) -> String {
let mut lines = vec![];
lines.push(format!("Project: {}", stats.project_path.display()));
lines.push(format!("Folder ID: {}", stats.folder_id));
if let Some(hash) = &stats.workspace_hash {
lines.push(format!("Workspace Hash: {}", hash));
} else {
lines.push("Workspace Hash: (not found)".to_string());
}
lines.push(String::new());
lines.push(format!(
"Chat Sessions: {}",
stats
.chat_sessions
.map(|count| count.to_string())
.unwrap_or_else(|| "unknown".to_string())
));
lines.push(format!(
"Workspace Storage: {}",
utils::format_size(stats.workspace_size)
));
lines.push(format!(
"Projects Data: {}",
utils::format_size(stats.projects_size)
));
lines.push(format!(
"Total Cursor Data: {}",
utils::format_size(stats.workspace_size + stats.projects_size)
));
lines.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stats_default() {
let stats = Stats::default();
assert_eq!(stats.chat_sessions, None);
assert_eq!(stats.workspace_size, 0);
assert_eq!(stats.projects_size, 0);
}
#[test]
fn test_format_stats_unknown_chat_count() {
let stats = Stats {
project_path: PathBuf::from("/tmp/project"),
chat_sessions: None,
workspace_size: 0,
projects_size: 0,
folder_id: "folder-id".to_string(),
workspace_hash: None,
};
let formatted = format_stats(&stats);
assert!(formatted.contains("Chat Sessions: unknown"));
}
}