use crate::fetch::rsync::{RsyncFetchError, RsyncFetcher}; use crate::policy::{Policy, SyncPreference}; use crate::report::{RfcRef, Warning}; use crate::storage::RocksStore; use crate::sync::rrdp::{ Fetcher as HttpFetcher, RrdpSyncError, sync_from_notification, }; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RepoSyncSource { Rrdp, Rsync, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct RepoSyncResult { pub source: RepoSyncSource, pub objects_written: usize, pub warnings: Vec, } #[derive(Debug, thiserror::Error)] pub enum RepoSyncError { #[error("RRDP sync failed: {0}")] Rrdp(#[from] RrdpSyncError), #[error("rsync fallback failed: {0}")] Rsync(#[from] RsyncFetchError), #[error("storage error: {0}")] Storage(String), } /// Sync a publication point into `raw_objects`. /// /// v1 behavior: /// - If `rrdp_notification_uri` is present and `policy.sync_preference` is `rrdp_then_rsync`, /// try RRDP snapshot sync first (RFC 8182 §3.4.1-§3.4.3). /// - On RRDP failure, fall back to rsync (RFC 8182 §3.4.5). /// - If `sync_preference` is `rsync_only` or there is no RRDP URI, use rsync. pub fn sync_publication_point( store: &RocksStore, policy: &Policy, rrdp_notification_uri: Option<&str>, rsync_base_uri: &str, http_fetcher: &dyn HttpFetcher, rsync_fetcher: &dyn RsyncFetcher, ) -> Result { match (policy.sync_preference, rrdp_notification_uri) { (SyncPreference::RrdpThenRsync, Some(notification_uri)) => { match try_rrdp_sync(store, notification_uri, http_fetcher) { Ok(written) => Ok(RepoSyncResult { source: RepoSyncSource::Rrdp, objects_written: written, warnings: Vec::new(), }), Err(err) => { let warnings = vec![ Warning::new(format!("RRDP failed; falling back to rsync: {err}")) .with_rfc_refs(&[RfcRef("RFC 8182 §3.4.5")]) .with_context(notification_uri), ]; let written = rsync_sync_into_raw_objects(store, rsync_base_uri, rsync_fetcher)?; Ok(RepoSyncResult { source: RepoSyncSource::Rsync, objects_written: written, warnings, }) } } } _ => { let written = rsync_sync_into_raw_objects(store, rsync_base_uri, rsync_fetcher)?; Ok(RepoSyncResult { source: RepoSyncSource::Rsync, objects_written: written, warnings: Vec::new(), }) } } } fn try_rrdp_sync( store: &RocksStore, notification_uri: &str, http_fetcher: &dyn HttpFetcher, ) -> Result { let notification_xml = http_fetcher .fetch(notification_uri) .map_err(RrdpSyncError::Fetch)?; sync_from_notification(store, notification_uri, ¬ification_xml, http_fetcher) } fn rsync_sync_into_raw_objects( store: &RocksStore, rsync_base_uri: &str, rsync_fetcher: &dyn RsyncFetcher, ) -> Result { let objects = rsync_fetcher.fetch_objects(rsync_base_uri)?; let mut written = 0usize; for (rsync_uri, bytes) in objects { store .put_raw(&rsync_uri, &bytes) .map_err(|e| RepoSyncError::Storage(e.to_string()))?; written += 1; } Ok(written) }