spacedrive/.tasks/FSYNC-005-advanced-features.md
2025-10-14 20:39:56 -07:00

9.2 KiB

id, title, status, assignee, parent, priority, tags, design_doc, last_updated, related_tasks
id title status assignee parent priority tags design_doc last_updated related_tasks
FSYNC-005 Advanced Features (Scheduling, Progress, Conflicts) To Do james FSYNC-000 Medium
scheduler
progress
conflicts
polish
workbench/FILE_SYNC_IMPLEMENTATION_PLAN.md 2025-10-14
FSYNC-003
FSYNC-004

Description

Polish File Sync with production-ready features: automatic scheduling, aggregated progress tracking, advanced conflict resolution strategies, and monitoring capabilities.

Goal: Transform MVP into production-ready system with excellent user experience.

Implementation Steps

1. Schedule Processing

// Location: core/src/service/file_sync/scheduler.rs

impl FileSyncService {
    pub async fn start_scheduler(self: Arc<Self>) {
        tokio::spawn(async move {
            self.run_scheduler().await
        });
    }

    async fn run_scheduler(&self) -> Result<()> {
        let mut tick = interval(Duration::from_secs(60));

        loop {
            tick.tick().await;
            self.process_scheduled_syncs().await?;
        }
    }

    async fn process_scheduled_syncs(&self) -> Result<()> {
        let enabled = self.conduit_manager.list_enabled().await?;

        for conduit in enabled {
            if self.is_sync_due(&conduit).await {
                info!("Triggering scheduled sync for conduit {}", conduit.id);
                self.sync_now(conduit.id).await?;
            }
        }
    }

    async fn is_sync_due(&self, conduit: &sync_conduit::Model) -> bool {
        match conduit.schedule.as_str() {
            "manual" => false,
            "instant" => true,  // TODO: integrate with file watcher
            s if s.starts_with("interval:") => {
                // Parse "interval:5m", "interval:1h" etc.
                self.parse_interval_due(s, conduit.last_sync_completed_at)
            }
            _ => false,
        }
    }
}

Schedule Formats:

  • "manual" - Only triggered via API
  • "instant" - Triggers on filesystem change (requires watcher integration)
  • "interval:5m" - Every 5 minutes
  • "interval:1h" - Every hour
  • "interval:1d" - Daily

Watcher Integration (Instant Mode):

// Subscribe to location watcher events
// When files change in source or target directory:
//   - Check if conduit has instant schedule
//   - Debounce rapid changes (wait 5s after last change)
//   - Trigger sync_now()

2. Progress Aggregation

// Location: core/src/service/file_sync/progress.rs

#[derive(Debug, Clone, Serialize)]
pub struct AggregatedSyncProgress {
    pub phase: String,                    // "copying" | "deleting" | "verifying"
    pub copy_progress: Option<CopyProgress>,
    pub delete_progress: Option<DeleteProgress>,
    pub total_files: usize,
    pub completed_files: usize,
    pub total_bytes: u64,
    pub completed_bytes: u64,
    pub current_speed_mbps: f64,
    pub eta_seconds: Option<u64>,
}

impl FileSyncService {
    pub async fn get_sync_progress(
        &self,
        conduit_id: i32,
    ) -> Result<Option<AggregatedSyncProgress>> {
        let syncs = self.active_syncs.read().await;
        let Some(sync_op) = syncs.get(&conduit_id) else {
            return Ok(None);
        };

        let mut progress = AggregatedSyncProgress::default();

        // Aggregate copy job progress
        if let Some(job_id) = sync_op.source_to_target.copy_job_id {
            if let Ok(copy_prog) = self.job_manager.get_progress(job_id).await {
                progress.copy_progress = Some(copy_prog);
                progress.total_files += copy_prog.total_files;
                progress.completed_files += copy_prog.completed_files;
                progress.total_bytes += copy_prog.total_bytes;
                progress.completed_bytes += copy_prog.completed_bytes;
            }
        }

        // Aggregate delete job progress
        if let Some(job_id) = sync_op.source_to_target.delete_job_id {
            if let Ok(delete_prog) = self.job_manager.get_progress(job_id).await {
                progress.delete_progress = Some(delete_prog);
                progress.total_files += delete_prog.total_files;
                progress.completed_files += delete_prog.completed_files;
            }
        }

        // Calculate speed and ETA
        progress.current_speed_mbps = self.calculate_current_speed(&sync_op);
        progress.eta_seconds = self.calculate_eta(&progress);

        Ok(Some(progress))
    }
}

