mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2025-12-11 20:15:30 +01:00
1. Watermark Messages: - Nov 16: Simple - single my_state_watermark, single state_watermark in response - Main: Complex - per-resource my_resource_watermarks HashMap, resource counts, content hashes 2. DataAvailableNotification: - Nov 16: Doesn't exist - Main: New message type that triggers watermark exchanges 3. FK Mapper: - Nov 16: Sets missing FKs to NULL, applies all records - Main: Marks records with missing FKs as failed, filters them out 4. Watermark Storage (internal, not in messages): - Nov 16: Single last_watermark per resource - Main: Dual watermarks - cursor_watermark and
274 lines
7.9 KiB
Rust
274 lines
7.9 KiB
Rust
//! Comprehensive phase snapshot test
|
|
//!
|
|
//! This test indexes Desktop with Content mode and captures:
|
|
//! - All events received during each phase
|
|
//! - Directory listing query results after each phase
|
|
//! - Saves snapshots to disk for manual inspection
|
|
|
|
use sd_core::{
|
|
infra::{
|
|
db::entities,
|
|
event::Event,
|
|
},
|
|
location::{create_location, IndexMode, LocationCreateArgs},
|
|
Core,
|
|
};
|
|
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter};
|
|
use std::{sync::Arc, time::Duration};
|
|
use tempfile::TempDir;
|
|
|
|
#[tokio::test]
|
|
async fn capture_phase_snapshots() -> Result<(), Box<dyn std::error::Error>> {
|
|
tracing_subscriber::fmt::init();
|
|
eprintln!("\nPHASE SNAPSHOT TEST - Indexing Desktop\n");
|
|
eprintln!("{}", "=".repeat(80));
|
|
|
|
// Setup
|
|
let temp_dir = TempDir::new()?;
|
|
let core = Core::new(temp_dir.path().to_path_buf()).await?;
|
|
|
|
let library = core
|
|
.libraries
|
|
.create_library("Phase Snapshot Test", None, core.context.clone())
|
|
.await?;
|
|
|
|
eprintln!("Created test library");
|
|
|
|
// Get Desktop path
|
|
let desktop_path = dirs::desktop_dir()
|
|
.expect("Could not find Desktop directory");
|
|
|
|
eprintln!("Will index: {:?}", desktop_path);
|
|
|
|
// Register device
|
|
let db = library.db();
|
|
let device = core.device.to_device()?;
|
|
let device_record = match entities::device::Entity::find()
|
|
.filter(entities::device::Column::Uuid.eq(device.id))
|
|
.one(db.conn())
|
|
.await?
|
|
{
|
|
Some(existing) => existing,
|
|
None => {
|
|
let device_model: entities::device::ActiveModel = device.into();
|
|
device_model.insert(db.conn()).await?
|
|
}
|
|
};
|
|
|
|
// Create output directory for snapshots in project root
|
|
let snapshot_dir = std::path::PathBuf::from("/Users/jamespine/Projects/spacedrive/test_snapshots");
|
|
std::fs::create_dir_all(&snapshot_dir)?;
|
|
eprintln!("Snapshots will be saved to: {:?}\n", snapshot_dir);
|
|
|
|
// Collect all events
|
|
let events_collected = Arc::new(tokio::sync::Mutex::new(Vec::new()));
|
|
let events_clone = events_collected.clone();
|
|
let mut subscriber = core.events.subscribe();
|
|
|
|
tokio::spawn(async move {
|
|
while let Ok(event) = subscriber.recv().await {
|
|
events_clone.lock().await.push(event);
|
|
}
|
|
});
|
|
|
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
|
|
eprintln!("Starting Content mode indexing of Desktop...\n");
|
|
|
|
let location_args = LocationCreateArgs {
|
|
path: desktop_path.clone(),
|
|
name: Some("Desktop".to_string()),
|
|
index_mode: IndexMode::Content,
|
|
};
|
|
|
|
let _location_db_id = create_location(
|
|
library.clone(),
|
|
&core.events,
|
|
location_args,
|
|
device_record.id,
|
|
)
|
|
.await?;
|
|
|
|
// Monitor job completion
|
|
eprintln!("Waiting for indexing job to complete...\n");
|
|
|
|
let mut indexing_started = false;
|
|
let mut processing_complete = false;
|
|
let mut content_phase_complete = false;
|
|
let mut job_completed = false;
|
|
|
|
let mut phase_events: Vec<(String, Vec<Event>)> = Vec::new();
|
|
let mut current_phase_events: Vec<Event> = Vec::new();
|
|
let mut current_phase_name = String::from("Initial");
|
|
|
|
let mut last_processed_event_count = 0;
|
|
|
|
// Poll for up to 2 minutes
|
|
for iteration in 0..120 {
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
let events = events_collected.lock().await;
|
|
let all_events_count = events.len();
|
|
|
|
// Process only new events since last check
|
|
let new_events: Vec<Event> = if all_events_count > last_processed_event_count {
|
|
events[last_processed_event_count..].to_vec()
|
|
} else {
|
|
vec![]
|
|
};
|
|
|
|
last_processed_event_count = all_events_count;
|
|
drop(events); // Release lock
|
|
|
|
for event in new_events {
|
|
match &event {
|
|
Event::IndexingStarted { .. } if !indexing_started => {
|
|
eprintln!("Phase: Discovery Started");
|
|
indexing_started = true;
|
|
|
|
// Save previous phase
|
|
if !current_phase_events.is_empty() {
|
|
phase_events.push((current_phase_name.clone(), current_phase_events.clone()));
|
|
}
|
|
current_phase_name = "Discovery".to_string();
|
|
current_phase_events.clear();
|
|
}
|
|
Event::JobProgress { job_type, message, .. } if job_type == "indexer" => {
|
|
if let Some(msg) = message {
|
|
if msg.contains("Processing entries") && !processing_complete {
|
|
eprintln!("Phase: Processing");
|
|
processing_complete = true;
|
|
|
|
// Save previous phase
|
|
phase_events.push((current_phase_name.clone(), current_phase_events.clone()));
|
|
current_phase_name = "Processing".to_string();
|
|
current_phase_events.clear();
|
|
} else if msg.contains("Generating content identities") && !content_phase_complete {
|
|
eprintln!("Phase: Content Identification");
|
|
content_phase_complete = true;
|
|
|
|
// Save previous phase
|
|
phase_events.push((current_phase_name.clone(), current_phase_events.clone()));
|
|
current_phase_name = "Content".to_string();
|
|
current_phase_events.clear();
|
|
}
|
|
}
|
|
}
|
|
Event::IndexingCompleted { .. } => {
|
|
if !job_completed {
|
|
eprintln!("Indexing Complete\n");
|
|
job_completed = true;
|
|
|
|
// Save final phase
|
|
phase_events.push((current_phase_name.clone(), current_phase_events.clone()));
|
|
}
|
|
}
|
|
Event::JobCompleted { job_type, .. } => {
|
|
if job_type == "indexer" && !job_completed {
|
|
eprintln!("Job Complete\n");
|
|
job_completed = true;
|
|
|
|
// Save final phase
|
|
if !current_phase_events.is_empty() {
|
|
phase_events.push((current_phase_name.clone(), current_phase_events.clone()));
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
current_phase_events.push(event);
|
|
}
|
|
|
|
if job_completed {
|
|
break;
|
|
}
|
|
}
|
|
|
|
eprintln!("Captured {} phases\n", phase_events.len());
|
|
|
|
// Save phase events to disk with detailed structure
|
|
for (phase_name, phase_event_list) in &phase_events {
|
|
let mut phase_data = serde_json::json!({
|
|
"phase_name": phase_name,
|
|
"total_events": phase_event_list.len(),
|
|
"file_batches": [],
|
|
"all_files": [],
|
|
});
|
|
|
|
let mut all_files_in_phase = vec![];
|
|
|
|
for event in phase_event_list {
|
|
if let Event::ResourceChangedBatch { resource_type, resources } = event {
|
|
if resource_type == "file" {
|
|
if let Some(files_array) = resources.as_array() {
|
|
// Save batch metadata
|
|
phase_data["file_batches"].as_array_mut().unwrap().push(serde_json::json!({
|
|
"batch_size": files_array.len(),
|
|
"sample_file": files_array.first(),
|
|
}));
|
|
|
|
// Collect all files
|
|
for file in files_array {
|
|
all_files_in_phase.push(file.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
phase_data["all_files"] = serde_json::json!(all_files_in_phase);
|
|
phase_data["total_files"] = serde_json::json!(all_files_in_phase.len());
|
|
|
|
let filename = snapshot_dir.join(format!("phase_{}.json", phase_name));
|
|
let json = serde_json::to_string_pretty(&phase_data)?;
|
|
std::fs::write(&filename, json)?;
|
|
eprintln!("Saved phase {} with {} total files across {} batches",
|
|
phase_name,
|
|
all_files_in_phase.len(),
|
|
phase_data["file_batches"].as_array().unwrap().len()
|
|
);
|
|
}
|
|
|
|
// Query directory listing and save snapshot
|
|
eprintln!("\nQuerying final directory listing state...\n");
|
|
|
|
use sd_core::infra::db::entities::entry;
|
|
|
|
// Just load all entries and construct Files to see what query would return
|
|
let entries_with_content = entry::Entity::find()
|
|
.filter(entry::Column::Kind.eq(0)) // Files only
|
|
.all(db.conn())
|
|
.await?;
|
|
|
|
eprintln!("Found {} file entries in database", entries_with_content.len());
|
|
|
|
// Save ALL entries to check ID matching
|
|
let all_entries: Vec<_> = entries_with_content.iter().map(|e| {
|
|
serde_json::json!({
|
|
"entry_id": e.id,
|
|
"entry_uuid": e.uuid,
|
|
"name": e.name,
|
|
"content_id_fk": e.content_id,
|
|
"extension": e.extension,
|
|
})
|
|
}).collect();
|
|
|
|
let entries_file = snapshot_dir.join("db_entries_all.json");
|
|
std::fs::write(&entries_file, serde_json::to_string_pretty(&all_entries)?)?;
|
|
eprintln!("Saved {} database entries", all_entries.len());
|
|
|
|
eprintln!("\n{}", "=".repeat(80));
|
|
eprintln!("TEST COMPLETE");
|
|
eprintln!("{}", "=".repeat(80));
|
|
eprintln!("\nSnapshots saved to: {:?}", snapshot_dir);
|
|
eprintln!("\nFiles created:");
|
|
for entry in std::fs::read_dir(&snapshot_dir)? {
|
|
let entry = entry?;
|
|
eprintln!(" - {}", entry.file_name().to_string_lossy());
|
|
}
|
|
eprintln!();
|
|
|
|
Ok(())
|
|
}
|