tandem_runtime/
workspace_index.rs1use std::path::PathBuf;
2use std::sync::Arc;
3
4use ignore::WalkBuilder;
5use serde::Serialize;
6use tokio::sync::RwLock;
7
8#[derive(Debug, Clone, Serialize, Default)]
9pub struct WorkspaceIndexSnapshot {
10 pub root: String,
11 pub file_count: usize,
12 pub indexed_at: Option<String>,
13 pub largest_files: Vec<IndexedFile>,
14}
15
16#[derive(Debug, Clone, Serialize)]
17pub struct IndexedFile {
18 pub path: String,
19 pub bytes: u64,
20}
21
22#[derive(Clone)]
23pub struct WorkspaceIndex {
24 root: Arc<PathBuf>,
25 snapshot: Arc<RwLock<WorkspaceIndexSnapshot>>,
26}
27
28impl WorkspaceIndex {
29 pub async fn new(root: impl Into<PathBuf>) -> Self {
30 let root = root.into();
31 let initial = WorkspaceIndexSnapshot {
32 root: root.to_string_lossy().to_string(),
33 ..WorkspaceIndexSnapshot::default()
34 };
35 let this = Self {
36 root: Arc::new(root),
37 snapshot: Arc::new(RwLock::new(initial)),
38 };
39 let clone = this.clone();
40 tokio::spawn(async move {
41 let _ = clone.refresh().await;
42 });
43 this
44 }
45
46 pub async fn refresh(&self) -> WorkspaceIndexSnapshot {
47 let root = self.root.clone();
48 let (mut files, count) = tokio::task::spawn_blocking(move || {
49 let mut files = Vec::new();
50 let mut count = 0usize;
51 for entry in WalkBuilder::new(root.as_path()).build().flatten() {
52 if !entry.file_type().map(|f| f.is_file()).unwrap_or(false) {
53 continue;
54 }
55 count += 1;
56 if let Ok(meta) = entry.metadata() {
57 files.push(IndexedFile {
58 path: relativize(root.as_path(), entry.path()),
59 bytes: meta.len(),
60 });
61 }
62 }
63 (files, count)
64 })
65 .await
66 .unwrap_or_default();
67
68 files.sort_by(|a, b| b.bytes.cmp(&a.bytes));
69 let largest_files = files.into_iter().take(20).collect::<Vec<_>>();
70 let snapshot = WorkspaceIndexSnapshot {
71 root: self.root.to_string_lossy().to_string(),
72 file_count: count,
73 indexed_at: Some(chrono::Utc::now().to_rfc3339()),
74 largest_files,
75 };
76 *self.snapshot.write().await = snapshot.clone();
77 snapshot
78 }
79
80 pub async fn snapshot(&self) -> WorkspaceIndexSnapshot {
81 self.snapshot.read().await.clone()
82 }
83}
84
85fn relativize(root: &std::path::Path, path: &std::path::Path) -> String {
86 path.strip_prefix(root)
87 .map(|v| v.to_string_lossy().to_string())
88 .unwrap_or_else(|_| path.to_string_lossy().to_string())
89}