Back to Spacedrive

Service Integration & API

.tasks/core/FSYNC-004-service-integration.md

0.4.36.9 KB
Original Source

Description

Wire FileSyncService into Spacedrive infrastructure - add service to CoreContext, implement API routes for UI integration, and set up event handling for real-time updates.

Goal: Make File Sync accessible from UI with full CRUD, sync triggering, and status monitoring.

Implementation Steps

1. Add to Services

rust
// Location: core/src/service/mod.rs

pub struct Services {
    // ... existing services
    pub file_sync: Arc<FileSyncService>,
}

impl Services {
    pub fn new(context: Arc<CoreContext>) -> Self {
        // ... initialize other services

        let file_sync = Arc::new(FileSyncService::new(
            context.library_db(),
            context.job_manager.clone(),
            context.clone(),
        ));

        Self {
            // ... other services
            file_sync,
        }
    }
}

2. Create API Routes

rust
// Location: core/src/api/sync.rs

pub fn mount() -> Router {
    Router::new()
        // Conduit Management
        .query("listConduits", |ctx, _: ()| async move { ... })
        .mutation("createConduit", |ctx, input: CreateConduitInput| async move { ... })
        .mutation("updateConduit", |ctx, input: UpdateConduitInput| async move { ... })
        .mutation("deleteConduit", |ctx, conduit_id: i32| async move { ... })

        // Sync Operations
        .mutation("syncNow", |ctx, conduit_id: i32| async move { ... })
        .mutation("pauseSync", |ctx, conduit_id: i32| async move { ... })
        .mutation("resumeSync", |ctx, conduit_id: i32| async move { ... })

        // Status & History
        .query("getSyncStatus", |ctx, conduit_id: i32| async move { ... })
        .query("getSyncProgress", |ctx, conduit_id: i32| async move { ... })
        .query("getSyncHistory", |ctx, input: HistoryInput| async move { ... })
        .query("getConflicts", |ctx, conduit_id: i32| async move { ... })
}

API Types:

rust
// Request/Response types

#[derive(Type, Deserialize)]
pub struct CreateConduitInput {
    pub source_entry_id: i32,
    pub target_entry_id: i32,
    pub sync_mode: String,           // "mirror" | "bidirectional" | "selective"
    pub schedule: String,            // "instant" | "interval:5m" | "manual"
}

#[derive(Type, Serialize)]
pub struct SyncConduitResponse {
    pub id: i32,
    pub uuid: String,
    pub source_entry_id: i32,
    pub target_entry_id: i32,
    pub sync_mode: String,
    pub enabled: bool,
    pub last_sync_completed_at: Option<String>,
    pub total_syncs: i64,
    pub files_synced: i64,
    pub bytes_transferred: i64,
}

#[derive(Type, Serialize)]
pub struct SyncStatusResponse {
    pub conduit_id: i32,
    pub is_syncing: bool,
    pub current_phase: Option<String>,  // "copying" | "deleting" | "verifying"
    pub last_completed: Option<String>,
    pub last_error: Option<String>,
}

#[derive(Type, Serialize)]
pub struct SyncProgressResponse {
    pub conduit_id: i32,
    pub phase: String,
    pub total_files: usize,
    pub completed_files: usize,
    pub total_bytes: u64,
    pub completed_bytes: u64,
    pub copy_progress: Option<CopyProgress>,
    pub delete_progress: Option<DeleteProgress>,
}

3. Event Integration

rust
// Location: core/src/service/file_sync/events.rs

pub enum FileSyncEvent {
    ConduitCreated { conduit_id: i32, uuid: Uuid },
    ConduitUpdated { conduit_id: i32 },
    ConduitDeleted { conduit_id: i32 },

    SyncStarted { conduit_id: i32, generation: i64 },
    SyncProgress { conduit_id: i32, progress: SyncProgress },
    SyncCompleted { conduit_id: i32, generation: i64, stats: SyncStats },
    SyncFailed { conduit_id: i32, error: String },

    ConflictDetected { conduit_id: i32, conflict: SyncConflict },
    VerificationComplete { conduit_id: i32, verified: bool },
}

impl FileSyncService {
    fn emit_event(&self, event: FileSyncEvent) {
        if let Some(event_bus) = &self.context.event_bus {
            event_bus.emit("file_sync", serde_json::to_value(event).unwrap());
        }
    }
}

Event Emission Points:

  • sync_now() → SyncStarted
  • monitor_sync() progress loop → SyncProgress
  • monitor_sync() completion → SyncCompleted
  • monitor_sync() error → SyncFailed
  • resolver → ConflictDetected
  • verification → VerificationComplete

4. Register API Router

rust
// Location: core/src/api/mod.rs

pub fn mount() -> Router {
    Router::new()
        // ... existing routes
        .merge("sync.", crate::api::sync::mount())
}

Files to Create/Modify

API Implementation:

  • core/src/api/sync.rs - API routes and types

Event Integration:

  • core/src/service/file_sync/events.rs - Event types and emission

Service Registration:

  • core/src/service/mod.rs - Add file_sync to Services struct
  • core/src/api/mod.rs - Register sync router

Acceptance Criteria

  • FileSyncService added to Services struct
  • Service initializes with correct dependencies
  • API routes implemented for conduit CRUD
  • syncNow mutation triggers sync and returns handle
  • getSyncStatus query returns current sync state
  • getSyncProgress query returns aggregated progress
  • getSyncHistory query returns generation history
  • Events emitted at appropriate lifecycle points
  • UI can create conduit between two directories
  • UI can trigger manual sync
  • UI shows real-time progress via events
  • UI displays sync history and statistics
  • Integration test: Full API workflow (create → sync → monitor)

API Usage Examples

Create Sync Conduit

typescript
const conduit = await core.sync.createConduit({
	sourceEntryId: 123,
	targetEntryId: 456,
	syncMode: "mirror",
	schedule: "manual",
});

Trigger Sync

typescript
const handle = await core.sync.syncNow(conduit.id);

// Subscribe to progress
core.events.on("file_sync", (event) => {
	if (event.type === "SyncProgress" && event.conduit_id === conduit.id) {
		console.log(
			`Progress: ${event.progress.completed_files}/${event.progress.total_files}`,
		);
	}
});

Monitor Status

typescript
const status = await core.sync.getSyncStatus(conduit.id);
console.log(status.is_syncing, status.last_completed);

const progress = await core.sync.getSyncProgress(conduit.id);
console.log(progress.phase, progress.completed_bytes, progress.total_bytes);

UI Integration Points

Location Context Menu:

  • "Sync to..." option on directory right-click
  • Opens modal to select target location and configure sync mode

Sync Status Panel:

  • List of all conduits with status indicators
  • Per-conduit progress bars during active sync
  • History view showing past generations

Settings:

  • Configure schedule, bandwidth limits, conflict resolution
  • Enable/disable conduits
  • View and resolve conflicts

References

  • Implementation: FILE_SYNC_IMPLEMENTATION_PLAN.md (Lines 1499-1630)
  • API patterns: core/src/api/jobs.rs (similar structure)
  • Related: FSYNC-003 (FileSyncService core), FSYNC-005 (progress aggregation)