mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2025-12-11 20:15:30 +01:00
318 lines
9.2 KiB
Markdown
318 lines
9.2 KiB
Markdown
---
|
|
id: FSYNC-005
|
|
title: Advanced Features (Scheduling, Progress, Conflicts)
|
|
status: To Do
|
|
assignee: james
|
|
parent: FSYNC-000
|
|
priority: Medium
|
|
tags: [scheduler, progress, conflicts, polish]
|
|
design_doc: workbench/FILE_SYNC_IMPLEMENTATION_PLAN.md
|
|
last_updated: 2025-10-14
|
|
related_tasks: [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
|
|
|
|
```rust
|
|
// 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):**
|
|
|
|
```rust
|
|
// 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
|
|
|
|
```rust
|
|
// 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
|
|
|
|
```rust
|
|
// 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
|
|
|
|
```rust
|
|
// 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)
|