use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
use std::time::Duration;
use std::time::Instant;
use crate::channel::Sender;
use crate::config::NonRustInclusion;
use crate::constants::CARGO_TOML;
use crate::constants::GIT_DIR;
use crate::constants::NEW_PROJECT_DEBOUNCE;
use crate::enrichment;
use crate::http::HttpClient;
use crate::project;
use crate::project::AbsolutePath;
use crate::project::GitRepoPresence;
use crate::project::RootItem;
use crate::project::RootItem::NonRust;
use crate::scan;
use crate::scan::BackgroundMsg;
use crate::scan::FetchContext;
use crate::scan::ProjectDetailRequest;
pub(super) fn spawn_project_refresh(
background_tx: Sender<BackgroundMsg>,
project_root: AbsolutePath,
) {
rayon::spawn(move || {
let Some(item) = scan::discover_project_item(&project_root).or_else(|| {
let cargo_toml = project_root.join(CARGO_TOML);
project::from_cargo_toml(&cargo_toml)
.ok()
.map(scan::cargo_project_to_item)
}) else {
return;
};
let disk_entries = scan::disk_usage_batch_for_item(&item);
let root_path = AbsolutePath::from(item.path().to_path_buf());
let _ = background_tx.send(BackgroundMsg::ProjectRefreshed { item });
let _ = background_tx.send(BackgroundMsg::DiskUsageBatch {
root_path,
entries: disk_entries,
});
});
}
pub(super) fn spawn_project_refresh_after(
background_tx: Sender<BackgroundMsg>,
project_root: AbsolutePath,
delay: Duration,
) {
rayon::spawn(move || {
if !delay.is_zero() {
std::thread::sleep(delay);
}
spawn_project_refresh(background_tx, project_root);
});
}
pub(super) fn probe_new_projects(
background_tx: &Sender<BackgroundMsg>,
pending_new: &mut HashMap<AbsolutePath, Instant>,
discovered: &mut HashSet<AbsolutePath>,
_: u32,
non_rust: NonRustInclusion,
client: &HttpClient,
) {
let now = Instant::now();
let ready: Vec<AbsolutePath> = pending_new
.iter()
.filter(|(_, deadline)| now >= **deadline)
.map(|(path, _)| path.clone())
.collect();
for dir in ready {
pending_new.remove(&dir);
if !dir.is_dir() {
discovered.remove(&dir);
let _ = background_tx.send(BackgroundMsg::DiskUsage {
path: dir,
bytes: 0,
});
continue;
}
if discovered.contains(&dir) {
continue;
}
if let Some(item) = probe_project(&dir, non_rust) {
discovered.insert(dir.clone());
let abs_path = AbsolutePath::from(item.path().to_path_buf());
let project_name = item.name().map(str::to_string);
let repo_presence = if project::git_repo_root(&abs_path).is_some() {
GitRepoPresence::InRepo
} else {
GitRepoPresence::OutsideRepo
};
let disk_entries = scan::disk_usage_batch_for_item(&item);
let _ = background_tx.send(BackgroundMsg::ProjectDiscovered { item });
let _ = background_tx.send(BackgroundMsg::DiskUsageBatch {
root_path: abs_path.clone(),
entries: disk_entries,
});
if abs_path.join(CARGO_TOML).exists() {
spawn_project_refresh_after(
background_tx.clone(),
abs_path.clone(),
NEW_PROJECT_DEBOUNCE,
);
}
let sender = background_tx.clone();
let fetch_context = FetchContext {
client: client.clone(),
};
enrichment::spawn_language_scan(abs_path.clone(), background_tx.clone());
rayon::spawn(move || {
let request = ProjectDetailRequest {
sender: &sender,
fetch_context: &fetch_context,
abs_path: &abs_path,
name: project_name.as_deref(),
repo_presence,
};
scan::fetch_project_details(&request);
});
}
}
}
pub(super) fn project_level_dir(
event_path: &Path,
watch_roots: &[AbsolutePath],
project_parents: &HashSet<AbsolutePath>,
) -> Option<AbsolutePath> {
let mut path = event_path.to_path_buf();
let mut marker_candidate: Option<AbsolutePath> = None;
loop {
let parent = path.parent()?;
if path.join(CARGO_TOML).exists() || path.join(GIT_DIR).exists() {
marker_candidate = Some(AbsolutePath::from(path.clone()));
}
if watch_roots.iter().any(|r| parent == r.as_path()) || project_parents.contains(parent) {
return Some(marker_candidate.unwrap_or_else(|| AbsolutePath::from(path)));
}
if !watch_roots.iter().any(|r| path.starts_with(r.as_path())) {
return None;
}
path = parent.to_path_buf();
}
}
pub(super) fn probe_project(dir: &Path, non_rust: NonRustInclusion) -> Option<RootItem> {
let cargo_toml = dir.join(CARGO_TOML);
if cargo_toml.exists() {
return scan::discover_project_item(dir);
}
if non_rust.includes_non_rust() && dir.join(GIT_DIR).is_dir() {
return Some(NonRust(project::from_git_dir(dir)));
}
None
}