use std::sync::atomic::{AtomicU64, Ordering};
use super::*;
static FACADE_FILE_CONTENT_READS: AtomicU64 = AtomicU64::new(0);
pub fn facade_file_content_reads() -> u64 {
FACADE_FILE_CONTENT_READS.load(Ordering::Relaxed)
}
#[deprecated(note = "built-in watch sync no longer skips based on process memory")]
pub fn facade_watch_memory_skips() -> u64 {
0
}
pub(super) fn sync_facade_database(project_root: &Path, started: Instant) -> SyncResult {
crate::utils::debug_rss("sync:start");
let files_checked = existing_source_files(project_root).len();
crate::utils::debug_rss("sync:after existing_source_files");
let changes = changed_facade_files(project_root).unwrap_or_default();
crate::utils::debug_rss("sync:after changed_facade_files");
let mut changed_file_paths = Vec::new();
changed_file_paths.extend(changes.added.iter().cloned());
changed_file_paths.extend(changes.modified.iter().cloned());
changed_file_paths.extend(changes.removed.iter().cloned());
let nodes_updated = if changed_file_paths.is_empty() {
0
} else {
let result = index_facade_changed_files(project_root, Instant::now(), &changes);
crate::utils::debug_rss("sync:after index_facade_changed_files");
result.nodes_created
};
SyncResult {
files_checked,
files_added: changes.added.len(),
files_modified: changes.modified.len(),
files_removed: changes.removed.len(),
nodes_updated,
duration_ms: started.elapsed().as_millis() as u64,
changed_file_paths: (!changed_file_paths.is_empty()).then_some(changed_file_paths),
memory_skipped: false,
}
}
pub(super) fn changed_facade_files(project_root: &Path) -> Result<ChangedFiles, String> {
let conn = open_facade_database(project_root)?;
let tracked = read_facade_file_stats(&conn)?;
let current_files = existing_source_files(project_root)
.into_iter()
.collect::<HashSet<_>>();
let mut changes = ChangedFiles::default();
for path in tracked.keys() {
if !current_files.contains(path) {
changes.removed.push(path.clone());
}
}
for path in current_files {
let stored = tracked.get(&path);
if let Some(stored) = stored {
if let Ok(metadata) = fs::metadata(project_root.join(&path)) {
let disk_size = metadata.len() as ByteSize;
let disk_mtime = metadata.modified().ok().map(system_time_ms);
if disk_size == stored.size && disk_mtime == Some(stored.modified_at) {
continue;
}
}
}
FACADE_FILE_CONTENT_READS.fetch_add(1, Ordering::Relaxed);
let content = fs::read_to_string(project_root.join(&path))
.map_err(|err| format!("failed to read {path}: {err}"))?;
let current_hash = hash_content(&content);
match stored {
None => changes.added.push(path),
Some(stored) if stored.content_hash != current_hash => changes.modified.push(path),
_ => {}
}
}
changes.added.sort();
changes.modified.sort();
changes.removed.sort();
Ok(changes)
}
pub(super) struct FacadeFileStat {
pub size: ByteSize,
pub modified_at: TimestampMs,
pub content_hash: String,
}
pub(super) fn read_facade_file_stats(
conn: &Connection,
) -> Result<HashMap<String, FacadeFileStat>, String> {
let mut stmt = conn
.prepare("SELECT path, size, modified_at, content_hash FROM files")
.map_err(|err| format!("failed to prepare file stat query: {err}"))?;
let rows = stmt
.query_map([], |row| {
Ok((
row.get::<_, String>(0)?,
FacadeFileStat {
size: row.get::<_, i64>(1)? as ByteSize,
modified_at: row.get::<_, i64>(2)? as TimestampMs,
content_hash: row.get::<_, String>(3)?,
},
))
})
.map_err(|err| format!("failed to query file stats: {err}"))?;
let mut out = HashMap::new();
for row in rows {
let (path, stat) = row.map_err(|err| format!("failed to read file stat row: {err}"))?;
out.insert(path, stat);
}
Ok(out)
}