Progress Tracking:

  • Query job manager for individual job progress
  • Aggregate totals across all active jobs
  • Calculate transfer speed from job metrics
  • Estimate completion time

3. Advanced Conflict Resolution

// Location: core/src/service/file_sync/conflict.rs

pub struct ConflictResolver {
    strategy: ConflictStrategy,
}

pub enum ConflictStrategy {
    NewestWins,           // Use most recent modification (default)
    SourceWins,           // Source always wins
    TargetWins,           // Target always wins
    LargestWins,          // Keep larger file (useful for media)
    CreateConflictFile,   // Create "file (conflict 2025-10-14).txt"
    PromptUser,           // Queue for user decision
}

pub enum ConflictResolution {
    UseSource,
    UseTarget,
    CreateConflictCopy {
        original: entry::Model,
        conflicted: entry::Model,
    },
    PromptUser(SyncConflict),
}

impl ConflictResolver {
    pub fn resolve(&self, conflict: SyncConflict) -> ConflictResolution {
        match self.strategy {
            ConflictStrategy::NewestWins => {
                if conflict.source_entry.updated_at > conflict.target_entry.updated_at {
                    ConflictResolution::UseSource
                } else {
                    ConflictResolution::UseTarget
                }
            }
            ConflictStrategy::LargestWins => {
                if conflict.source_entry.size > conflict.target_entry.size {
                    ConflictResolution::UseSource
                } else {
                    ConflictResolution::UseTarget
                }
            }
            ConflictStrategy::CreateConflictFile => {
                // Generate conflict filename with timestamp
                ConflictResolution::CreateConflictCopy {
                    original: conflict.target_entry,
                    conflicted: conflict.source_entry,
                }
            }
            ConflictStrategy::PromptUser => {
                // Queue for UI resolution
                ConflictResolution::PromptUser(conflict)
            }
            _ => { /* ... */ }
        }
    }
}

Conflict Filename Format:

original.txt
→ original (conflict 2025-10-14 Device-Name).txt

4. Monitoring & Telemetry

// Location: core/src/service/file_sync/telemetry.rs

pub struct SyncTelemetry {
    pub total_conduits: usize,
    pub active_syncs: usize,
    pub total_syncs_24h: i64,
    pub total_bytes_24h: i64,
    pub average_sync_duration: Duration,
    pub conflict_count_24h: i64,
    pub error_count_24h: i64,
}

impl FileSyncService {
    pub async fn get_telemetry(&self) -> Result<SyncTelemetry> {
        // Query database for statistics
        // Aggregate across all conduits
        // Calculate averages and totals
    }
}

Metrics to Track:

  • Total conduits created
  • Active sync count
  • Syncs completed (24h, 7d, 30d)
  • Total bytes transferred
  • Average sync duration
  • Conflict frequency
  • Error rates

Files to Create

Scheduler:

  • core/src/service/file_sync/scheduler.rs - Background scheduler

Progress:

  • core/src/service/file_sync/progress.rs - Progress aggregation

Conflicts:

  • core/src/service/file_sync/conflict.rs - Conflict resolution strategies (enhanced)

Monitoring:

  • core/src/service/file_sync/telemetry.rs - Telemetry and metrics

Acceptance Criteria

  • Scheduler runs in background checking for due syncs
  • Interval schedules parsed correctly (5m, 1h, 1d formats)
  • Instant mode triggers on filesystem changes (with debouncing)
  • get_sync_progress aggregates across copy and delete jobs
  • Progress includes current speed and ETA
  • ConflictResolver supports all strategies
  • CreateConflictFile generates unique filenames
  • PromptUser queues conflicts for UI resolution
  • Telemetry endpoint returns accurate statistics
  • UI displays real-time progress with speed and ETA
  • UI shows conflict queue with resolution options
  • Integration test: Scheduled sync triggers automatically
  • Integration test: Conflict resolution strategies work correctly

User Experience Improvements

Real-Time Progress:

  • Show current file being copied
  • Display transfer speed (MB/s)
  • Show ETA for completion
  • Indicate phase (copying/deleting/verifying)

Conflict Management:

  • Highlight conflicts in sync status
  • Preview both versions before resolution
  • Batch resolution for multiple conflicts
  • Remember user's preferred strategy

Scheduling UI:

  • Visual schedule picker
  • Next sync time indicator
  • Manual sync button always available
  • Pause/resume controls

References

  • Implementation: FILE_SYNC_IMPLEMENTATION_PLAN.md (Lines 1856-2073)
  • Scheduler: Lines 1921-2014
  • Progress: Lines 2016-2073
  • Related: FSYNC-003 (resolver conflicts), FSYNC-004 (API for conflicts)