mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-19 05:45:01 -04:00
Update TODO list and enhance job logging configuration
- Added critical tasks for remote file access and updater functionality to the TODO list. - Updated job logging configuration to include an option for logging ephemeral jobs, improving logging flexibility. - Enhanced the job manager to conditionally log job events based on persistence settings, ensuring better tracking of job statuses. - Refactored the inspector component to remove unnecessary console logs, streamlining the codebase. - Improved the selection context to eliminate redundant logging during file synchronization, enhancing performance.
This commit is contained in:
17
TODO
17
TODO
@@ -23,11 +23,14 @@
|
||||
Journey to v2.0.0-pre.1:
|
||||
✔ Get all desktop release CI working @done(25-12-16 23:51)
|
||||
☐ Ephemeral sidecars @critical
|
||||
☐ Remote file access on demand @critical
|
||||
☐ Open files with default app (cross platform) @critical
|
||||
☐ Sometimes quick preview reporting file not found @today (happens in the second column of column view)
|
||||
☐ Ensure updater and changelog are working @today
|
||||
☐ Drop external items INTO spacedrive explorer @today
|
||||
☐ Connection info on device panel (lan/relay)
|
||||
✔ Grid view render bug, shows as column for split second on first render of results @done(25-12-22 07:49)
|
||||
☐ Ensure updater and changelog are working
|
||||
☐ Drop external items INTO spacedrive explorer
|
||||
☐ Job sound: make copy sound a varient, not play also
|
||||
✔ Job sound: make copy sound a varient, not play also @done(25-12-24 07:24)
|
||||
☐ Run now button in Location Inspector doesn't work well @today
|
||||
✔ Sidebar active based on Explorer path @today @done(25-12-20 07:59)
|
||||
☐ Delete location UX improvement
|
||||
@@ -36,10 +39,9 @@ Journey to v2.0.0-pre.1:
|
||||
# It has an issue where it conflicts with the tab focus navigation and is out of order often
|
||||
☐ Device owned data count mismatch reconciliation
|
||||
☐ Synced directories seem to be missing relationships to form tree (sometimes)
|
||||
☐ Drag selection area + command to add to selection
|
||||
☐ Drag selection area + command to add to selection @today
|
||||
✔ Back/forward button navigation not working @critical @done(25-12-22 05:43)
|
||||
# Investigate sync integrity
|
||||
☐ Remote file access on demand @critical
|
||||
✔ Refetch all queries when window focus @today @done(25-12-20 09:55)
|
||||
✔ Fix job reactivity @today @done(25-12-21 06:14)
|
||||
# CLI reactivity works fine, but interface job manager often doesn't show up for jobs, generating thubmnails ALWAYS does, but speech, splat and indexing don't show in the UI
|
||||
@@ -60,9 +62,10 @@ Journey to v2.0.0-pre.1:
|
||||
☐ Make default home folders SpaceItems
|
||||
☐ Choose custom device name on mobile setup flow
|
||||
# Decide how this works in the context of sync, likely organize by device
|
||||
☐ New Space Item resource event not working
|
||||
✔ New Space Item resource event not working @done(25-12-24 07:25)
|
||||
✔ Can't drag and drop onto space items to copy paste @done(25-12-16 23:51)
|
||||
☐ Sometimes device shows as online even if its not
|
||||
☐ Stale detection background discovery reindex @critical
|
||||
# Looks like cache state issue, even though the devices due
|
||||
☐ Eject volume button
|
||||
☐ Index mode not showing in location inspector
|
||||
@@ -82,10 +85,8 @@ Journey to v2.0.0-pre.1:
|
||||
☐ History tab of the File Inspector is dummy data
|
||||
☐ Improve copy/paste/move flow to actually validate before showing modal
|
||||
☐ Fix three device sync
|
||||
☐ Add box selections
|
||||
☐ Refactor the CLI
|
||||
☐ Improve inspector design
|
||||
☐ Stale detection background discovery reindex
|
||||
# See INDEX-009 for stale detection
|
||||
☐ RAW support
|
||||
☐ Sign Windows build
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Default permissions for Spacedrive",
|
||||
"windows": ["main"],
|
||||
"windows": ["main", "inspector-*", "quick-preview-*", "settings-*", "job-manager"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:event:allow-listen",
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"default":{"identifier":"default","description":"Default permissions for Spacedrive","local":true,"windows":["main"],"permissions":["core:default","core:event:allow-listen","core:event:allow-emit","core:window:allow-create","core:window:allow-close","core:window:allow-get-all-windows","core:window:allow-start-dragging","core:webview:allow-create-webview-window","core:path:default","dialog:allow-open","dialog:allow-save","shell:allow-open","fs:allow-home-read-recursive","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}
|
||||
{"default":{"identifier":"default","description":"Default permissions for Spacedrive","local":true,"windows":["main","inspector-*","quick-preview-*","settings-*","job-manager"],"permissions":["core:default","core:event:allow-listen","core:event:allow-emit","core:window:allow-create","core:window:allow-close","core:window:allow-get-all-windows","core:window:allow-start-dragging","core:webview:allow-create-webview-window","core:path:default","dialog:allow-open","dialog:allow-save","shell:allow-open","fs:allow-home-read-recursive","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}
|
||||
@@ -159,9 +159,19 @@ impl SpacedriveWindow {
|
||||
(320.0, 400.0),
|
||||
true,
|
||||
true, // always on top
|
||||
false,
|
||||
true, // transparent for macOS styling
|
||||
)?;
|
||||
|
||||
// Apply macOS titlebar styling
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Ok(ns_window) = window.ns_window() {
|
||||
unsafe {
|
||||
sd_desktop_macos::set_titlebar_style(&ns_window, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for window close to notify main window
|
||||
let app_handle = app.clone();
|
||||
window.on_window_event(move |event| {
|
||||
@@ -372,7 +382,7 @@ fn create_window(
|
||||
always_on_top: bool,
|
||||
transparent: bool,
|
||||
) -> Result<WebviewWindow, String> {
|
||||
let window = WebviewWindowBuilder::new(app, label, WebviewUrl::App(url.into()))
|
||||
let mut builder = WebviewWindowBuilder::new(app, label, WebviewUrl::App(url.into()))
|
||||
.title(title)
|
||||
.inner_size(size.0, size.1)
|
||||
.min_inner_size(min_size.0, min_size.1)
|
||||
@@ -380,12 +390,21 @@ fn create_window(
|
||||
.decorations(decorations)
|
||||
.transparent(transparent)
|
||||
.always_on_top(always_on_top)
|
||||
.center()
|
||||
.center();
|
||||
|
||||
// Enable DevTools in dev mode
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
builder = builder.devtools(true);
|
||||
}
|
||||
|
||||
let window = builder
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create window: {}", e))?;
|
||||
|
||||
window.show().ok();
|
||||
window.set_focus().ok();
|
||||
|
||||
Ok(window)
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
log_directory: "job_logs".to_string(),
|
||||
max_file_size: 10 * 1024 * 1024, // 10MB
|
||||
include_debug: true, // Include debug logs for full detail
|
||||
log_ephemeral_jobs: false,
|
||||
};
|
||||
|
||||
config.save()?;
|
||||
|
||||
@@ -33,6 +33,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
log_directory: "job_logs".to_string(),
|
||||
max_file_size: 10 * 1024 * 1024,
|
||||
include_debug: true,
|
||||
log_ephemeral_jobs: false,
|
||||
};
|
||||
|
||||
config.save()?;
|
||||
|
||||
@@ -85,6 +85,10 @@ pub struct JobLoggingConfig {
|
||||
|
||||
/// Whether to include debug logs
|
||||
pub include_debug: bool,
|
||||
|
||||
/// Whether to create log files for ephemeral (non-persistent) jobs
|
||||
#[serde(default)]
|
||||
pub log_ephemeral_jobs: bool,
|
||||
}
|
||||
|
||||
impl Default for JobLoggingConfig {
|
||||
@@ -94,6 +98,7 @@ impl Default for JobLoggingConfig {
|
||||
log_directory: "job_logs".to_string(),
|
||||
max_file_size: 10 * 1024 * 1024, // 10MB default
|
||||
include_debug: false,
|
||||
log_ephemeral_jobs: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,6 +306,20 @@ impl JobManager {
|
||||
// Create persistence completion channel
|
||||
let (persistence_complete_tx, persistence_complete_rx) = tokio::sync::oneshot::channel();
|
||||
|
||||
// Only enable job file logging for persistent jobs (or if explicitly configured)
|
||||
let job_logging_config = if should_persist
|
||||
|| self
|
||||
.context
|
||||
.job_logging_config
|
||||
.as_ref()
|
||||
.map(|c| c.log_ephemeral_jobs)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.context.job_logging_config.clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Create executor using the erased job
|
||||
let executor = erased_job.create_executor(
|
||||
job_id,
|
||||
@@ -321,7 +335,7 @@ impl JobManager {
|
||||
handle.output.clone(),
|
||||
networking,
|
||||
volume_manager,
|
||||
self.context.job_logging_config.clone(),
|
||||
job_logging_config,
|
||||
Some(library.job_logs_dir()),
|
||||
Some(persistence_complete_tx),
|
||||
);
|
||||
@@ -367,18 +381,16 @@ impl JobManager {
|
||||
info!("Job {} status changed to: {:?}", job_id_clone, status);
|
||||
match status {
|
||||
JobStatus::Running => {
|
||||
// Only emit events for persistent jobs
|
||||
if should_persist {
|
||||
event_bus.emit(Event::JobStarted {
|
||||
job_id: job_id_clone.to_string(),
|
||||
job_type: job_type_str.to_string(),
|
||||
device_id,
|
||||
});
|
||||
info!("Emitted JobStarted event for job {}", job_id_clone);
|
||||
}
|
||||
// Emit event for all jobs
|
||||
event_bus.emit(Event::JobStarted {
|
||||
job_id: job_id_clone.to_string(),
|
||||
job_type: job_type_str.to_string(),
|
||||
device_id,
|
||||
});
|
||||
info!("Emitted JobStarted event for job {}", job_id_clone);
|
||||
}
|
||||
JobStatus::Completed => {
|
||||
// Only emit events and trigger statistics for persistent jobs
|
||||
// Emit completion event for all jobs
|
||||
if should_persist {
|
||||
// Get the final output from the handle before removing the job
|
||||
let output = {
|
||||
@@ -440,7 +452,7 @@ impl JobManager {
|
||||
break;
|
||||
}
|
||||
JobStatus::Failed => {
|
||||
// Only emit events for persistent jobs
|
||||
// Emit event for all jobs
|
||||
if should_persist {
|
||||
event_bus.emit(Event::JobFailed {
|
||||
job_id: job_id_clone.to_string(),
|
||||
@@ -455,7 +467,7 @@ impl JobManager {
|
||||
break;
|
||||
}
|
||||
JobStatus::Cancelled => {
|
||||
// Only emit events for persistent jobs
|
||||
// Emit event for all jobs
|
||||
if should_persist {
|
||||
event_bus.emit(Event::JobCancelled {
|
||||
job_id: job_id_clone.to_string(),
|
||||
@@ -684,6 +696,20 @@ impl JobManager {
|
||||
// Create persistence completion channel
|
||||
let (persistence_complete_tx, persistence_complete_rx) = tokio::sync::oneshot::channel();
|
||||
|
||||
// Only enable job file logging for persistent jobs (or if explicitly configured)
|
||||
let job_logging_config = if should_persist
|
||||
|| self
|
||||
.context
|
||||
.job_logging_config
|
||||
.as_ref()
|
||||
.map(|c| c.log_ephemeral_jobs)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.context.job_logging_config.clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Create executor
|
||||
let executor = JobExecutor::new(
|
||||
job,
|
||||
@@ -700,7 +726,7 @@ impl JobManager {
|
||||
handle.output.clone(),
|
||||
networking,
|
||||
volume_manager,
|
||||
self.context.job_logging_config.clone(),
|
||||
job_logging_config,
|
||||
Some(library.job_logs_dir()),
|
||||
Some(persistence_complete_tx),
|
||||
);
|
||||
@@ -745,18 +771,16 @@ impl JobManager {
|
||||
info!("Job {} status changed to: {:?}", job_id_clone, status);
|
||||
match status {
|
||||
JobStatus::Running => {
|
||||
// Only emit events for persistent jobs
|
||||
if should_persist {
|
||||
event_bus.emit(Event::JobStarted {
|
||||
job_id: job_id_clone.to_string(),
|
||||
job_type: job_type_str.to_string(),
|
||||
device_id,
|
||||
});
|
||||
info!("Emitted JobStarted event for job {}", job_id_clone);
|
||||
}
|
||||
// Emit event for all jobs
|
||||
event_bus.emit(Event::JobStarted {
|
||||
job_id: job_id_clone.to_string(),
|
||||
job_type: job_type_str.to_string(),
|
||||
device_id,
|
||||
});
|
||||
info!("Emitted JobStarted event for job {}", job_id_clone);
|
||||
}
|
||||
JobStatus::Completed => {
|
||||
// Only emit events and trigger statistics for persistent jobs
|
||||
// Emit completion event for all jobs
|
||||
if should_persist {
|
||||
// Get the final output from the handle before removing the job
|
||||
let output = {
|
||||
@@ -818,7 +842,7 @@ impl JobManager {
|
||||
break;
|
||||
}
|
||||
JobStatus::Failed => {
|
||||
// Only emit events for persistent jobs
|
||||
// Emit event for all jobs
|
||||
if should_persist {
|
||||
event_bus.emit(Event::JobFailed {
|
||||
job_id: job_id_clone.to_string(),
|
||||
@@ -833,7 +857,7 @@ impl JobManager {
|
||||
break;
|
||||
}
|
||||
JobStatus::Cancelled => {
|
||||
// Only emit events for persistent jobs
|
||||
// Emit event for all jobs
|
||||
if should_persist {
|
||||
event_bus.emit(Event::JobCancelled {
|
||||
job_id: job_id_clone.to_string(),
|
||||
|
||||
@@ -136,7 +136,7 @@ impl PersistentEventHandler {
|
||||
"Handler is running, creating worker for location {}",
|
||||
location_id
|
||||
);
|
||||
self.ensure_worker(meta).await?;
|
||||
self.ensure_worker(meta.clone()).await?;
|
||||
} else {
|
||||
debug!(
|
||||
"Handler not running yet, worker will be created on start for location {}",
|
||||
@@ -407,8 +407,16 @@ impl PersistentEventHandler {
|
||||
context: Arc<CoreContext>,
|
||||
config: PersistentHandlerConfig,
|
||||
) -> Result<()> {
|
||||
use sd_fs_watcher::FsEventKind;
|
||||
use std::collections::HashMap;
|
||||
|
||||
info!("Location worker started for {}", meta.id);
|
||||
|
||||
// Buffer for pending removes - maps inode to (path, timestamp, is_directory)
|
||||
// These are Remove events that might be part of a rename operation.
|
||||
let mut pending_removes: HashMap<u64, (PathBuf, Instant, Option<bool>)> = HashMap::new();
|
||||
const RENAME_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||
|
||||
while let Some(first_event) = rx.recv().await {
|
||||
// Start batching window
|
||||
let mut batch = vec![first_event];
|
||||
@@ -432,12 +440,51 @@ impl PersistentEventHandler {
|
||||
meta.id
|
||||
);
|
||||
|
||||
// Evict expired pending removes
|
||||
let now = Instant::now();
|
||||
let expired: Vec<u64> = pending_removes
|
||||
.iter()
|
||||
.filter(|(_, (_, ts, _))| now.duration_since(*ts) > RENAME_TIMEOUT)
|
||||
.map(|(inode, _)| *inode)
|
||||
.collect();
|
||||
|
||||
// Process expired removes as actual deletions
|
||||
let mut expired_events = Vec::new();
|
||||
for inode in expired {
|
||||
if let Some((path, _, is_dir)) = pending_removes.remove(&inode) {
|
||||
debug!(
|
||||
"Evicting expired pending remove: {} (inode {})",
|
||||
path.display(),
|
||||
inode
|
||||
);
|
||||
expired_events.push(FsEvent::remove(path));
|
||||
}
|
||||
}
|
||||
|
||||
// Detect renames by matching Remove+Create pairs using database inodes.
|
||||
// On macOS, renames arrive as separate Remove and Create events across batches.
|
||||
let batch = Self::detect_renames_from_db(
|
||||
&context,
|
||||
meta.library_id,
|
||||
batch,
|
||||
&mut pending_removes,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Combine expired removes with processed batch
|
||||
let mut final_batch = expired_events;
|
||||
final_batch.extend(batch);
|
||||
|
||||
if final_batch.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pass FsEvent batch directly to responder
|
||||
if let Err(e) = responder::apply_batch(
|
||||
&context,
|
||||
meta.library_id,
|
||||
meta.id,
|
||||
batch,
|
||||
final_batch,
|
||||
meta.rule_toggles,
|
||||
&meta.root_path,
|
||||
None, // volume_backend - TODO: resolve from context
|
||||
@@ -451,6 +498,143 @@ impl PersistentEventHandler {
|
||||
info!("Location worker stopped for {}", meta.id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Detect renames by matching Remove+Create pairs using database inodes.
|
||||
///
|
||||
/// Remove events with inodes are buffered in `pending_removes`. Create events
|
||||
/// check against this buffer to detect renames. This handles the case where
|
||||
/// Remove and Create arrive in separate batches (common on macOS FSEvents).
|
||||
async fn detect_renames_from_db(
|
||||
context: &Arc<CoreContext>,
|
||||
library_id: Uuid,
|
||||
events: Vec<FsEvent>,
|
||||
pending_removes: &mut std::collections::HashMap<u64, (PathBuf, Instant, Option<bool>)>,
|
||||
) -> Vec<FsEvent> {
|
||||
use sd_fs_watcher::FsEventKind;
|
||||
|
||||
let Some(library) = context.get_library(library_id).await else {
|
||||
return events;
|
||||
};
|
||||
let db = library.db().conn();
|
||||
|
||||
let mut result: Vec<FsEvent> = Vec::new();
|
||||
|
||||
for event in events {
|
||||
match &event.kind {
|
||||
FsEventKind::Remove => {
|
||||
// Query database for inode
|
||||
let inode = Self::get_inode_from_db(db, &event.path).await;
|
||||
if let Some(inode) = inode {
|
||||
debug!(
|
||||
"Buffering Remove event: {} with inode {} for potential rename",
|
||||
event.path.display(),
|
||||
inode
|
||||
);
|
||||
// Buffer for potential rename detection
|
||||
pending_removes
|
||||
.insert(inode, (event.path, Instant::now(), event.is_directory));
|
||||
} else {
|
||||
// No inode in DB, emit as regular Remove
|
||||
result.push(event);
|
||||
}
|
||||
}
|
||||
FsEventKind::Create => {
|
||||
// Get inode from filesystem
|
||||
let inode = Self::get_inode_from_fs(&event.path).await;
|
||||
|
||||
if let Some(inode) = inode {
|
||||
// Check if this matches a pending remove
|
||||
if let Some((old_path, _, is_dir)) = pending_removes.remove(&inode) {
|
||||
// Found a match - emit Rename
|
||||
info!(
|
||||
"Detected rename via database inode {}: {} -> {}",
|
||||
inode,
|
||||
old_path.display(),
|
||||
event.path.display()
|
||||
);
|
||||
let rename_event = if let Some(is_dir) = is_dir.or(event.is_directory) {
|
||||
FsEvent::rename_with_dir_flag(old_path, event.path, is_dir)
|
||||
} else {
|
||||
FsEvent::rename(old_path, event.path)
|
||||
};
|
||||
result.push(rename_event);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// No matching pending remove, emit as regular Create
|
||||
result.push(event);
|
||||
}
|
||||
_ => {
|
||||
result.push(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Get the inode for a path from the database.
|
||||
async fn get_inode_from_db(
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
path: &std::path::Path,
|
||||
) -> Option<u64> {
|
||||
use crate::infra::db::entities::{directory_paths, entry};
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||
|
||||
// First try as directory (lookup via directory_paths)
|
||||
let path_str = path.to_string_lossy().to_string();
|
||||
if let Ok(Some(dir_record)) = directory_paths::Entity::find()
|
||||
.filter(directory_paths::Column::Path.eq(&path_str))
|
||||
.one(db)
|
||||
.await
|
||||
{
|
||||
if let Ok(Some(entry_record)) =
|
||||
entry::Entity::find_by_id(dir_record.entry_id).one(db).await
|
||||
{
|
||||
if let Some(inode) = entry_record.inode {
|
||||
return Some(inode as u64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try as file (lookup via parent directory + name)
|
||||
let parent = path.parent()?;
|
||||
let name = path.file_stem()?.to_str()?;
|
||||
let ext = path.extension().and_then(|e| e.to_str());
|
||||
|
||||
let parent_str = parent.to_string_lossy().to_string();
|
||||
let parent_dir = directory_paths::Entity::find()
|
||||
.filter(directory_paths::Column::Path.eq(&parent_str))
|
||||
.one(db)
|
||||
.await
|
||||
.ok()??;
|
||||
|
||||
let mut query = entry::Entity::find()
|
||||
.filter(entry::Column::ParentId.eq(parent_dir.entry_id))
|
||||
.filter(entry::Column::Name.eq(name));
|
||||
|
||||
if let Some(e) = ext {
|
||||
query = query.filter(entry::Column::Extension.eq(e.to_lowercase()));
|
||||
} else {
|
||||
query = query.filter(entry::Column::Extension.is_null());
|
||||
}
|
||||
|
||||
let entry_record = query.one(db).await.ok()??;
|
||||
entry_record.inode.map(|i| i as u64)
|
||||
}
|
||||
|
||||
/// Get the inode for a path from the filesystem.
|
||||
async fn get_inode_from_fs(path: &std::path::Path) -> Option<u64> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
tokio::fs::metadata(path).await.ok().map(|m| m.ino())
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -58,7 +58,7 @@ impl LibraryAction for IndexVerifyAction {
|
||||
);
|
||||
|
||||
// Step 1: Scan filesystem to get current state
|
||||
let fs_entries = self.run_ephemeral_index(&library, &context, &path).await?;
|
||||
let fs_entries = self.run_ephemeral_index(&library, &path).await?;
|
||||
|
||||
// Step 2: Query database for existing entries in this path
|
||||
let db_entries = self.query_database_entries(&library, &path).await?;
|
||||
@@ -95,7 +95,6 @@ impl IndexVerifyAction {
|
||||
async fn run_ephemeral_index(
|
||||
&self,
|
||||
library: &Arc<crate::library::Library>,
|
||||
context: &Arc<CoreContext>,
|
||||
path: &Path,
|
||||
) -> Result<HashMap<PathBuf, crate::ops::indexing::database_storage::EntryMetadata>, ActionError>
|
||||
{
|
||||
@@ -109,9 +108,6 @@ impl IndexVerifyAction {
|
||||
ActionError::from(std::io::Error::new(std::io::ErrorKind::Other, e))
|
||||
})?));
|
||||
|
||||
// Subscribe to job events before dispatching
|
||||
let mut event_subscriber = context.events.subscribe();
|
||||
|
||||
// Create indexer job config for ephemeral scanning
|
||||
let config = IndexerJobConfig {
|
||||
location_id: None, // Ephemeral - no location
|
||||
@@ -133,55 +129,22 @@ impl IndexVerifyAction {
|
||||
ActionError::Internal(format!("Failed to dispatch indexer job: {}", e))
|
||||
})?;
|
||||
|
||||
let job_id = job_handle.id().to_string();
|
||||
let job_id = job_handle.id();
|
||||
tracing::debug!(
|
||||
"Waiting for ephemeral indexer job {} to complete...",
|
||||
job_id
|
||||
);
|
||||
|
||||
// Listen for job completion events
|
||||
loop {
|
||||
match event_subscriber.recv().await {
|
||||
Ok(event) => match event {
|
||||
crate::infra::event::Event::JobCompleted {
|
||||
job_id: completed_id,
|
||||
..
|
||||
} if completed_id == job_id => {
|
||||
tracing::debug!("Ephemeral indexer job {} completed", job_id);
|
||||
break;
|
||||
}
|
||||
crate::infra::event::Event::JobFailed {
|
||||
job_id: failed_id,
|
||||
error,
|
||||
..
|
||||
} if failed_id == job_id => {
|
||||
return Err(ActionError::Internal(format!(
|
||||
"Ephemeral indexer job failed: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
crate::infra::event::Event::JobCancelled {
|
||||
job_id: cancelled_id,
|
||||
..
|
||||
} if cancelled_id == job_id => {
|
||||
return Err(ActionError::Internal(
|
||||
"Ephemeral indexer job was cancelled".to_string(),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
// Not our job event, keep listening
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(ActionError::Internal(format!(
|
||||
"Failed to receive job event: {}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Wait for the job to complete using the handle's built-in wait mechanism
|
||||
job_handle
|
||||
.wait()
|
||||
.await
|
||||
.map_err(|e| ActionError::Internal(format!("Ephemeral indexer job failed: {}", e)))?;
|
||||
|
||||
tracing::debug!("Ephemeral indexer job completed, extracting results");
|
||||
tracing::debug!(
|
||||
"Ephemeral indexer job {} completed, extracting results",
|
||||
job_id
|
||||
);
|
||||
|
||||
// Extract the results from our shared ephemeral index
|
||||
let entries = {
|
||||
|
||||
@@ -211,6 +211,7 @@ impl TestConfigBuilder {
|
||||
log_directory: "job_logs".to_string(),
|
||||
max_file_size: 10 * 1024 * 1024, // 10MB
|
||||
include_debug: false,
|
||||
log_ephemeral_jobs: false,
|
||||
},
|
||||
services: ServiceConfig {
|
||||
networking_enabled: self.networking_enabled,
|
||||
|
||||
@@ -152,14 +152,12 @@ export function PopoutInspector() {
|
||||
|
||||
platform.onSelectedFilesChanged((fileIds) => {
|
||||
if (mounted) {
|
||||
console.log("[PopoutInspector] Received selection change:", fileIds);
|
||||
setSelectedFileIds(fileIds);
|
||||
}
|
||||
}).then((unlistenFn) => {
|
||||
if (mounted) {
|
||||
unlisten = unlistenFn;
|
||||
} else {
|
||||
// Component unmounted before listener was set up, clean up immediately
|
||||
unlistenFn();
|
||||
}
|
||||
}).catch((err) => {
|
||||
@@ -175,11 +173,6 @@ export function PopoutInspector() {
|
||||
// Fetch the first selected file
|
||||
const firstFileId = selectedFileIds[0] || null;
|
||||
|
||||
console.log("[PopoutInspector] Current state:", {
|
||||
selectedFileIds,
|
||||
firstFileId,
|
||||
});
|
||||
|
||||
const { data: file, isLoading } = useLibraryQuery(
|
||||
{
|
||||
type: "files.by_id",
|
||||
@@ -190,11 +183,6 @@ export function PopoutInspector() {
|
||||
}
|
||||
);
|
||||
|
||||
console.log("[PopoutInspector] Query result:", {
|
||||
file: file?.id,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
// Compute inspector variant
|
||||
const variant: InspectorVariant = file
|
||||
? { type: "file", file }
|
||||
|
||||
@@ -30,7 +30,6 @@ export function SelectionProvider({ children }: { children: ReactNode }) {
|
||||
const fileIds = selectedFiles.map((f) => f.id);
|
||||
|
||||
if (platform.setSelectedFileIds) {
|
||||
console.log("[SelectionContext] Syncing selected files to platform:", fileIds);
|
||||
platform.setSelectedFileIds(fileIds).catch((err) => {
|
||||
console.error("Failed to sync selected files to platform:", err);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user