Skip to main content

modde_sources/
traits.rs

1//! Core download-source contract: the [`DownloadSource`] trait, the
2//! [`AnySource`] enum-dispatch wrapper, and the supporting value types
3//! ([`DownloadHandle`], [`VerifiedFile`], [`ProgressCallback`]).
4
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8
9use modde_core::manifest::wabbajack::DownloadDirective;
10
11use crate::error::SourceResult;
12
13/// Progress callback: (`bytes_downloaded`, `total_bytes`).
14/// `total_bytes` may be 0 if unknown.
15pub type ProgressCallback = Arc<dyn Fn(u64, u64) + Send + Sync>;
16
17/// A resolved download ready for fetching.
18#[derive(Debug, Clone)]
19pub struct DownloadHandle {
20    pub url: String,
21    pub candidate_urls: Vec<String>,
22    pub headers: HashMap<String, String>,
23    pub expected_hash: u64,
24    pub size_hint: Option<u64>,
25}
26
27/// A downloaded and hash-verified file.
28#[derive(Debug, Clone)]
29pub struct VerifiedFile {
30    pub path: PathBuf,
31    pub hash: u64,
32}
33
34/// Trait for download source implementations.
35///
36/// Each source (Nexus, GitHub, Google Drive, Mega, Direct URL) implements this
37/// trait. `AnySource` provides zero-cost enum dispatch, while this trait enables
38/// `impl DownloadSource` generics for monomorphization in hot paths.
39pub trait DownloadSource: Send + Sync {
40    /// Check if this source can handle the given directive.
41    fn can_handle(&self, directive: &DownloadDirective) -> bool;
42
43    /// Resolve a directive into a download handle with a concrete URL.
44    fn resolve(
45        &self,
46        directive: &DownloadDirective,
47    ) -> impl std::future::Future<Output = SourceResult<DownloadHandle>> + Send;
48
49    /// Download the file to `dest` with progress reporting and verify its hash.
50    fn download_with_progress(
51        &self,
52        handle: DownloadHandle,
53        dest: &Path,
54        progress: ProgressCallback,
55    ) -> impl std::future::Future<Output = SourceResult<VerifiedFile>> + Send;
56
57    /// Download the file to `dest` and verify its hash (no-op progress).
58    fn download(
59        &self,
60        handle: DownloadHandle,
61        dest: &Path,
62    ) -> impl std::future::Future<Output = SourceResult<VerifiedFile>> + Send {
63        let noop: ProgressCallback = Arc::new(|_, _| {});
64        self.download_with_progress(handle, dest, noop)
65    }
66}
67
68/// Enum dispatch for all download source implementations.
69///
70/// Wraps concrete source types for heterogeneous collections (e.g. `Vec<AnySource>`).
71/// Each variant implements `DownloadSource`; the enum delegates via match.
72pub enum AnySource {
73    Nexus(crate::nexus::NexusSource),
74    GitHub(crate::github::GitHubSource),
75    GoogleDrive(crate::gdrive::GoogleDriveSource),
76    Mega(crate::mega::MegaSource),
77    MediaFire(crate::mediafire::MediaFireSource),
78    Manual(crate::manual::ManualSource),
79    Direct(crate::direct::DirectSource),
80    WabbajackCdn(crate::wabbajack::cdn::WabbajackCdnSource),
81}
82
83/// Dispatch a non-async method call to the inner source variant.
84macro_rules! dispatch {
85    ($self:expr, $method:ident ( $($arg:expr),* )) => {
86        match $self {
87            AnySource::Nexus(s) => s.$method($($arg),*),
88            AnySource::GitHub(s) => s.$method($($arg),*),
89            AnySource::GoogleDrive(s) => s.$method($($arg),*),
90            AnySource::Mega(s) => s.$method($($arg),*),
91            AnySource::MediaFire(s) => s.$method($($arg),*),
92            AnySource::Manual(s) => s.$method($($arg),*),
93            AnySource::Direct(s) => s.$method($($arg),*),
94            AnySource::WabbajackCdn(s) => s.$method($($arg),*),
95        }
96    };
97}
98
99/// Dispatch an async method call to the inner source variant.
100macro_rules! dispatch_async {
101    ($self:expr, $method:ident ( $($arg:expr),* )) => {
102        match $self {
103            AnySource::Nexus(s) => s.$method($($arg),*).await,
104            AnySource::GitHub(s) => s.$method($($arg),*).await,
105            AnySource::GoogleDrive(s) => s.$method($($arg),*).await,
106            AnySource::Mega(s) => s.$method($($arg),*).await,
107            AnySource::MediaFire(s) => s.$method($($arg),*).await,
108            AnySource::Manual(s) => s.$method($($arg),*).await,
109            AnySource::Direct(s) => s.$method($($arg),*).await,
110            AnySource::WabbajackCdn(s) => s.$method($($arg),*).await,
111        }
112    };
113}
114
115impl DownloadSource for AnySource {
116    fn can_handle(&self, directive: &DownloadDirective) -> bool {
117        dispatch!(self, can_handle(directive))
118    }
119
120    async fn resolve(&self, directive: &DownloadDirective) -> SourceResult<DownloadHandle> {
121        dispatch_async!(self, resolve(directive))
122    }
123
124    async fn download_with_progress(
125        &self,
126        handle: DownloadHandle,
127        dest: &Path,
128        progress: ProgressCallback,
129    ) -> SourceResult<VerifiedFile> {
130        dispatch_async!(self, download_with_progress(handle, dest, progress))
131    }
132}