Skip to main content

modde_sources/
traits.rs

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