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
196 lines
5.8 KiB
Rust
196 lines
5.8 KiB
Rust
//! Test to map exact File structure at each indexing phase
|
||
|
||
use sd_core::{
|
||
infra::{
|
||
db::entities,
|
||
event::{Event, EventSubscriber},
|
||
},
|
||
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 map_file_structure_per_phase() -> Result<(), Box<dyn std::error::Error>> {
|
||
tracing_subscriber::fmt::init();
|
||
eprintln!("\nMAPPING FILE STRUCTURE AT EACH PHASE\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 Test", None, core.context.clone())
|
||
.await?;
|
||
|
||
// Create test files
|
||
let test_dir = temp_dir.path().join("test");
|
||
tokio::fs::create_dir_all(&test_dir).await?;
|
||
|
||
for i in 1..=3 {
|
||
tokio::fs::write(
|
||
test_dir.join(format!("file{}.txt", i)),
|
||
format!("Content {}", i)
|
||
).await?;
|
||
}
|
||
|
||
eprintln!("Created test directory with 3 files\n");
|
||
|
||
// 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?
|
||
}
|
||
};
|
||
|
||
// Collect 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 indexing...\n");
|
||
|
||
let location_args = LocationCreateArgs {
|
||
path: test_dir.clone(),
|
||
name: Some("Test".to_string()),
|
||
index_mode: IndexMode::Content,
|
||
};
|
||
|
||
create_location(
|
||
library.clone(),
|
||
&core.events,
|
||
location_args,
|
||
device_record.id,
|
||
)
|
||
.await?;
|
||
|
||
// Wait for completion
|
||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||
|
||
// Analyze
|
||
let events = events_collected.lock().await;
|
||
let mut batch_num = 0;
|
||
|
||
eprintln!("RESOURCE EVENT FILE STRUCTURES:\n");
|
||
|
||
for event in events.iter() {
|
||
if let Event::ResourceChangedBatch { resource_type, resources } = event {
|
||
if resource_type == "file" {
|
||
batch_num += 1;
|
||
|
||
if let Some(files) = resources.as_array() {
|
||
eprintln!("\n{}", "=".repeat(80));
|
||
eprintln!("BATCH #{} ({} files)", batch_num, files.len());
|
||
eprintln!("{}", "=".repeat(80));
|
||
|
||
if let Some(file) = files.first() {
|
||
eprintln!("\nSample File JSON:");
|
||
eprintln!("{}\n", serde_json::to_string_pretty(&file).unwrap());
|
||
|
||
eprintln!("Key Fields:");
|
||
eprintln!(" id: {}", file.get("id").unwrap_or(&serde_json::Value::Null));
|
||
eprintln!(" name: {}", file.get("name").unwrap_or(&serde_json::Value::Null));
|
||
|
||
if let Some(sd_path) = file.get("sd_path") {
|
||
eprintln!("\n sd_path:");
|
||
if let Some(phys) = sd_path.get("Physical") {
|
||
eprintln!(" Type: Physical");
|
||
eprintln!(" device_slug: {}", phys.get("device_slug").unwrap_or(&serde_json::Value::Null));
|
||
eprintln!(" path: {}", phys.get("path").unwrap_or(&serde_json::Value::Null));
|
||
} else if let Some(content) = sd_path.get("Content") {
|
||
eprintln!(" Type: Content");
|
||
eprintln!(" content_id: {}", content.get("content_id").unwrap_or(&serde_json::Value::Null));
|
||
} else if let Some(cloud) = sd_path.get("Cloud") {
|
||
eprintln!(" Type: Cloud");
|
||
eprintln!(" service: {}", cloud.get("service").unwrap_or(&serde_json::Value::Null));
|
||
eprintln!(" path: {}", cloud.get("path").unwrap_or(&serde_json::Value::Null));
|
||
}
|
||
}
|
||
|
||
eprintln!("\n content_identity: {}",
|
||
if file.get("content_identity").and_then(|v| v.as_object()).is_some() {
|
||
"PRESENT"
|
||
} else {
|
||
"NULL"
|
||
}
|
||
);
|
||
|
||
if let Some(ci) = file.get("content_identity") {
|
||
if let Some(ci_obj) = ci.as_object() {
|
||
eprintln!(" uuid: {}", ci_obj.get("uuid").unwrap_or(&serde_json::Value::Null));
|
||
eprintln!(" content_hash: {}", ci_obj.get("content_hash").unwrap_or(&serde_json::Value::Null));
|
||
}
|
||
}
|
||
|
||
eprintln!("\n sidecars: {} items",
|
||
file.get("sidecars").and_then(|s| s.as_array()).map(|a| a.len()).unwrap_or(0)
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
eprintln!("\n{}", "=".repeat(80));
|
||
eprintln!("EVENT SUMMARY: {} file batches emitted", batch_num);
|
||
eprintln!("{}", "=".repeat(80));
|
||
|
||
// Now manually check what entries look like in database
|
||
eprintln!("\n\nCHECKING DATABASE ENTRIES:\n");
|
||
eprintln!("{}", "=".repeat(80));
|
||
|
||
use sd_core::infra::db::entities::entry;
|
||
use sea_orm::EntityTrait;
|
||
|
||
let db_entries = entry::Entity::find()
|
||
.all(db.conn())
|
||
.await?;
|
||
|
||
eprintln!("Database has {} entries\n", db_entries.len());
|
||
|
||
for entry in db_entries.iter().take(3) {
|
||
if entry.kind == 0 { // File
|
||
eprintln!("Entry ID: {}", entry.id);
|
||
eprintln!(" UUID: {:?}", entry.uuid);
|
||
eprintln!(" Name: {}", entry.name);
|
||
eprintln!(" content_id (db FK): {:?}", entry.content_id);
|
||
eprintln!();
|
||
}
|
||
}
|
||
|
||
eprintln!("{}", "=".repeat(80));
|
||
eprintln!("KEY INSIGHT:");
|
||
eprintln!("{}", "=".repeat(80));
|
||
eprintln!("\nEvent files use:");
|
||
eprintln!(" - id = entry.uuid");
|
||
eprintln!(" - sd_path = Content {{content_id}}");
|
||
eprintln!("\nDirectory query SHOULD use:");
|
||
eprintln!(" - id = entry.uuid (SAME)");
|
||
eprintln!(" - sd_path = Physical {{path}} (DIFFERENT)");
|
||
eprintln!("\n️ If IDs match, normalized cache should work!");
|
||
eprintln!("️ If IDs don't match, we have a bigger problem.");
|
||
eprintln!();
|
||
|
||
Ok(())
|
||
}
|