Skip to main content

windows_webview/
download.rs

1use super::*;
2use crate::handler::subscription;
3
4/// The state of a [`DownloadOperation`].
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum DownloadState {
7    /// The download is in progress.
8    InProgress,
9    /// The download has stopped before completing, with a
10    /// [reason](DownloadOperation::interrupt_reason). It may be resumable.
11    Interrupted,
12    /// The download completed successfully.
13    Completed,
14}
15
16impl DownloadState {
17    fn from_raw(value: COREWEBVIEW2_DOWNLOAD_STATE) -> Self {
18        match value {
19            1 => Self::Interrupted,
20            2 => Self::Completed,
21            _ => Self::InProgress,
22        }
23    }
24}
25
26/// Why a [`DownloadOperation`] was [interrupted](DownloadState::Interrupted).
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28#[non_exhaustive]
29pub enum DownloadInterruptReason {
30    /// No interruption; the download is progressing or completed.
31    None,
32    /// A file-system operation failed.
33    FileFailed,
34    /// The destination file could not be accessed.
35    FileAccessDenied,
36    /// The destination has insufficient free space.
37    FileNoSpace,
38    /// The destination file name is too long.
39    FileNameTooLong,
40    /// The file exceeds an allowed size.
41    FileTooLarge,
42    /// The file was identified as malicious.
43    FileMalicious,
44    /// A temporary file-system error interrupted the download.
45    FileTransientError,
46    /// Policy blocked the file.
47    FileBlockedByPolicy,
48    /// A file security check failed.
49    FileSecurityCheckFailed,
50    /// The received file was shorter than expected.
51    FileTooShort,
52    /// The received file's hash did not match.
53    FileHashMismatch,
54    /// The network request failed.
55    NetworkFailed,
56    /// The network request timed out.
57    NetworkTimeout,
58    /// The network connection was lost.
59    NetworkDisconnected,
60    /// The network server is unavailable.
61    NetworkServerDown,
62    /// The network request was invalid.
63    NetworkInvalidRequest,
64    /// The server reported a failure.
65    ServerFailed,
66    /// The server does not support the required byte range.
67    ServerNoRange,
68    /// The server returned invalid content.
69    ServerBadContent,
70    /// The server requires authorization.
71    ServerUnauthorized,
72    /// The server certificate could not be accepted.
73    ServerCertificateProblem,
74    /// The server forbade the request.
75    ServerForbidden,
76    /// The server returned an unexpected response.
77    ServerUnexpectedResponse,
78    /// The received length did not match the server's declared content length.
79    ServerContentLengthMismatch,
80    /// A cross-origin server redirect interrupted the download.
81    ServerCrossOriginRedirect,
82    /// The user canceled the download.
83    UserCanceled,
84    /// The user shut down the browser.
85    UserShutdown,
86    /// The user paused the download.
87    UserPaused,
88    /// The process responsible for the download crashed.
89    DownloadProcessCrashed,
90    /// An interrupt reason not represented by the other variants.
91    Unknown,
92}
93
94impl DownloadInterruptReason {
95    fn from_raw(value: COREWEBVIEW2_DOWNLOAD_INTERRUPT_REASON) -> Self {
96        match value {
97            0 => Self::None,
98            1 => Self::FileFailed,
99            2 => Self::FileAccessDenied,
100            3 => Self::FileNoSpace,
101            4 => Self::FileNameTooLong,
102            5 => Self::FileTooLarge,
103            6 => Self::FileMalicious,
104            7 => Self::FileTransientError,
105            8 => Self::FileBlockedByPolicy,
106            9 => Self::FileSecurityCheckFailed,
107            10 => Self::FileTooShort,
108            11 => Self::FileHashMismatch,
109            12 => Self::NetworkFailed,
110            13 => Self::NetworkTimeout,
111            14 => Self::NetworkDisconnected,
112            15 => Self::NetworkServerDown,
113            16 => Self::NetworkInvalidRequest,
114            17 => Self::ServerFailed,
115            18 => Self::ServerNoRange,
116            19 => Self::ServerBadContent,
117            20 => Self::ServerUnauthorized,
118            21 => Self::ServerCertificateProblem,
119            22 => Self::ServerForbidden,
120            23 => Self::ServerUnexpectedResponse,
121            24 => Self::ServerContentLengthMismatch,
122            25 => Self::ServerCrossOriginRedirect,
123            26 => Self::UserCanceled,
124            27 => Self::UserShutdown,
125            28 => Self::UserPaused,
126            29 => Self::DownloadProcessCrashed,
127            _ => Self::Unknown,
128        }
129    }
130}
131
132/// An in-progress or finished download.
133#[derive(Clone)]
134pub struct DownloadOperation(pub(crate) ICoreWebView2DownloadOperation);
135
136impl DownloadOperation {
137    /// Returns the URI the content is being downloaded from.
138    pub fn uri(&self) -> String {
139        unsafe { string::take_result(self.0.Uri()) }
140    }
141
142    /// Returns the `Content-Disposition` header value from the download's HTTP
143    /// response, if any.
144    pub fn content_disposition(&self) -> String {
145        unsafe { string::take_result(self.0.ContentDisposition()) }
146    }
147
148    /// Returns the MIME type of the downloaded content.
149    pub fn mime_type(&self) -> String {
150        unsafe { string::take_result(self.0.MimeType()) }
151    }
152
153    /// Returns the expected total size of the download in bytes, or `0` if it is
154    /// unknown.
155    pub fn total_bytes_to_receive(&self) -> i64 {
156        unsafe { self.0.TotalBytesToReceive() }.unwrap_or(0)
157    }
158
159    /// Returns the number of bytes received so far.
160    pub fn bytes_received(&self) -> i64 {
161        unsafe { self.0.BytesReceived() }.unwrap_or(0)
162    }
163
164    /// Returns the absolute path the download is being written to.
165    pub fn result_file_path(&self) -> String {
166        unsafe { string::take_result(self.0.ResultFilePath()) }
167    }
168
169    /// Returns the current [`DownloadState`].
170    pub fn state(&self) -> DownloadState {
171        unsafe { self.0.State() }.map_or(DownloadState::InProgress, DownloadState::from_raw)
172    }
173
174    /// Returns why the download was interrupted, or
175    /// [`DownloadInterruptReason::None`] if it has not been interrupted.
176    pub fn interrupt_reason(&self) -> DownloadInterruptReason {
177        unsafe { self.0.InterruptReason() }.map_or(
178            DownloadInterruptReason::None,
179            DownloadInterruptReason::from_raw,
180        )
181    }
182
183    /// Returns `true` if an [interrupted](DownloadState::Interrupted) download can
184    /// be [resumed](Self::resume).
185    pub fn can_resume(&self) -> bool {
186        unsafe { self.0.CanResume() }.is_ok_and(|value| value.as_bool())
187    }
188
189    /// Cancels the download. The file is deleted if it was not yet complete.
190    pub fn cancel(&self) -> Result<()> {
191        unsafe { self.0.Cancel() }.ok()
192    }
193
194    /// Pauses the download. It stays [in progress](DownloadState::InProgress)
195    /// until [resumed](Self::resume) or [canceled](Self::cancel).
196    pub fn pause(&self) -> Result<()> {
197        unsafe { self.0.Pause() }.ok()
198    }
199
200    /// Resumes a paused or [interrupted](DownloadState::Interrupted) download.
201    pub fn resume(&self) -> Result<()> {
202        unsafe { self.0.Resume() }.ok()
203    }
204
205    subscription! {
206        /// Subscribes to the bytes-received-changed event, raised as the
207        /// download makes progress. The handler receives this operation so it
208        /// can read the updated [`bytes_received`](Self::bytes_received).
209        on_bytes_received_changed(DownloadOperation) =>
210            BytesReceivedChanged, add_BytesReceivedChanged / remove_BytesReceivedChanged
211    }
212
213    subscription! {
214        /// Subscribes to the state-changed event, raised when the download's
215        /// [`state`](Self::state) changes (for example to completed or
216        /// interrupted). The handler receives this operation.
217        on_state_changed(DownloadOperation) =>
218            DownloadStateChanged, add_StateChanged / remove_StateChanged
219    }
220}
221
222/// Details about a download that is about to start.
223pub struct DownloadStartingArgs(pub(crate) ICoreWebView2DownloadStartingEventArgs);
224
225impl DownloadStartingArgs {
226    /// Returns the [`DownloadOperation`] for the download.
227    pub fn download_operation(&self) -> Result<DownloadOperation> {
228        unsafe { Ok(DownloadOperation(self.0.DownloadOperation()?)) }
229    }
230
231    /// Returns `true` if the download is currently marked to be canceled.
232    pub fn is_cancelled(&self) -> bool {
233        unsafe { self.0.Cancel() }.is_ok_and(|value| value.as_bool())
234    }
235
236    /// Sets whether the download is canceled.
237    pub fn set_cancel(&self, cancel: bool) -> Result<()> {
238        unsafe { self.0.SetCancel(cancel) }.ok()
239    }
240
241    /// Returns the absolute path the download will be written to.
242    pub fn result_file_path(&self) -> String {
243        unsafe { string::take_result(self.0.ResultFilePath()) }
244    }
245
246    /// Overrides the absolute path the download is written to, choosing a custom
247    /// destination instead of the default.
248    pub fn set_result_file_path(&self, path: &str) -> Result<()> {
249        let path = HSTRING::from(path);
250        unsafe { self.0.SetResultFilePath(&path) }.ok()
251    }
252
253    /// Returns `true` if the host has marked the download as handled,
254    /// suppressing the default download dialog.
255    pub fn is_handled(&self) -> bool {
256        unsafe { self.0.Handled() }.is_ok_and(|value| value.as_bool())
257    }
258
259    /// Marks the download as handled, suppressing the default download dialog so
260    /// the host can present its own UI.
261    pub fn set_handled(&self, handled: bool) -> Result<()> {
262        unsafe { self.0.SetHandled(handled) }.ok()
263    }
264
265    /// Takes a [`Deferral`] so the download can be resolved after the handler
266    /// returns, for example once the user has chosen a destination.
267    pub fn defer(&self) -> Result<Deferral> {
268        Ok(Deferral::new(unsafe { self.0.GetDeferral()? }))
269    }
270}