fix: DriveMonitor config.csv sync uses Last-Modified in addition to ETag
All checks were successful
BotServer CI/CD / build (push) Successful in 5m46s

ETag in MinIO is an MD5 content hash, so re-uploading the same content
preserves the ETag. Add last_modified comparison so config.csv changes
that don't alter content hash still get synced. Also fixes EmbeddingConfig
fallback from previous commit.
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-04-13 08:33:37 -03:00
parent 1148069652
commit dbec0df923

View file

@ -37,6 +37,8 @@ pub struct FileState {
pub last_failed_at: Option<DateTime<Utc>>, pub last_failed_at: Option<DateTime<Utc>>,
#[serde(default)] #[serde(default)]
pub fail_count: u32, pub fail_count: u32,
#[serde(default)]
pub last_modified: Option<String>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -680,13 +682,14 @@ impl DriveMonitor {
if path.ends_with('/') || !path.to_ascii_lowercase().ends_with(".bas") { if path.ends_with('/') || !path.to_ascii_lowercase().ends_with(".bas") {
continue; continue;
} }
let file_state = FileState { let file_state = FileState {
etag: obj.e_tag().unwrap_or_default().to_string(), etag: obj.e_tag().unwrap_or_default().to_string(),
indexed: false, indexed: false,
last_failed_at: None, last_failed_at: None,
fail_count: 0, fail_count: 0,
last_modified: obj.last_modified().map(|dt| dt.to_string()),
}; };
current_files.insert(path, file_state); current_files.insert(path, file_state);
} }
if !list_objects.is_truncated.unwrap_or(false) { if !list_objects.is_truncated.unwrap_or(false) {
break; break;
@ -904,7 +907,7 @@ impl DriveMonitor {
} }
} }
let mut states = self.file_states.write().await; let mut states = self.file_states.write().await;
states.insert(prompt_state_key, FileState { etag, indexed: false, last_failed_at: None, fail_count: 0 }); states.insert(prompt_state_key, FileState { etag, indexed: false, last_failed_at: None, fail_count: 0, last_modified: None });
drop(states); drop(states);
let file_states_clone = Arc::clone(&self.file_states); let file_states_clone = Arc::clone(&self.file_states);
let work_root_clone = self.work_root.clone(); let work_root_clone = self.work_root.clone();
@ -922,14 +925,24 @@ impl DriveMonitor {
debug!("check_gbot: Found config.csv at path: {}", path); debug!("check_gbot: Found config.csv at path: {}", path);
let etag = obj.e_tag().unwrap_or_default().to_string(); let etag = obj.e_tag().unwrap_or_default().to_string();
let last_modified = obj.last_modified().map(|dt| dt.to_string());
let config_state_key = format!("__config__{}", path); let config_state_key = format!("__config__{}", path);
let should_sync = { let should_sync = {
let states = self.file_states.read().await; let states = self.file_states.read().await;
match states.get(&config_state_key) { match states.get(&config_state_key) {
Some(prev) => prev.etag != etag, Some(prev) => {
let etag_changed = prev.etag != etag;
let mod_changed = match (&prev.last_modified, &last_modified) {
(Some(prev_dt), Some(new_dt)) => prev_dt != new_dt,
(None, Some(_)) => true,
_ => false,
};
etag_changed || mod_changed
}
None => true, None => true,
} }
}; };
debug!("check_gbot: config.csv should_sync={} (etag={}, last_modified={:?})", should_sync, etag, last_modified);
if should_sync { if should_sync {
match client match client
.head_object() .head_object()
@ -1061,9 +1074,9 @@ impl DriveMonitor {
self.broadcast_theme_change(&csv_content).await?; self.broadcast_theme_change(&csv_content).await?;
} }
// Update file_states with config.csv ETag // Update file_states with config.csv ETag and last_modified
let mut states = self.file_states.write().await; let mut states = self.file_states.write().await;
states.insert(config_state_key, FileState { etag, indexed: false, last_failed_at: None, fail_count: 0 }); states.insert(config_state_key, FileState { etag, indexed: false, last_failed_at: None, fail_count: 0, last_modified });
drop(states); drop(states);
let file_states_clone = Arc::clone(&self.file_states); let file_states_clone = Arc::clone(&self.file_states);
let work_root_clone = self.work_root.clone(); let work_root_clone = self.work_root.clone();
@ -1487,13 +1500,14 @@ impl DriveMonitor {
continue; continue;
} }
let file_state = FileState { let file_state = FileState {
etag: obj.e_tag().unwrap_or_default().to_string(), etag: obj.e_tag().unwrap_or_default().to_string(),
indexed: false, indexed: false,
last_failed_at: None, last_failed_at: None,
fail_count: 0, fail_count: 0,
last_modified: obj.last_modified().map(|dt| dt.to_string()),
}; };
current_files.insert(path.clone(), file_state); current_files.insert(path.clone(), file_state);
} }
if !list_objects.is_truncated.unwrap_or(false) { if !list_objects.is_truncated.unwrap_or(false) {