Skip to main content

haki_dl/
api.rs

1//! High-level async API entry points.
2
3use std::fmt;
4use std::sync::Arc;
5
6use crate::cancellation::CancellationToken;
7use crate::config::DownloadOptions;
8use crate::error::{Error, Result};
9use crate::event::ProgressEvent;
10use crate::manifest::StreamSelector;
11use crate::session::DownloadSession;
12
13/// Main async library client.
14#[derive(Clone, Debug, Default)]
15pub struct DownloadClient;
16
17impl DownloadClient {
18    /// Creates a new client using default components.
19    pub fn new() -> Self {
20        Self
21    }
22
23    /// Validates and plans a new session without starting network or download work.
24    pub fn prepare(&self, request: DownloadRequest) -> Result<DownloadSession> {
25        if request.input.trim().is_empty() {
26            return Err(Error::config("input must not be empty"));
27        }
28        Ok(DownloadSession::new(request))
29    }
30}
31
32/// Cloneable callback for structured live progress events.
33///
34/// The callback receives typed [`ProgressEvent`] values from API sessions. It is
35/// separate from CLI console rendering and does not require parsing terminal
36/// progress text.
37type ProgressCallbackFn = dyn Fn(&ProgressEvent) -> Result<()> + Send + Sync + 'static;
38
39#[derive(Clone)]
40pub struct ProgressCallback {
41    callback: Arc<ProgressCallbackFn>,
42}
43
44impl ProgressCallback {
45    /// Creates a callback from a closure.
46    pub fn new<F>(callback: F) -> Self
47    where
48        F: Fn(&ProgressEvent) -> Result<()> + Send + Sync + 'static,
49    {
50        Self {
51            callback: Arc::new(callback),
52        }
53    }
54
55    /// Emits one event to the callback.
56    pub fn emit(&self, event: &ProgressEvent) -> Result<()> {
57        (self.callback)(event)
58    }
59}
60
61impl fmt::Debug for ProgressCallback {
62    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
63        formatter.write_str("ProgressCallback")
64    }
65}
66
67/// API request for a download or recording session.
68#[derive(Clone, Debug)]
69pub struct DownloadRequest {
70    /// Manifest URL, local path, or direct media URL.
71    pub input: String,
72    /// Typed options.
73    pub options: DownloadOptions,
74    /// Stream selection strategy.
75    pub stream_selector: StreamSelector,
76    /// Cancellation token observed by the session.
77    pub cancellation_token: CancellationToken,
78    /// Optional live progress callback invoked as events are emitted.
79    pub progress_callback: Option<ProgressCallback>,
80}
81
82impl DownloadRequest {
83    /// Creates a request with default options.
84    pub fn new(input: impl Into<String>) -> Self {
85        Self {
86            input: input.into(),
87            options: DownloadOptions::default(),
88            stream_selector: StreamSelector::default(),
89            cancellation_token: CancellationToken::new(),
90            progress_callback: None,
91        }
92    }
93
94    /// Replaces the request options.
95    pub fn with_options(mut self, options: DownloadOptions) -> Self {
96        self.options = options;
97        self
98    }
99
100    /// Replaces the stream selector.
101    pub fn with_stream_selector(mut self, stream_selector: StreamSelector) -> Self {
102        self.stream_selector = stream_selector;
103        self
104    }
105
106    /// Replaces the cancellation token.
107    pub fn with_cancellation_token(mut self, cancellation_token: CancellationToken) -> Self {
108        self.cancellation_token = cancellation_token;
109        self
110    }
111
112    /// Replaces the structured live progress callback.
113    pub fn with_progress_callback(mut self, progress_callback: ProgressCallback) -> Self {
114        self.progress_callback = Some(progress_callback);
115        self
116    }
117}
118
119/// Live-recording API entry point.
120#[derive(Clone, Debug)]
121pub struct LiveRecorder {
122    request: DownloadRequest,
123}
124
125impl LiveRecorder {
126    /// Creates a live recorder from a request.
127    pub fn new(request: DownloadRequest) -> Self {
128        Self { request }
129    }
130
131    /// Returns the underlying request.
132    pub fn request(&self) -> &DownloadRequest {
133        &self.request
134    }
135
136    /// Starts the live recording session through the same async API pipeline as normal downloads.
137    pub async fn start(self) -> Result<Vec<ProgressEvent>> {
138        Box::pin(DownloadClient::new().prepare(self.request)?.start()).await
139    }
140}